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);
70 /***/ function(module, exports, __webpack_require__) {
72 __webpack_require__(2);
73 module.exports = angular;
78 /***/ function(module, exports) {
81 * @license AngularJS v1.4.8
82 * (c) 2010-2015 Google, Inc. http://angularjs.org
85 (function(window, document, undefined) {'use strict';
90 * This object provides a utility for producing rich Error messages within
91 * Angular. It can be called as follows:
93 * var exampleMinErr = minErr('example');
94 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
96 * The above creates an instance of minErr in the example namespace. The
97 * resulting error will have a namespaced error code of example.one. The
98 * resulting error will replace {0} with the value of foo, and {1} with the
99 * value of bar. The object is not restricted in the number of arguments it can
102 * If fewer arguments are specified than necessary for interpolation, the extra
103 * interpolation markers will be preserved in the final string.
105 * Since data will be parsed statically during a build step, some restrictions
106 * are applied with respect to how minErr instances are created and called.
107 * Instances should have names of the form namespaceMinErr for a minErr created
108 * using minErr('namespace') . Error codes, namespaces and template strings
109 * should all be static strings, not variables or general expressions.
111 * @param {string} module The namespace to use for the new minErr instance.
112 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
113 * error from returned function, for cases when a particular type of error is useful.
114 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
117 function minErr(module, ErrorConstructor) {
118 ErrorConstructor = ErrorConstructor || Error;
120 var SKIP_INDEXES = 2;
122 var templateArgs = arguments,
123 code = templateArgs[0],
124 message = '[' + (module ? module + ':' : '') + code + '] ',
125 template = templateArgs[1],
128 message += template.replace(/\{\d+\}/g, function(match) {
129 var index = +match.slice(1, -1),
130 shiftedIndex = index + SKIP_INDEXES;
132 if (shiftedIndex < templateArgs.length) {
133 return toDebugString(templateArgs[shiftedIndex]);
139 message += '\nhttp://errors.angularjs.org/1.4.8/' +
140 (module ? module + '/' : '') + code;
142 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
143 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
144 encodeURIComponent(toDebugString(templateArgs[i]));
147 return new ErrorConstructor(message);
151 /* We need to tell jshint what variables are being exported */
152 /* global angular: true,
163 REGEX_STRING_REGEXP: true,
164 VALIDITY_STATE_PROPERTY: true,
168 manualLowercase: true,
169 manualUppercase: true,
202 escapeForRegexp: true,
215 toJsonReplacer: true,
218 convertTimezoneToLocal: true,
219 timezoneToOffset: true,
221 tryDecodeURIComponent: true,
224 encodeUriSegment: true,
225 encodeUriQuery: true,
228 getTestability: true,
233 assertNotHasOwnProperty: true,
236 hasOwnProperty: true,
239 NODE_TYPE_ELEMENT: true,
240 NODE_TYPE_ATTRIBUTE: true,
241 NODE_TYPE_TEXT: true,
242 NODE_TYPE_COMMENT: true,
243 NODE_TYPE_DOCUMENT: true,
244 NODE_TYPE_DOCUMENT_FRAGMENT: true,
247 ////////////////////////////////////
256 * The ng module is loaded by default when an AngularJS application is started. The module itself
257 * contains the essential components for an AngularJS application to function. The table below
258 * lists a high level breakdown of each of the services/factories, filters, directives and testing
259 * components available within this core module.
261 * <div doc-module-components="ng"></div>
264 var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
266 // The name of a form control's ValidityState property.
267 // This is used so that it's possible for internal tests to create mock ValidityStates.
268 var VALIDITY_STATE_PROPERTY = 'validity';
272 * @name angular.lowercase
276 * @description Converts the specified string to lowercase.
277 * @param {string} string String to be converted to lowercase.
278 * @returns {string} Lowercased string.
280 var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
281 var hasOwnProperty = Object.prototype.hasOwnProperty;
285 * @name angular.uppercase
289 * @description Converts the specified string to uppercase.
290 * @param {string} string String to be converted to uppercase.
291 * @returns {string} Uppercased string.
293 var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
296 var manualLowercase = function(s) {
297 /* jshint bitwise: false */
299 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
302 var manualUppercase = function(s) {
303 /* jshint bitwise: false */
305 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
310 // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
311 // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
312 // with correct but slower alternatives.
313 if ('i' !== 'I'.toLowerCase()) {
314 lowercase = manualLowercase;
315 uppercase = manualUppercase;
320 msie, // holds major version number for IE, or NaN if UA is not IE.
321 jqLite, // delay binding since jQuery could be loaded after us.
322 jQuery, // delay binding
326 toString = Object.prototype.toString,
327 getPrototypeOf = Object.getPrototypeOf,
328 ngMinErr = minErr('ng'),
331 angular = window.angular || (window.angular = {}),
336 * documentMode is an IE-only property
337 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
339 msie = document.documentMode;
345 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
348 function isArrayLike(obj) {
350 // `null`, `undefined` and `window` are not array-like
351 if (obj == null || isWindow(obj)) return false;
353 // arrays, strings and jQuery/jqLite objects are array like
354 // * jqLite is either the jQuery or jqLite constructor function
355 // * we have to check the existance of jqLite first as this method is called
356 // via the forEach method when constructing the jqLite object in the first place
357 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
359 // Support: iOS 8.2 (not reproducible in simulator)
360 // "length" in obj used to prevent JIT error (gh-11508)
361 var length = "length" in Object(obj) && obj.length;
363 // NodeList objects (with `item` method) and
364 // other objects with suitable length characteristics are array-like
365 return isNumber(length) &&
366 (length >= 0 && (length - 1) in obj || typeof obj.item == 'function');
371 * @name angular.forEach
376 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
377 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
378 * is the value of an object property or an array element, `key` is the object property key or
379 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
381 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
382 * using the `hasOwnProperty` method.
385 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
386 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
387 * return the value provided.
390 var values = {name: 'misko', gender: 'male'};
392 angular.forEach(values, function(value, key) {
393 this.push(key + ': ' + value);
395 expect(log).toEqual(['name: misko', 'gender: male']);
398 * @param {Object|Array} obj Object to iterate over.
399 * @param {Function} iterator Iterator function.
400 * @param {Object=} context Object to become context (`this`) for the iterator function.
401 * @returns {Object|Array} Reference to `obj`.
404 function forEach(obj, iterator, context) {
407 if (isFunction(obj)) {
409 // Need to check if hasOwnProperty exists,
410 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
411 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
412 iterator.call(context, obj[key], key, obj);
415 } else if (isArray(obj) || isArrayLike(obj)) {
416 var isPrimitive = typeof obj !== 'object';
417 for (key = 0, length = obj.length; key < length; key++) {
418 if (isPrimitive || key in obj) {
419 iterator.call(context, obj[key], key, obj);
422 } else if (obj.forEach && obj.forEach !== forEach) {
423 obj.forEach(iterator, context, obj);
424 } else if (isBlankObject(obj)) {
425 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
427 iterator.call(context, obj[key], key, obj);
429 } else if (typeof obj.hasOwnProperty === 'function') {
430 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
432 if (obj.hasOwnProperty(key)) {
433 iterator.call(context, obj[key], key, obj);
437 // Slow path for objects which do not have a method `hasOwnProperty`
439 if (hasOwnProperty.call(obj, key)) {
440 iterator.call(context, obj[key], key, obj);
448 function forEachSorted(obj, iterator, context) {
449 var keys = Object.keys(obj).sort();
450 for (var i = 0; i < keys.length; i++) {
451 iterator.call(context, obj[keys[i]], keys[i]);
458 * when using forEach the params are value, key, but it is often useful to have key, value.
459 * @param {function(string, *)} iteratorFn
460 * @returns {function(*, string)}
462 function reverseParams(iteratorFn) {
463 return function(value, key) { iteratorFn(key, value); };
467 * A consistent way of creating unique IDs in angular.
469 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
470 * we hit number precision issues in JavaScript.
472 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
474 * @returns {number} an unique alpha-numeric string
482 * Set or clear the hashkey for an object.
484 * @param h the hashkey (!truthy to delete the hashkey)
486 function setHashKey(obj, h) {
490 delete obj.$$hashKey;
495 function baseExtend(dst, objs, deep) {
496 var h = dst.$$hashKey;
498 for (var i = 0, ii = objs.length; i < ii; ++i) {
500 if (!isObject(obj) && !isFunction(obj)) continue;
501 var keys = Object.keys(obj);
502 for (var j = 0, jj = keys.length; j < jj; j++) {
506 if (deep && isObject(src)) {
508 dst[key] = new Date(src.valueOf());
509 } else if (isRegExp(src)) {
510 dst[key] = new RegExp(src);
511 } else if (src.nodeName) {
512 dst[key] = src.cloneNode(true);
513 } else if (isElement(src)) {
514 dst[key] = src.clone();
516 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
517 baseExtend(dst[key], [src], true);
531 * @name angular.extend
536 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
537 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
538 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
540 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
541 * {@link angular.merge} for this.
543 * @param {Object} dst Destination object.
544 * @param {...Object} src Source object(s).
545 * @returns {Object} Reference to `dst`.
547 function extend(dst) {
548 return baseExtend(dst, slice.call(arguments, 1), false);
554 * @name angular.merge
559 * Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
560 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
561 * by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
563 * Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
564 * objects, performing a deep copy.
566 * @param {Object} dst Destination object.
567 * @param {...Object} src Source object(s).
568 * @returns {Object} Reference to `dst`.
570 function merge(dst) {
571 return baseExtend(dst, slice.call(arguments, 1), true);
576 function toInt(str) {
577 return parseInt(str, 10);
581 function inherit(parent, extra) {
582 return extend(Object.create(parent), extra);
592 * A function that performs no operations. This function can be useful when writing code in the
595 function foo(callback) {
596 var result = calculateResult();
597 (callback || angular.noop)(result);
607 * @name angular.identity
612 * A function that returns its first argument. This function is useful when writing code in the
616 function transformer(transformationFn, value) {
617 return (transformationFn || angular.identity)(value);
620 * @param {*} value to be returned.
621 * @returns {*} the value passed in.
623 function identity($) {return $;}
624 identity.$inject = [];
627 function valueFn(value) {return function() {return value;};}
629 function hasCustomToString(obj) {
630 return isFunction(obj.toString) && obj.toString !== toString;
636 * @name angular.isUndefined
641 * Determines if a reference is undefined.
643 * @param {*} value Reference to check.
644 * @returns {boolean} True if `value` is undefined.
646 function isUndefined(value) {return typeof value === 'undefined';}
651 * @name angular.isDefined
656 * Determines if a reference is defined.
658 * @param {*} value Reference to check.
659 * @returns {boolean} True if `value` is defined.
661 function isDefined(value) {return typeof value !== 'undefined';}
666 * @name angular.isObject
671 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
672 * considered to be objects. Note that JavaScript arrays are objects.
674 * @param {*} value Reference to check.
675 * @returns {boolean} True if `value` is an `Object` but not `null`.
677 function isObject(value) {
678 // http://jsperf.com/isobject4
679 return value !== null && typeof value === 'object';
684 * Determine if a value is an object with a null prototype
686 * @returns {boolean} True if `value` is an `Object` with a null prototype
688 function isBlankObject(value) {
689 return value !== null && typeof value === 'object' && !getPrototypeOf(value);
695 * @name angular.isString
700 * Determines if a reference is a `String`.
702 * @param {*} value Reference to check.
703 * @returns {boolean} True if `value` is a `String`.
705 function isString(value) {return typeof value === 'string';}
710 * @name angular.isNumber
715 * Determines if a reference is a `Number`.
717 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
719 * If you wish to exclude these then you can use the native
720 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
723 * @param {*} value Reference to check.
724 * @returns {boolean} True if `value` is a `Number`.
726 function isNumber(value) {return typeof value === 'number';}
731 * @name angular.isDate
736 * Determines if a value is a date.
738 * @param {*} value Reference to check.
739 * @returns {boolean} True if `value` is a `Date`.
741 function isDate(value) {
742 return toString.call(value) === '[object Date]';
748 * @name angular.isArray
753 * Determines if a reference is an `Array`.
755 * @param {*} value Reference to check.
756 * @returns {boolean} True if `value` is an `Array`.
758 var isArray = Array.isArray;
762 * @name angular.isFunction
767 * Determines if a reference is a `Function`.
769 * @param {*} value Reference to check.
770 * @returns {boolean} True if `value` is a `Function`.
772 function isFunction(value) {return typeof value === 'function';}
776 * Determines if a value is a regular expression object.
779 * @param {*} value Reference to check.
780 * @returns {boolean} True if `value` is a `RegExp`.
782 function isRegExp(value) {
783 return toString.call(value) === '[object RegExp]';
788 * Checks if `obj` is a window object.
791 * @param {*} obj Object to check
792 * @returns {boolean} True if `obj` is a window obj.
794 function isWindow(obj) {
795 return obj && obj.window === obj;
799 function isScope(obj) {
800 return obj && obj.$evalAsync && obj.$watch;
804 function isFile(obj) {
805 return toString.call(obj) === '[object File]';
809 function isFormData(obj) {
810 return toString.call(obj) === '[object FormData]';
814 function isBlob(obj) {
815 return toString.call(obj) === '[object Blob]';
819 function isBoolean(value) {
820 return typeof value === 'boolean';
824 function isPromiseLike(obj) {
825 return obj && isFunction(obj.then);
829 var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
830 function isTypedArray(value) {
831 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
835 var trim = function(value) {
836 return isString(value) ? value.trim() : value;
840 // http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
841 // Prereq: s is a string.
842 var escapeForRegexp = function(s) {
843 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
844 replace(/\x08/g, '\\x08');
850 * @name angular.isElement
855 * Determines if a reference is a DOM element (or wrapped jQuery element).
857 * @param {*} value Reference to check.
858 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
860 function isElement(node) {
862 (node.nodeName // we are a direct element
863 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API
867 * @param str 'key1,key2,...'
868 * @returns {object} in the form of {key1:true, key2:true, ...}
870 function makeMap(str) {
871 var obj = {}, items = str.split(","), i;
872 for (i = 0; i < items.length; i++) {
873 obj[items[i]] = true;
879 function nodeName_(element) {
880 return lowercase(element.nodeName || (element[0] && element[0].nodeName));
883 function includes(array, obj) {
884 return Array.prototype.indexOf.call(array, obj) != -1;
887 function arrayRemove(array, value) {
888 var index = array.indexOf(value);
890 array.splice(index, 1);
902 * Creates a deep copy of `source`, which should be an object or an array.
904 * * If no destination is supplied, a copy of the object or array is created.
905 * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
906 * are deleted and then all elements/properties from the source are copied to it.
907 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
908 * * If `source` is identical to 'destination' an exception will be thrown.
910 * @param {*} source The source that will be used to make a copy.
911 * Can be any type, including primitives, `null`, and `undefined`.
912 * @param {(Object|Array)=} destination Destination into which the source is copied. If
913 * provided, must be of the same type as `source`.
914 * @returns {*} The copy or updated `destination`, if `destination` was specified.
917 <example module="copyExample">
918 <file name="index.html">
919 <div ng-controller="ExampleController">
920 <form novalidate class="simple-form">
921 Name: <input type="text" ng-model="user.name" /><br />
922 E-mail: <input type="email" ng-model="user.email" /><br />
923 Gender: <input type="radio" ng-model="user.gender" value="male" />male
924 <input type="radio" ng-model="user.gender" value="female" />female<br />
925 <button ng-click="reset()">RESET</button>
926 <button ng-click="update(user)">SAVE</button>
928 <pre>form = {{user | json}}</pre>
929 <pre>master = {{master | json}}</pre>
933 angular.module('copyExample', [])
934 .controller('ExampleController', ['$scope', function($scope) {
937 $scope.update = function(user) {
938 // Example with 1 argument
939 $scope.master= angular.copy(user);
942 $scope.reset = function() {
943 // Example with 2 arguments
944 angular.copy($scope.master, $scope.user);
953 function copy(source, destination) {
954 var stackSource = [];
958 if (isTypedArray(destination)) {
959 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
961 if (source === destination) {
962 throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
965 // Empty the destination object
966 if (isArray(destination)) {
967 destination.length = 0;
969 forEach(destination, function(value, key) {
970 if (key !== '$$hashKey') {
971 delete destination[key];
976 stackSource.push(source);
977 stackDest.push(destination);
978 return copyRecurse(source, destination);
981 return copyElement(source);
983 function copyRecurse(source, destination) {
984 var h = destination.$$hashKey;
986 if (isArray(source)) {
987 for (var i = 0, ii = source.length; i < ii; i++) {
988 destination.push(copyElement(source[i]));
990 } else if (isBlankObject(source)) {
991 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
992 for (key in source) {
993 destination[key] = copyElement(source[key]);
995 } else if (source && typeof source.hasOwnProperty === 'function') {
996 // Slow path, which must rely on hasOwnProperty
997 for (key in source) {
998 if (source.hasOwnProperty(key)) {
999 destination[key] = copyElement(source[key]);
1003 // Slowest path --- hasOwnProperty can't be called as a method
1004 for (key in source) {
1005 if (hasOwnProperty.call(source, key)) {
1006 destination[key] = copyElement(source[key]);
1010 setHashKey(destination, h);
1014 function copyElement(source) {
1016 if (!isObject(source)) {
1020 // Already copied values
1021 var index = stackSource.indexOf(source);
1023 return stackDest[index];
1026 if (isWindow(source) || isScope(source)) {
1027 throw ngMinErr('cpws',
1028 "Can't copy! Making copies of Window or Scope instances is not supported.");
1031 var needsRecurse = false;
1034 if (isArray(source)) {
1036 needsRecurse = true;
1037 } else if (isTypedArray(source)) {
1038 destination = new source.constructor(source);
1039 } else if (isDate(source)) {
1040 destination = new Date(source.getTime());
1041 } else if (isRegExp(source)) {
1042 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
1043 destination.lastIndex = source.lastIndex;
1044 } else if (isFunction(source.cloneNode)) {
1045 destination = source.cloneNode(true);
1047 destination = Object.create(getPrototypeOf(source));
1048 needsRecurse = true;
1051 stackSource.push(source);
1052 stackDest.push(destination);
1055 ? copyRecurse(source, destination)
1061 * Creates a shallow copy of an object, an array or a primitive.
1063 * Assumes that there are no proto properties for objects.
1065 function shallowCopy(src, dst) {
1069 for (var i = 0, ii = src.length; i < ii; i++) {
1072 } else if (isObject(src)) {
1075 for (var key in src) {
1076 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
1077 dst[key] = src[key];
1088 * @name angular.equals
1093 * Determines if two objects or two values are equivalent. Supports value types, regular
1094 * expressions, arrays and objects.
1096 * Two objects or values are considered equivalent if at least one of the following is true:
1098 * * Both objects or values pass `===` comparison.
1099 * * Both objects or values are of the same type and all of their properties are equal by
1100 * comparing them with `angular.equals`.
1101 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1102 * * Both values represent the same regular expression (In JavaScript,
1103 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1104 * representation matches).
1106 * During a property comparison, properties of `function` type and properties with names
1107 * that begin with `$` are ignored.
1109 * Scope and DOMWindow objects are being compared only by identify (`===`).
1111 * @param {*} o1 Object or value to compare.
1112 * @param {*} o2 Object or value to compare.
1113 * @returns {boolean} True if arguments are equal.
1115 function equals(o1, o2) {
1116 if (o1 === o2) return true;
1117 if (o1 === null || o2 === null) return false;
1118 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1119 var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
1121 if (t1 == 'object') {
1123 if (!isArray(o2)) return false;
1124 if ((length = o1.length) == o2.length) {
1125 for (key = 0; key < length; key++) {
1126 if (!equals(o1[key], o2[key])) return false;
1130 } else if (isDate(o1)) {
1131 if (!isDate(o2)) return false;
1132 return equals(o1.getTime(), o2.getTime());
1133 } else if (isRegExp(o1)) {
1134 return isRegExp(o2) ? o1.toString() == o2.toString() : false;
1136 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1137 isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1138 keySet = createMap();
1140 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1141 if (!equals(o1[key], o2[key])) return false;
1145 if (!(key in keySet) &&
1146 key.charAt(0) !== '$' &&
1147 isDefined(o2[key]) &&
1148 !isFunction(o2[key])) return false;
1157 var csp = function() {
1158 if (!isDefined(csp.rules)) {
1161 var ngCspElement = (document.querySelector('[ng-csp]') ||
1162 document.querySelector('[data-ng-csp]'));
1165 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1166 ngCspElement.getAttribute('data-ng-csp');
1168 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1169 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1173 noUnsafeEval: noUnsafeEval(),
1174 noInlineStyle: false
1181 function noUnsafeEval() {
1183 /* jshint -W031, -W054 */
1185 /* jshint +W031, +W054 */
1199 * @param {string=} ngJq the name of the library available under `window`
1200 * to be used for angular.element
1202 * Use this directive to force the angular.element library. This should be
1203 * used to force either jqLite by leaving ng-jq blank or setting the name of
1204 * the jquery variable under window (eg. jQuery).
1206 * Since angular looks for this directive when it is loaded (doesn't wait for the
1207 * DOMContentLoaded event), it must be placed on an element that comes before the script
1208 * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1212 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1221 * This example shows how to use a jQuery based library of a different name.
1222 * The library name must be available at the top most 'window'.
1225 <html ng-app ng-jq="jQueryLib">
1231 var jq = function() {
1232 if (isDefined(jq.name_)) return jq.name_;
1234 var i, ii = ngAttrPrefixes.length, prefix, name;
1235 for (i = 0; i < ii; ++i) {
1236 prefix = ngAttrPrefixes[i];
1237 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
1238 name = el.getAttribute(prefix + 'jq');
1243 return (jq.name_ = name);
1246 function concat(array1, array2, index) {
1247 return array1.concat(slice.call(array2, index));
1250 function sliceArgs(args, startIndex) {
1251 return slice.call(args, startIndex || 0);
1258 * @name angular.bind
1263 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1264 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1265 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1266 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1268 * @param {Object} self Context which `fn` should be evaluated in.
1269 * @param {function()} fn Function to be bound.
1270 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1271 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1274 function bind(self, fn) {
1275 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1276 if (isFunction(fn) && !(fn instanceof RegExp)) {
1277 return curryArgs.length
1279 return arguments.length
1280 ? fn.apply(self, concat(curryArgs, arguments, 0))
1281 : fn.apply(self, curryArgs);
1284 return arguments.length
1285 ? fn.apply(self, arguments)
1289 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
1295 function toJsonReplacer(key, value) {
1298 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1300 } else if (isWindow(value)) {
1302 } else if (value && document === value) {
1304 } else if (isScope(value)) {
1314 * @name angular.toJson
1319 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1320 * stripped since angular uses this notation internally.
1322 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
1323 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1324 * If set to an integer, the JSON output will contain that many spaces per indentation.
1325 * @returns {string|undefined} JSON-ified string representing `obj`.
1327 function toJson(obj, pretty) {
1328 if (typeof obj === 'undefined') return undefined;
1329 if (!isNumber(pretty)) {
1330 pretty = pretty ? 2 : null;
1332 return JSON.stringify(obj, toJsonReplacer, pretty);
1338 * @name angular.fromJson
1343 * Deserializes a JSON string.
1345 * @param {string} json JSON string to deserialize.
1346 * @returns {Object|Array|string|number} Deserialized JSON string.
1348 function fromJson(json) {
1349 return isString(json)
1355 function timezoneToOffset(timezone, fallback) {
1356 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1357 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1361 function addDateMinutes(date, minutes) {
1362 date = new Date(date.getTime());
1363 date.setMinutes(date.getMinutes() + minutes);
1368 function convertTimezoneToLocal(date, timezone, reverse) {
1369 reverse = reverse ? -1 : 1;
1370 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
1371 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
1376 * @returns {string} Returns the string representation of the element.
1378 function startingTag(element) {
1379 element = jqLite(element).clone();
1381 // turns out IE does not let you set .html() on elements which
1382 // are not allowed to have children. So we just ignore it.
1385 var elemHtml = jqLite('<div>').append(element).html();
1387 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1389 match(/^(<[^>]+>)/)[1].
1390 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
1392 return lowercase(elemHtml);
1398 /////////////////////////////////////////////////
1401 * Tries to decode the URI component without throwing an exception.
1404 * @param str value potential URI component to check.
1405 * @returns {boolean} True if `value` can be decoded
1406 * with the decodeURIComponent function.
1408 function tryDecodeURIComponent(value) {
1410 return decodeURIComponent(value);
1412 // Ignore any invalid uri component
1418 * Parses an escaped url query string into key-value pairs.
1419 * @returns {Object.<string,boolean|Array>}
1421 function parseKeyValue(/**string*/keyValue) {
1423 forEach((keyValue || "").split('&'), function(keyValue) {
1424 var splitPoint, key, val;
1426 key = keyValue = keyValue.replace(/\+/g,'%20');
1427 splitPoint = keyValue.indexOf('=');
1428 if (splitPoint !== -1) {
1429 key = keyValue.substring(0, splitPoint);
1430 val = keyValue.substring(splitPoint + 1);
1432 key = tryDecodeURIComponent(key);
1433 if (isDefined(key)) {
1434 val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1435 if (!hasOwnProperty.call(obj, key)) {
1437 } else if (isArray(obj[key])) {
1440 obj[key] = [obj[key],val];
1448 function toKeyValue(obj) {
1450 forEach(obj, function(value, key) {
1451 if (isArray(value)) {
1452 forEach(value, function(arrayValue) {
1453 parts.push(encodeUriQuery(key, true) +
1454 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1457 parts.push(encodeUriQuery(key, true) +
1458 (value === true ? '' : '=' + encodeUriQuery(value, true)));
1461 return parts.length ? parts.join('&') : '';
1466 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1467 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1470 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1471 * pct-encoded = "%" HEXDIG HEXDIG
1472 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1473 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1474 * / "*" / "+" / "," / ";" / "="
1476 function encodeUriSegment(val) {
1477 return encodeUriQuery(val, true).
1478 replace(/%26/gi, '&').
1479 replace(/%3D/gi, '=').
1480 replace(/%2B/gi, '+');
1485 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1486 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1487 * encoded per http://tools.ietf.org/html/rfc3986:
1488 * query = *( pchar / "/" / "?" )
1489 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1490 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1491 * pct-encoded = "%" HEXDIG HEXDIG
1492 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1493 * / "*" / "+" / "," / ";" / "="
1495 function encodeUriQuery(val, pctEncodeSpaces) {
1496 return encodeURIComponent(val).
1497 replace(/%40/gi, '@').
1498 replace(/%3A/gi, ':').
1499 replace(/%24/g, '$').
1500 replace(/%2C/gi, ',').
1501 replace(/%3B/gi, ';').
1502 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1505 var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1507 function getNgAttribute(element, ngAttr) {
1508 var attr, i, ii = ngAttrPrefixes.length;
1509 for (i = 0; i < ii; ++i) {
1510 attr = ngAttrPrefixes[i] + ngAttr;
1511 if (isString(attr = element.getAttribute(attr))) {
1524 * @param {angular.Module} ngApp an optional application
1525 * {@link angular.module module} name to load.
1526 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1527 * created in "strict-di" mode. This means that the application will fail to invoke functions which
1528 * do not use explicit function annotation (and are thus unsuitable for minification), as described
1529 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1530 * tracking down the root of these bugs.
1534 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1535 * designates the **root element** of the application and is typically placed near the root element
1536 * of the page - e.g. on the `<body>` or `<html>` tags.
1538 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1539 * found in the document will be used to define the root element to auto-bootstrap as an
1540 * application. To run multiple applications in an HTML document you must manually bootstrap them using
1541 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
1543 * You can specify an **AngularJS module** to be used as the root module for the application. This
1544 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1545 * should contain the application code needed or have dependencies on other modules that will
1546 * contain the code. See {@link angular.module} for more information.
1548 * In the example below if the `ngApp` directive were not placed on the `html` element then the
1549 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1550 * would not be resolved to `3`.
1552 * `ngApp` is the easiest, and most common way to bootstrap an application.
1554 <example module="ngAppDemo">
1555 <file name="index.html">
1556 <div ng-controller="ngAppDemoController">
1557 I can add: {{a}} + {{b}} = {{ a+b }}
1560 <file name="script.js">
1561 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1568 * Using `ngStrictDi`, you would see something like this:
1570 <example ng-app-included="true">
1571 <file name="index.html">
1572 <div ng-app="ngAppStrictDemo" ng-strict-di>
1573 <div ng-controller="GoodController1">
1574 I can add: {{a}} + {{b}} = {{ a+b }}
1576 <p>This renders because the controller does not fail to
1577 instantiate, by using explicit annotation style (see
1578 script.js for details)
1582 <div ng-controller="GoodController2">
1583 Name: <input ng-model="name"><br />
1586 <p>This renders because the controller does not fail to
1587 instantiate, by using explicit annotation style
1588 (see script.js for details)
1592 <div ng-controller="BadController">
1593 I can add: {{a}} + {{b}} = {{ a+b }}
1595 <p>The controller could not be instantiated, due to relying
1596 on automatic function annotations (which are disabled in
1597 strict mode). As such, the content of this section is not
1598 interpolated, and there should be an error in your web console.
1603 <file name="script.js">
1604 angular.module('ngAppStrictDemo', [])
1605 // BadController will fail to instantiate, due to relying on automatic function annotation,
1606 // rather than an explicit annotation
1607 .controller('BadController', function($scope) {
1611 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1612 // due to using explicit annotations using the array style and $inject property, respectively.
1613 .controller('GoodController1', ['$scope', function($scope) {
1617 .controller('GoodController2', GoodController2);
1618 function GoodController2($scope) {
1619 $scope.name = "World";
1621 GoodController2.$inject = ['$scope'];
1623 <file name="style.css">
1624 div[ng-controller] {
1626 -webkit-border-radius: 4px;
1631 div[ng-controller^=Good] {
1632 border-color: #d6e9c6;
1633 background-color: #dff0d8;
1636 div[ng-controller^=Bad] {
1637 border-color: #ebccd1;
1638 background-color: #f2dede;
1645 function angularInit(element, bootstrap) {
1650 // The element `element` has priority over any other element
1651 forEach(ngAttrPrefixes, function(prefix) {
1652 var name = prefix + 'app';
1654 if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1655 appElement = element;
1656 module = element.getAttribute(name);
1659 forEach(ngAttrPrefixes, function(prefix) {
1660 var name = prefix + 'app';
1663 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1664 appElement = candidate;
1665 module = candidate.getAttribute(name);
1669 config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
1670 bootstrap(appElement, module ? [module] : [], config);
1676 * @name angular.bootstrap
1679 * Use this function to manually start up angular application.
1681 * See: {@link guide/bootstrap Bootstrap}
1683 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
1684 * They must use {@link ng.directive:ngApp ngApp}.
1686 * Angular will detect if it has been loaded into the browser more than once and only allow the
1687 * first loaded script to be bootstrapped and will report a warning to the browser console for
1688 * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1689 * multiple instances of Angular try to work on the DOM.
1695 * <div ng-controller="WelcomeController">
1699 * <script src="angular.js"></script>
1701 * var app = angular.module('demo', [])
1702 * .controller('WelcomeController', function($scope) {
1703 * $scope.greeting = 'Welcome!';
1705 * angular.bootstrap(document, ['demo']);
1711 * @param {DOMElement} element DOM element which is the root of angular application.
1712 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1713 * Each item in the array should be the name of a predefined module or a (DI annotated)
1714 * function that will be invoked by the injector as a `config` block.
1715 * See: {@link angular.module modules}
1716 * @param {Object=} config an object for defining configuration options for the application. The
1717 * following keys are supported:
1719 * * `strictDi` - disable automatic function annotation for the application. This is meant to
1720 * assist in finding bugs which break minified code. Defaults to `false`.
1722 * @returns {auto.$injector} Returns the newly created injector for this app.
1724 function bootstrap(element, modules, config) {
1725 if (!isObject(config)) config = {};
1726 var defaultConfig = {
1729 config = extend(defaultConfig, config);
1730 var doBootstrap = function() {
1731 element = jqLite(element);
1733 if (element.injector()) {
1734 var tag = (element[0] === document) ? 'document' : startingTag(element);
1735 //Encode angle brackets to prevent input from being sanitized to empty string #8683
1738 "App Already Bootstrapped with this Element '{0}'",
1739 tag.replace(/</,'<').replace(/>/,'>'));
1742 modules = modules || [];
1743 modules.unshift(['$provide', function($provide) {
1744 $provide.value('$rootElement', element);
1747 if (config.debugInfoEnabled) {
1748 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1749 modules.push(['$compileProvider', function($compileProvider) {
1750 $compileProvider.debugInfoEnabled(true);
1754 modules.unshift('ng');
1755 var injector = createInjector(modules, config.strictDi);
1756 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1757 function bootstrapApply(scope, element, compile, injector) {
1758 scope.$apply(function() {
1759 element.data('$injector', injector);
1760 compile(element)(scope);
1767 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1768 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1770 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1771 config.debugInfoEnabled = true;
1772 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1775 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1776 return doBootstrap();
1779 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1780 angular.resumeBootstrap = function(extraModules) {
1781 forEach(extraModules, function(module) {
1782 modules.push(module);
1784 return doBootstrap();
1787 if (isFunction(angular.resumeDeferredBootstrap)) {
1788 angular.resumeDeferredBootstrap();
1794 * @name angular.reloadWithDebugInfo
1797 * Use this function to reload the current application with debug information turned on.
1798 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
1800 * See {@link ng.$compileProvider#debugInfoEnabled} for more.
1802 function reloadWithDebugInfo() {
1803 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
1804 window.location.reload();
1808 * @name angular.getTestability
1811 * Get the testability service for the instance of Angular on the given
1813 * @param {DOMElement} element DOM element which is the root of angular application.
1815 function getTestability(rootElement) {
1816 var injector = angular.element(rootElement).injector();
1818 throw ngMinErr('test',
1819 'no injector found for element argument to getTestability');
1821 return injector.get('$$testability');
1824 var SNAKE_CASE_REGEXP = /[A-Z]/g;
1825 function snake_case(name, separator) {
1826 separator = separator || '_';
1827 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
1828 return (pos ? separator : '') + letter.toLowerCase();
1832 var bindJQueryFired = false;
1833 var skipDestroyOnNextJQueryCleanData;
1834 function bindJQuery() {
1835 var originalCleanData;
1837 if (bindJQueryFired) {
1841 // bind to jQuery if present;
1843 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
1844 !jqName ? undefined : // use jqLite
1845 window[jqName]; // use jQuery specified by `ngJq`
1847 // Use jQuery if it exists with proper functionality, otherwise default to us.
1848 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
1849 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
1850 // versions. It will not work for sure with jQuery <1.7, though.
1851 if (jQuery && jQuery.fn.on) {
1854 scope: JQLitePrototype.scope,
1855 isolateScope: JQLitePrototype.isolateScope,
1856 controller: JQLitePrototype.controller,
1857 injector: JQLitePrototype.injector,
1858 inheritedData: JQLitePrototype.inheritedData
1861 // All nodes removed from the DOM via various jQuery APIs like .remove()
1862 // are passed through jQuery.cleanData. Monkey-patch this method to fire
1863 // the $destroy event on all removed nodes.
1864 originalCleanData = jQuery.cleanData;
1865 jQuery.cleanData = function(elems) {
1867 if (!skipDestroyOnNextJQueryCleanData) {
1868 for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1869 events = jQuery._data(elem, "events");
1870 if (events && events.$destroy) {
1871 jQuery(elem).triggerHandler('$destroy');
1875 skipDestroyOnNextJQueryCleanData = false;
1877 originalCleanData(elems);
1883 angular.element = jqLite;
1885 // Prevent double-proxying.
1886 bindJQueryFired = true;
1890 * throw error if the argument is falsy.
1892 function assertArg(arg, name, reason) {
1894 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
1899 function assertArgFn(arg, name, acceptArrayAnnotation) {
1900 if (acceptArrayAnnotation && isArray(arg)) {
1901 arg = arg[arg.length - 1];
1904 assertArg(isFunction(arg), name, 'not a function, got ' +
1905 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
1910 * throw error if the name given is hasOwnProperty
1911 * @param {String} name the name to test
1912 * @param {String} context the context in which the name is used, such as module or directive
1914 function assertNotHasOwnProperty(name, context) {
1915 if (name === 'hasOwnProperty') {
1916 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
1921 * Return the value accessible from the object by path. Any undefined traversals are ignored
1922 * @param {Object} obj starting object
1923 * @param {String} path path to traverse
1924 * @param {boolean} [bindFnToScope=true]
1925 * @returns {Object} value as accessible by path
1927 //TODO(misko): this function needs to be removed
1928 function getter(obj, path, bindFnToScope) {
1929 if (!path) return obj;
1930 var keys = path.split('.');
1932 var lastInstance = obj;
1933 var len = keys.length;
1935 for (var i = 0; i < len; i++) {
1938 obj = (lastInstance = obj)[key];
1941 if (!bindFnToScope && isFunction(obj)) {
1942 return bind(lastInstance, obj);
1948 * Return the DOM siblings between the first and last node in the given array.
1949 * @param {Array} array like object
1950 * @returns {Array} the inputted object or a jqLite collection containing the nodes
1952 function getBlockNodes(nodes) {
1953 // TODO(perf): update `nodes` instead of creating a new object?
1954 var node = nodes[0];
1955 var endNode = nodes[nodes.length - 1];
1958 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
1959 if (blockNodes || nodes[i] !== node) {
1961 blockNodes = jqLite(slice.call(nodes, 0, i));
1963 blockNodes.push(node);
1967 return blockNodes || nodes;
1972 * Creates a new object without a prototype. This object is useful for lookup without having to
1973 * guard against prototypically inherited properties via hasOwnProperty.
1975 * Related micro-benchmarks:
1976 * - http://jsperf.com/object-create2
1977 * - http://jsperf.com/proto-map-lookup/2
1978 * - http://jsperf.com/for-in-vs-object-keys2
1982 function createMap() {
1983 return Object.create(null);
1986 var NODE_TYPE_ELEMENT = 1;
1987 var NODE_TYPE_ATTRIBUTE = 2;
1988 var NODE_TYPE_TEXT = 3;
1989 var NODE_TYPE_COMMENT = 8;
1990 var NODE_TYPE_DOCUMENT = 9;
1991 var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
1995 * @name angular.Module
1999 * Interface for configuring angular {@link angular.module modules}.
2002 function setupModuleLoader(window) {
2004 var $injectorMinErr = minErr('$injector');
2005 var ngMinErr = minErr('ng');
2007 function ensure(obj, name, factory) {
2008 return obj[name] || (obj[name] = factory());
2011 var angular = ensure(window, 'angular', Object);
2013 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
2014 angular.$$minErr = angular.$$minErr || minErr;
2016 return ensure(angular, 'module', function() {
2017 /** @type {Object.<string, angular.Module>} */
2022 * @name angular.module
2026 * The `angular.module` is a global place for creating, registering and retrieving Angular
2028 * All modules (angular core or 3rd party) that should be available to an application must be
2029 * registered using this mechanism.
2031 * Passing one argument retrieves an existing {@link angular.Module},
2032 * whereas passing more than one argument creates a new {@link angular.Module}
2037 * A module is a collection of services, directives, controllers, filters, and configuration information.
2038 * `angular.module` is used to configure the {@link auto.$injector $injector}.
2041 * // Create a new module
2042 * var myModule = angular.module('myModule', []);
2044 * // register a new service
2045 * myModule.value('appName', 'MyCoolApp');
2047 * // configure existing services inside initialization blocks.
2048 * myModule.config(['$locationProvider', function($locationProvider) {
2049 * // Configure existing providers
2050 * $locationProvider.hashPrefix('!');
2054 * Then you can create an injector and load your modules like this:
2057 * var injector = angular.injector(['ng', 'myModule'])
2060 * However it's more likely that you'll just use
2061 * {@link ng.directive:ngApp ngApp} or
2062 * {@link angular.bootstrap} to simplify this process for you.
2064 * @param {!string} name The name of the module to create or retrieve.
2065 * @param {!Array.<string>=} requires If specified then new module is being created. If
2066 * unspecified then the module is being retrieved for further configuration.
2067 * @param {Function=} configFn Optional configuration function for the module. Same as
2068 * {@link angular.Module#config Module#config()}.
2069 * @returns {module} new module with the {@link angular.Module} api.
2071 return function module(name, requires, configFn) {
2072 var assertNotHasOwnProperty = function(name, context) {
2073 if (name === 'hasOwnProperty') {
2074 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2078 assertNotHasOwnProperty(name, 'module');
2079 if (requires && modules.hasOwnProperty(name)) {
2080 modules[name] = null;
2082 return ensure(modules, name, function() {
2084 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
2085 "the module name or forgot to load it. If registering a module ensure that you " +
2086 "specify the dependencies as the second argument.", name);
2089 /** @type {!Array.<Array.<*>>} */
2090 var invokeQueue = [];
2092 /** @type {!Array.<Function>} */
2093 var configBlocks = [];
2095 /** @type {!Array.<Function>} */
2098 var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2100 /** @type {angular.Module} */
2101 var moduleInstance = {
2103 _invokeQueue: invokeQueue,
2104 _configBlocks: configBlocks,
2105 _runBlocks: runBlocks,
2109 * @name angular.Module#requires
2113 * Holds the list of modules which the injector will load before the current module is
2120 * @name angular.Module#name
2124 * Name of the module.
2131 * @name angular.Module#provider
2133 * @param {string} name service name
2134 * @param {Function} providerType Construction function for creating new instance of the
2137 * See {@link auto.$provide#provider $provide.provider()}.
2139 provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2143 * @name angular.Module#factory
2145 * @param {string} name service name
2146 * @param {Function} providerFunction Function for creating new instance of the service.
2148 * See {@link auto.$provide#factory $provide.factory()}.
2150 factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2154 * @name angular.Module#service
2156 * @param {string} name service name
2157 * @param {Function} constructor A constructor function that will be instantiated.
2159 * See {@link auto.$provide#service $provide.service()}.
2161 service: invokeLaterAndSetModuleName('$provide', 'service'),
2165 * @name angular.Module#value
2167 * @param {string} name service name
2168 * @param {*} object Service instance object.
2170 * See {@link auto.$provide#value $provide.value()}.
2172 value: invokeLater('$provide', 'value'),
2176 * @name angular.Module#constant
2178 * @param {string} name constant name
2179 * @param {*} object Constant value.
2181 * Because the constants are fixed, they get applied before other provide methods.
2182 * See {@link auto.$provide#constant $provide.constant()}.
2184 constant: invokeLater('$provide', 'constant', 'unshift'),
2188 * @name angular.Module#decorator
2190 * @param {string} The name of the service to decorate.
2191 * @param {Function} This function will be invoked when the service needs to be
2192 * instantiated and should return the decorated service instance.
2194 * See {@link auto.$provide#decorator $provide.decorator()}.
2196 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
2200 * @name angular.Module#animation
2202 * @param {string} name animation name
2203 * @param {Function} animationFactory Factory function for creating new instance of an
2207 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2210 * Defines an animation hook that can be later used with
2211 * {@link $animate $animate} service and directives that use this service.
2214 * module.animation('.animation-name', function($inject1, $inject2) {
2216 * eventName : function(element, done) {
2217 * //code to run the animation
2218 * //once complete, then run done()
2219 * return function cancellationFunction(element) {
2220 * //code to cancel the animation
2227 * See {@link ng.$animateProvider#register $animateProvider.register()} and
2228 * {@link ngAnimate ngAnimate module} for more information.
2230 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2234 * @name angular.Module#filter
2236 * @param {string} name Filter name - this must be a valid angular expression identifier
2237 * @param {Function} filterFactory Factory function for creating new instance of filter.
2239 * See {@link ng.$filterProvider#register $filterProvider.register()}.
2241 * <div class="alert alert-warning">
2242 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
2243 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2244 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2245 * (`myapp_subsection_filterx`).
2248 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2252 * @name angular.Module#controller
2254 * @param {string|Object} name Controller name, or an object map of controllers where the
2255 * keys are the names and the values are the constructors.
2256 * @param {Function} constructor Controller constructor function.
2258 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2260 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2264 * @name angular.Module#directive
2266 * @param {string|Object} name Directive name, or an object map of directives where the
2267 * keys are the names and the values are the factories.
2268 * @param {Function} directiveFactory Factory function for creating new instance of
2271 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2273 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2277 * @name angular.Module#config
2279 * @param {Function} configFn Execute this function on module load. Useful for service
2282 * Use this method to register work which needs to be performed on module loading.
2283 * For more about how to configure services, see
2284 * {@link providers#provider-recipe Provider Recipe}.
2290 * @name angular.Module#run
2292 * @param {Function} initializationFn Execute this function after injector creation.
2293 * Useful for application initialization.
2295 * Use this method to register work which should be performed when the injector is done
2296 * loading all modules.
2298 run: function(block) {
2299 runBlocks.push(block);
2308 return moduleInstance;
2311 * @param {string} provider
2312 * @param {string} method
2313 * @param {String=} insertMethod
2314 * @returns {angular.Module}
2316 function invokeLater(provider, method, insertMethod, queue) {
2317 if (!queue) queue = invokeQueue;
2319 queue[insertMethod || 'push']([provider, method, arguments]);
2320 return moduleInstance;
2325 * @param {string} provider
2326 * @param {string} method
2327 * @returns {angular.Module}
2329 function invokeLaterAndSetModuleName(provider, method) {
2330 return function(recipeName, factoryFunction) {
2331 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
2332 invokeQueue.push([provider, method, arguments]);
2333 return moduleInstance;
2342 /* global: toDebugString: true */
2344 function serializeObject(obj) {
2347 return JSON.stringify(obj, function(key, val) {
2348 val = toJsonReplacer(key, val);
2349 if (isObject(val)) {
2351 if (seen.indexOf(val) >= 0) return '...';
2359 function toDebugString(obj) {
2360 if (typeof obj === 'function') {
2361 return obj.toString().replace(/ \{[\s\S]*$/, '');
2362 } else if (isUndefined(obj)) {
2364 } else if (typeof obj !== 'string') {
2365 return serializeObject(obj);
2370 /* global angularModule: true,
2375 htmlAnchorDirective,
2384 ngBindHtmlDirective,
2385 ngBindTemplateDirective,
2387 ngClassEvenDirective,
2388 ngClassOddDirective,
2390 ngControllerDirective,
2395 ngIncludeFillContentDirective,
2397 ngNonBindableDirective,
2398 ngPluralizeDirective,
2403 ngSwitchWhenDirective,
2404 ngSwitchDefaultDirective,
2406 ngTranscludeDirective,
2419 ngModelOptionsDirective,
2420 ngAttributeAliasDirectives,
2423 $AnchorScrollProvider,
2425 $CoreAnimateCssProvider,
2426 $$CoreAnimateQueueProvider,
2427 $$CoreAnimateRunnerProvider,
2429 $CacheFactoryProvider,
2430 $ControllerProvider,
2432 $ExceptionHandlerProvider,
2434 $$ForceReflowProvider,
2435 $InterpolateProvider,
2439 $HttpParamSerializerProvider,
2440 $HttpParamSerializerJQLikeProvider,
2441 $HttpBackendProvider,
2442 $xhrFactoryProvider,
2449 $$SanitizeUriProvider,
2451 $SceDelegateProvider,
2453 $TemplateCacheProvider,
2454 $TemplateRequestProvider,
2455 $$TestabilityProvider,
2460 $$CookieReaderProvider
2466 * @name angular.version
2469 * An object that contains information about the current AngularJS version.
2471 * This object has the following properties:
2473 * - `full` – `{string}` – Full version string, such as "0.9.18".
2474 * - `major` – `{number}` – Major version number, such as "0".
2475 * - `minor` – `{number}` – Minor version number, such as "9".
2476 * - `dot` – `{number}` – Dot version number, such as "18".
2477 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2480 full: '1.4.8', // all of these placeholder strings will be replaced by grunt's
2481 major: 1, // package task
2484 codeName: 'ice-manipulation'
2488 function publishExternalAPI(angular) {
2490 'bootstrap': bootstrap,
2497 'injector': createInjector,
2501 'fromJson': fromJson,
2502 'identity': identity,
2503 'isUndefined': isUndefined,
2504 'isDefined': isDefined,
2505 'isString': isString,
2506 'isFunction': isFunction,
2507 'isObject': isObject,
2508 'isNumber': isNumber,
2509 'isElement': isElement,
2513 'lowercase': lowercase,
2514 'uppercase': uppercase,
2515 'callbacks': {counter: 0},
2516 'getTestability': getTestability,
2519 'reloadWithDebugInfo': reloadWithDebugInfo
2522 angularModule = setupModuleLoader(window);
2524 angularModule('ng', ['ngLocale'], ['$provide',
2525 function ngModule($provide) {
2526 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2528 $$sanitizeUri: $$SanitizeUriProvider
2530 $provide.provider('$compile', $CompileProvider).
2532 a: htmlAnchorDirective,
2533 input: inputDirective,
2534 textarea: inputDirective,
2535 form: formDirective,
2536 script: scriptDirective,
2537 select: selectDirective,
2538 style: styleDirective,
2539 option: optionDirective,
2540 ngBind: ngBindDirective,
2541 ngBindHtml: ngBindHtmlDirective,
2542 ngBindTemplate: ngBindTemplateDirective,
2543 ngClass: ngClassDirective,
2544 ngClassEven: ngClassEvenDirective,
2545 ngClassOdd: ngClassOddDirective,
2546 ngCloak: ngCloakDirective,
2547 ngController: ngControllerDirective,
2548 ngForm: ngFormDirective,
2549 ngHide: ngHideDirective,
2550 ngIf: ngIfDirective,
2551 ngInclude: ngIncludeDirective,
2552 ngInit: ngInitDirective,
2553 ngNonBindable: ngNonBindableDirective,
2554 ngPluralize: ngPluralizeDirective,
2555 ngRepeat: ngRepeatDirective,
2556 ngShow: ngShowDirective,
2557 ngStyle: ngStyleDirective,
2558 ngSwitch: ngSwitchDirective,
2559 ngSwitchWhen: ngSwitchWhenDirective,
2560 ngSwitchDefault: ngSwitchDefaultDirective,
2561 ngOptions: ngOptionsDirective,
2562 ngTransclude: ngTranscludeDirective,
2563 ngModel: ngModelDirective,
2564 ngList: ngListDirective,
2565 ngChange: ngChangeDirective,
2566 pattern: patternDirective,
2567 ngPattern: patternDirective,
2568 required: requiredDirective,
2569 ngRequired: requiredDirective,
2570 minlength: minlengthDirective,
2571 ngMinlength: minlengthDirective,
2572 maxlength: maxlengthDirective,
2573 ngMaxlength: maxlengthDirective,
2574 ngValue: ngValueDirective,
2575 ngModelOptions: ngModelOptionsDirective
2578 ngInclude: ngIncludeFillContentDirective
2580 directive(ngAttributeAliasDirectives).
2581 directive(ngEventDirectives);
2583 $anchorScroll: $AnchorScrollProvider,
2584 $animate: $AnimateProvider,
2585 $animateCss: $CoreAnimateCssProvider,
2586 $$animateQueue: $$CoreAnimateQueueProvider,
2587 $$AnimateRunner: $$CoreAnimateRunnerProvider,
2588 $browser: $BrowserProvider,
2589 $cacheFactory: $CacheFactoryProvider,
2590 $controller: $ControllerProvider,
2591 $document: $DocumentProvider,
2592 $exceptionHandler: $ExceptionHandlerProvider,
2593 $filter: $FilterProvider,
2594 $$forceReflow: $$ForceReflowProvider,
2595 $interpolate: $InterpolateProvider,
2596 $interval: $IntervalProvider,
2597 $http: $HttpProvider,
2598 $httpParamSerializer: $HttpParamSerializerProvider,
2599 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2600 $httpBackend: $HttpBackendProvider,
2601 $xhrFactory: $xhrFactoryProvider,
2602 $location: $LocationProvider,
2604 $parse: $ParseProvider,
2605 $rootScope: $RootScopeProvider,
2609 $sceDelegate: $SceDelegateProvider,
2610 $sniffer: $SnifferProvider,
2611 $templateCache: $TemplateCacheProvider,
2612 $templateRequest: $TemplateRequestProvider,
2613 $$testability: $$TestabilityProvider,
2614 $timeout: $TimeoutProvider,
2615 $window: $WindowProvider,
2616 $$rAF: $$RAFProvider,
2617 $$jqLite: $$jqLiteProvider,
2618 $$HashMap: $$HashMapProvider,
2619 $$cookieReader: $$CookieReaderProvider
2625 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2626 * Any commits to this file should be reviewed with security in mind. *
2627 * Changes to this file can potentially create security vulnerabilities. *
2628 * An approval from 2 Core members with history of modifying *
2629 * this file is required. *
2631 * Does the change somehow allow for arbitrary javascript to be executed? *
2632 * Or allows for someone to change the prototype of built-in objects? *
2633 * Or gives undesired access to variables likes document or window? *
2634 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2636 /* global JQLitePrototype: true,
2637 addEventListenerFn: true,
2638 removeEventListenerFn: true,
2643 //////////////////////////////////
2645 //////////////////////////////////
2649 * @name angular.element
2654 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
2656 * If jQuery is available, `angular.element` is an alias for the
2657 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2658 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
2660 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
2661 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
2662 * commonly needed functionality with the goal of having a very small footprint.</div>
2664 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
2666 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
2667 * jqLite; they are never raw DOM references.</div>
2669 * ## Angular's jqLite
2670 * jqLite provides only the following jQuery methods:
2672 * - [`addClass()`](http://api.jquery.com/addClass/)
2673 * - [`after()`](http://api.jquery.com/after/)
2674 * - [`append()`](http://api.jquery.com/append/)
2675 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
2676 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
2677 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2678 * - [`clone()`](http://api.jquery.com/clone/)
2679 * - [`contents()`](http://api.jquery.com/contents/)
2680 * - [`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'.
2681 * - [`data()`](http://api.jquery.com/data/)
2682 * - [`detach()`](http://api.jquery.com/detach/)
2683 * - [`empty()`](http://api.jquery.com/empty/)
2684 * - [`eq()`](http://api.jquery.com/eq/)
2685 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
2686 * - [`hasClass()`](http://api.jquery.com/hasClass/)
2687 * - [`html()`](http://api.jquery.com/html/)
2688 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
2689 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2690 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
2691 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
2692 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
2693 * - [`prepend()`](http://api.jquery.com/prepend/)
2694 * - [`prop()`](http://api.jquery.com/prop/)
2695 * - [`ready()`](http://api.jquery.com/ready/)
2696 * - [`remove()`](http://api.jquery.com/remove/)
2697 * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
2698 * - [`removeClass()`](http://api.jquery.com/removeClass/)
2699 * - [`removeData()`](http://api.jquery.com/removeData/)
2700 * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
2701 * - [`text()`](http://api.jquery.com/text/)
2702 * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
2703 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
2704 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter
2705 * - [`val()`](http://api.jquery.com/val/)
2706 * - [`wrap()`](http://api.jquery.com/wrap/)
2708 * ## jQuery/jqLite Extras
2709 * Angular also provides the following additional methods and events to both jQuery and jqLite:
2712 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
2713 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
2714 * element before it is removed.
2717 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
2718 * retrieves controller associated with the `ngController` directive. If `name` is provided as
2719 * camelCase directive name, then the controller for this directive will be retrieved (e.g.
2721 * - `injector()` - retrieves the injector of the current element or its parent.
2722 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
2723 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
2725 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
2726 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2727 * scope. Calling `scope()` on this element always returns the original non-isolate scope.
2728 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
2729 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
2730 * parent element is reached.
2732 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
2733 * @returns {Object} jQuery object.
2736 JQLite.expando = 'ng339';
2738 var jqCache = JQLite.cache = {},
2740 addEventListenerFn = function(element, type, fn) {
2741 element.addEventListener(type, fn, false);
2743 removeEventListenerFn = function(element, type, fn) {
2744 element.removeEventListener(type, fn, false);
2748 * !!! This is an undocumented "private" function !!!
2750 JQLite._data = function(node) {
2751 //jQuery always returns an object on cache miss
2752 return this.cache[node[this.expando]] || {};
2755 function jqNextId() { return ++jqId; }
2758 var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
2759 var MOZ_HACK_REGEXP = /^moz([A-Z])/;
2760 var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
2761 var jqLiteMinErr = minErr('jqLite');
2764 * Converts snake_case to camelCase.
2765 * Also there is special case for Moz prefix starting with upper case letter.
2766 * @param name Name to normalize
2768 function camelCase(name) {
2770 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
2771 return offset ? letter.toUpperCase() : letter;
2773 replace(MOZ_HACK_REGEXP, 'Moz$1');
2776 var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
2777 var HTML_REGEXP = /<|&#?\w+;/;
2778 var TAG_NAME_REGEXP = /<([\w:-]+)/;
2779 var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
2782 'option': [1, '<select multiple="multiple">', '</select>'],
2784 'thead': [1, '<table>', '</table>'],
2785 'col': [2, '<table><colgroup>', '</colgroup></table>'],
2786 'tr': [2, '<table><tbody>', '</tbody></table>'],
2787 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
2788 '_default': [0, "", ""]
2791 wrapMap.optgroup = wrapMap.option;
2792 wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
2793 wrapMap.th = wrapMap.td;
2796 function jqLiteIsTextNode(html) {
2797 return !HTML_REGEXP.test(html);
2800 function jqLiteAcceptsData(node) {
2801 // The window object can accept data but has no nodeType
2802 // Otherwise we are only interested in elements (1) and documents (9)
2803 var nodeType = node.nodeType;
2804 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
2807 function jqLiteHasData(node) {
2808 for (var key in jqCache[node.ng339]) {
2814 function jqLiteBuildFragment(html, context) {
2816 fragment = context.createDocumentFragment(),
2819 if (jqLiteIsTextNode(html)) {
2820 // Convert non-html into a text node
2821 nodes.push(context.createTextNode(html));
2823 // Convert html into DOM nodes
2824 tmp = tmp || fragment.appendChild(context.createElement("div"));
2825 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
2826 wrap = wrapMap[tag] || wrapMap._default;
2827 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
2829 // Descend through wrappers to the right content
2832 tmp = tmp.lastChild;
2835 nodes = concat(nodes, tmp.childNodes);
2837 tmp = fragment.firstChild;
2838 tmp.textContent = "";
2841 // Remove wrapper from fragment
2842 fragment.textContent = "";
2843 fragment.innerHTML = ""; // Clear inner HTML
2844 forEach(nodes, function(node) {
2845 fragment.appendChild(node);
2851 function jqLiteParseHTML(html, context) {
2852 context = context || document;
2855 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
2856 return [context.createElement(parsed[1])];
2859 if ((parsed = jqLiteBuildFragment(html, context))) {
2860 return parsed.childNodes;
2867 // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2868 var jqLiteContains = Node.prototype.contains || function(arg) {
2869 // jshint bitwise: false
2870 return !!(this.compareDocumentPosition(arg) & 16);
2871 // jshint bitwise: true
2874 /////////////////////////////////////////////
2875 function JQLite(element) {
2876 if (element instanceof JQLite) {
2882 if (isString(element)) {
2883 element = trim(element);
2886 if (!(this instanceof JQLite)) {
2887 if (argIsString && element.charAt(0) != '<') {
2888 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
2890 return new JQLite(element);
2894 jqLiteAddNodes(this, jqLiteParseHTML(element));
2896 jqLiteAddNodes(this, element);
2900 function jqLiteClone(element) {
2901 return element.cloneNode(true);
2904 function jqLiteDealoc(element, onlyDescendants) {
2905 if (!onlyDescendants) jqLiteRemoveData(element);
2907 if (element.querySelectorAll) {
2908 var descendants = element.querySelectorAll('*');
2909 for (var i = 0, l = descendants.length; i < l; i++) {
2910 jqLiteRemoveData(descendants[i]);
2915 function jqLiteOff(element, type, fn, unsupported) {
2916 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
2918 var expandoStore = jqLiteExpandoStore(element);
2919 var events = expandoStore && expandoStore.events;
2920 var handle = expandoStore && expandoStore.handle;
2922 if (!handle) return; //no listeners registered
2925 for (type in events) {
2926 if (type !== '$destroy') {
2927 removeEventListenerFn(element, type, handle);
2929 delete events[type];
2933 var removeHandler = function(type) {
2934 var listenerFns = events[type];
2935 if (isDefined(fn)) {
2936 arrayRemove(listenerFns || [], fn);
2938 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
2939 removeEventListenerFn(element, type, handle);
2940 delete events[type];
2944 forEach(type.split(' '), function(type) {
2945 removeHandler(type);
2946 if (MOUSE_EVENT_MAP[type]) {
2947 removeHandler(MOUSE_EVENT_MAP[type]);
2953 function jqLiteRemoveData(element, name) {
2954 var expandoId = element.ng339;
2955 var expandoStore = expandoId && jqCache[expandoId];
2959 delete expandoStore.data[name];
2963 if (expandoStore.handle) {
2964 if (expandoStore.events.$destroy) {
2965 expandoStore.handle({}, '$destroy');
2969 delete jqCache[expandoId];
2970 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
2975 function jqLiteExpandoStore(element, createIfNecessary) {
2976 var expandoId = element.ng339,
2977 expandoStore = expandoId && jqCache[expandoId];
2979 if (createIfNecessary && !expandoStore) {
2980 element.ng339 = expandoId = jqNextId();
2981 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
2984 return expandoStore;
2988 function jqLiteData(element, key, value) {
2989 if (jqLiteAcceptsData(element)) {
2991 var isSimpleSetter = isDefined(value);
2992 var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
2993 var massGetter = !key;
2994 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
2995 var data = expandoStore && expandoStore.data;
2997 if (isSimpleSetter) { // data('key', value)
3000 if (massGetter) { // data()
3003 if (isSimpleGetter) { // data('key')
3004 // don't force creation of expandoStore if it doesn't exist yet
3005 return data && data[key];
3006 } else { // mass-setter: data({key1: val1, key2: val2})
3014 function jqLiteHasClass(element, selector) {
3015 if (!element.getAttribute) return false;
3016 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
3017 indexOf(" " + selector + " ") > -1);
3020 function jqLiteRemoveClass(element, cssClasses) {
3021 if (cssClasses && element.setAttribute) {
3022 forEach(cssClasses.split(' '), function(cssClass) {
3023 element.setAttribute('class', trim(
3024 (" " + (element.getAttribute('class') || '') + " ")
3025 .replace(/[\n\t]/g, " ")
3026 .replace(" " + trim(cssClass) + " ", " "))
3032 function jqLiteAddClass(element, cssClasses) {
3033 if (cssClasses && element.setAttribute) {
3034 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
3035 .replace(/[\n\t]/g, " ");
3037 forEach(cssClasses.split(' '), function(cssClass) {
3038 cssClass = trim(cssClass);
3039 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
3040 existingClasses += cssClass + ' ';
3044 element.setAttribute('class', trim(existingClasses));
3049 function jqLiteAddNodes(root, elements) {
3050 // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
3054 // if a Node (the most common case)
3055 if (elements.nodeType) {
3056 root[root.length++] = elements;
3058 var length = elements.length;
3060 // if an Array or NodeList and not a Window
3061 if (typeof length === 'number' && elements.window !== elements) {
3063 for (var i = 0; i < length; i++) {
3064 root[root.length++] = elements[i];
3068 root[root.length++] = elements;
3075 function jqLiteController(element, name) {
3076 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
3079 function jqLiteInheritedData(element, name, value) {
3080 // if element is the document object work with the html element instead
3081 // this makes $(document).scope() possible
3082 if (element.nodeType == NODE_TYPE_DOCUMENT) {
3083 element = element.documentElement;
3085 var names = isArray(name) ? name : [name];
3088 for (var i = 0, ii = names.length; i < ii; i++) {
3089 if (isDefined(value = jqLite.data(element, names[i]))) return value;
3092 // If dealing with a document fragment node with a host element, and no parent, use the host
3093 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
3094 // to lookup parent controllers.
3095 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
3099 function jqLiteEmpty(element) {
3100 jqLiteDealoc(element, true);
3101 while (element.firstChild) {
3102 element.removeChild(element.firstChild);
3106 function jqLiteRemove(element, keepData) {
3107 if (!keepData) jqLiteDealoc(element);
3108 var parent = element.parentNode;
3109 if (parent) parent.removeChild(element);
3113 function jqLiteDocumentLoaded(action, win) {
3114 win = win || window;
3115 if (win.document.readyState === 'complete') {
3116 // Force the action to be run async for consistent behaviour
3117 // from the action's point of view
3118 // i.e. it will definitely not be in a $apply
3119 win.setTimeout(action);
3121 // No need to unbind this handler as load is only ever called once
3122 jqLite(win).on('load', action);
3126 //////////////////////////////////////////
3127 // Functions which are declared directly.
3128 //////////////////////////////////////////
3129 var JQLitePrototype = JQLite.prototype = {
3130 ready: function(fn) {
3133 function trigger() {
3139 // check if document is already loaded
3140 if (document.readyState === 'complete') {
3141 setTimeout(trigger);
3143 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
3144 // we can not use jqLite since we are not done loading and jQuery could be loaded later.
3146 JQLite(window).on('load', trigger); // fallback to window.onload for others
3150 toString: function() {
3152 forEach(this, function(e) { value.push('' + e);});
3153 return '[' + value.join(', ') + ']';
3156 eq: function(index) {
3157 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3166 //////////////////////////////////////////
3167 // Functions iterating getter/setters.
3168 // these functions return self on setter and
3170 //////////////////////////////////////////
3171 var BOOLEAN_ATTR = {};
3172 forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3173 BOOLEAN_ATTR[lowercase(value)] = value;
3175 var BOOLEAN_ELEMENTS = {};
3176 forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3177 BOOLEAN_ELEMENTS[value] = true;
3179 var ALIASED_ATTR = {
3180 'ngMinlength': 'minlength',
3181 'ngMaxlength': 'maxlength',
3184 'ngPattern': 'pattern'
3187 function getBooleanAttrName(element, name) {
3188 // check dom last since we will most likely fail on name
3189 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3191 // booleanAttr is here twice to minimize DOM access
3192 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3195 function getAliasedAttrName(name) {
3196 return ALIASED_ATTR[name];
3201 removeData: jqLiteRemoveData,
3202 hasData: jqLiteHasData
3203 }, function(fn, name) {
3209 inheritedData: jqLiteInheritedData,
3211 scope: function(element) {
3212 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3213 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3216 isolateScope: function(element) {
3217 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3218 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3221 controller: jqLiteController,
3223 injector: function(element) {
3224 return jqLiteInheritedData(element, '$injector');
3227 removeAttr: function(element, name) {
3228 element.removeAttribute(name);
3231 hasClass: jqLiteHasClass,
3233 css: function(element, name, value) {
3234 name = camelCase(name);
3236 if (isDefined(value)) {
3237 element.style[name] = value;
3239 return element.style[name];
3243 attr: function(element, name, value) {
3244 var nodeType = element.nodeType;
3245 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
3248 var lowercasedName = lowercase(name);
3249 if (BOOLEAN_ATTR[lowercasedName]) {
3250 if (isDefined(value)) {
3252 element[name] = true;
3253 element.setAttribute(name, lowercasedName);
3255 element[name] = false;
3256 element.removeAttribute(lowercasedName);
3259 return (element[name] ||
3260 (element.attributes.getNamedItem(name) || noop).specified)
3264 } else if (isDefined(value)) {
3265 element.setAttribute(name, value);
3266 } else if (element.getAttribute) {
3267 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
3268 // some elements (e.g. Document) don't have get attribute, so return undefined
3269 var ret = element.getAttribute(name, 2);
3270 // normalize non-existing attributes to undefined (as jQuery)
3271 return ret === null ? undefined : ret;
3275 prop: function(element, name, value) {
3276 if (isDefined(value)) {
3277 element[name] = value;
3279 return element[name];
3287 function getText(element, value) {
3288 if (isUndefined(value)) {
3289 var nodeType = element.nodeType;
3290 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3292 element.textContent = value;
3296 val: function(element, value) {
3297 if (isUndefined(value)) {
3298 if (element.multiple && nodeName_(element) === 'select') {
3300 forEach(element.options, function(option) {
3301 if (option.selected) {
3302 result.push(option.value || option.text);
3305 return result.length === 0 ? null : result;
3307 return element.value;
3309 element.value = value;
3312 html: function(element, value) {
3313 if (isUndefined(value)) {
3314 return element.innerHTML;
3316 jqLiteDealoc(element, true);
3317 element.innerHTML = value;
3321 }, function(fn, name) {
3323 * Properties: writes return selection, reads return first value
3325 JQLite.prototype[name] = function(arg1, arg2) {
3327 var nodeCount = this.length;
3329 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3330 // in a way that survives minification.
3331 // jqLiteEmpty takes no arguments but is a setter.
3332 if (fn !== jqLiteEmpty &&
3333 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
3334 if (isObject(arg1)) {
3336 // we are a write, but the object properties are the key/values
3337 for (i = 0; i < nodeCount; i++) {
3338 if (fn === jqLiteData) {
3339 // data() takes the whole object in jQuery
3343 fn(this[i], key, arg1[key]);
3347 // return self for chaining
3350 // we are a read, so read the first child.
3351 // TODO: do we still need this?
3353 // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3354 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3355 for (var j = 0; j < jj; j++) {
3356 var nodeValue = fn(this[j], arg1, arg2);
3357 value = value ? value + nodeValue : nodeValue;
3362 // we are a write, so apply to all children
3363 for (i = 0; i < nodeCount; i++) {
3364 fn(this[i], arg1, arg2);
3366 // return self for chaining
3372 function createEventHandler(element, events) {
3373 var eventHandler = function(event, type) {
3374 // jQuery specific api
3375 event.isDefaultPrevented = function() {
3376 return event.defaultPrevented;
3379 var eventFns = events[type || event.type];
3380 var eventFnsLength = eventFns ? eventFns.length : 0;
3382 if (!eventFnsLength) return;
3384 if (isUndefined(event.immediatePropagationStopped)) {
3385 var originalStopImmediatePropagation = event.stopImmediatePropagation;
3386 event.stopImmediatePropagation = function() {
3387 event.immediatePropagationStopped = true;
3389 if (event.stopPropagation) {
3390 event.stopPropagation();
3393 if (originalStopImmediatePropagation) {
3394 originalStopImmediatePropagation.call(event);
3399 event.isImmediatePropagationStopped = function() {
3400 return event.immediatePropagationStopped === true;
3403 // Some events have special handlers that wrap the real handler
3404 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
3406 // Copy event handlers in case event handlers array is modified during execution.
3407 if ((eventFnsLength > 1)) {
3408 eventFns = shallowCopy(eventFns);
3411 for (var i = 0; i < eventFnsLength; i++) {
3412 if (!event.isImmediatePropagationStopped()) {
3413 handlerWrapper(element, event, eventFns[i]);
3418 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3419 // events on `element`
3420 eventHandler.elem = element;
3421 return eventHandler;
3424 function defaultHandlerWrapper(element, event, handler) {
3425 handler.call(element, event);
3428 function specialMouseHandlerWrapper(target, event, handler) {
3429 // Refer to jQuery's implementation of mouseenter & mouseleave
3430 // Read about mouseenter and mouseleave:
3431 // http://www.quirksmode.org/js/events_mouse.html#link8
3432 var related = event.relatedTarget;
3433 // For mousenter/leave call the handler if related is outside the target.
3434 // NB: No relatedTarget if the mouse left/entered the browser window
3435 if (!related || (related !== target && !jqLiteContains.call(target, related))) {
3436 handler.call(target, event);
3440 //////////////////////////////////////////
3441 // Functions iterating traversal.
3442 // These functions chain results into a single
3444 //////////////////////////////////////////
3446 removeData: jqLiteRemoveData,
3448 on: function jqLiteOn(element, type, fn, unsupported) {
3449 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3451 // Do not add event handlers to non-elements because they will not be cleaned up.
3452 if (!jqLiteAcceptsData(element)) {
3456 var expandoStore = jqLiteExpandoStore(element, true);
3457 var events = expandoStore.events;
3458 var handle = expandoStore.handle;
3461 handle = expandoStore.handle = createEventHandler(element, events);
3464 // http://jsperf.com/string-indexof-vs-split
3465 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3466 var i = types.length;
3468 var addHandler = function(type, specialHandlerWrapper, noEventListener) {
3469 var eventFns = events[type];
3472 eventFns = events[type] = [];
3473 eventFns.specialHandlerWrapper = specialHandlerWrapper;
3474 if (type !== '$destroy' && !noEventListener) {
3475 addEventListenerFn(element, type, handle);
3484 if (MOUSE_EVENT_MAP[type]) {
3485 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
3486 addHandler(type, undefined, true);
3495 one: function(element, type, fn) {
3496 element = jqLite(element);
3498 //add the listener twice so that when it is called
3499 //you can remove the original function and still be
3500 //able to call element.off(ev, fn) normally
3501 element.on(type, function onFn() {
3502 element.off(type, fn);
3503 element.off(type, onFn);
3505 element.on(type, fn);
3508 replaceWith: function(element, replaceNode) {
3509 var index, parent = element.parentNode;
3510 jqLiteDealoc(element);
3511 forEach(new JQLite(replaceNode), function(node) {
3513 parent.insertBefore(node, index.nextSibling);
3515 parent.replaceChild(node, element);
3521 children: function(element) {
3523 forEach(element.childNodes, function(element) {
3524 if (element.nodeType === NODE_TYPE_ELEMENT) {
3525 children.push(element);
3531 contents: function(element) {
3532 return element.contentDocument || element.childNodes || [];
3535 append: function(element, node) {
3536 var nodeType = element.nodeType;
3537 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3539 node = new JQLite(node);
3541 for (var i = 0, ii = node.length; i < ii; i++) {
3542 var child = node[i];
3543 element.appendChild(child);
3547 prepend: function(element, node) {
3548 if (element.nodeType === NODE_TYPE_ELEMENT) {
3549 var index = element.firstChild;
3550 forEach(new JQLite(node), function(child) {
3551 element.insertBefore(child, index);
3556 wrap: function(element, wrapNode) {
3557 wrapNode = jqLite(wrapNode).eq(0).clone()[0];
3558 var parent = element.parentNode;
3560 parent.replaceChild(wrapNode, element);
3562 wrapNode.appendChild(element);
3565 remove: jqLiteRemove,
3567 detach: function(element) {
3568 jqLiteRemove(element, true);
3571 after: function(element, newElement) {
3572 var index = element, parent = element.parentNode;
3573 newElement = new JQLite(newElement);
3575 for (var i = 0, ii = newElement.length; i < ii; i++) {
3576 var node = newElement[i];
3577 parent.insertBefore(node, index.nextSibling);
3582 addClass: jqLiteAddClass,
3583 removeClass: jqLiteRemoveClass,
3585 toggleClass: function(element, selector, condition) {
3587 forEach(selector.split(' '), function(className) {
3588 var classCondition = condition;
3589 if (isUndefined(classCondition)) {
3590 classCondition = !jqLiteHasClass(element, className);
3592 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
3597 parent: function(element) {
3598 var parent = element.parentNode;
3599 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3602 next: function(element) {
3603 return element.nextElementSibling;
3606 find: function(element, selector) {
3607 if (element.getElementsByTagName) {
3608 return element.getElementsByTagName(selector);
3616 triggerHandler: function(element, event, extraParameters) {
3618 var dummyEvent, eventFnsCopy, handlerArgs;
3619 var eventName = event.type || event;
3620 var expandoStore = jqLiteExpandoStore(element);
3621 var events = expandoStore && expandoStore.events;
3622 var eventFns = events && events[eventName];
3625 // Create a dummy event to pass to the handlers
3627 preventDefault: function() { this.defaultPrevented = true; },
3628 isDefaultPrevented: function() { return this.defaultPrevented === true; },
3629 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
3630 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
3631 stopPropagation: noop,
3636 // If a custom event was provided then extend our dummy event with it
3638 dummyEvent = extend(dummyEvent, event);
3641 // Copy event handlers in case event handlers array is modified during execution.
3642 eventFnsCopy = shallowCopy(eventFns);
3643 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
3645 forEach(eventFnsCopy, function(fn) {
3646 if (!dummyEvent.isImmediatePropagationStopped()) {
3647 fn.apply(element, handlerArgs);
3652 }, function(fn, name) {
3654 * chaining functions
3656 JQLite.prototype[name] = function(arg1, arg2, arg3) {
3659 for (var i = 0, ii = this.length; i < ii; i++) {
3660 if (isUndefined(value)) {
3661 value = fn(this[i], arg1, arg2, arg3);
3662 if (isDefined(value)) {
3663 // any function which returns a value needs to be wrapped
3664 value = jqLite(value);
3667 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
3670 return isDefined(value) ? value : this;
3673 // bind legacy bind/unbind to on/off
3674 JQLite.prototype.bind = JQLite.prototype.on;
3675 JQLite.prototype.unbind = JQLite.prototype.off;
3679 // Provider for private $$jqLite service
3680 function $$jqLiteProvider() {
3681 this.$get = function $$jqLite() {
3682 return extend(JQLite, {
3683 hasClass: function(node, classes) {
3684 if (node.attr) node = node[0];
3685 return jqLiteHasClass(node, classes);
3687 addClass: function(node, classes) {
3688 if (node.attr) node = node[0];
3689 return jqLiteAddClass(node, classes);
3691 removeClass: function(node, classes) {
3692 if (node.attr) node = node[0];
3693 return jqLiteRemoveClass(node, classes);
3700 * Computes a hash of an 'obj'.
3703 * number is number as string
3704 * object is either result of calling $$hashKey function on the object or uniquely generated id,
3705 * that is also assigned to the $$hashKey property of the object.
3708 * @returns {string} hash string such that the same input will have the same hash string.
3709 * The resulting string key is in 'type:hashKey' format.
3711 function hashKey(obj, nextUidFn) {
3712 var key = obj && obj.$$hashKey;
3715 if (typeof key === 'function') {
3716 key = obj.$$hashKey();
3721 var objType = typeof obj;
3722 if (objType == 'function' || (objType == 'object' && obj !== null)) {
3723 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
3725 key = objType + ':' + obj;
3732 * HashMap which can use objects as keys
3734 function HashMap(array, isolatedUid) {
3737 this.nextUid = function() {
3741 forEach(array, this.put, this);
3743 HashMap.prototype = {
3745 * Store key value pair
3746 * @param key key to store can be any type
3747 * @param value value to store can be any type
3749 put: function(key, value) {
3750 this[hashKey(key, this.nextUid)] = value;
3755 * @returns {Object} the value for the key
3757 get: function(key) {
3758 return this[hashKey(key, this.nextUid)];
3762 * Remove the key/value pair
3765 remove: function(key) {
3766 var value = this[key = hashKey(key, this.nextUid)];
3772 var $$HashMapProvider = [function() {
3773 this.$get = [function() {
3781 * @name angular.injector
3785 * Creates an injector object that can be used for retrieving services as well as for
3786 * dependency injection (see {@link guide/di dependency injection}).
3788 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
3789 * {@link angular.module}. The `ng` module must be explicitly added.
3790 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
3791 * disallows argument name annotation inference.
3792 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
3797 * // create an injector
3798 * var $injector = angular.injector(['ng']);
3800 * // use the injector to kick off your application
3801 * // use the type inference to auto inject arguments, or use implicit injection
3802 * $injector.invoke(function($rootScope, $compile, $document) {
3803 * $compile($document)($rootScope);
3804 * $rootScope.$digest();
3808 * Sometimes you want to get access to the injector of a currently running Angular app
3809 * from outside Angular. Perhaps, you want to inject and compile some markup after the
3810 * application has been bootstrapped. You can do this using the extra `injector()` added
3811 * to JQuery/jqLite elements. See {@link angular.element}.
3813 * *This is fairly rare but could be the case if a third party library is injecting the
3816 * In the following example a new block of HTML containing a `ng-controller`
3817 * directive is added to the end of the document body by JQuery. We then compile and link
3818 * it into the current AngularJS scope.
3821 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
3822 * $(document.body).append($div);
3824 * angular.element(document).injector().invoke(function($compile) {
3825 * var scope = angular.element($div).scope();
3826 * $compile($div)(scope);
3837 * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
3840 var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
3841 var FN_ARG_SPLIT = /,/;
3842 var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
3843 var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
3844 var $injectorMinErr = minErr('$injector');
3846 function anonFn(fn) {
3847 // For anonymous functions, showing at the very least the function signature can help in
3849 var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
3850 args = fnText.match(FN_ARGS);
3852 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
3857 function annotate(fn, strictDi, name) {
3863 if (typeof fn === 'function') {
3864 if (!($inject = fn.$inject)) {
3868 if (!isString(name) || !name) {
3869 name = fn.name || anonFn(fn);
3871 throw $injectorMinErr('strictdi',
3872 '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
3874 fnText = fn.toString().replace(STRIP_COMMENTS, '');
3875 argDecl = fnText.match(FN_ARGS);
3876 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
3877 arg.replace(FN_ARG, function(all, underscore, name) {
3882 fn.$inject = $inject;
3884 } else if (isArray(fn)) {
3885 last = fn.length - 1;
3886 assertArgFn(fn[last], 'fn');
3887 $inject = fn.slice(0, last);
3889 assertArgFn(fn, 'fn', true);
3894 ///////////////////////////////////////
3902 * `$injector` is used to retrieve object instances as defined by
3903 * {@link auto.$provide provider}, instantiate types, invoke methods,
3906 * The following always holds true:
3909 * var $injector = angular.injector();
3910 * expect($injector.get('$injector')).toBe($injector);
3911 * expect($injector.invoke(function($injector) {
3913 * })).toBe($injector);
3916 * # Injection Function Annotation
3918 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
3919 * following are all valid ways of annotating function with injection arguments and are equivalent.
3922 * // inferred (only works if code not minified/obfuscated)
3923 * $injector.invoke(function(serviceA){});
3926 * function explicit(serviceA) {};
3927 * explicit.$inject = ['serviceA'];
3928 * $injector.invoke(explicit);
3931 * $injector.invoke(['serviceA', function(serviceA){}]);
3936 * In JavaScript calling `toString()` on a function returns the function definition. The definition
3937 * can then be parsed and the function arguments can be extracted. This method of discovering
3938 * annotations is disallowed when the injector is in strict mode.
3939 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
3942 * ## `$inject` Annotation
3943 * By adding an `$inject` property onto a function the injection parameters can be specified.
3946 * As an array of injection names, where the last item in the array is the function to call.
3951 * @name $injector#get
3954 * Return an instance of the service.
3956 * @param {string} name The name of the instance to retrieve.
3957 * @param {string=} caller An optional string to provide the origin of the function call for error messages.
3958 * @return {*} The instance.
3963 * @name $injector#invoke
3966 * Invoke the method and supply the method arguments from the `$injector`.
3968 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
3969 * injected according to the {@link guide/di $inject Annotation} rules.
3970 * @param {Object=} self The `this` for the invoked method.
3971 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3972 * object first, before the `$injector` is consulted.
3973 * @returns {*} the value returned by the invoked `fn` function.
3978 * @name $injector#has
3981 * Allows the user to query if the particular service exists.
3983 * @param {string} name Name of the service to query.
3984 * @returns {boolean} `true` if injector has given service.
3989 * @name $injector#instantiate
3991 * Create a new instance of JS type. The method takes a constructor function, invokes the new
3992 * operator, and supplies all of the arguments to the constructor function as specified by the
3993 * constructor annotation.
3995 * @param {Function} Type Annotated constructor function.
3996 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3997 * object first, before the `$injector` is consulted.
3998 * @returns {Object} new instance of `Type`.
4003 * @name $injector#annotate
4006 * Returns an array of service names which the function is requesting for injection. This API is
4007 * used by the injector to determine which services need to be injected into the function when the
4008 * function is invoked. There are three ways in which the function can be annotated with the needed
4013 * The simplest form is to extract the dependencies from the arguments of the function. This is done
4014 * by converting the function into a string using `toString()` method and extracting the argument
4018 * function MyController($scope, $route) {
4023 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4026 * You can disallow this method by using strict injection mode.
4028 * This method does not work with code minification / obfuscation. For this reason the following
4029 * annotation strategies are supported.
4031 * # The `$inject` property
4033 * If a function has an `$inject` property and its value is an array of strings, then the strings
4034 * represent names of services to be injected into the function.
4037 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
4040 * // Define function dependencies
4041 * MyController['$inject'] = ['$scope', '$route'];
4044 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4047 * # The array notation
4049 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
4050 * is very inconvenient. In these situations using the array notation to specify the dependencies in
4051 * a way that survives minification is a better choice:
4054 * // We wish to write this (not minification / obfuscation safe)
4055 * injector.invoke(function($compile, $rootScope) {
4059 * // We are forced to write break inlining
4060 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
4063 * tmpFn.$inject = ['$compile', '$rootScope'];
4064 * injector.invoke(tmpFn);
4066 * // To better support inline function the inline annotation is supported
4067 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
4072 * expect(injector.annotate(
4073 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
4074 * ).toEqual(['$compile', '$rootScope']);
4077 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
4078 * be retrieved as described above.
4080 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
4082 * @returns {Array.<string>} The names of the services which the function requires.
4094 * The {@link auto.$provide $provide} service has a number of methods for registering components
4095 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
4096 * {@link angular.Module}.
4098 * An Angular **service** is a singleton object created by a **service factory**. These **service
4099 * factories** are functions which, in turn, are created by a **service provider**.
4100 * The **service providers** are constructor functions. When instantiated they must contain a
4101 * property called `$get`, which holds the **service factory** function.
4103 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
4104 * correct **service provider**, instantiating it and then calling its `$get` **service factory**
4105 * function to get the instance of the **service**.
4107 * Often services have no configuration options and there is no need to add methods to the service
4108 * provider. The provider will be no more than a constructor function with a `$get` property. For
4109 * these cases the {@link auto.$provide $provide} service has additional helper methods to register
4110 * services without specifying a provider.
4112 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the
4113 * {@link auto.$injector $injector}
4114 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by
4115 * providers and services.
4116 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by
4117 * services, not providers.
4118 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`,
4119 * that will be wrapped in a **service provider** object, whose `$get` property will contain the
4120 * given factory function.
4121 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class`
4122 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
4123 * a new object using the given constructor function.
4125 * See the individual methods for more information and examples.
4130 * @name $provide#provider
4133 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4134 * are constructor functions, whose instances are responsible for "providing" a factory for a
4137 * Service provider names start with the name of the service they provide followed by `Provider`.
4138 * For example, the {@link ng.$log $log} service has a provider called
4139 * {@link ng.$logProvider $logProvider}.
4141 * Service provider objects can have additional methods which allow configuration of the provider
4142 * and its service. Importantly, you can configure what kind of service is created by the `$get`
4143 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4144 * method {@link ng.$logProvider#debugEnabled debugEnabled}
4145 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4148 * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4150 * @param {(Object|function())} provider If the provider is:
4152 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4153 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4154 * - `Constructor`: a new instance of the provider will be created using
4155 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4157 * @returns {Object} registered provider instance
4161 * The following example shows how to create a simple event tracking service and register it using
4162 * {@link auto.$provide#provider $provide.provider()}.
4165 * // Define the eventTracker provider
4166 * function EventTrackerProvider() {
4167 * var trackingUrl = '/track';
4169 * // A provider method for configuring where the tracked events should been saved
4170 * this.setTrackingUrl = function(url) {
4171 * trackingUrl = url;
4174 * // The service factory function
4175 * this.$get = ['$http', function($http) {
4176 * var trackedEvents = {};
4178 * // Call this to track an event
4179 * event: function(event) {
4180 * var count = trackedEvents[event] || 0;
4182 * trackedEvents[event] = count;
4185 * // Call this to save the tracked events to the trackingUrl
4186 * save: function() {
4187 * $http.post(trackingUrl, trackedEvents);
4193 * describe('eventTracker', function() {
4196 * beforeEach(module(function($provide) {
4197 * // Register the eventTracker provider
4198 * $provide.provider('eventTracker', EventTrackerProvider);
4201 * beforeEach(module(function(eventTrackerProvider) {
4202 * // Configure eventTracker provider
4203 * eventTrackerProvider.setTrackingUrl('/custom-track');
4206 * it('tracks events', inject(function(eventTracker) {
4207 * expect(eventTracker.event('login')).toEqual(1);
4208 * expect(eventTracker.event('login')).toEqual(2);
4211 * it('saves to the tracking url', inject(function(eventTracker, $http) {
4212 * postSpy = spyOn($http, 'post');
4213 * eventTracker.event('login');
4214 * eventTracker.save();
4215 * expect(postSpy).toHaveBeenCalled();
4216 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4217 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4218 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4226 * @name $provide#factory
4229 * Register a **service factory**, which will be called to return the service instance.
4230 * This is short for registering a service where its provider consists of only a `$get` property,
4231 * which is the given service factory function.
4232 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4233 * configure your service in a provider.
4235 * @param {string} name The name of the instance.
4236 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4237 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4238 * @returns {Object} registered provider instance
4241 * Here is an example of registering a service
4243 * $provide.factory('ping', ['$http', function($http) {
4244 * return function ping() {
4245 * return $http.send('/ping');
4249 * You would then inject and use this service like this:
4251 * someModule.controller('Ctrl', ['ping', function(ping) {
4260 * @name $provide#service
4263 * Register a **service constructor**, which will be invoked with `new` to create the service
4265 * This is short for registering a service where its provider's `$get` property is the service
4266 * constructor function that will be used to instantiate the service instance.
4268 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4271 * @param {string} name The name of the instance.
4272 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4273 * that will be instantiated.
4274 * @returns {Object} registered provider instance
4277 * Here is an example of registering a service using
4278 * {@link auto.$provide#service $provide.service(class)}.
4280 * var Ping = function($http) {
4281 * this.$http = $http;
4284 * Ping.$inject = ['$http'];
4286 * Ping.prototype.send = function() {
4287 * return this.$http.get('/ping');
4289 * $provide.service('ping', Ping);
4291 * You would then inject and use this service like this:
4293 * someModule.controller('Ctrl', ['ping', function(ping) {
4302 * @name $provide#value
4305 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4306 * number, an array, an object or a function. This is short for registering a service where its
4307 * provider's `$get` property is a factory function that takes no arguments and returns the **value
4310 * Value services are similar to constant services, except that they cannot be injected into a
4311 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4313 * {@link auto.$provide#decorator decorator}.
4315 * @param {string} name The name of the instance.
4316 * @param {*} value The value.
4317 * @returns {Object} registered provider instance
4320 * Here are some examples of creating value services.
4322 * $provide.value('ADMIN_USER', 'admin');
4324 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4326 * $provide.value('halfOf', function(value) {
4335 * @name $provide#constant
4338 * Register a **constant service**, such as a string, a number, an array, an object or a function,
4339 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
4340 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4341 * be overridden by an Angular {@link auto.$provide#decorator decorator}.
4343 * @param {string} name The name of the constant.
4344 * @param {*} value The constant value.
4345 * @returns {Object} registered instance
4348 * Here a some examples of creating constants:
4350 * $provide.constant('SHARD_HEIGHT', 306);
4352 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4354 * $provide.constant('double', function(value) {
4363 * @name $provide#decorator
4366 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator
4367 * intercepts the creation of a service, allowing it to override or modify the behaviour of the
4368 * service. The object returned by the decorator may be the original service, or a new service
4369 * object which replaces or wraps and delegates to the original service.
4371 * @param {string} name The name of the service to decorate.
4372 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4373 * instantiated and should return the decorated service instance. The function is called using
4374 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4375 * Local injection arguments:
4377 * * `$delegate` - The original service instance, which can be monkey patched, configured,
4378 * decorated or delegated to.
4381 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4382 * calls to {@link ng.$log#error $log.warn()}.
4384 * $provide.decorator('$log', ['$delegate', function($delegate) {
4385 * $delegate.warn = $delegate.error;
4392 function createInjector(modulesToLoad, strictDi) {
4393 strictDi = (strictDi === true);
4394 var INSTANTIATING = {},
4395 providerSuffix = 'Provider',
4397 loadedModules = new HashMap([], true),
4400 provider: supportObject(provider),
4401 factory: supportObject(factory),
4402 service: supportObject(service),
4403 value: supportObject(value),
4404 constant: supportObject(constant),
4405 decorator: decorator
4408 providerInjector = (providerCache.$injector =
4409 createInternalInjector(providerCache, function(serviceName, caller) {
4410 if (angular.isString(caller)) {
4413 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
4416 instanceInjector = (instanceCache.$injector =
4417 createInternalInjector(instanceCache, function(serviceName, caller) {
4418 var provider = providerInjector.get(serviceName + providerSuffix, caller);
4419 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
4423 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
4425 return instanceInjector;
4427 ////////////////////////////////////
4429 ////////////////////////////////////
4431 function supportObject(delegate) {
4432 return function(key, value) {
4433 if (isObject(key)) {
4434 forEach(key, reverseParams(delegate));
4436 return delegate(key, value);
4441 function provider(name, provider_) {
4442 assertNotHasOwnProperty(name, 'service');
4443 if (isFunction(provider_) || isArray(provider_)) {
4444 provider_ = providerInjector.instantiate(provider_);
4446 if (!provider_.$get) {
4447 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
4449 return providerCache[name + providerSuffix] = provider_;
4452 function enforceReturnValue(name, factory) {
4453 return function enforcedReturnValue() {
4454 var result = instanceInjector.invoke(factory, this);
4455 if (isUndefined(result)) {
4456 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
4462 function factory(name, factoryFn, enforce) {
4463 return provider(name, {
4464 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
4468 function service(name, constructor) {
4469 return factory(name, ['$injector', function($injector) {
4470 return $injector.instantiate(constructor);
4474 function value(name, val) { return factory(name, valueFn(val), false); }
4476 function constant(name, value) {
4477 assertNotHasOwnProperty(name, 'constant');
4478 providerCache[name] = value;
4479 instanceCache[name] = value;
4482 function decorator(serviceName, decorFn) {
4483 var origProvider = providerInjector.get(serviceName + providerSuffix),
4484 orig$get = origProvider.$get;
4486 origProvider.$get = function() {
4487 var origInstance = instanceInjector.invoke(orig$get, origProvider);
4488 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
4492 ////////////////////////////////////
4494 ////////////////////////////////////
4495 function loadModules(modulesToLoad) {
4496 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
4497 var runBlocks = [], moduleFn;
4498 forEach(modulesToLoad, function(module) {
4499 if (loadedModules.get(module)) return;
4500 loadedModules.put(module, true);
4502 function runInvokeQueue(queue) {
4504 for (i = 0, ii = queue.length; i < ii; i++) {
4505 var invokeArgs = queue[i],
4506 provider = providerInjector.get(invokeArgs[0]);
4508 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
4513 if (isString(module)) {
4514 moduleFn = angularModule(module);
4515 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
4516 runInvokeQueue(moduleFn._invokeQueue);
4517 runInvokeQueue(moduleFn._configBlocks);
4518 } else if (isFunction(module)) {
4519 runBlocks.push(providerInjector.invoke(module));
4520 } else if (isArray(module)) {
4521 runBlocks.push(providerInjector.invoke(module));
4523 assertArgFn(module, 'module');
4526 if (isArray(module)) {
4527 module = module[module.length - 1];
4529 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
4530 // Safari & FF's stack traces don't contain error.message content
4531 // unlike those of Chrome and IE
4532 // So if stack doesn't contain message, we create a new string that contains both.
4533 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
4535 e = e.message + '\n' + e.stack;
4537 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
4538 module, e.stack || e.message || e);
4544 ////////////////////////////////////
4545 // internal Injector
4546 ////////////////////////////////////
4548 function createInternalInjector(cache, factory) {
4550 function getService(serviceName, caller) {
4551 if (cache.hasOwnProperty(serviceName)) {
4552 if (cache[serviceName] === INSTANTIATING) {
4553 throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
4554 serviceName + ' <- ' + path.join(' <- '));
4556 return cache[serviceName];
4559 path.unshift(serviceName);
4560 cache[serviceName] = INSTANTIATING;
4561 return cache[serviceName] = factory(serviceName, caller);
4563 if (cache[serviceName] === INSTANTIATING) {
4564 delete cache[serviceName];
4573 function invoke(fn, self, locals, serviceName) {
4574 if (typeof locals === 'string') {
4575 serviceName = locals;
4580 $inject = createInjector.$$annotate(fn, strictDi, serviceName),
4584 for (i = 0, length = $inject.length; i < length; i++) {
4586 if (typeof key !== 'string') {
4587 throw $injectorMinErr('itkn',
4588 'Incorrect injection token! Expected service name as string, got {0}', key);
4591 locals && locals.hasOwnProperty(key)
4593 : getService(key, serviceName)
4600 // http://jsperf.com/angularjs-invoke-apply-vs-switch
4602 return fn.apply(self, args);
4605 function instantiate(Type, locals, serviceName) {
4606 // Check if Type is annotated and use just the given function at n-1 as parameter
4607 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
4608 // Object creation: http://jsperf.com/create-constructor/2
4609 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
4610 var returnedValue = invoke(Type, instance, locals, serviceName);
4612 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
4617 instantiate: instantiate,
4619 annotate: createInjector.$$annotate,
4620 has: function(name) {
4621 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
4627 createInjector.$$annotate = annotate;
4631 * @name $anchorScrollProvider
4634 * Use `$anchorScrollProvider` to disable automatic scrolling whenever
4635 * {@link ng.$location#hash $location.hash()} changes.
4637 function $AnchorScrollProvider() {
4639 var autoScrollingEnabled = true;
4643 * @name $anchorScrollProvider#disableAutoScrolling
4646 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
4647 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
4648 * Use this method to disable automatic scrolling.
4650 * If automatic scrolling is disabled, one must explicitly call
4651 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
4654 this.disableAutoScrolling = function() {
4655 autoScrollingEnabled = false;
4660 * @name $anchorScroll
4663 * @requires $location
4664 * @requires $rootScope
4667 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
4668 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
4670 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
4672 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
4673 * match any anchor whenever it changes. This can be disabled by calling
4674 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
4676 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
4677 * vertical scroll-offset (either fixed or dynamic).
4679 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
4680 * {@link ng.$location#hash $location.hash()} will be used.
4682 * @property {(number|function|jqLite)} yOffset
4683 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
4684 * positioned elements at the top of the page, such as navbars, headers etc.
4686 * `yOffset` can be specified in various ways:
4687 * - **number**: A fixed number of pixels to be used as offset.<br /><br />
4688 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
4689 * a number representing the offset (in pixels).<br /><br />
4690 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
4691 * the top of the page to the element's bottom will be used as offset.<br />
4692 * **Note**: The element will be taken into account only as long as its `position` is set to
4693 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
4694 * their height and/or positioning according to the viewport's size.
4697 * <div class="alert alert-warning">
4698 * In order for `yOffset` to work properly, scrolling should take place on the document's root and
4699 * not some child element.
4703 <example module="anchorScrollExample">
4704 <file name="index.html">
4705 <div id="scrollArea" ng-controller="ScrollController">
4706 <a ng-click="gotoBottom()">Go to bottom</a>
4707 <a id="bottom"></a> You're at the bottom!
4710 <file name="script.js">
4711 angular.module('anchorScrollExample', [])
4712 .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
4713 function ($scope, $location, $anchorScroll) {
4714 $scope.gotoBottom = function() {
4715 // set the location.hash to the id of
4716 // the element you wish to scroll to.
4717 $location.hash('bottom');
4719 // call $anchorScroll()
4724 <file name="style.css">
4738 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
4739 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
4742 <example module="anchorScrollOffsetExample">
4743 <file name="index.html">
4744 <div class="fixed-header" ng-controller="headerCtrl">
4745 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
4749 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
4753 <file name="script.js">
4754 angular.module('anchorScrollOffsetExample', [])
4755 .run(['$anchorScroll', function($anchorScroll) {
4756 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
4758 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
4759 function ($anchorScroll, $location, $scope) {
4760 $scope.gotoAnchor = function(x) {
4761 var newHash = 'anchor' + x;
4762 if ($location.hash() !== newHash) {
4763 // set the $location.hash to `newHash` and
4764 // $anchorScroll will automatically scroll to it
4765 $location.hash('anchor' + x);
4767 // call $anchorScroll() explicitly,
4768 // since $location.hash hasn't changed
4775 <file name="style.css">
4781 border: 2px dashed DarkOrchid;
4782 padding: 10px 10px 200px 10px;
4786 background-color: rgba(0, 0, 0, 0.2);
4789 top: 0; left: 0; right: 0;
4793 display: inline-block;
4799 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
4800 var document = $window.document;
4802 // Helper function to get first anchor from a NodeList
4803 // (using `Array#some()` instead of `angular#forEach()` since it's more performant
4804 // and working in all supported browsers.)
4805 function getFirstAnchor(list) {
4807 Array.prototype.some.call(list, function(element) {
4808 if (nodeName_(element) === 'a') {
4816 function getYOffset() {
4818 var offset = scroll.yOffset;
4820 if (isFunction(offset)) {
4822 } else if (isElement(offset)) {
4823 var elem = offset[0];
4824 var style = $window.getComputedStyle(elem);
4825 if (style.position !== 'fixed') {
4828 offset = elem.getBoundingClientRect().bottom;
4830 } else if (!isNumber(offset)) {
4837 function scrollTo(elem) {
4839 elem.scrollIntoView();
4841 var offset = getYOffset();
4844 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
4845 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
4846 // top of the viewport.
4848 // IF the number of pixels from the top of `elem` to the end of the page's content is less
4849 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
4850 // way down the page.
4852 // This is often the case for elements near the bottom of the page.
4854 // In such cases we do not need to scroll the whole `offset` up, just the difference between
4855 // the top of the element and the offset, which is enough to align the top of `elem` at the
4856 // desired position.
4857 var elemTop = elem.getBoundingClientRect().top;
4858 $window.scrollBy(0, elemTop - offset);
4861 $window.scrollTo(0, 0);
4865 function scroll(hash) {
4866 hash = isString(hash) ? hash : $location.hash();
4869 // empty hash, scroll to the top of the page
4870 if (!hash) scrollTo(null);
4872 // element with given id
4873 else if ((elm = document.getElementById(hash))) scrollTo(elm);
4875 // first anchor with given name :-D
4876 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
4878 // no element and hash == 'top', scroll to the top of the page
4879 else if (hash === 'top') scrollTo(null);
4882 // does not scroll when user clicks on anchor link that is currently on
4883 // (no url change, no $location.hash() change), browser native does scroll
4884 if (autoScrollingEnabled) {
4885 $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
4886 function autoScrollWatchAction(newVal, oldVal) {
4887 // skip the initial scroll if $location.hash is empty
4888 if (newVal === oldVal && newVal === '') return;
4890 jqLiteDocumentLoaded(function() {
4891 $rootScope.$evalAsync(scroll);
4900 var $animateMinErr = minErr('$animate');
4901 var ELEMENT_NODE = 1;
4902 var NG_ANIMATE_CLASSNAME = 'ng-animate';
4904 function mergeClasses(a,b) {
4905 if (!a && !b) return '';
4908 if (isArray(a)) a = a.join(' ');
4909 if (isArray(b)) b = b.join(' ');
4913 function extractElementNode(element) {
4914 for (var i = 0; i < element.length; i++) {
4915 var elm = element[i];
4916 if (elm.nodeType === ELEMENT_NODE) {
4922 function splitClasses(classes) {
4923 if (isString(classes)) {
4924 classes = classes.split(' ');
4927 // Use createMap() to prevent class assumptions involving property names in
4929 var obj = createMap();
4930 forEach(classes, function(klass) {
4931 // sometimes the split leaves empty string values
4932 // incase extra spaces were applied to the options
4940 // if any other type of options value besides an Object value is
4941 // passed into the $animate.method() animation then this helper code
4942 // will be run which will ignore it. While this patch is not the
4943 // greatest solution to this, a lot of existing plugins depend on
4944 // $animate to either call the callback (< 1.2) or return a promise
4945 // that can be changed. This helper function ensures that the options
4946 // are wiped clean incase a callback function is provided.
4947 function prepareAnimateOptions(options) {
4948 return isObject(options)
4953 var $$CoreAnimateRunnerProvider = function() {
4954 this.$get = ['$q', '$$rAF', function($q, $$rAF) {
4955 function AnimateRunner() {}
4956 AnimateRunner.all = noop;
4957 AnimateRunner.chain = noop;
4958 AnimateRunner.prototype = {
4964 then: function(pass, fail) {
4965 return $q(function(resolve) {
4969 }).then(pass, fail);
4972 return AnimateRunner;
4976 // this is prefixed with Core since it conflicts with
4977 // the animateQueueProvider defined in ngAnimate/animateQueue.js
4978 var $$CoreAnimateQueueProvider = function() {
4979 var postDigestQueue = new HashMap();
4980 var postDigestElements = [];
4982 this.$get = ['$$AnimateRunner', '$rootScope',
4983 function($$AnimateRunner, $rootScope) {
4990 push: function(element, event, options, domOperation) {
4991 domOperation && domOperation();
4993 options = options || {};
4994 options.from && element.css(options.from);
4995 options.to && element.css(options.to);
4997 if (options.addClass || options.removeClass) {
4998 addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
5001 return new $$AnimateRunner(); // jshint ignore:line
5006 function updateData(data, classes, value) {
5007 var changed = false;
5009 classes = isString(classes) ? classes.split(' ') :
5010 isArray(classes) ? classes : [];
5011 forEach(classes, function(className) {
5014 data[className] = value;
5021 function handleCSSClassChanges() {
5022 forEach(postDigestElements, function(element) {
5023 var data = postDigestQueue.get(element);
5025 var existing = splitClasses(element.attr('class'));
5028 forEach(data, function(status, className) {
5029 var hasClass = !!existing[className];
5030 if (status !== hasClass) {
5032 toAdd += (toAdd.length ? ' ' : '') + className;
5034 toRemove += (toRemove.length ? ' ' : '') + className;
5039 forEach(element, function(elm) {
5040 toAdd && jqLiteAddClass(elm, toAdd);
5041 toRemove && jqLiteRemoveClass(elm, toRemove);
5043 postDigestQueue.remove(element);
5046 postDigestElements.length = 0;
5050 function addRemoveClassesPostDigest(element, add, remove) {
5051 var data = postDigestQueue.get(element) || {};
5053 var classesAdded = updateData(data, add, true);
5054 var classesRemoved = updateData(data, remove, false);
5056 if (classesAdded || classesRemoved) {
5058 postDigestQueue.put(element, data);
5059 postDigestElements.push(element);
5061 if (postDigestElements.length === 1) {
5062 $rootScope.$$postDigest(handleCSSClassChanges);
5071 * @name $animateProvider
5074 * Default implementation of $animate that doesn't perform any animations, instead just
5075 * synchronously performs DOM updates and resolves the returned runner promise.
5077 * In order to enable animations the `ngAnimate` module has to be loaded.
5079 * To see the functional implementation check out `src/ngAnimate/animate.js`.
5081 var $AnimateProvider = ['$provide', function($provide) {
5082 var provider = this;
5084 this.$$registeredAnimations = Object.create(null);
5088 * @name $animateProvider#register
5091 * Registers a new injectable animation factory function. The factory function produces the
5092 * animation object which contains callback functions for each event that is expected to be
5095 * * `eventFn`: `function(element, ... , doneFunction, options)`
5096 * The element to animate, the `doneFunction` and the options fed into the animation. Depending
5097 * on the type of animation additional arguments will be injected into the animation function. The
5098 * list below explains the function signatures for the different animation methods:
5100 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
5101 * - addClass: function(element, addedClasses, doneFunction, options)
5102 * - removeClass: function(element, removedClasses, doneFunction, options)
5103 * - enter, leave, move: function(element, doneFunction, options)
5104 * - animate: function(element, fromStyles, toStyles, doneFunction, options)
5106 * Make sure to trigger the `doneFunction` once the animation is fully complete.
5110 * //enter, leave, move signature
5111 * eventFn : function(element, done, options) {
5112 * //code to run the animation
5113 * //once complete, then run done()
5114 * return function endFunction(wasCancelled) {
5115 * //code to cancel the animation
5121 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
5122 * @param {Function} factory The factory function that will be executed to return the animation
5125 this.register = function(name, factory) {
5126 if (name && name.charAt(0) !== '.') {
5127 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name);
5130 var key = name + '-animation';
5131 provider.$$registeredAnimations[name.substr(1)] = key;
5132 $provide.factory(key, factory);
5137 * @name $animateProvider#classNameFilter
5140 * Sets and/or returns the CSS class regular expression that is checked when performing
5141 * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5142 * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5143 * When setting the `classNameFilter` value, animations will only be performed on elements
5144 * that successfully match the filter expression. This in turn can boost performance
5145 * for low-powered devices as well as applications containing a lot of structural operations.
5146 * @param {RegExp=} expression The className expression which will be checked against all animations
5147 * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5149 this.classNameFilter = function(expression) {
5150 if (arguments.length === 1) {
5151 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
5152 if (this.$$classNameFilter) {
5153 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)");
5154 if (reservedRegex.test(this.$$classNameFilter.toString())) {
5155 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
5160 return this.$$classNameFilter;
5163 this.$get = ['$$animateQueue', function($$animateQueue) {
5164 function domInsert(element, parentElement, afterElement) {
5165 // if for some reason the previous element was removed
5166 // from the dom sometime before this code runs then let's
5167 // just stick to using the parent element as the anchor
5169 var afterNode = extractElementNode(afterElement);
5170 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5171 afterElement = null;
5174 afterElement ? afterElement.after(element) : parentElement.prepend(element);
5180 * @description The $animate service exposes a series of DOM utility methods that provide support
5181 * for animation hooks. The default behavior is the application of DOM operations, however,
5182 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5183 * to ensure that animation runs with the triggered DOM operation.
5185 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
5186 * included and only when it is active then the animation hooks that `$animate` triggers will be
5187 * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5188 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5189 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5191 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5193 * To learn more about enabling animation support, click here to visit the
5194 * {@link ngAnimate ngAnimate module page}.
5197 // we don't call it directly since non-existant arguments may
5198 // be interpreted as null within the sub enabled function
5205 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5206 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5207 * is fired with the following params:
5210 * $animate.on('enter', container,
5211 * function callback(element, phase) {
5212 * // cool we detected an enter animation within the container
5217 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5218 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5219 * as well as among its children
5220 * @param {Function} callback the callback function that will be fired when the listener is triggered
5222 * The arguments present in the callback function are:
5223 * * `element` - The captured DOM element that the animation was fired on.
5224 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5226 on: $$animateQueue.on,
5231 * @name $animate#off
5233 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5234 * can be used in three different ways depending on the arguments:
5237 * // remove all the animation event listeners listening for `enter`
5238 * $animate.off('enter');
5240 * // remove all the animation event listeners listening for `enter` on the given element and its children
5241 * $animate.off('enter', container);
5243 * // remove the event listener function provided by `listenerFn` that is set
5244 * // to listen for `enter` on the given `element` as well as its children
5245 * $animate.off('enter', container, callback);
5248 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
5249 * @param {DOMElement=} container the container element the event listener was placed on
5250 * @param {Function=} callback the callback function that was registered as the listener
5252 off: $$animateQueue.off,
5256 * @name $animate#pin
5258 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5259 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
5260 * element despite being outside the realm of the application or within another application. Say for example if the application
5261 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5262 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5263 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5265 * Note that this feature is only active when the `ngAnimate` module is used.
5267 * @param {DOMElement} element the external element that will be pinned
5268 * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5270 pin: $$animateQueue.pin,
5275 * @name $animate#enabled
5277 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5278 * function can be called in four ways:
5281 * // returns true or false
5282 * $animate.enabled();
5284 * // changes the enabled state for all animations
5285 * $animate.enabled(false);
5286 * $animate.enabled(true);
5288 * // returns true or false if animations are enabled for an element
5289 * $animate.enabled(element);
5291 * // changes the enabled state for an element and its children
5292 * $animate.enabled(element, true);
5293 * $animate.enabled(element, false);
5296 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5297 * @param {boolean=} enabled whether or not the animations will be enabled for the element
5299 * @return {boolean} whether or not animations are enabled
5301 enabled: $$animateQueue.enabled,
5305 * @name $animate#cancel
5307 * @description Cancels the provided animation.
5309 * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
5311 cancel: function(runner) {
5312 runner.end && runner.end();
5318 * @name $animate#enter
5320 * @description Inserts the element into the DOM either after the `after` element (if provided) or
5321 * as the first child within the `parent` element and then triggers an animation.
5322 * A promise is returned that will be resolved during the next digest once the animation
5325 * @param {DOMElement} element the element which will be inserted into the DOM
5326 * @param {DOMElement} parent the parent element which will append the element as
5327 * a child (so long as the after element is not present)
5328 * @param {DOMElement=} after the sibling element after which the element will be appended
5329 * @param {object=} options an optional collection of options/styles that will be applied to the element
5331 * @return {Promise} the animation callback promise
5333 enter: function(element, parent, after, options) {
5334 parent = parent && jqLite(parent);
5335 after = after && jqLite(after);
5336 parent = parent || after.parent();
5337 domInsert(element, parent, after);
5338 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
5344 * @name $animate#move
5346 * @description Inserts (moves) the element into its new position in the DOM either after
5347 * the `after` element (if provided) or as the first child within the `parent` element
5348 * and then triggers an animation. A promise is returned that will be resolved
5349 * during the next digest once the animation has completed.
5351 * @param {DOMElement} element the element which will be moved into the new DOM position
5352 * @param {DOMElement} parent the parent element which will append the element as
5353 * a child (so long as the after element is not present)
5354 * @param {DOMElement=} after the sibling element after which the element will be appended
5355 * @param {object=} options an optional collection of options/styles that will be applied to the element
5357 * @return {Promise} the animation callback promise
5359 move: function(element, parent, after, options) {
5360 parent = parent && jqLite(parent);
5361 after = after && jqLite(after);
5362 parent = parent || after.parent();
5363 domInsert(element, parent, after);
5364 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
5369 * @name $animate#leave
5371 * @description Triggers an animation and then removes the element from the DOM.
5372 * When the function is called a promise is returned that will be resolved during the next
5373 * digest once the animation has completed.
5375 * @param {DOMElement} element the element which will be removed from the DOM
5376 * @param {object=} options an optional collection of options/styles that will be applied to the element
5378 * @return {Promise} the animation callback promise
5380 leave: function(element, options) {
5381 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
5388 * @name $animate#addClass
5391 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
5392 * execution, the addClass operation will only be handled after the next digest and it will not trigger an
5393 * animation if element already contains the CSS class or if the class is removed at a later step.
5394 * Note that class-based animations are treated differently compared to structural animations
5395 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5396 * depending if CSS or JavaScript animations are used.
5398 * @param {DOMElement} element the element which the CSS classes will be applied to
5399 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
5400 * @param {object=} options an optional collection of options/styles that will be applied to the element
5402 * @return {Promise} the animation callback promise
5404 addClass: function(element, className, options) {
5405 options = prepareAnimateOptions(options);
5406 options.addClass = mergeClasses(options.addclass, className);
5407 return $$animateQueue.push(element, 'addClass', options);
5412 * @name $animate#removeClass
5415 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
5416 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
5417 * animation if element does not contain the CSS class or if the class is added at a later step.
5418 * Note that class-based animations are treated differently compared to structural animations
5419 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5420 * depending if CSS or JavaScript animations are used.
5422 * @param {DOMElement} element the element which the CSS classes will be applied to
5423 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5424 * @param {object=} options an optional collection of options/styles that will be applied to the element
5426 * @return {Promise} the animation callback promise
5428 removeClass: function(element, className, options) {
5429 options = prepareAnimateOptions(options);
5430 options.removeClass = mergeClasses(options.removeClass, className);
5431 return $$animateQueue.push(element, 'removeClass', options);
5436 * @name $animate#setClass
5439 * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
5440 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
5441 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
5442 * passed. Note that class-based animations are treated differently compared to structural animations
5443 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5444 * depending if CSS or JavaScript animations are used.
5446 * @param {DOMElement} element the element which the CSS classes will be applied to
5447 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5448 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5449 * @param {object=} options an optional collection of options/styles that will be applied to the element
5451 * @return {Promise} the animation callback promise
5453 setClass: function(element, add, remove, options) {
5454 options = prepareAnimateOptions(options);
5455 options.addClass = mergeClasses(options.addClass, add);
5456 options.removeClass = mergeClasses(options.removeClass, remove);
5457 return $$animateQueue.push(element, 'setClass', options);
5462 * @name $animate#animate
5465 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
5466 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
5467 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and
5468 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
5469 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
5471 * @param {DOMElement} element the element which the CSS styles will be applied to
5472 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
5473 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
5474 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
5475 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
5476 * (Note that if no animation is detected then this value will not be appplied to the element.)
5477 * @param {object=} options an optional collection of options/styles that will be applied to the element
5479 * @return {Promise} the animation callback promise
5481 animate: function(element, from, to, className, options) {
5482 options = prepareAnimateOptions(options);
5483 options.from = options.from ? extend(options.from, from) : from;
5484 options.to = options.to ? extend(options.to, to) : to;
5486 className = className || 'ng-inline-animate';
5487 options.tempClasses = mergeClasses(options.tempClasses, className);
5488 return $$animateQueue.push(element, 'animate', options);
5500 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
5501 * then the `$animateCss` service will actually perform animations.
5503 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
5505 var $CoreAnimateCssProvider = function() {
5506 this.$get = ['$$rAF', '$q', function($$rAF, $q) {
5508 var RAFPromise = function() {};
5509 RAFPromise.prototype = {
5510 done: function(cancel) {
5511 this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
5516 cancel: function() {
5519 getPromise: function() {
5521 this.defer = $q.defer();
5523 return this.defer.promise;
5525 then: function(f1,f2) {
5526 return this.getPromise().then(f1,f2);
5528 'catch': function(f1) {
5529 return this.getPromise()['catch'](f1);
5531 'finally': function(f1) {
5532 return this.getPromise()['finally'](f1);
5536 return function(element, options) {
5537 // there is no point in applying the styles since
5538 // there is no animation that goes on at all in
5539 // this version of $animateCss.
5540 if (options.cleanupStyles) {
5541 options.from = options.to = null;
5545 element.css(options.from);
5546 options.from = null;
5549 var closed, runner = new RAFPromise();
5567 if (options.addClass) {
5568 element.addClass(options.addClass);
5569 options.addClass = null;
5571 if (options.removeClass) {
5572 element.removeClass(options.removeClass);
5573 options.removeClass = null;
5576 element.css(options.to);
5584 /* global stripHash: true */
5587 * ! This is a private undocumented service !
5592 * This object has two goals:
5594 * - hide all the global state in the browser caused by the window object
5595 * - abstract away all the browser specific features and inconsistencies
5597 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
5598 * service, which can be used for convenient testing of the application without the interaction with
5599 * the real browser apis.
5602 * @param {object} window The global window object.
5603 * @param {object} document jQuery wrapped document.
5604 * @param {object} $log window.console or an object with the same interface.
5605 * @param {object} $sniffer $sniffer service
5607 function Browser(window, document, $log, $sniffer) {
5609 rawDocument = document[0],
5610 location = window.location,
5611 history = window.history,
5612 setTimeout = window.setTimeout,
5613 clearTimeout = window.clearTimeout,
5614 pendingDeferIds = {};
5616 self.isMock = false;
5618 var outstandingRequestCount = 0;
5619 var outstandingRequestCallbacks = [];
5621 // TODO(vojta): remove this temporary api
5622 self.$$completeOutstandingRequest = completeOutstandingRequest;
5623 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
5626 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
5627 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
5629 function completeOutstandingRequest(fn) {
5631 fn.apply(null, sliceArgs(arguments, 1));
5633 outstandingRequestCount--;
5634 if (outstandingRequestCount === 0) {
5635 while (outstandingRequestCallbacks.length) {
5637 outstandingRequestCallbacks.pop()();
5646 function getHash(url) {
5647 var index = url.indexOf('#');
5648 return index === -1 ? '' : url.substr(index);
5653 * Note: this method is used only by scenario runner
5654 * TODO(vojta): prefix this method with $$ ?
5655 * @param {function()} callback Function that will be called when no outstanding request
5657 self.notifyWhenNoOutstandingRequests = function(callback) {
5658 if (outstandingRequestCount === 0) {
5661 outstandingRequestCallbacks.push(callback);
5665 //////////////////////////////////////////////////////////////
5667 //////////////////////////////////////////////////////////////
5669 var cachedState, lastHistoryState,
5670 lastBrowserUrl = location.href,
5671 baseElement = document.find('base'),
5672 pendingLocation = null;
5675 lastHistoryState = cachedState;
5678 * @name $browser#url
5682 * Without any argument, this method just returns current value of location.href.
5685 * With at least one argument, this method sets url to new value.
5686 * If html5 history api supported, pushState/replaceState is used, otherwise
5687 * location.href/location.replace is used.
5688 * Returns its own instance to allow chaining
5690 * NOTE: this api is intended for use only by the $location service. Please use the
5691 * {@link ng.$location $location service} to change url.
5693 * @param {string} url New url (when used as setter)
5694 * @param {boolean=} replace Should new url replace current history record?
5695 * @param {object=} state object to use with pushState/replaceState
5697 self.url = function(url, replace, state) {
5698 // In modern browsers `history.state` is `null` by default; treating it separately
5699 // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
5700 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
5701 if (isUndefined(state)) {
5705 // Android Browser BFCache causes location, history reference to become stale.
5706 if (location !== window.location) location = window.location;
5707 if (history !== window.history) history = window.history;
5711 var sameState = lastHistoryState === state;
5713 // Don't change anything if previous and current URLs and states match. This also prevents
5714 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
5715 // See https://github.com/angular/angular.js/commit/ffb2701
5716 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
5719 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
5720 lastBrowserUrl = url;
5721 lastHistoryState = state;
5722 // Don't use history API if only the hash changed
5723 // due to a bug in IE10/IE11 which leads
5724 // to not firing a `hashchange` nor `popstate` event
5725 // in some cases (see #9143).
5726 if ($sniffer.history && (!sameBase || !sameState)) {
5727 history[replace ? 'replaceState' : 'pushState'](state, '', url);
5729 // Do the assignment again so that those two variables are referentially identical.
5730 lastHistoryState = cachedState;
5732 if (!sameBase || pendingLocation) {
5733 pendingLocation = url;
5736 location.replace(url);
5737 } else if (!sameBase) {
5738 location.href = url;
5740 location.hash = getHash(url);
5742 if (location.href !== url) {
5743 pendingLocation = url;
5749 // - pendingLocation is needed as browsers don't allow to read out
5750 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
5751 // https://openradar.appspot.com/22186109).
5752 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
5753 return pendingLocation || location.href.replace(/%27/g,"'");
5758 * @name $browser#state
5761 * This method is a getter.
5763 * Return history.state or null if history.state is undefined.
5765 * @returns {object} state
5767 self.state = function() {
5771 var urlChangeListeners = [],
5772 urlChangeInit = false;
5774 function cacheStateAndFireUrlChange() {
5775 pendingLocation = null;
5780 function getCurrentState() {
5782 return history.state;
5784 // MSIE can reportedly throw when there is no state (UNCONFIRMED).
5788 // This variable should be used *only* inside the cacheState function.
5789 var lastCachedState = null;
5790 function cacheState() {
5791 // This should be the only place in $browser where `history.state` is read.
5792 cachedState = getCurrentState();
5793 cachedState = isUndefined(cachedState) ? null : cachedState;
5795 // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
5796 if (equals(cachedState, lastCachedState)) {
5797 cachedState = lastCachedState;
5799 lastCachedState = cachedState;
5802 function fireUrlChange() {
5803 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
5807 lastBrowserUrl = self.url();
5808 lastHistoryState = cachedState;
5809 forEach(urlChangeListeners, function(listener) {
5810 listener(self.url(), cachedState);
5815 * @name $browser#onUrlChange
5818 * Register callback function that will be called, when url changes.
5820 * It's only called when the url is changed from outside of angular:
5821 * - user types different url into address bar
5822 * - user clicks on history (forward/back) button
5823 * - user clicks on a link
5825 * It's not called when url is changed by $browser.url() method
5827 * The listener gets called with new url as parameter.
5829 * NOTE: this api is intended for use only by the $location service. Please use the
5830 * {@link ng.$location $location service} to monitor url changes in angular apps.
5832 * @param {function(string)} listener Listener function to be called when url changes.
5833 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
5835 self.onUrlChange = function(callback) {
5836 // TODO(vojta): refactor to use node's syntax for events
5837 if (!urlChangeInit) {
5838 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
5839 // don't fire popstate when user change the address bar and don't fire hashchange when url
5840 // changed by push/replaceState
5842 // html5 history api - popstate event
5843 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
5845 jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
5847 urlChangeInit = true;
5850 urlChangeListeners.push(callback);
5856 * Remove popstate and hashchange handler from window.
5858 * NOTE: this api is intended for use only by $rootScope.
5860 self.$$applicationDestroyed = function() {
5861 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
5865 * Checks whether the url has changed outside of Angular.
5866 * Needs to be exported to be able to check for changes that have been done in sync,
5867 * as hashchange/popstate events fire in async.
5869 self.$$checkUrlChange = fireUrlChange;
5871 //////////////////////////////////////////////////////////////
5873 //////////////////////////////////////////////////////////////
5876 * @name $browser#baseHref
5879 * Returns current <base href>
5880 * (always relative - without domain)
5882 * @returns {string} The current base href
5884 self.baseHref = function() {
5885 var href = baseElement.attr('href');
5886 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
5890 * @name $browser#defer
5891 * @param {function()} fn A function, who's execution should be deferred.
5892 * @param {number=} [delay=0] of milliseconds to defer the function execution.
5893 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
5896 * Executes a fn asynchronously via `setTimeout(fn, delay)`.
5898 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
5899 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
5900 * via `$browser.defer.flush()`.
5903 self.defer = function(fn, delay) {
5905 outstandingRequestCount++;
5906 timeoutId = setTimeout(function() {
5907 delete pendingDeferIds[timeoutId];
5908 completeOutstandingRequest(fn);
5910 pendingDeferIds[timeoutId] = true;
5916 * @name $browser#defer.cancel
5919 * Cancels a deferred task identified with `deferId`.
5921 * @param {*} deferId Token returned by the `$browser.defer` function.
5922 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
5925 self.defer.cancel = function(deferId) {
5926 if (pendingDeferIds[deferId]) {
5927 delete pendingDeferIds[deferId];
5928 clearTimeout(deferId);
5929 completeOutstandingRequest(noop);
5937 function $BrowserProvider() {
5938 this.$get = ['$window', '$log', '$sniffer', '$document',
5939 function($window, $log, $sniffer, $document) {
5940 return new Browser($window, $document, $log, $sniffer);
5946 * @name $cacheFactory
5949 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
5954 * var cache = $cacheFactory('cacheId');
5955 * expect($cacheFactory.get('cacheId')).toBe(cache);
5956 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
5958 * cache.put("key", "value");
5959 * cache.put("another key", "another value");
5961 * // We've specified no options on creation
5962 * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
5967 * @param {string} cacheId Name or id of the newly created cache.
5968 * @param {object=} options Options object that specifies the cache behavior. Properties:
5970 * - `{number=}` `capacity` — turns the cache into LRU cache.
5972 * @returns {object} Newly created cache object with the following set of methods:
5974 * - `{object}` `info()` — Returns id, size, and options of cache.
5975 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
5977 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
5978 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
5979 * - `{void}` `removeAll()` — Removes all cached values.
5980 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
5983 <example module="cacheExampleApp">
5984 <file name="index.html">
5985 <div ng-controller="CacheController">
5986 <input ng-model="newCacheKey" placeholder="Key">
5987 <input ng-model="newCacheValue" placeholder="Value">
5988 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
5990 <p ng-if="keys.length">Cached Values</p>
5991 <div ng-repeat="key in keys">
5992 <span ng-bind="key"></span>
5994 <b ng-bind="cache.get(key)"></b>
5998 <div ng-repeat="(key, value) in cache.info()">
5999 <span ng-bind="key"></span>
6001 <b ng-bind="value"></b>
6005 <file name="script.js">
6006 angular.module('cacheExampleApp', []).
6007 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
6009 $scope.cache = $cacheFactory('cacheId');
6010 $scope.put = function(key, value) {
6011 if (angular.isUndefined($scope.cache.get(key))) {
6012 $scope.keys.push(key);
6014 $scope.cache.put(key, angular.isUndefined(value) ? null : value);
6018 <file name="style.css">
6025 function $CacheFactoryProvider() {
6027 this.$get = function() {
6030 function cacheFactory(cacheId, options) {
6031 if (cacheId in caches) {
6032 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId);
6036 stats = extend({}, options, {id: cacheId}),
6038 capacity = (options && options.capacity) || Number.MAX_VALUE,
6039 lruHash = createMap(),
6045 * @name $cacheFactory.Cache
6048 * A cache object used to store and retrieve data, primarily used by
6049 * {@link $http $http} and the {@link ng.directive:script script} directive to cache
6050 * templates and other data.
6053 * angular.module('superCache')
6054 * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
6055 * return $cacheFactory('super-cache');
6062 * it('should behave like a cache', inject(function(superCache) {
6063 * superCache.put('key', 'value');
6064 * superCache.put('another key', 'another value');
6066 * expect(superCache.info()).toEqual({
6067 * id: 'super-cache',
6071 * superCache.remove('another key');
6072 * expect(superCache.get('another key')).toBeUndefined();
6074 * superCache.removeAll();
6075 * expect(superCache.info()).toEqual({
6076 * id: 'super-cache',
6082 return caches[cacheId] = {
6086 * @name $cacheFactory.Cache#put
6090 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
6091 * retrieved later, and incrementing the size of the cache if the key was not already
6092 * present in the cache. If behaving like an LRU cache, it will also remove stale
6093 * entries from the set.
6095 * It will not insert undefined values into the cache.
6097 * @param {string} key the key under which the cached data is stored.
6098 * @param {*} value the value to store alongside the key. If it is undefined, the key
6099 * will not be stored.
6100 * @returns {*} the value stored.
6102 put: function(key, value) {
6103 if (isUndefined(value)) return;
6104 if (capacity < Number.MAX_VALUE) {
6105 var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
6110 if (!(key in data)) size++;
6113 if (size > capacity) {
6114 this.remove(staleEnd.key);
6122 * @name $cacheFactory.Cache#get
6126 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
6128 * @param {string} key the key of the data to be retrieved
6129 * @returns {*} the value stored.
6131 get: function(key) {
6132 if (capacity < Number.MAX_VALUE) {
6133 var lruEntry = lruHash[key];
6135 if (!lruEntry) return;
6146 * @name $cacheFactory.Cache#remove
6150 * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
6152 * @param {string} key the key of the entry to be removed
6154 remove: function(key) {
6155 if (capacity < Number.MAX_VALUE) {
6156 var lruEntry = lruHash[key];
6158 if (!lruEntry) return;
6160 if (lruEntry == freshEnd) freshEnd = lruEntry.p;
6161 if (lruEntry == staleEnd) staleEnd = lruEntry.n;
6162 link(lruEntry.n,lruEntry.p);
6164 delete lruHash[key];
6167 if (!(key in data)) return;
6176 * @name $cacheFactory.Cache#removeAll
6180 * Clears the cache object of any entries.
6182 removeAll: function() {
6185 lruHash = createMap();
6186 freshEnd = staleEnd = null;
6192 * @name $cacheFactory.Cache#destroy
6196 * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
6197 * removing it from the {@link $cacheFactory $cacheFactory} set.
6199 destroy: function() {
6203 delete caches[cacheId];
6209 * @name $cacheFactory.Cache#info
6213 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
6215 * @returns {object} an object with the following properties:
6217 * <li>**id**: the id of the cache instance</li>
6218 * <li>**size**: the number of entries kept in the cache instance</li>
6219 * <li>**...**: any additional properties from the options object when creating the
6224 return extend({}, stats, {size: size});
6230 * makes the `entry` the freshEnd of the LRU linked list
6232 function refresh(entry) {
6233 if (entry != freshEnd) {
6236 } else if (staleEnd == entry) {
6240 link(entry.n, entry.p);
6241 link(entry, freshEnd);
6249 * bidirectionally links two entries of the LRU linked list
6251 function link(nextEntry, prevEntry) {
6252 if (nextEntry != prevEntry) {
6253 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
6254 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
6262 * @name $cacheFactory#info
6265 * Get information about all the caches that have been created
6267 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
6269 cacheFactory.info = function() {
6271 forEach(caches, function(cache, cacheId) {
6272 info[cacheId] = cache.info();
6280 * @name $cacheFactory#get
6283 * Get access to a cache object by the `cacheId` used when it was created.
6285 * @param {string} cacheId Name or id of a cache to access.
6286 * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
6288 cacheFactory.get = function(cacheId) {
6289 return caches[cacheId];
6293 return cacheFactory;
6299 * @name $templateCache
6302 * The first time a template is used, it is loaded in the template cache for quick retrieval. You
6303 * can load templates directly into the cache in a `script` tag, or by consuming the
6304 * `$templateCache` service directly.
6306 * Adding via the `script` tag:
6309 * <script type="text/ng-template" id="templateId.html">
6310 * <p>This is the content of the template</p>
6314 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
6315 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
6316 * element with ng-app attribute), otherwise the template will be ignored.
6318 * Adding via the `$templateCache` service:
6321 * var myApp = angular.module('myApp', []);
6322 * myApp.run(function($templateCache) {
6323 * $templateCache.put('templateId.html', 'This is the content of the template');
6327 * To retrieve the template later, simply use it in your HTML:
6329 * <div ng-include=" 'templateId.html' "></div>
6332 * or get it via Javascript:
6334 * $templateCache.get('templateId.html')
6337 * See {@link ng.$cacheFactory $cacheFactory}.
6340 function $TemplateCacheProvider() {
6341 this.$get = ['$cacheFactory', function($cacheFactory) {
6342 return $cacheFactory('templates');
6346 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6347 * Any commits to this file should be reviewed with security in mind. *
6348 * Changes to this file can potentially create security vulnerabilities. *
6349 * An approval from 2 Core members with history of modifying *
6350 * this file is required. *
6352 * Does the change somehow allow for arbitrary javascript to be executed? *
6353 * Or allows for someone to change the prototype of built-in objects? *
6354 * Or gives undesired access to variables likes document or window? *
6355 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
6357 /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
6359 * DOM-related variables:
6361 * - "node" - DOM Node
6362 * - "element" - DOM Element or Node
6363 * - "$node" or "$element" - jqLite-wrapped node or element
6366 * Compiler related stuff:
6368 * - "linkFn" - linking fn of a single directive
6369 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
6370 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
6371 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
6381 * Compiles an HTML string or DOM into a template and produces a template function, which
6382 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
6384 * The compilation is a process of walking the DOM tree and matching DOM elements to
6385 * {@link ng.$compileProvider#directive directives}.
6387 * <div class="alert alert-warning">
6388 * **Note:** This document is an in-depth reference of all directive options.
6389 * For a gentle introduction to directives with examples of common use cases,
6390 * see the {@link guide/directive directive guide}.
6393 * ## Comprehensive Directive API
6395 * There are many different options for a directive.
6397 * The difference resides in the return value of the factory function.
6398 * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
6399 * or just the `postLink` function (all other properties will have the default values).
6401 * <div class="alert alert-success">
6402 * **Best Practice:** It's recommended to use the "directive definition object" form.
6405 * Here's an example directive declared with a Directive Definition Object:
6408 * var myModule = angular.module(...);
6410 * myModule.directive('directiveName', function factory(injectables) {
6411 * var directiveDefinitionObject = {
6413 * template: '<div></div>', // or // function(tElement, tAttrs) { ... },
6415 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
6416 * transclude: false,
6418 * templateNamespace: 'html',
6420 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
6421 * controllerAs: 'stringIdentifier',
6422 * bindToController: false,
6423 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
6424 * compile: function compile(tElement, tAttrs, transclude) {
6426 * pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6427 * post: function postLink(scope, iElement, iAttrs, controller) { ... }
6430 * // return function postLink( ... ) { ... }
6434 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6435 * // post: function postLink(scope, iElement, iAttrs, controller) { ... }
6438 * // link: function postLink( ... ) { ... }
6440 * return directiveDefinitionObject;
6444 * <div class="alert alert-warning">
6445 * **Note:** Any unspecified options will use the default value. You can see the default values below.
6448 * Therefore the above can be simplified as:
6451 * var myModule = angular.module(...);
6453 * myModule.directive('directiveName', function factory(injectables) {
6454 * var directiveDefinitionObject = {
6455 * link: function postLink(scope, iElement, iAttrs) { ... }
6457 * return directiveDefinitionObject;
6459 * // return function postLink(scope, iElement, iAttrs) { ... }
6465 * ### Directive Definition Object
6467 * The directive definition object provides instructions to the {@link ng.$compile
6468 * compiler}. The attributes are:
6470 * #### `multiElement`
6471 * When this property is set to true, the HTML compiler will collect DOM nodes between
6472 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
6473 * together as the directive elements. It is recommended that this feature be used on directives
6474 * which are not strictly behavioural (such as {@link ngClick}), and which
6475 * do not manipulate or replace child nodes (such as {@link ngInclude}).
6478 * When there are multiple directives defined on a single DOM element, sometimes it
6479 * is necessary to specify the order in which the directives are applied. The `priority` is used
6480 * to sort the directives before their `compile` functions get called. Priority is defined as a
6481 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
6482 * are also run in priority order, but post-link functions are run in reverse order. The order
6483 * of directives with the same priority is undefined. The default priority is `0`.
6486 * If set to true then the current `priority` will be the last set of directives
6487 * which will execute (any directives at the current priority will still execute
6488 * as the order of execution on same `priority` is undefined). Note that expressions
6489 * and other directives used in the directive's template will also be excluded from execution.
6492 * The scope property can be `true`, an object or a falsy value:
6494 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope.
6496 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
6497 * the directive's element. If multiple directives on the same element request a new scope,
6498 * only one new scope is created. The new scope rule does not apply for the root of the template
6499 * since the root of the template always gets a new scope.
6501 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The
6502 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent
6503 * scope. This is useful when creating reusable components, which should not accidentally read or modify
6504 * data in the parent scope.
6506 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
6507 * directive's element. These local properties are useful for aliasing values for templates. The keys in
6508 * the object hash map to the name of the property on the isolate scope; the values define how the property
6509 * is bound to the parent scope, via matching attributes on the directive's element:
6511 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6512 * always a string since DOM attributes are strings. If no `attr` name is specified then the
6513 * attribute name is assumed to be the same as the local name.
6514 * Given `<widget my-attr="hello {{name}}">` and widget definition
6515 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
6516 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
6517 * `localName` property on the widget scope. The `name` is read from the parent scope (not
6520 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the
6521 * parent scope property of name defined via the value of the `attr` attribute. If no `attr`
6522 * name is specified then the attribute name is assumed to be the same as the local name.
6523 * Given `<widget my-attr="parentModel">` and widget definition of
6524 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
6525 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
6526 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
6527 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
6528 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
6529 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
6530 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
6532 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
6533 * If no `attr` name is specified then the attribute name is assumed to be the same as the
6534 * local name. Given `<widget my-attr="count = count + value">` and widget definition of
6535 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
6536 * a function wrapper for the `count = count + value` expression. Often it's desirable to
6537 * pass data from the isolated scope via an expression to the parent scope, this can be
6538 * done by passing a map of local variable names and values into the expression wrapper fn.
6539 * For example, if the expression is `increment(amount)` then we can specify the amount value
6540 * by calling the `localFn` as `localFn({amount: 22})`.
6542 * In general it's possible to apply more than one directive to one element, but there might be limitations
6543 * depending on the type of scope required by the directives. The following points will help explain these limitations.
6544 * For simplicity only two directives are taken into account, but it is also applicable for several directives:
6546 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
6547 * * **child scope** + **no scope** => Both directives will share one single child scope
6548 * * **child scope** + **child scope** => Both directives will share one single child scope
6549 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
6550 * its parent's scope
6551 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
6552 * be applied to the same element.
6553 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
6554 * cannot be applied to the same element.
6557 * #### `bindToController`
6558 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
6559 * allow a component to have its properties bound to the controller, rather than to scope. When the controller
6560 * is instantiated, the initial values of the isolate scope bindings are already available.
6563 * Controller constructor function. The controller is instantiated before the
6564 * pre-linking phase and can be accessed by other directives (see
6565 * `require` attribute). This allows the directives to communicate with each other and augment
6566 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
6568 * * `$scope` - Current scope associated with the element
6569 * * `$element` - Current element
6570 * * `$attrs` - Current attributes object for the element
6571 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
6572 * `function([scope], cloneLinkingFn, futureParentElement)`.
6573 * * `scope`: optional argument to override the scope.
6574 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
6575 * * `futureParentElement`:
6576 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
6577 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
6578 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
6579 * and when the `cloneLinkinFn` is passed,
6580 * as those elements need to created and cloned in a special way when they are defined outside their
6581 * usual containers (e.g. like `<svg>`).
6582 * * See also the `directive.templateNamespace` property.
6586 * Require another directive and inject its controller as the fourth argument to the linking function. The
6587 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
6588 * injected argument will be an array in corresponding order. If no such directive can be
6589 * found, or if the directive does not have a controller, then an error is raised (unless no link function
6590 * is specified, in which case error checking is skipped). The name can be prefixed with:
6592 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
6593 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
6594 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
6595 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
6596 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
6597 * `null` to the `link` fn if not found.
6598 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
6599 * `null` to the `link` fn if not found.
6602 * #### `controllerAs`
6603 * Identifier name for a reference to the controller in the directive's scope.
6604 * This allows the controller to be referenced from the directive template. This is especially
6605 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
6606 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
6607 * `controllerAs` reference might overwrite a property that already exists on the parent scope.
6611 * String of subset of `EACM` which restricts the directive to a specific directive
6612 * declaration style. If omitted, the defaults (elements and attributes) are used.
6614 * * `E` - Element name (default): `<my-directive></my-directive>`
6615 * * `A` - Attribute (default): `<div my-directive="exp"></div>`
6616 * * `C` - Class: `<div class="my-directive: exp;"></div>`
6617 * * `M` - Comment: `<!-- directive: my-directive exp -->`
6620 * #### `templateNamespace`
6621 * String representing the document type used by the markup in the template.
6622 * AngularJS needs this information as those elements need to be created and cloned
6623 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
6625 * * `html` - All root nodes in the template are HTML. Root nodes may also be
6626 * top-level elements such as `<svg>` or `<math>`.
6627 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
6628 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
6630 * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
6633 * HTML markup that may:
6634 * * Replace the contents of the directive's element (default).
6635 * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
6636 * * Wrap the contents of the directive's element (if `transclude` is true).
6640 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
6641 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
6642 * function api below) and returns a string value.
6645 * #### `templateUrl`
6646 * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
6648 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
6649 * for later when the template has been resolved. In the meantime it will continue to compile and link
6650 * sibling and parent elements as though this element had not contained any directives.
6652 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
6653 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
6654 * case when only one deeply nested directive has `templateUrl`.
6656 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}
6658 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
6659 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
6660 * a string value representing the url. In either case, the template URL is passed through {@link
6661 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
6664 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
6665 * specify what the template should replace. Defaults to `false`.
6667 * * `true` - the template will replace the directive's element.
6668 * * `false` - the template will replace the contents of the directive's element.
6670 * The replacement process migrates all of the attributes / classes from the old element to the new
6671 * one. See the {@link guide/directive#template-expanding-directive
6672 * Directives Guide} for an example.
6674 * There are very few scenarios where element replacement is required for the application function,
6675 * the main one being reusable custom components that are used within SVG contexts
6676 * (because SVG doesn't work with custom elements in the DOM tree).
6679 * Extract the contents of the element where the directive appears and make it available to the directive.
6680 * The contents are compiled and provided to the directive as a **transclusion function**. See the
6681 * {@link $compile#transclusion Transclusion} section below.
6683 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
6684 * directive's element or the entire element:
6686 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
6687 * * `'element'` - transclude the whole of the directive's element including any directives on this
6688 * element that defined at a lower priority than this directive. When used, the `template`
6689 * property is ignored.
6695 * function compile(tElement, tAttrs, transclude) { ... }
6698 * The compile function deals with transforming the template DOM. Since most directives do not do
6699 * template transformation, it is not used often. The compile function takes the following arguments:
6701 * * `tElement` - template element - The element where the directive has been declared. It is
6702 * safe to do template transformation on the element and child elements only.
6704 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
6705 * between all directive compile functions.
6707 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
6709 * <div class="alert alert-warning">
6710 * **Note:** The template instance and the link instance may be different objects if the template has
6711 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
6712 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
6713 * should be done in a linking function rather than in a compile function.
6716 * <div class="alert alert-warning">
6717 * **Note:** The compile function cannot handle directives that recursively use themselves in their
6718 * own templates or compile functions. Compiling these directives results in an infinite loop and a
6719 * stack overflow errors.
6721 * This can be avoided by manually using $compile in the postLink function to imperatively compile
6722 * a directive's template instead of relying on automatic template compilation via `template` or
6723 * `templateUrl` declaration or manual compilation inside the compile function.
6726 * <div class="alert alert-danger">
6727 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
6728 * e.g. does not know about the right outer scope. Please use the transclude function that is passed
6729 * to the link function instead.
6732 * A compile function can have a return value which can be either a function or an object.
6734 * * returning a (post-link) function - is equivalent to registering the linking function via the
6735 * `link` property of the config object when the compile function is empty.
6737 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
6738 * control when a linking function should be called during the linking phase. See info about
6739 * pre-linking and post-linking functions below.
6743 * This property is used only if the `compile` property is not defined.
6746 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
6749 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
6750 * executed after the template has been cloned. This is where most of the directive logic will be
6753 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
6754 * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
6756 * * `iElement` - instance element - The element where the directive is to be used. It is safe to
6757 * manipulate the children of the element only in `postLink` function since the children have
6758 * already been linked.
6760 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
6761 * between all directive linking functions.
6763 * * `controller` - the directive's required controller instance(s) - Instances are shared
6764 * among all directives, which allows the directives to use the controllers as a communication
6765 * channel. The exact value depends on the directive's `require` property:
6766 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
6767 * * `string`: the controller instance
6768 * * `array`: array of controller instances
6770 * If a required controller cannot be found, and it is optional, the instance is `null`,
6771 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
6773 * Note that you can also require the directive's own controller - it will be made available like
6774 * any other controller.
6776 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
6777 * This is the same as the `$transclude`
6778 * parameter of directive controllers, see there for details.
6779 * `function([scope], cloneLinkingFn, futureParentElement)`.
6781 * #### Pre-linking function
6783 * Executed before the child elements are linked. Not safe to do DOM transformation since the
6784 * compiler linking function will fail to locate the correct elements for linking.
6786 * #### Post-linking function
6788 * Executed after the child elements are linked.
6790 * Note that child elements that contain `templateUrl` directives will not have been compiled
6791 * and linked since they are waiting for their template to load asynchronously and their own
6792 * compilation and linking has been suspended until that occurs.
6794 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
6795 * for their async templates to be resolved.
6800 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
6801 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
6802 * scope from where they were taken.
6804 * Transclusion is used (often with {@link ngTransclude}) to insert the
6805 * original contents of a directive's element into a specified place in the template of the directive.
6806 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
6807 * content has access to the properties on the scope from which it was taken, even if the directive
6808 * has isolated scope.
6809 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
6811 * This makes it possible for the widget to have private state for its template, while the transcluded
6812 * content has access to its originating scope.
6814 * <div class="alert alert-warning">
6815 * **Note:** When testing an element transclude directive you must not place the directive at the root of the
6816 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
6817 * Testing Transclusion Directives}.
6820 * #### Transclusion Functions
6822 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
6823 * function** to the directive's `link` function and `controller`. This transclusion function is a special
6824 * **linking function** that will return the compiled contents linked to a new transclusion scope.
6826 * <div class="alert alert-info">
6827 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
6828 * ngTransclude will deal with it for us.
6831 * If you want to manually control the insertion and removal of the transcluded content in your directive
6832 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
6833 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
6835 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
6836 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
6837 * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
6839 * <div class="alert alert-info">
6840 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
6841 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
6844 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
6845 * attach function**:
6848 * var transcludedContent, transclusionScope;
6850 * $transclude(function(clone, scope) {
6851 * element.append(clone);
6852 * transcludedContent = clone;
6853 * transclusionScope = scope;
6857 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
6858 * associated transclusion scope:
6861 * transcludedContent.remove();
6862 * transclusionScope.$destroy();
6865 * <div class="alert alert-info">
6866 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
6867 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
6868 * then you are also responsible for calling `$destroy` on the transclusion scope.
6871 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
6872 * automatically destroy their transluded clones as necessary so you do not need to worry about this if
6873 * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
6876 * #### Transclusion Scopes
6878 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
6879 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
6880 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
6883 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
6889 * <div transclusion>
6895 * The `$parent` scope hierarchy will look like this:
6903 * but the scopes will inherit prototypically from different scopes to their `$parent`.
6914 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
6915 * `link()` or `compile()` functions. It has a variety of uses.
6917 * accessing *Normalized attribute names:*
6918 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
6919 * the attributes object allows for normalized access to
6922 * * *Directive inter-communication:* All directives share the same instance of the attributes
6923 * object which allows the directives to use the attributes object as inter directive
6926 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
6927 * allowing other directives to read the interpolated value.
6929 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
6930 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
6931 * the only way to easily get the actual value because during the linking phase the interpolation
6932 * hasn't been evaluated yet and so the value is at this time set to `undefined`.
6935 * function linkingFn(scope, elm, attrs, ctrl) {
6936 * // get the attribute value
6937 * console.log(attrs.ngModel);
6939 * // change the attribute
6940 * attrs.$set('ngModel', 'new value');
6942 * // observe changes to interpolated attribute
6943 * attrs.$observe('ngModel', function(value) {
6944 * console.log('ngModel has changed value to ' + value);
6951 * <div class="alert alert-warning">
6952 * **Note**: Typically directives are registered with `module.directive`. The example below is
6953 * to illustrate how `$compile` works.
6956 <example module="compileExample">
6957 <file name="index.html">
6959 angular.module('compileExample', [], function($compileProvider) {
6960 // configure new 'compile' directive by passing a directive
6961 // factory function. The factory function injects the '$compile'
6962 $compileProvider.directive('compile', function($compile) {
6963 // directive factory creates a link function
6964 return function(scope, element, attrs) {
6967 // watch the 'compile' expression for changes
6968 return scope.$eval(attrs.compile);
6971 // when the 'compile' expression changes
6972 // assign it into the current DOM
6973 element.html(value);
6975 // compile the new DOM and link it to the current
6977 // NOTE: we only compile .childNodes so that
6978 // we don't get into infinite loop compiling ourselves
6979 $compile(element.contents())(scope);
6985 .controller('GreeterController', ['$scope', function($scope) {
6986 $scope.name = 'Angular';
6987 $scope.html = 'Hello {{name}}';
6990 <div ng-controller="GreeterController">
6991 <input ng-model="name"> <br/>
6992 <textarea ng-model="html"></textarea> <br/>
6993 <div compile="html"></div>
6996 <file name="protractor.js" type="protractor">
6997 it('should auto compile', function() {
6998 var textarea = $('textarea');
6999 var output = $('div[compile]');
7000 // The initial state reads 'Hello Angular'.
7001 expect(output.getText()).toBe('Hello Angular');
7003 textarea.sendKeys('{{name}}!');
7004 expect(output.getText()).toBe('Angular!');
7011 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
7012 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
7014 * <div class="alert alert-danger">
7015 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
7016 * e.g. will not use the right outer scope. Please pass the transclude function as a
7017 * `parentBoundTranscludeFn` to the link function instead.
7020 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
7021 * root element(s), not their children)
7022 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
7023 * (a DOM element/tree) to a scope. Where:
7025 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
7026 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
7027 * `template` and call the `cloneAttachFn` function allowing the caller to attach the
7028 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
7029 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
7031 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
7032 * * `scope` - is the current scope with which the linking function is working with.
7034 * * `options` - An optional object hash with linking options. If `options` is provided, then the following
7035 * keys may be used to control linking behavior:
7037 * * `parentBoundTranscludeFn` - the transclude function made available to
7038 * directives; if given, it will be passed through to the link functions of
7039 * directives found in `element` during compilation.
7040 * * `transcludeControllers` - an object hash with keys that map controller names
7041 * to controller instances; if given, it will make the controllers
7042 * available to directives.
7043 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
7044 * the cloned elements; only needed for transcludes that are allowed to contain non html
7045 * elements (e.g. SVG elements). See also the directive.controller property.
7047 * Calling the linking function returns the element of the template. It is either the original
7048 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
7050 * After linking the view is not updated until after a call to $digest which typically is done by
7051 * Angular automatically.
7053 * If you need access to the bound view, there are two ways to do it:
7055 * - If you are not asking the linking function to clone the template, create the DOM element(s)
7056 * before you send them to the compiler and keep this reference around.
7058 * var element = $compile('<p>{{total}}</p>')(scope);
7061 * - if on the other hand, you need the element to be cloned, the view reference from the original
7062 * example would not point to the clone, but rather to the original template that was cloned. In
7063 * this case, you can access the clone via the cloneAttachFn:
7065 * var templateElement = angular.element('<p>{{total}}</p>'),
7068 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
7069 * //attach the clone to DOM document at the right place
7072 * //now we have reference to the cloned DOM via `clonedElement`
7076 * For information on how the compiler works, see the
7077 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
7080 var $compileMinErr = minErr('$compile');
7084 * @name $compileProvider
7088 $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
7089 function $CompileProvider($provide, $$sanitizeUriProvider) {
7090 var hasDirectives = {},
7091 Suffix = 'Directive',
7092 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
7093 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
7094 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
7095 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
7097 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
7098 // The assumption is that future DOM event attribute names will begin with
7099 // 'on' and be composed of only English letters.
7100 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
7102 function parseIsolateBindings(scope, directiveName, isController) {
7103 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
7107 forEach(scope, function(definition, scopeName) {
7108 var match = definition.match(LOCAL_REGEXP);
7111 throw $compileMinErr('iscp',
7112 "Invalid {3} for directive '{0}'." +
7113 " Definition: {... {1}: '{2}' ...}",
7114 directiveName, scopeName, definition,
7115 (isController ? "controller bindings definition" :
7116 "isolate scope definition"));
7119 bindings[scopeName] = {
7121 collection: match[2] === '*',
7122 optional: match[3] === '?',
7123 attrName: match[4] || scopeName
7130 function parseDirectiveBindings(directive, directiveName) {
7133 bindToController: null
7135 if (isObject(directive.scope)) {
7136 if (directive.bindToController === true) {
7137 bindings.bindToController = parseIsolateBindings(directive.scope,
7138 directiveName, true);
7139 bindings.isolateScope = {};
7141 bindings.isolateScope = parseIsolateBindings(directive.scope,
7142 directiveName, false);
7145 if (isObject(directive.bindToController)) {
7146 bindings.bindToController =
7147 parseIsolateBindings(directive.bindToController, directiveName, true);
7149 if (isObject(bindings.bindToController)) {
7150 var controller = directive.controller;
7151 var controllerAs = directive.controllerAs;
7153 // There is no controller, there may or may not be a controllerAs property
7154 throw $compileMinErr('noctrl',
7155 "Cannot bind to controller without directive '{0}'s controller.",
7157 } else if (!identifierForController(controller, controllerAs)) {
7158 // There is a controller, but no identifier or controllerAs property
7159 throw $compileMinErr('noident',
7160 "Cannot bind to controller without identifier for directive '{0}'.",
7167 function assertValidDirectiveName(name) {
7168 var letter = name.charAt(0);
7169 if (!letter || letter !== lowercase(letter)) {
7170 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
7172 if (name !== name.trim()) {
7173 throw $compileMinErr('baddir',
7174 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
7181 * @name $compileProvider#directive
7185 * Register a new directive with the compiler.
7187 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
7188 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
7189 * names and the values are the factories.
7190 * @param {Function|Array} directiveFactory An injectable directive factory function. See
7191 * {@link guide/directive} for more info.
7192 * @returns {ng.$compileProvider} Self for chaining.
7194 this.directive = function registerDirective(name, directiveFactory) {
7195 assertNotHasOwnProperty(name, 'directive');
7196 if (isString(name)) {
7197 assertValidDirectiveName(name);
7198 assertArg(directiveFactory, 'directiveFactory');
7199 if (!hasDirectives.hasOwnProperty(name)) {
7200 hasDirectives[name] = [];
7201 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
7202 function($injector, $exceptionHandler) {
7203 var directives = [];
7204 forEach(hasDirectives[name], function(directiveFactory, index) {
7206 var directive = $injector.invoke(directiveFactory);
7207 if (isFunction(directive)) {
7208 directive = { compile: valueFn(directive) };
7209 } else if (!directive.compile && directive.link) {
7210 directive.compile = valueFn(directive.link);
7212 directive.priority = directive.priority || 0;
7213 directive.index = index;
7214 directive.name = directive.name || name;
7215 directive.require = directive.require || (directive.controller && directive.name);
7216 directive.restrict = directive.restrict || 'EA';
7217 var bindings = directive.$$bindings =
7218 parseDirectiveBindings(directive, directive.name);
7219 if (isObject(bindings.isolateScope)) {
7220 directive.$$isolateBindings = bindings.isolateScope;
7222 directive.$$moduleName = directiveFactory.$$moduleName;
7223 directives.push(directive);
7225 $exceptionHandler(e);
7231 hasDirectives[name].push(directiveFactory);
7233 forEach(name, reverseParams(registerDirective));
7241 * @name $compileProvider#aHrefSanitizationWhitelist
7245 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7246 * urls during a[href] sanitization.
7248 * The sanitization is a security measure aimed at preventing XSS attacks via html links.
7250 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
7251 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
7252 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7253 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7255 * @param {RegExp=} regexp New regexp to whitelist urls with.
7256 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7257 * chaining otherwise.
7259 this.aHrefSanitizationWhitelist = function(regexp) {
7260 if (isDefined(regexp)) {
7261 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
7264 return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
7271 * @name $compileProvider#imgSrcSanitizationWhitelist
7275 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7276 * urls during img[src] sanitization.
7278 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
7280 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
7281 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
7282 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7283 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7285 * @param {RegExp=} regexp New regexp to whitelist urls with.
7286 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7287 * chaining otherwise.
7289 this.imgSrcSanitizationWhitelist = function(regexp) {
7290 if (isDefined(regexp)) {
7291 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
7294 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
7300 * @name $compileProvider#debugInfoEnabled
7302 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
7303 * current debugInfoEnabled state
7304 * @returns {*} current value if used as getter or itself (chaining) if used as setter
7309 * Call this method to enable/disable various debug runtime information in the compiler such as adding
7310 * binding information and a reference to the current scope on to DOM elements.
7311 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
7312 * * `ng-binding` CSS class
7313 * * `$binding` data property containing an array of the binding expressions
7315 * You may want to disable this in production for a significant performance boost. See
7316 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
7318 * The default value is true.
7320 var debugInfoEnabled = true;
7321 this.debugInfoEnabled = function(enabled) {
7322 if (isDefined(enabled)) {
7323 debugInfoEnabled = enabled;
7326 return debugInfoEnabled;
7330 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
7331 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
7332 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
7333 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
7335 var Attributes = function(element, attributesToCopy) {
7336 if (attributesToCopy) {
7337 var keys = Object.keys(attributesToCopy);
7340 for (i = 0, l = keys.length; i < l; i++) {
7342 this[key] = attributesToCopy[key];
7348 this.$$element = element;
7351 Attributes.prototype = {
7354 * @name $compile.directive.Attributes#$normalize
7358 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
7359 * `data-`) to its normalized, camelCase form.
7361 * Also there is special case for Moz prefix starting with upper case letter.
7363 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
7365 * @param {string} name Name to normalize
7367 $normalize: directiveNormalize,
7372 * @name $compile.directive.Attributes#$addClass
7376 * Adds the CSS class value specified by the classVal parameter to the element. If animations
7377 * are enabled then an animation will be triggered for the class addition.
7379 * @param {string} classVal The className value that will be added to the element
7381 $addClass: function(classVal) {
7382 if (classVal && classVal.length > 0) {
7383 $animate.addClass(this.$$element, classVal);
7389 * @name $compile.directive.Attributes#$removeClass
7393 * Removes the CSS class value specified by the classVal parameter from the element. If
7394 * animations are enabled then an animation will be triggered for the class removal.
7396 * @param {string} classVal The className value that will be removed from the element
7398 $removeClass: function(classVal) {
7399 if (classVal && classVal.length > 0) {
7400 $animate.removeClass(this.$$element, classVal);
7406 * @name $compile.directive.Attributes#$updateClass
7410 * Adds and removes the appropriate CSS class values to the element based on the difference
7411 * between the new and old CSS class values (specified as newClasses and oldClasses).
7413 * @param {string} newClasses The current CSS className value
7414 * @param {string} oldClasses The former CSS className value
7416 $updateClass: function(newClasses, oldClasses) {
7417 var toAdd = tokenDifference(newClasses, oldClasses);
7418 if (toAdd && toAdd.length) {
7419 $animate.addClass(this.$$element, toAdd);
7422 var toRemove = tokenDifference(oldClasses, newClasses);
7423 if (toRemove && toRemove.length) {
7424 $animate.removeClass(this.$$element, toRemove);
7429 * Set a normalized attribute on the element in a way such that all directives
7430 * can share the attribute. This function properly handles boolean attributes.
7431 * @param {string} key Normalized key. (ie ngAttribute)
7432 * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
7433 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
7435 * @param {string=} attrName Optional none normalized name. Defaults to key.
7437 $set: function(key, value, writeAttr, attrName) {
7438 // TODO: decide whether or not to throw an error if "class"
7439 //is set through this function since it may cause $updateClass to
7442 var node = this.$$element[0],
7443 booleanKey = getBooleanAttrName(node, key),
7444 aliasedKey = getAliasedAttrName(key),
7449 this.$$element.prop(key, value);
7450 attrName = booleanKey;
7451 } else if (aliasedKey) {
7452 this[aliasedKey] = value;
7453 observer = aliasedKey;
7458 // translate normalized key to actual key
7460 this.$attr[key] = attrName;
7462 attrName = this.$attr[key];
7464 this.$attr[key] = attrName = snake_case(key, '-');
7468 nodeName = nodeName_(this.$$element);
7470 if ((nodeName === 'a' && key === 'href') ||
7471 (nodeName === 'img' && key === 'src')) {
7472 // sanitize a[href] and img[src] values
7473 this[key] = value = $$sanitizeUri(value, key === 'src');
7474 } else if (nodeName === 'img' && key === 'srcset') {
7475 // sanitize img[srcset] values
7478 // first check if there are spaces because it's not the same pattern
7479 var trimmedSrcset = trim(value);
7480 // ( 999x ,| 999w ,| ,|, )
7481 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
7482 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
7484 // split srcset into tuple of uri and descriptor except for the last item
7485 var rawUris = trimmedSrcset.split(pattern);
7488 var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
7489 for (var i = 0; i < nbrUrisWith2parts; i++) {
7490 var innerIdx = i * 2;
7492 result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
7493 // add the descriptor
7494 result += (" " + trim(rawUris[innerIdx + 1]));
7497 // split the last item into uri and descriptor
7498 var lastTuple = trim(rawUris[i * 2]).split(/\s/);
7500 // sanitize the last uri
7501 result += $$sanitizeUri(trim(lastTuple[0]), true);
7503 // and add the last descriptor if any
7504 if (lastTuple.length === 2) {
7505 result += (" " + trim(lastTuple[1]));
7507 this[key] = value = result;
7510 if (writeAttr !== false) {
7511 if (value === null || isUndefined(value)) {
7512 this.$$element.removeAttr(attrName);
7514 this.$$element.attr(attrName, value);
7519 var $$observers = this.$$observers;
7520 $$observers && forEach($$observers[observer], function(fn) {
7524 $exceptionHandler(e);
7532 * @name $compile.directive.Attributes#$observe
7536 * Observes an interpolated attribute.
7538 * The observer function will be invoked once during the next `$digest` following
7539 * compilation. The observer is then invoked whenever the interpolated value
7542 * @param {string} key Normalized key. (ie ngAttribute) .
7543 * @param {function(interpolatedValue)} fn Function that will be called whenever
7544 the interpolated value of the attribute changes.
7545 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
7546 * @returns {function()} Returns a deregistration function for this observer.
7548 $observe: function(key, fn) {
7550 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
7551 listeners = ($$observers[key] || ($$observers[key] = []));
7554 $rootScope.$evalAsync(function() {
7555 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
7556 // no one registered attribute interpolation function, so lets call it manually
7562 arrayRemove(listeners, fn);
7568 function safeAddClass($element, className) {
7570 $element.addClass(className);
7572 // ignore, since it means that we are trying to set class on
7573 // SVG element, where class name is read-only.
7578 var startSymbol = $interpolate.startSymbol(),
7579 endSymbol = $interpolate.endSymbol(),
7580 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
7582 : function denormalizeTemplate(template) {
7583 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
7585 NG_ATTR_BINDING = /^ngAttr[A-Z]/;
7586 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
7588 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
7589 var bindings = $element.data('$binding') || [];
7591 if (isArray(binding)) {
7592 bindings = bindings.concat(binding);
7594 bindings.push(binding);
7597 $element.data('$binding', bindings);
7600 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
7601 safeAddClass($element, 'ng-binding');
7604 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
7605 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
7606 $element.data(dataName, scope);
7609 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
7610 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
7615 //================================
7617 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
7618 previousCompileContext) {
7619 if (!($compileNodes instanceof jqLite)) {
7620 // jquery always rewraps, whereas we need to preserve the original selector so that we can
7622 $compileNodes = jqLite($compileNodes);
7624 // We can not compile top level text elements since text nodes can be merged and we will
7625 // not be able to attach scope data to them, so we will wrap them in <span>
7626 forEach($compileNodes, function(node, index) {
7627 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
7628 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
7631 var compositeLinkFn =
7632 compileNodes($compileNodes, transcludeFn, $compileNodes,
7633 maxPriority, ignoreDirective, previousCompileContext);
7634 compile.$$addScopeClass($compileNodes);
7635 var namespace = null;
7636 return function publicLinkFn(scope, cloneConnectFn, options) {
7637 assertArg(scope, 'scope');
7639 if (previousCompileContext && previousCompileContext.needsNewScope) {
7640 // A parent directive did a replace and a directive on this element asked
7641 // for transclusion, which caused us to lose a layer of element on which
7642 // we could hold the new transclusion scope, so we will create it manually
7644 scope = scope.$parent.$new();
7647 options = options || {};
7648 var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
7649 transcludeControllers = options.transcludeControllers,
7650 futureParentElement = options.futureParentElement;
7652 // When `parentBoundTranscludeFn` is passed, it is a
7653 // `controllersBoundTransclude` function (it was previously passed
7654 // as `transclude` to directive.link) so we must unwrap it to get
7655 // its `boundTranscludeFn`
7656 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
7657 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
7661 namespace = detectNamespaceForChildElements(futureParentElement);
7664 if (namespace !== 'html') {
7665 // When using a directive with replace:true and templateUrl the $compileNodes
7666 // (or a child element inside of them)
7667 // might change, so we need to recreate the namespace adapted compileNodes
7668 // for call to the link function.
7669 // Note: This will already clone the nodes...
7671 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
7673 } else if (cloneConnectFn) {
7674 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
7675 // and sometimes changes the structure of the DOM.
7676 $linkNode = JQLitePrototype.clone.call($compileNodes);
7678 $linkNode = $compileNodes;
7681 if (transcludeControllers) {
7682 for (var controllerName in transcludeControllers) {
7683 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
7687 compile.$$addScopeInfo($linkNode, scope);
7689 if (cloneConnectFn) cloneConnectFn($linkNode, scope);
7690 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
7695 function detectNamespaceForChildElements(parentElement) {
7696 // TODO: Make this detect MathML as well...
7697 var node = parentElement && parentElement[0];
7701 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
7706 * Compile function matches each node in nodeList against the directives. Once all directives
7707 * for a particular node are collected their compile functions are executed. The compile
7708 * functions return values - the linking functions - are combined into a composite linking
7709 * function, which is the a linking function for the node.
7711 * @param {NodeList} nodeList an array of nodes or NodeList to compile
7712 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7713 * scope argument is auto-generated to the new child of the transcluded parent scope.
7714 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
7715 * the rootElement must be set the jqLite collection of the compile root. This is
7716 * needed so that the jqLite collection items can be replaced with widgets.
7717 * @param {number=} maxPriority Max directive priority.
7718 * @returns {Function} A composite linking function of all of the matched directives or null.
7720 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
7721 previousCompileContext) {
7723 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
7725 for (var i = 0; i < nodeList.length; i++) {
7726 attrs = new Attributes();
7728 // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
7729 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
7732 nodeLinkFn = (directives.length)
7733 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
7734 null, [], [], previousCompileContext)
7737 if (nodeLinkFn && nodeLinkFn.scope) {
7738 compile.$$addScopeClass(attrs.$$element);
7741 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
7742 !(childNodes = nodeList[i].childNodes) ||
7745 : compileNodes(childNodes,
7747 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
7748 && nodeLinkFn.transclude) : transcludeFn);
7750 if (nodeLinkFn || childLinkFn) {
7751 linkFns.push(i, nodeLinkFn, childLinkFn);
7753 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
7756 //use the previous context only for the first element in the virtual group
7757 previousCompileContext = null;
7760 // return a linking function if we have found anything, null otherwise
7761 return linkFnFound ? compositeLinkFn : null;
7763 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
7764 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
7768 if (nodeLinkFnFound) {
7769 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
7770 // offsets don't get screwed up
7771 var nodeListLength = nodeList.length;
7772 stableNodeList = new Array(nodeListLength);
7774 // create a sparse array by only copying the elements which have a linkFn
7775 for (i = 0; i < linkFns.length; i+=3) {
7777 stableNodeList[idx] = nodeList[idx];
7780 stableNodeList = nodeList;
7783 for (i = 0, ii = linkFns.length; i < ii;) {
7784 node = stableNodeList[linkFns[i++]];
7785 nodeLinkFn = linkFns[i++];
7786 childLinkFn = linkFns[i++];
7789 if (nodeLinkFn.scope) {
7790 childScope = scope.$new();
7791 compile.$$addScopeInfo(jqLite(node), childScope);
7796 if (nodeLinkFn.transcludeOnThisElement) {
7797 childBoundTranscludeFn = createBoundTranscludeFn(
7798 scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
7800 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
7801 childBoundTranscludeFn = parentBoundTranscludeFn;
7803 } else if (!parentBoundTranscludeFn && transcludeFn) {
7804 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
7807 childBoundTranscludeFn = null;
7810 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
7812 } else if (childLinkFn) {
7813 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
7819 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
7821 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
7823 if (!transcludedScope) {
7824 transcludedScope = scope.$new(false, containingScope);
7825 transcludedScope.$$transcluded = true;
7828 return transcludeFn(transcludedScope, cloneFn, {
7829 parentBoundTranscludeFn: previousBoundTranscludeFn,
7830 transcludeControllers: controllers,
7831 futureParentElement: futureParentElement
7835 return boundTranscludeFn;
7839 * Looks for directives on the given node and adds them to the directive collection which is
7842 * @param node Node to search.
7843 * @param directives An array to which the directives are added to. This array is sorted before
7844 * the function returns.
7845 * @param attrs The shared attrs object which is used to populate the normalized attributes.
7846 * @param {number=} maxPriority Max directive priority.
7848 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
7849 var nodeType = node.nodeType,
7850 attrsMap = attrs.$attr,
7855 case NODE_TYPE_ELEMENT: /* Element */
7856 // use the node name: <directive>
7857 addDirective(directives,
7858 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
7860 // iterate over the attributes
7861 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
7862 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
7863 var attrStartName = false;
7864 var attrEndName = false;
7868 value = trim(attr.value);
7870 // support ngAttr attribute binding
7871 ngAttrName = directiveNormalize(name);
7872 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
7873 name = name.replace(PREFIX_REGEXP, '')
7874 .substr(8).replace(/_(.)/g, function(match, letter) {
7875 return letter.toUpperCase();
7879 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
7880 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
7881 attrStartName = name;
7882 attrEndName = name.substr(0, name.length - 5) + 'end';
7883 name = name.substr(0, name.length - 6);
7886 nName = directiveNormalize(name.toLowerCase());
7887 attrsMap[nName] = name;
7888 if (isNgAttr || !attrs.hasOwnProperty(nName)) {
7889 attrs[nName] = value;
7890 if (getBooleanAttrName(node, nName)) {
7891 attrs[nName] = true; // presence means true
7894 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
7895 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
7899 // use class as directive
7900 className = node.className;
7901 if (isObject(className)) {
7902 // Maybe SVGAnimatedString
7903 className = className.animVal;
7905 if (isString(className) && className !== '') {
7906 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
7907 nName = directiveNormalize(match[2]);
7908 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
7909 attrs[nName] = trim(match[3]);
7911 className = className.substr(match.index + match[0].length);
7915 case NODE_TYPE_TEXT: /* Text Node */
7917 // Workaround for #11781
7918 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) {
7919 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
7920 node.parentNode.removeChild(node.nextSibling);
7923 addTextInterpolateDirective(directives, node.nodeValue);
7925 case NODE_TYPE_COMMENT: /* Comment */
7927 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
7929 nName = directiveNormalize(match[1]);
7930 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
7931 attrs[nName] = trim(match[2]);
7935 // turns out that under some circumstances IE9 throws errors when one attempts to read
7936 // comment's node value.
7937 // Just ignore it and continue. (Can't seem to reproduce in test case.)
7942 directives.sort(byPriority);
7947 * Given a node with an directive-start it collects all of the siblings until it finds
7954 function groupScan(node, attrStart, attrEnd) {
7957 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
7960 throw $compileMinErr('uterdir',
7961 "Unterminated attribute, found '{0}' but no matching '{1}' found.",
7962 attrStart, attrEnd);
7964 if (node.nodeType == NODE_TYPE_ELEMENT) {
7965 if (node.hasAttribute(attrStart)) depth++;
7966 if (node.hasAttribute(attrEnd)) depth--;
7969 node = node.nextSibling;
7970 } while (depth > 0);
7975 return jqLite(nodes);
7979 * Wrapper for linking function which converts normal linking function into a grouped
7984 * @returns {Function}
7986 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
7987 return function(scope, element, attrs, controllers, transcludeFn) {
7988 element = groupScan(element[0], attrStart, attrEnd);
7989 return linkFn(scope, element, attrs, controllers, transcludeFn);
7994 * Once the directives have been collected, their compile functions are executed. This method
7995 * is responsible for inlining directive templates as well as terminating the application
7996 * of the directives if the terminal directive has been reached.
7998 * @param {Array} directives Array of collected directives to execute their compile function.
7999 * this needs to be pre-sorted by priority order.
8000 * @param {Node} compileNode The raw DOM node to apply the compile functions to
8001 * @param {Object} templateAttrs The shared attribute function
8002 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
8003 * scope argument is auto-generated to the new
8004 * child of the transcluded parent scope.
8005 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
8006 * argument has the root jqLite array so that we can replace nodes
8008 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
8009 * compiling the transclusion.
8010 * @param {Array.<Function>} preLinkFns
8011 * @param {Array.<Function>} postLinkFns
8012 * @param {Object} previousCompileContext Context used for previous compilation of the current
8014 * @returns {Function} linkFn
8016 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
8017 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
8018 previousCompileContext) {
8019 previousCompileContext = previousCompileContext || {};
8021 var terminalPriority = -Number.MAX_VALUE,
8022 newScopeDirective = previousCompileContext.newScopeDirective,
8023 controllerDirectives = previousCompileContext.controllerDirectives,
8024 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
8025 templateDirective = previousCompileContext.templateDirective,
8026 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
8027 hasTranscludeDirective = false,
8028 hasTemplate = false,
8029 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
8030 $compileNode = templateAttrs.$$element = jqLite(compileNode),
8034 replaceDirective = originalReplaceDirective,
8035 childTranscludeFn = transcludeFn,
8039 // executes all directives on the current element
8040 for (var i = 0, ii = directives.length; i < ii; i++) {
8041 directive = directives[i];
8042 var attrStart = directive.$$start;
8043 var attrEnd = directive.$$end;
8045 // collect multiblock sections
8047 $compileNode = groupScan(compileNode, attrStart, attrEnd);
8049 $template = undefined;
8051 if (terminalPriority > directive.priority) {
8052 break; // prevent further processing of directives
8055 if (directiveValue = directive.scope) {
8057 // skip the check for directives with async templates, we'll check the derived sync
8058 // directive when the template arrives
8059 if (!directive.templateUrl) {
8060 if (isObject(directiveValue)) {
8061 // This directive is trying to add an isolated scope.
8062 // Check that there is no scope of any kind already
8063 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
8064 directive, $compileNode);
8065 newIsolateScopeDirective = directive;
8067 // This directive is trying to add a child scope.
8068 // Check that there is no isolated scope already
8069 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
8074 newScopeDirective = newScopeDirective || directive;
8077 directiveName = directive.name;
8079 if (!directive.templateUrl && directive.controller) {
8080 directiveValue = directive.controller;
8081 controllerDirectives = controllerDirectives || createMap();
8082 assertNoDuplicate("'" + directiveName + "' controller",
8083 controllerDirectives[directiveName], directive, $compileNode);
8084 controllerDirectives[directiveName] = directive;
8087 if (directiveValue = directive.transclude) {
8088 hasTranscludeDirective = true;
8090 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
8091 // This option should only be used by directives that know how to safely handle element transclusion,
8092 // where the transcluded nodes are added or replaced after linking.
8093 if (!directive.$$tlb) {
8094 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
8095 nonTlbTranscludeDirective = directive;
8098 if (directiveValue == 'element') {
8099 hasElementTranscludeDirective = true;
8100 terminalPriority = directive.priority;
8101 $template = $compileNode;
8102 $compileNode = templateAttrs.$$element =
8103 jqLite(document.createComment(' ' + directiveName + ': ' +
8104 templateAttrs[directiveName] + ' '));
8105 compileNode = $compileNode[0];
8106 replaceWith(jqCollection, sliceArgs($template), compileNode);
8108 childTranscludeFn = compile($template, transcludeFn, terminalPriority,
8109 replaceDirective && replaceDirective.name, {
8111 // - controllerDirectives - otherwise we'll create duplicates controllers
8112 // - newIsolateScopeDirective or templateDirective - combining templates with
8113 // element transclusion doesn't make sense.
8115 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
8116 // on the same element more than once.
8117 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8120 $template = jqLite(jqLiteClone(compileNode)).contents();
8121 $compileNode.empty(); // clear contents
8122 childTranscludeFn = compile($template, transcludeFn, undefined,
8123 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
8127 if (directive.template) {
8129 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8130 templateDirective = directive;
8132 directiveValue = (isFunction(directive.template))
8133 ? directive.template($compileNode, templateAttrs)
8134 : directive.template;
8136 directiveValue = denormalizeTemplate(directiveValue);
8138 if (directive.replace) {
8139 replaceDirective = directive;
8140 if (jqLiteIsTextNode(directiveValue)) {
8143 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
8145 compileNode = $template[0];
8147 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8148 throw $compileMinErr('tplrt',
8149 "Template for directive '{0}' must have exactly one root element. {1}",
8153 replaceWith(jqCollection, $compileNode, compileNode);
8155 var newTemplateAttrs = {$attr: {}};
8157 // combine directives from the original node and from the template:
8158 // - take the array of directives for this element
8159 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
8160 // - collect directives from the template and sort them by priority
8161 // - combine directives as: processed + template + unprocessed
8162 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
8163 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
8165 if (newIsolateScopeDirective || newScopeDirective) {
8166 // The original directive caused the current element to be replaced but this element
8167 // also needs to have a new scope, so we need to tell the template directives
8168 // that they would need to get their scope from further up, if they require transclusion
8169 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
8171 directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
8172 mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
8174 ii = directives.length;
8176 $compileNode.html(directiveValue);
8180 if (directive.templateUrl) {
8182 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8183 templateDirective = directive;
8185 if (directive.replace) {
8186 replaceDirective = directive;
8189 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
8190 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
8191 controllerDirectives: controllerDirectives,
8192 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
8193 newIsolateScopeDirective: newIsolateScopeDirective,
8194 templateDirective: templateDirective,
8195 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8197 ii = directives.length;
8198 } else if (directive.compile) {
8200 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
8201 if (isFunction(linkFn)) {
8202 addLinkFns(null, linkFn, attrStart, attrEnd);
8203 } else if (linkFn) {
8204 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
8207 $exceptionHandler(e, startingTag($compileNode));
8211 if (directive.terminal) {
8212 nodeLinkFn.terminal = true;
8213 terminalPriority = Math.max(terminalPriority, directive.priority);
8218 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
8219 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
8220 nodeLinkFn.templateOnThisElement = hasTemplate;
8221 nodeLinkFn.transclude = childTranscludeFn;
8223 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
8225 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
8228 ////////////////////
8230 function addLinkFns(pre, post, attrStart, attrEnd) {
8232 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
8233 pre.require = directive.require;
8234 pre.directiveName = directiveName;
8235 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8236 pre = cloneAndAnnotateFn(pre, {isolateScope: true});
8238 preLinkFns.push(pre);
8241 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
8242 post.require = directive.require;
8243 post.directiveName = directiveName;
8244 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8245 post = cloneAndAnnotateFn(post, {isolateScope: true});
8247 postLinkFns.push(post);
8252 function getControllers(directiveName, require, $element, elementControllers) {
8255 if (isString(require)) {
8256 var match = require.match(REQUIRE_PREFIX_REGEXP);
8257 var name = require.substring(match[0].length);
8258 var inheritType = match[1] || match[3];
8259 var optional = match[2] === '?';
8261 //If only parents then start at the parent element
8262 if (inheritType === '^^') {
8263 $element = $element.parent();
8264 //Otherwise attempt getting the controller from elementControllers in case
8265 //the element is transcluded (and has no data) and to avoid .data if possible
8267 value = elementControllers && elementControllers[name];
8268 value = value && value.instance;
8272 var dataName = '$' + name + 'Controller';
8273 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
8276 if (!value && !optional) {
8277 throw $compileMinErr('ctreq',
8278 "Controller '{0}', required by directive '{1}', can't be found!",
8279 name, directiveName);
8281 } else if (isArray(require)) {
8283 for (var i = 0, ii = require.length; i < ii; i++) {
8284 value[i] = getControllers(directiveName, require[i], $element, elementControllers);
8288 return value || null;
8291 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
8292 var elementControllers = createMap();
8293 for (var controllerKey in controllerDirectives) {
8294 var directive = controllerDirectives[controllerKey];
8296 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
8299 $transclude: transcludeFn
8302 var controller = directive.controller;
8303 if (controller == '@') {
8304 controller = attrs[directive.name];
8307 var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
8309 // For directives with element transclusion the element is a comment,
8310 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
8311 // clean up (http://bugs.jquery.com/ticket/8335).
8312 // Instead, we save the controllers for the element in a local hash and attach to .data
8313 // later, once we have the actual element.
8314 elementControllers[directive.name] = controllerInstance;
8315 if (!hasElementTranscludeDirective) {
8316 $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
8319 return elementControllers;
8322 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
8323 var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
8324 attrs, removeScopeBindingWatches, removeControllerBindingWatches;
8326 if (compileNode === linkNode) {
8327 attrs = templateAttrs;
8328 $element = templateAttrs.$$element;
8330 $element = jqLite(linkNode);
8331 attrs = new Attributes($element, templateAttrs);
8334 controllerScope = scope;
8335 if (newIsolateScopeDirective) {
8336 isolateScope = scope.$new(true);
8337 } else if (newScopeDirective) {
8338 controllerScope = scope.$parent;
8341 if (boundTranscludeFn) {
8342 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
8343 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
8344 transcludeFn = controllersBoundTransclude;
8345 transcludeFn.$$boundTransclude = boundTranscludeFn;
8348 if (controllerDirectives) {
8349 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
8352 if (newIsolateScopeDirective) {
8353 // Initialize isolate scope bindings for new isolate scope directive.
8354 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
8355 templateDirective === newIsolateScopeDirective.$$originalDirective)));
8356 compile.$$addScopeClass($element, true);
8357 isolateScope.$$isolateBindings =
8358 newIsolateScopeDirective.$$isolateBindings;
8359 removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
8360 isolateScope.$$isolateBindings,
8361 newIsolateScopeDirective);
8362 if (removeScopeBindingWatches) {
8363 isolateScope.$on('$destroy', removeScopeBindingWatches);
8367 // Initialize bindToController bindings
8368 for (var name in elementControllers) {
8369 var controllerDirective = controllerDirectives[name];
8370 var controller = elementControllers[name];
8371 var bindings = controllerDirective.$$bindings.bindToController;
8373 if (controller.identifier && bindings) {
8374 removeControllerBindingWatches =
8375 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8378 var controllerResult = controller();
8379 if (controllerResult !== controller.instance) {
8380 // If the controller constructor has a return value, overwrite the instance
8381 // from setupControllers
8382 controller.instance = controllerResult;
8383 $element.data('$' + controllerDirective.name + 'Controller', controllerResult);
8384 removeControllerBindingWatches && removeControllerBindingWatches();
8385 removeControllerBindingWatches =
8386 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8391 for (i = 0, ii = preLinkFns.length; i < ii; i++) {
8392 linkFn = preLinkFns[i];
8393 invokeLinkFn(linkFn,
8394 linkFn.isolateScope ? isolateScope : scope,
8397 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8403 // We only pass the isolate scope, if the isolate directive has a template,
8404 // otherwise the child elements do not belong to the isolate directive.
8405 var scopeToChild = scope;
8406 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
8407 scopeToChild = isolateScope;
8409 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
8412 for (i = postLinkFns.length - 1; i >= 0; i--) {
8413 linkFn = postLinkFns[i];
8414 invokeLinkFn(linkFn,
8415 linkFn.isolateScope ? isolateScope : scope,
8418 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8423 // This is the function that is injected as `$transclude`.
8424 // Note: all arguments are optional!
8425 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
8426 var transcludeControllers;
8428 // No scope passed in:
8429 if (!isScope(scope)) {
8430 futureParentElement = cloneAttachFn;
8431 cloneAttachFn = scope;
8435 if (hasElementTranscludeDirective) {
8436 transcludeControllers = elementControllers;
8438 if (!futureParentElement) {
8439 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
8441 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
8446 // Depending upon the context in which a directive finds itself it might need to have a new isolated
8447 // or child scope created. For instance:
8448 // * if the directive has been pulled into a template because another directive with a higher priority
8449 // asked for element transclusion
8450 // * if the directive itself asks for transclusion but it is at the root of a template and the original
8451 // element was replaced. See https://github.com/angular/angular.js/issues/12936
8452 function markDirectiveScope(directives, isolateScope, newScope) {
8453 for (var j = 0, jj = directives.length; j < jj; j++) {
8454 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
8459 * looks up the directive and decorates it with exception handling and proper parameters. We
8460 * call this the boundDirective.
8462 * @param {string} name name of the directive to look up.
8463 * @param {string} location The directive must be found in specific format.
8464 * String containing any of theses characters:
8466 * * `E`: element name
8470 * @returns {boolean} true if directive was added.
8472 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
8474 if (name === ignoreDirective) return null;
8476 if (hasDirectives.hasOwnProperty(name)) {
8477 for (var directive, directives = $injector.get(name + Suffix),
8478 i = 0, ii = directives.length; i < ii; i++) {
8480 directive = directives[i];
8481 if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
8482 directive.restrict.indexOf(location) != -1) {
8483 if (startAttrName) {
8484 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
8486 tDirectives.push(directive);
8489 } catch (e) { $exceptionHandler(e); }
8497 * looks up the directive and returns true if it is a multi-element directive,
8498 * and therefore requires DOM nodes between -start and -end markers to be grouped
8501 * @param {string} name name of the directive to look up.
8502 * @returns true if directive was registered as multi-element.
8504 function directiveIsMultiElement(name) {
8505 if (hasDirectives.hasOwnProperty(name)) {
8506 for (var directive, directives = $injector.get(name + Suffix),
8507 i = 0, ii = directives.length; i < ii; i++) {
8508 directive = directives[i];
8509 if (directive.multiElement) {
8518 * When the element is replaced with HTML template then the new attributes
8519 * on the template need to be merged with the existing attributes in the DOM.
8520 * The desired effect is to have both of the attributes present.
8522 * @param {object} dst destination attributes (original DOM)
8523 * @param {object} src source attributes (from the directive template)
8525 function mergeTemplateAttributes(dst, src) {
8526 var srcAttr = src.$attr,
8527 dstAttr = dst.$attr,
8528 $element = dst.$$element;
8530 // reapply the old attributes to the new element
8531 forEach(dst, function(value, key) {
8532 if (key.charAt(0) != '$') {
8533 if (src[key] && src[key] !== value) {
8534 value += (key === 'style' ? ';' : ' ') + src[key];
8536 dst.$set(key, value, true, srcAttr[key]);
8540 // copy the new attributes on the old attrs object
8541 forEach(src, function(value, key) {
8542 if (key == 'class') {
8543 safeAddClass($element, value);
8544 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
8545 } else if (key == 'style') {
8546 $element.attr('style', $element.attr('style') + ';' + value);
8547 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
8548 // `dst` will never contain hasOwnProperty as DOM parser won't let it.
8549 // You will get an "InvalidCharacterError: DOM Exception 5" error if you
8550 // have an attribute like "has-own-property" or "data-has-own-property", etc.
8551 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
8553 dstAttr[key] = srcAttr[key];
8559 function compileTemplateUrl(directives, $compileNode, tAttrs,
8560 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
8562 afterTemplateNodeLinkFn,
8563 afterTemplateChildLinkFn,
8564 beforeTemplateCompileNode = $compileNode[0],
8565 origAsyncDirective = directives.shift(),
8566 derivedSyncDirective = inherit(origAsyncDirective, {
8567 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
8569 templateUrl = (isFunction(origAsyncDirective.templateUrl))
8570 ? origAsyncDirective.templateUrl($compileNode, tAttrs)
8571 : origAsyncDirective.templateUrl,
8572 templateNamespace = origAsyncDirective.templateNamespace;
8574 $compileNode.empty();
8576 $templateRequest(templateUrl)
8577 .then(function(content) {
8578 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
8580 content = denormalizeTemplate(content);
8582 if (origAsyncDirective.replace) {
8583 if (jqLiteIsTextNode(content)) {
8586 $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
8588 compileNode = $template[0];
8590 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8591 throw $compileMinErr('tplrt',
8592 "Template for directive '{0}' must have exactly one root element. {1}",
8593 origAsyncDirective.name, templateUrl);
8596 tempTemplateAttrs = {$attr: {}};
8597 replaceWith($rootElement, $compileNode, compileNode);
8598 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
8600 if (isObject(origAsyncDirective.scope)) {
8601 // the original directive that caused the template to be loaded async required
8603 markDirectiveScope(templateDirectives, true);
8605 directives = templateDirectives.concat(directives);
8606 mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
8608 compileNode = beforeTemplateCompileNode;
8609 $compileNode.html(content);
8612 directives.unshift(derivedSyncDirective);
8614 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
8615 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
8616 previousCompileContext);
8617 forEach($rootElement, function(node, i) {
8618 if (node == compileNode) {
8619 $rootElement[i] = $compileNode[0];
8622 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
8624 while (linkQueue.length) {
8625 var scope = linkQueue.shift(),
8626 beforeTemplateLinkNode = linkQueue.shift(),
8627 linkRootElement = linkQueue.shift(),
8628 boundTranscludeFn = linkQueue.shift(),
8629 linkNode = $compileNode[0];
8631 if (scope.$$destroyed) continue;
8633 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
8634 var oldClasses = beforeTemplateLinkNode.className;
8636 if (!(previousCompileContext.hasElementTranscludeDirective &&
8637 origAsyncDirective.replace)) {
8638 // it was cloned therefore we have to clone as well.
8639 linkNode = jqLiteClone(compileNode);
8641 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
8643 // Copy in CSS classes from original node
8644 safeAddClass(jqLite(linkNode), oldClasses);
8646 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8647 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8649 childBoundTranscludeFn = boundTranscludeFn;
8651 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
8652 childBoundTranscludeFn);
8657 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
8658 var childBoundTranscludeFn = boundTranscludeFn;
8659 if (scope.$$destroyed) return;
8661 linkQueue.push(scope,
8664 childBoundTranscludeFn);
8666 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8667 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8669 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
8676 * Sorting function for bound directives.
8678 function byPriority(a, b) {
8679 var diff = b.priority - a.priority;
8680 if (diff !== 0) return diff;
8681 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
8682 return a.index - b.index;
8685 function assertNoDuplicate(what, previousDirective, directive, element) {
8687 function wrapModuleNameIfDefined(moduleName) {
8689 (' (module: ' + moduleName + ')') :
8693 if (previousDirective) {
8694 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
8695 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
8696 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
8701 function addTextInterpolateDirective(directives, text) {
8702 var interpolateFn = $interpolate(text, true);
8703 if (interpolateFn) {
8706 compile: function textInterpolateCompileFn(templateNode) {
8707 var templateNodeParent = templateNode.parent(),
8708 hasCompileParent = !!templateNodeParent.length;
8710 // When transcluding a template that has bindings in the root
8711 // we don't have a parent and thus need to add the class during linking fn.
8712 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
8714 return function textInterpolateLinkFn(scope, node) {
8715 var parent = node.parent();
8716 if (!hasCompileParent) compile.$$addBindingClass(parent);
8717 compile.$$addBindingInfo(parent, interpolateFn.expressions);
8718 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
8719 node[0].nodeValue = value;
8728 function wrapTemplate(type, template) {
8729 type = lowercase(type || 'html');
8733 var wrapper = document.createElement('div');
8734 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
8735 return wrapper.childNodes[0].childNodes;
8742 function getTrustedContext(node, attrNormalizedName) {
8743 if (attrNormalizedName == "srcdoc") {
8746 var tag = nodeName_(node);
8747 // maction[xlink:href] can source SVG. It's not limited to <maction>.
8748 if (attrNormalizedName == "xlinkHref" ||
8749 (tag == "form" && attrNormalizedName == "action") ||
8750 (tag != "img" && (attrNormalizedName == "src" ||
8751 attrNormalizedName == "ngSrc"))) {
8752 return $sce.RESOURCE_URL;
8757 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
8758 var trustedContext = getTrustedContext(node, name);
8759 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
8761 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
8763 // no interpolation found -> ignore
8764 if (!interpolateFn) return;
8767 if (name === "multiple" && nodeName_(node) === "select") {
8768 throw $compileMinErr("selmulti",
8769 "Binding to the 'multiple' attribute is not supported. Element: {0}",
8775 compile: function() {
8777 pre: function attrInterpolatePreLinkFn(scope, element, attr) {
8778 var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
8780 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
8781 throw $compileMinErr('nodomevents',
8782 "Interpolations for HTML DOM event attributes are disallowed. Please use the " +
8783 "ng- versions (such as ng-click instead of onclick) instead.");
8786 // If the attribute has changed since last $interpolate()ed
8787 var newValue = attr[name];
8788 if (newValue !== value) {
8789 // we need to interpolate again since the attribute value has been updated
8790 // (e.g. by another directive's compile function)
8791 // ensure unset/empty values make interpolateFn falsy
8792 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
8796 // if attribute was updated so that there is no interpolation going on we don't want to
8797 // register any observers
8798 if (!interpolateFn) return;
8800 // initialize attr object so that it's ready in case we need the value for isolate
8801 // scope initialization, otherwise the value would not be available from isolate
8802 // directive's linking fn during linking phase
8803 attr[name] = interpolateFn(scope);
8805 ($$observers[name] || ($$observers[name] = [])).$$inter = true;
8806 (attr.$$observers && attr.$$observers[name].$$scope || scope).
8807 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
8808 //special case for class attribute addition + removal
8809 //so that class changes can tap into the animation
8810 //hooks provided by the $animate service. Be sure to
8811 //skip animations when the first digest occurs (when
8812 //both the new and the old values are the same) since
8813 //the CSS classes are the non-interpolated values
8814 if (name === 'class' && newValue != oldValue) {
8815 attr.$updateClass(newValue, oldValue);
8817 attr.$set(name, newValue);
8828 * This is a special jqLite.replaceWith, which can replace items which
8829 * have no parents, provided that the containing jqLite collection is provided.
8831 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
8832 * in the root of the tree.
8833 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
8834 * the shell, but replace its DOM node reference.
8835 * @param {Node} newNode The new DOM node.
8837 function replaceWith($rootElement, elementsToRemove, newNode) {
8838 var firstElementToRemove = elementsToRemove[0],
8839 removeCount = elementsToRemove.length,
8840 parent = firstElementToRemove.parentNode,
8844 for (i = 0, ii = $rootElement.length; i < ii; i++) {
8845 if ($rootElement[i] == firstElementToRemove) {
8846 $rootElement[i++] = newNode;
8847 for (var j = i, j2 = j + removeCount - 1,
8848 jj = $rootElement.length;
8849 j < jj; j++, j2++) {
8851 $rootElement[j] = $rootElement[j2];
8853 delete $rootElement[j];
8856 $rootElement.length -= removeCount - 1;
8858 // If the replaced element is also the jQuery .context then replace it
8859 // .context is a deprecated jQuery api, so we should set it only when jQuery set it
8860 // http://api.jquery.com/context/
8861 if ($rootElement.context === firstElementToRemove) {
8862 $rootElement.context = newNode;
8870 parent.replaceChild(newNode, firstElementToRemove);
8873 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
8874 var fragment = document.createDocumentFragment();
8875 fragment.appendChild(firstElementToRemove);
8877 if (jqLite.hasData(firstElementToRemove)) {
8878 // Copy over user data (that includes Angular's $scope etc.). Don't copy private
8879 // data here because there's no public interface in jQuery to do that and copying over
8880 // event listeners (which is the main use of private data) wouldn't work anyway.
8881 jqLite.data(newNode, jqLite.data(firstElementToRemove));
8883 // Remove data of the replaced element. We cannot just call .remove()
8884 // on the element it since that would deallocate scope that is needed
8885 // for the new node. Instead, remove the data "manually".
8887 delete jqLite.cache[firstElementToRemove[jqLite.expando]];
8889 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
8890 // the replaced element. The cleanData version monkey-patched by Angular would cause
8891 // the scope to be trashed and we do need the very same scope to work with the new
8892 // element. However, we cannot just cache the non-patched version and use it here as
8893 // that would break if another library patches the method after Angular does (one
8894 // example is jQuery UI). Instead, set a flag indicating scope destroying should be
8895 // skipped this one time.
8896 skipDestroyOnNextJQueryCleanData = true;
8897 jQuery.cleanData([firstElementToRemove]);
8901 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
8902 var element = elementsToRemove[k];
8903 jqLite(element).remove(); // must do this way to clean up expando
8904 fragment.appendChild(element);
8905 delete elementsToRemove[k];
8908 elementsToRemove[0] = newNode;
8909 elementsToRemove.length = 1;
8913 function cloneAndAnnotateFn(fn, annotation) {
8914 return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
8918 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
8920 linkFn(scope, $element, attrs, controllers, transcludeFn);
8922 $exceptionHandler(e, startingTag($element));
8927 // Set up $watches for isolate scope and controller bindings. This process
8928 // only occurs for isolate scopes and new scopes with controllerAs.
8929 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
8930 var removeWatchCollection = [];
8931 forEach(bindings, function(definition, scopeName) {
8932 var attrName = definition.attrName,
8933 optional = definition.optional,
8934 mode = definition.mode, // @, =, or &
8936 parentGet, parentSet, compare;
8941 if (!optional && !hasOwnProperty.call(attrs, attrName)) {
8942 destination[scopeName] = attrs[attrName] = void 0;
8944 attrs.$observe(attrName, function(value) {
8945 if (isString(value)) {
8946 destination[scopeName] = value;
8949 attrs.$$observers[attrName].$$scope = scope;
8950 if (isString(attrs[attrName])) {
8951 // If the attribute has been provided then we trigger an interpolation to ensure
8952 // the value is there for use in the link fn
8953 destination[scopeName] = $interpolate(attrs[attrName])(scope);
8958 if (!hasOwnProperty.call(attrs, attrName)) {
8959 if (optional) break;
8960 attrs[attrName] = void 0;
8962 if (optional && !attrs[attrName]) break;
8964 parentGet = $parse(attrs[attrName]);
8965 if (parentGet.literal) {
8968 compare = function(a, b) { return a === b || (a !== a && b !== b); };
8970 parentSet = parentGet.assign || function() {
8971 // reset the change, or we will throw this exception on every $digest
8972 lastValue = destination[scopeName] = parentGet(scope);
8973 throw $compileMinErr('nonassign',
8974 "Expression '{0}' used with directive '{1}' is non-assignable!",
8975 attrs[attrName], directive.name);
8977 lastValue = destination[scopeName] = parentGet(scope);
8978 var parentValueWatch = function parentValueWatch(parentValue) {
8979 if (!compare(parentValue, destination[scopeName])) {
8980 // we are out of sync and need to copy
8981 if (!compare(parentValue, lastValue)) {
8982 // parent changed and it has precedence
8983 destination[scopeName] = parentValue;
8985 // if the parent can be assigned then do so
8986 parentSet(scope, parentValue = destination[scopeName]);
8989 return lastValue = parentValue;
8991 parentValueWatch.$stateful = true;
8993 if (definition.collection) {
8994 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
8996 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
8998 removeWatchCollection.push(removeWatch);
9002 // Don't assign Object.prototype method to scope
9003 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
9005 // Don't assign noop to destination if expression is not valid
9006 if (parentGet === noop && optional) break;
9008 destination[scopeName] = function(locals) {
9009 return parentGet(scope, locals);
9015 return removeWatchCollection.length && function removeWatches() {
9016 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
9017 removeWatchCollection[i]();
9024 var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
9026 * Converts all accepted directives format into proper directive name.
9027 * @param name Name to normalize
9029 function directiveNormalize(name) {
9030 return camelCase(name.replace(PREFIX_REGEXP, ''));
9035 * @name $compile.directive.Attributes
9038 * A shared object between directive compile / linking functions which contains normalized DOM
9039 * element attributes. The values reflect current binding state `{{ }}`. The normalization is
9040 * needed since all of these are treated as equivalent in Angular:
9043 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
9049 * @name $compile.directive.Attributes#$attr
9052 * A map of DOM element attribute names to the normalized name. This is
9053 * needed to do reverse lookup from normalized name back to actual name.
9059 * @name $compile.directive.Attributes#$set
9063 * Set DOM element attribute value.
9066 * @param {string} name Normalized element attribute name of the property to modify. The name is
9067 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
9068 * property to the original name.
9069 * @param {string} value Value to set the attribute to. The value can be an interpolated string.
9075 * Closure compiler type information
9078 function nodesetLinkingFn(
9079 /* angular.Scope */ scope,
9080 /* NodeList */ nodeList,
9081 /* Element */ rootElement,
9082 /* function(Function) */ boundTranscludeFn
9085 function directiveLinkingFn(
9086 /* nodesetLinkingFn */ nodesetLinkingFn,
9087 /* angular.Scope */ scope,
9089 /* Element */ rootElement,
9090 /* function(Function) */ boundTranscludeFn
9093 function tokenDifference(str1, str2) {
9095 tokens1 = str1.split(/\s+/),
9096 tokens2 = str2.split(/\s+/);
9099 for (var i = 0; i < tokens1.length; i++) {
9100 var token = tokens1[i];
9101 for (var j = 0; j < tokens2.length; j++) {
9102 if (token == tokens2[j]) continue outer;
9104 values += (values.length > 0 ? ' ' : '') + token;
9109 function removeComments(jqNodes) {
9110 jqNodes = jqLite(jqNodes);
9111 var i = jqNodes.length;
9118 var node = jqNodes[i];
9119 if (node.nodeType === NODE_TYPE_COMMENT) {
9120 splice.call(jqNodes, i, 1);
9126 var $controllerMinErr = minErr('$controller');
9129 var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
9130 function identifierForController(controller, ident) {
9131 if (ident && isString(ident)) return ident;
9132 if (isString(controller)) {
9133 var match = CNTRL_REG.exec(controller);
9134 if (match) return match[3];
9141 * @name $controllerProvider
9143 * The {@link ng.$controller $controller service} is used by Angular to create new
9146 * This provider allows controller registration via the
9147 * {@link ng.$controllerProvider#register register} method.
9149 function $ControllerProvider() {
9150 var controllers = {},
9155 * @name $controllerProvider#register
9156 * @param {string|Object} name Controller name, or an object map of controllers where the keys are
9157 * the names and the values are the constructors.
9158 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
9159 * annotations in the array notation).
9161 this.register = function(name, constructor) {
9162 assertNotHasOwnProperty(name, 'controller');
9163 if (isObject(name)) {
9164 extend(controllers, name);
9166 controllers[name] = constructor;
9172 * @name $controllerProvider#allowGlobals
9173 * @description If called, allows `$controller` to find controller constructors on `window`
9175 this.allowGlobals = function() {
9180 this.$get = ['$injector', '$window', function($injector, $window) {
9185 * @requires $injector
9187 * @param {Function|string} constructor If called with a function then it's considered to be the
9188 * controller constructor function. Otherwise it's considered to be a string which is used
9189 * to retrieve the controller constructor using the following steps:
9191 * * check if a controller with given name is registered via `$controllerProvider`
9192 * * check if evaluating the string on the current scope returns a constructor
9193 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
9194 * `window` object (not recommended)
9196 * The string can use the `controller as property` syntax, where the controller instance is published
9197 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
9198 * to work correctly.
9200 * @param {Object} locals Injection locals for Controller.
9201 * @return {Object} Instance of given controller.
9204 * `$controller` service is responsible for instantiating controllers.
9206 * It's just a simple call to {@link auto.$injector $injector}, but extracted into
9207 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
9209 return function(expression, locals, later, ident) {
9211 // param `later` --- indicates that the controller's constructor is invoked at a later time.
9212 // If true, $controller will allocate the object with the correct
9213 // prototype chain, but will not invoke the controller until a returned
9214 // callback is invoked.
9215 // param `ident` --- An optional label which overrides the label parsed from the controller
9216 // expression, if any.
9217 var instance, match, constructor, identifier;
9218 later = later === true;
9219 if (ident && isString(ident)) {
9223 if (isString(expression)) {
9224 match = expression.match(CNTRL_REG);
9226 throw $controllerMinErr('ctrlfmt',
9227 "Badly formed controller string '{0}'. " +
9228 "Must match `__name__ as __id__` or `__name__`.", expression);
9230 constructor = match[1],
9231 identifier = identifier || match[3];
9232 expression = controllers.hasOwnProperty(constructor)
9233 ? controllers[constructor]
9234 : getter(locals.$scope, constructor, true) ||
9235 (globals ? getter($window, constructor, true) : undefined);
9237 assertArgFn(expression, constructor, true);
9241 // Instantiate controller later:
9242 // This machinery is used to create an instance of the object before calling the
9243 // controller's constructor itself.
9245 // This allows properties to be added to the controller before the constructor is
9246 // invoked. Primarily, this is used for isolate scope bindings in $compile.
9248 // This feature is not intended for use by applications, and is thus not documented
9250 // Object creation: http://jsperf.com/create-constructor/2
9251 var controllerPrototype = (isArray(expression) ?
9252 expression[expression.length - 1] : expression).prototype;
9253 instance = Object.create(controllerPrototype || null);
9256 addIdentifier(locals, identifier, instance, constructor || expression.name);
9260 return instantiate = extend(function() {
9261 var result = $injector.invoke(expression, instance, locals, constructor);
9262 if (result !== instance && (isObject(result) || isFunction(result))) {
9265 // If result changed, re-assign controllerAs value to scope.
9266 addIdentifier(locals, identifier, instance, constructor || expression.name);
9272 identifier: identifier
9276 instance = $injector.instantiate(expression, locals, constructor);
9279 addIdentifier(locals, identifier, instance, constructor || expression.name);
9285 function addIdentifier(locals, identifier, instance, name) {
9286 if (!(locals && isObject(locals.$scope))) {
9287 throw minErr('$controller')('noscp',
9288 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
9292 locals.$scope[identifier] = instance;
9303 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
9306 <example module="documentExample">
9307 <file name="index.html">
9308 <div ng-controller="ExampleController">
9309 <p>$document title: <b ng-bind="title"></b></p>
9310 <p>window.document title: <b ng-bind="windowTitle"></b></p>
9313 <file name="script.js">
9314 angular.module('documentExample', [])
9315 .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
9316 $scope.title = $document[0].title;
9317 $scope.windowTitle = angular.element(window.document)[0].title;
9322 function $DocumentProvider() {
9323 this.$get = ['$window', function(window) {
9324 return jqLite(window.document);
9330 * @name $exceptionHandler
9334 * Any uncaught exception in angular expressions is delegated to this service.
9335 * The default implementation simply delegates to `$log.error` which logs it into
9336 * the browser console.
9338 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
9339 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
9344 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
9345 * return function(exception, cause) {
9346 * exception.message += ' (caused by "' + cause + '")';
9352 * This example will override the normal action of `$exceptionHandler`, to make angular
9353 * exceptions fail hard when they happen, instead of just logging to the console.
9356 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
9357 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
9358 * (unless executed during a digest).
9360 * If you wish, you can manually delegate exceptions, e.g.
9361 * `try { ... } catch(e) { $exceptionHandler(e); }`
9363 * @param {Error} exception Exception associated with the error.
9364 * @param {string=} cause optional information about the context in which
9365 * the error was thrown.
9368 function $ExceptionHandlerProvider() {
9369 this.$get = ['$log', function($log) {
9370 return function(exception, cause) {
9371 $log.error.apply($log, arguments);
9376 var $$ForceReflowProvider = function() {
9377 this.$get = ['$document', function($document) {
9378 return function(domNode) {
9379 //the line below will force the browser to perform a repaint so
9380 //that all the animated elements within the animation frame will
9381 //be properly updated and drawn on screen. This is required to
9382 //ensure that the preparation animation is properly flushed so that
9383 //the active state picks up from there. DO NOT REMOVE THIS LINE.
9384 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
9385 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
9386 //WILL TAKE YEARS AWAY FROM YOUR LIFE.
9388 if (!domNode.nodeType && domNode instanceof jqLite) {
9389 domNode = domNode[0];
9392 domNode = $document[0].body;
9394 return domNode.offsetWidth + 1;
9399 var APPLICATION_JSON = 'application/json';
9400 var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
9401 var JSON_START = /^\[|^\{(?!\{)/;
9406 var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
9407 var $httpMinErr = minErr('$http');
9408 var $httpMinErrLegacyFn = function(method) {
9410 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method);
9414 function serializeValue(v) {
9416 return isDate(v) ? v.toISOString() : toJson(v);
9422 function $HttpParamSerializerProvider() {
9425 * @name $httpParamSerializer
9428 * Default {@link $http `$http`} params serializer that converts objects to strings
9429 * according to the following rules:
9431 * * `{'foo': 'bar'}` results in `foo=bar`
9432 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
9433 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
9434 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
9436 * Note that serializer will sort the request parameters alphabetically.
9439 this.$get = function() {
9440 return function ngParamSerializer(params) {
9441 if (!params) return '';
9443 forEachSorted(params, function(value, key) {
9444 if (value === null || isUndefined(value)) return;
9445 if (isArray(value)) {
9446 forEach(value, function(v, k) {
9447 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
9450 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
9454 return parts.join('&');
9459 function $HttpParamSerializerJQLikeProvider() {
9462 * @name $httpParamSerializerJQLike
9465 * Alternative {@link $http `$http`} params serializer that follows
9466 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
9467 * The serializer will also sort the params alphabetically.
9469 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
9476 * paramSerializer: '$httpParamSerializerJQLike'
9480 * It is also possible to set it as the default `paramSerializer` in the
9481 * {@link $httpProvider#defaults `$httpProvider`}.
9483 * Additionally, you can inject the serializer and use it explicitly, for example to serialize
9484 * form data for submission:
9487 * .controller(function($http, $httpParamSerializerJQLike) {
9493 * data: $httpParamSerializerJQLike(myData),
9495 * 'Content-Type': 'application/x-www-form-urlencoded'
9503 this.$get = function() {
9504 return function jQueryLikeParamSerializer(params) {
9505 if (!params) return '';
9507 serialize(params, '', true);
9508 return parts.join('&');
9510 function serialize(toSerialize, prefix, topLevel) {
9511 if (toSerialize === null || isUndefined(toSerialize)) return;
9512 if (isArray(toSerialize)) {
9513 forEach(toSerialize, function(value, index) {
9514 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
9516 } else if (isObject(toSerialize) && !isDate(toSerialize)) {
9517 forEachSorted(toSerialize, function(value, key) {
9518 serialize(value, prefix +
9519 (topLevel ? '' : '[') +
9521 (topLevel ? '' : ']'));
9524 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
9531 function defaultHttpResponseTransform(data, headers) {
9532 if (isString(data)) {
9533 // Strip json vulnerability protection prefix and trim whitespace
9534 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
9537 var contentType = headers('Content-Type');
9538 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
9539 data = fromJson(tempData);
9547 function isJsonLike(str) {
9548 var jsonStart = str.match(JSON_START);
9549 return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
9553 * Parse headers into key value object
9555 * @param {string} headers Raw headers as a string
9556 * @returns {Object} Parsed headers as key value object
9558 function parseHeaders(headers) {
9559 var parsed = createMap(), i;
9561 function fillInParsed(key, val) {
9563 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
9567 if (isString(headers)) {
9568 forEach(headers.split('\n'), function(line) {
9569 i = line.indexOf(':');
9570 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
9572 } else if (isObject(headers)) {
9573 forEach(headers, function(headerVal, headerKey) {
9574 fillInParsed(lowercase(headerKey), trim(headerVal));
9583 * Returns a function that provides access to parsed headers.
9585 * Headers are lazy parsed when first requested.
9588 * @param {(string|Object)} headers Headers to provide access to.
9589 * @returns {function(string=)} Returns a getter function which if called with:
9591 * - if called with single an argument returns a single header value or null
9592 * - if called with no arguments returns an object containing all headers.
9594 function headersGetter(headers) {
9597 return function(name) {
9598 if (!headersObj) headersObj = parseHeaders(headers);
9601 var value = headersObj[lowercase(name)];
9602 if (value === void 0) {
9614 * Chain all given functions
9616 * This function is used for both request and response transforming
9618 * @param {*} data Data to transform.
9619 * @param {function(string=)} headers HTTP headers getter fn.
9620 * @param {number} status HTTP status code of the response.
9621 * @param {(Function|Array.<Function>)} fns Function or an array of functions.
9622 * @returns {*} Transformed data.
9624 function transformData(data, headers, status, fns) {
9625 if (isFunction(fns)) {
9626 return fns(data, headers, status);
9629 forEach(fns, function(fn) {
9630 data = fn(data, headers, status);
9637 function isSuccess(status) {
9638 return 200 <= status && status < 300;
9644 * @name $httpProvider
9646 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
9648 function $HttpProvider() {
9651 * @name $httpProvider#defaults
9654 * Object containing default values for all {@link ng.$http $http} requests.
9656 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
9657 * that will provide the cache for all requests who set their `cache` property to `true`.
9658 * If you set the `defaults.cache = false` then only requests that specify their own custom
9659 * cache object will be cached. See {@link $http#caching $http Caching} for more information.
9661 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
9662 * Defaults value is `'XSRF-TOKEN'`.
9664 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
9665 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
9667 * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
9668 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
9669 * setting default headers.
9670 * - **`defaults.headers.common`**
9671 * - **`defaults.headers.post`**
9672 * - **`defaults.headers.put`**
9673 * - **`defaults.headers.patch`**
9676 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
9677 * used to the prepare string representation of request parameters (specified as an object).
9678 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
9679 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
9682 var defaults = this.defaults = {
9683 // transform incoming response data
9684 transformResponse: [defaultHttpResponseTransform],
9686 // transform outgoing request data
9687 transformRequest: [function(d) {
9688 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
9694 'Accept': 'application/json, text/plain, */*'
9696 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9697 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9698 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
9701 xsrfCookieName: 'XSRF-TOKEN',
9702 xsrfHeaderName: 'X-XSRF-TOKEN',
9704 paramSerializer: '$httpParamSerializer'
9707 var useApplyAsync = false;
9710 * @name $httpProvider#useApplyAsync
9713 * Configure $http service to combine processing of multiple http responses received at around
9714 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
9715 * significant performance improvement for bigger applications that make many HTTP requests
9716 * concurrently (common during application bootstrap).
9718 * Defaults to false. If no value is specified, returns the current configured value.
9720 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
9721 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
9722 * to load and share the same digest cycle.
9724 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9725 * otherwise, returns the current configured value.
9727 this.useApplyAsync = function(value) {
9728 if (isDefined(value)) {
9729 useApplyAsync = !!value;
9732 return useApplyAsync;
9735 var useLegacyPromise = true;
9738 * @name $httpProvider#useLegacyPromiseExtensions
9741 * Configure `$http` service to return promises without the shorthand methods `success` and `error`.
9742 * This should be used to make sure that applications work without these methods.
9744 * Defaults to true. If no value is specified, returns the current configured value.
9746 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods.
9748 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9749 * otherwise, returns the current configured value.
9751 this.useLegacyPromiseExtensions = function(value) {
9752 if (isDefined(value)) {
9753 useLegacyPromise = !!value;
9756 return useLegacyPromise;
9761 * @name $httpProvider#interceptors
9764 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
9765 * pre-processing of request or postprocessing of responses.
9767 * These service factories are ordered by request, i.e. they are applied in the same order as the
9768 * array, on request, but reverse order, on response.
9770 * {@link ng.$http#interceptors Interceptors detailed info}
9772 var interceptorFactories = this.interceptors = [];
9774 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
9775 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
9777 var defaultCache = $cacheFactory('$http');
9780 * Make sure that default param serializer is exposed as a function
9782 defaults.paramSerializer = isString(defaults.paramSerializer) ?
9783 $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
9786 * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
9787 * The reversal is needed so that we can build up the interception chain around the
9790 var reversedInterceptors = [];
9792 forEach(interceptorFactories, function(interceptorFactory) {
9793 reversedInterceptors.unshift(isString(interceptorFactory)
9794 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
9801 * @requires ng.$httpBackend
9802 * @requires $cacheFactory
9803 * @requires $rootScope
9805 * @requires $injector
9808 * The `$http` service is a core Angular service that facilitates communication with the remote
9809 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
9810 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
9812 * For unit testing applications that use `$http` service, see
9813 * {@link ngMock.$httpBackend $httpBackend mock}.
9815 * For a higher level of abstraction, please check out the {@link ngResource.$resource
9816 * $resource} service.
9818 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
9819 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
9820 * it is important to familiarize yourself with these APIs and the guarantees they provide.
9824 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
9825 * that is used to generate an HTTP request and returns a {@link ng.$q promise}.
9828 * // Simple GET request example:
9832 * }).then(function successCallback(response) {
9833 * // this callback will be called asynchronously
9834 * // when the response is available
9835 * }, function errorCallback(response) {
9836 * // called asynchronously if an error occurs
9837 * // or server returns response with an error status.
9841 * The response object has these properties:
9843 * - **data** – `{string|Object}` – The response body transformed with the transform
9845 * - **status** – `{number}` – HTTP status code of the response.
9846 * - **headers** – `{function([headerName])}` – Header getter function.
9847 * - **config** – `{Object}` – The configuration object that was used to generate the request.
9848 * - **statusText** – `{string}` – HTTP status text of the response.
9850 * A response status code between 200 and 299 is considered a success status and
9851 * will result in the success callback being called. Note that if the response is a redirect,
9852 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
9853 * called for such responses.
9856 * ## Shortcut methods
9858 * Shortcut methods are also available. All shortcut methods require passing in the URL, and
9859 * request data must be passed in for POST/PUT requests. An optional config can be passed as the
9863 * $http.get('/someUrl', config).then(successCallback, errorCallback);
9864 * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
9867 * Complete list of shortcut methods:
9869 * - {@link ng.$http#get $http.get}
9870 * - {@link ng.$http#head $http.head}
9871 * - {@link ng.$http#post $http.post}
9872 * - {@link ng.$http#put $http.put}
9873 * - {@link ng.$http#delete $http.delete}
9874 * - {@link ng.$http#jsonp $http.jsonp}
9875 * - {@link ng.$http#patch $http.patch}
9878 * ## Writing Unit Tests that use $http
9879 * When unit testing (using {@link ngMock ngMock}), it is necessary to call
9880 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
9881 * request using trained responses.
9884 * $httpBackend.expectGET(...);
9886 * $httpBackend.flush();
9889 * ## Deprecation Notice
9890 * <div class="alert alert-danger">
9891 * The `$http` legacy promise methods `success` and `error` have been deprecated.
9892 * Use the standard `then` method instead.
9893 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to
9894 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error.
9897 * ## Setting HTTP Headers
9899 * The $http service will automatically add certain HTTP headers to all requests. These defaults
9900 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
9901 * object, which currently contains this default configuration:
9903 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
9904 * - `Accept: application/json, text/plain, * / *`
9905 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
9906 * - `Content-Type: application/json`
9907 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
9908 * - `Content-Type: application/json`
9910 * To add or overwrite these defaults, simply add or remove a property from these configuration
9911 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
9912 * with the lowercased HTTP method name as the key, e.g.
9913 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
9915 * The defaults can also be set at runtime via the `$http.defaults` object in the same
9916 * fashion. For example:
9919 * module.run(function($http) {
9920 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'
9924 * In addition, you can supply a `headers` property in the config object passed when
9925 * calling `$http(config)`, which overrides the defaults without changing them globally.
9927 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
9928 * Use the `headers` property, setting the desired header to `undefined`. For example:
9933 * url: 'http://example.com',
9935 * 'Content-Type': undefined
9937 * data: { test: 'test' }
9940 * $http(req).then(function(){...}, function(){...});
9943 * ## Transforming Requests and Responses
9945 * Both requests and responses can be transformed using transformation functions: `transformRequest`
9946 * and `transformResponse`. These properties can be a single function that returns
9947 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
9948 * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
9950 * ### Default Transformations
9952 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
9953 * `defaults.transformResponse` properties. If a request does not provide its own transformations
9954 * then these will be applied.
9956 * You can augment or replace the default transformations by modifying these properties by adding to or
9957 * replacing the array.
9959 * Angular provides the following default transformations:
9961 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`):
9963 * - If the `data` property of the request configuration object contains an object, serialize it
9966 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`):
9968 * - If XSRF prefix is detected, strip it (see Security Considerations section below).
9969 * - If JSON response is detected, deserialize it using a JSON parser.
9972 * ### Overriding the Default Transformations Per Request
9974 * If you wish override the request/response transformations only for a single request then provide
9975 * `transformRequest` and/or `transformResponse` properties on the configuration object passed
9978 * Note that if you provide these properties on the config object the default transformations will be
9979 * overwritten. If you wish to augment the default transformations then you must include them in your
9980 * local transformation array.
9982 * The following code demonstrates adding a new response transformation to be run after the default response
9983 * transformations have been run.
9986 * function appendTransform(defaults, transform) {
9988 * // We can't guarantee that the default transformation is an array
9989 * defaults = angular.isArray(defaults) ? defaults : [defaults];
9991 * // Append the new transformation to the defaults
9992 * return defaults.concat(transform);
9998 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
9999 * return doTransform(value);
10007 * To enable caching, set the request configuration `cache` property to `true` (to use default
10008 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}).
10009 * When the cache is enabled, `$http` stores the response from the server in the specified
10010 * cache. The next time the same request is made, the response is served from the cache without
10011 * sending a request to the server.
10013 * Note that even if the response is served from cache, delivery of the data is asynchronous in
10014 * the same way that real requests are.
10016 * If there are multiple GET requests for the same URL that should be cached using the same
10017 * cache, but the cache is not populated yet, only one request to the server will be made and
10018 * the remaining requests will be fulfilled using the response from the first request.
10020 * You can change the default cache to a new object (built with
10021 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the
10022 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set
10023 * their `cache` property to `true` will now use this cache object.
10025 * If you set the default cache to `false` then only requests that specify their own custom
10026 * cache object will be cached.
10030 * Before you start creating interceptors, be sure to understand the
10031 * {@link ng.$q $q and deferred/promise APIs}.
10033 * For purposes of global error handling, authentication, or any kind of synchronous or
10034 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
10035 * able to intercept requests before they are handed to the server and
10036 * responses before they are handed over to the application code that
10037 * initiated these requests. The interceptors leverage the {@link ng.$q
10038 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
10040 * The interceptors are service factories that are registered with the `$httpProvider` by
10041 * adding them to the `$httpProvider.interceptors` array. The factory is called and
10042 * injected with dependencies (if specified) and returns the interceptor.
10044 * There are two kinds of interceptors (and two kinds of rejection interceptors):
10046 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
10047 * modify the `config` object or create a new one. The function needs to return the `config`
10048 * object directly, or a promise containing the `config` or a new `config` object.
10049 * * `requestError`: interceptor gets called when a previous interceptor threw an error or
10050 * resolved with a rejection.
10051 * * `response`: interceptors get called with http `response` object. The function is free to
10052 * modify the `response` object or create a new one. The function needs to return the `response`
10053 * object directly, or as a promise containing the `response` or a new `response` object.
10054 * * `responseError`: interceptor gets called when a previous interceptor threw an error or
10055 * resolved with a rejection.
10059 * // register the interceptor as a service
10060 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
10062 * // optional method
10063 * 'request': function(config) {
10064 * // do something on success
10068 * // optional method
10069 * 'requestError': function(rejection) {
10070 * // do something on error
10071 * if (canRecover(rejection)) {
10072 * return responseOrNewPromise
10074 * return $q.reject(rejection);
10079 * // optional method
10080 * 'response': function(response) {
10081 * // do something on success
10085 * // optional method
10086 * 'responseError': function(rejection) {
10087 * // do something on error
10088 * if (canRecover(rejection)) {
10089 * return responseOrNewPromise
10091 * return $q.reject(rejection);
10096 * $httpProvider.interceptors.push('myHttpInterceptor');
10099 * // alternatively, register the interceptor via an anonymous factory
10100 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
10102 * 'request': function(config) {
10106 * 'response': function(response) {
10113 * ## Security Considerations
10115 * When designing web applications, consider security threats from:
10117 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10118 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
10120 * Both server and the client must cooperate in order to eliminate these threats. Angular comes
10121 * pre-configured with strategies that address these issues, but for this to work backend server
10122 * cooperation is required.
10124 * ### JSON Vulnerability Protection
10126 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10127 * allows third party website to turn your JSON resource URL into
10128 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
10129 * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
10130 * Angular will automatically strip the prefix before processing it as JSON.
10132 * For example if your server needs to return:
10137 * which is vulnerable to attack, your server can return:
10143 * Angular will strip the prefix, before processing the JSON.
10146 * ### Cross Site Request Forgery (XSRF) Protection
10148 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which
10149 * an unauthorized site can gain your user's private data. Angular provides a mechanism
10150 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
10151 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
10152 * JavaScript that runs on your domain could read the cookie, your server can be assured that
10153 * the XHR came from JavaScript running on your domain. The header will not be set for
10154 * cross-domain requests.
10156 * To take advantage of this, your server needs to set a token in a JavaScript readable session
10157 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
10158 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
10159 * that only JavaScript running on your domain could have sent the request. The token must be
10160 * unique for each user and must be verifiable by the server (to prevent the JavaScript from
10161 * making up its own tokens). We recommend that the token is a digest of your site's
10162 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography))
10163 * for added security.
10165 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
10166 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
10167 * or the per-request config object.
10169 * In order to prevent collisions in environments where multiple Angular apps share the
10170 * same domain or subdomain, we recommend that each application uses unique cookie name.
10172 * @param {object} config Object describing the request to be made and how it should be
10173 * processed. The object has following properties:
10175 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
10176 * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
10177 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
10178 * with the `paramSerializer` and appended as GET parameters.
10179 * - **data** – `{string|Object}` – Data to be sent as the request message data.
10180 * - **headers** – `{Object}` – Map of strings or functions which return strings representing
10181 * HTTP headers to send to the server. If the return value of a function is null, the
10182 * header will not be sent. Functions accept a config object as an argument.
10183 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
10184 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
10185 * - **transformRequest** –
10186 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
10187 * transform function or an array of such functions. The transform function takes the http
10188 * request body and headers and returns its transformed (typically serialized) version.
10189 * See {@link ng.$http#overriding-the-default-transformations-per-request
10190 * Overriding the Default Transformations}
10191 * - **transformResponse** –
10192 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
10193 * transform function or an array of such functions. The transform function takes the http
10194 * response body, headers and status and returns its transformed (typically deserialized) version.
10195 * See {@link ng.$http#overriding-the-default-transformations-per-request
10196 * Overriding the Default TransformationjqLiks}
10197 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
10198 * prepare the string representation of request parameters (specified as an object).
10199 * If specified as string, it is interpreted as function registered with the
10200 * {@link $injector $injector}, which means you can create your own serializer
10201 * by registering it as a {@link auto.$provide#service service}.
10202 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
10203 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
10204 * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
10205 * GET request, otherwise if a cache instance built with
10206 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
10208 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
10209 * that should abort the request when resolved.
10210 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
10211 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
10212 * for more information.
10213 * - **responseType** - `{string}` - see
10214 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
10216 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
10217 * when the request succeeds or fails.
10220 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
10221 * requests. This is primarily meant to be used for debugging purposes.
10225 <example module="httpExample">
10226 <file name="index.html">
10227 <div ng-controller="FetchController">
10228 <select ng-model="method" aria-label="Request method">
10229 <option>GET</option>
10230 <option>JSONP</option>
10232 <input type="text" ng-model="url" size="80" aria-label="URL" />
10233 <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
10234 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
10235 <button id="samplejsonpbtn"
10236 ng-click="updateModel('JSONP',
10237 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
10240 <button id="invalidjsonpbtn"
10241 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
10244 <pre>http status code: {{status}}</pre>
10245 <pre>http response data: {{data}}</pre>
10248 <file name="script.js">
10249 angular.module('httpExample', [])
10250 .controller('FetchController', ['$scope', '$http', '$templateCache',
10251 function($scope, $http, $templateCache) {
10252 $scope.method = 'GET';
10253 $scope.url = 'http-hello.html';
10255 $scope.fetch = function() {
10256 $scope.code = null;
10257 $scope.response = null;
10259 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
10260 then(function(response) {
10261 $scope.status = response.status;
10262 $scope.data = response.data;
10263 }, function(response) {
10264 $scope.data = response.data || "Request failed";
10265 $scope.status = response.status;
10269 $scope.updateModel = function(method, url) {
10270 $scope.method = method;
10275 <file name="http-hello.html">
10278 <file name="protractor.js" type="protractor">
10279 var status = element(by.binding('status'));
10280 var data = element(by.binding('data'));
10281 var fetchBtn = element(by.id('fetchbtn'));
10282 var sampleGetBtn = element(by.id('samplegetbtn'));
10283 var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
10284 var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
10286 it('should make an xhr GET request', function() {
10287 sampleGetBtn.click();
10289 expect(status.getText()).toMatch('200');
10290 expect(data.getText()).toMatch(/Hello, \$http!/);
10293 // Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
10294 // it('should make a JSONP request to angularjs.org', function() {
10295 // sampleJsonpBtn.click();
10296 // fetchBtn.click();
10297 // expect(status.getText()).toMatch('200');
10298 // expect(data.getText()).toMatch(/Super Hero!/);
10301 it('should make JSONP request to invalid URL and invoke the error handler',
10303 invalidJsonpBtn.click();
10305 expect(status.getText()).toMatch('0');
10306 expect(data.getText()).toMatch('Request failed');
10311 function $http(requestConfig) {
10313 if (!angular.isObject(requestConfig)) {
10314 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
10317 var config = extend({
10319 transformRequest: defaults.transformRequest,
10320 transformResponse: defaults.transformResponse,
10321 paramSerializer: defaults.paramSerializer
10324 config.headers = mergeHeaders(requestConfig);
10325 config.method = uppercase(config.method);
10326 config.paramSerializer = isString(config.paramSerializer) ?
10327 $injector.get(config.paramSerializer) : config.paramSerializer;
10329 var serverRequest = function(config) {
10330 var headers = config.headers;
10331 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
10333 // strip content-type if data is undefined
10334 if (isUndefined(reqData)) {
10335 forEach(headers, function(value, header) {
10336 if (lowercase(header) === 'content-type') {
10337 delete headers[header];
10342 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
10343 config.withCredentials = defaults.withCredentials;
10347 return sendReq(config, reqData).then(transformResponse, transformResponse);
10350 var chain = [serverRequest, undefined];
10351 var promise = $q.when(config);
10353 // apply interceptors
10354 forEach(reversedInterceptors, function(interceptor) {
10355 if (interceptor.request || interceptor.requestError) {
10356 chain.unshift(interceptor.request, interceptor.requestError);
10358 if (interceptor.response || interceptor.responseError) {
10359 chain.push(interceptor.response, interceptor.responseError);
10363 while (chain.length) {
10364 var thenFn = chain.shift();
10365 var rejectFn = chain.shift();
10367 promise = promise.then(thenFn, rejectFn);
10370 if (useLegacyPromise) {
10371 promise.success = function(fn) {
10372 assertArgFn(fn, 'fn');
10374 promise.then(function(response) {
10375 fn(response.data, response.status, response.headers, config);
10380 promise.error = function(fn) {
10381 assertArgFn(fn, 'fn');
10383 promise.then(null, function(response) {
10384 fn(response.data, response.status, response.headers, config);
10389 promise.success = $httpMinErrLegacyFn('success');
10390 promise.error = $httpMinErrLegacyFn('error');
10395 function transformResponse(response) {
10396 // make a copy since the response must be cacheable
10397 var resp = extend({}, response);
10398 resp.data = transformData(response.data, response.headers, response.status,
10399 config.transformResponse);
10400 return (isSuccess(response.status))
10405 function executeHeaderFns(headers, config) {
10406 var headerContent, processedHeaders = {};
10408 forEach(headers, function(headerFn, header) {
10409 if (isFunction(headerFn)) {
10410 headerContent = headerFn(config);
10411 if (headerContent != null) {
10412 processedHeaders[header] = headerContent;
10415 processedHeaders[header] = headerFn;
10419 return processedHeaders;
10422 function mergeHeaders(config) {
10423 var defHeaders = defaults.headers,
10424 reqHeaders = extend({}, config.headers),
10425 defHeaderName, lowercaseDefHeaderName, reqHeaderName;
10427 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
10429 // using for-in instead of forEach to avoid unecessary iteration after header has been found
10430 defaultHeadersIteration:
10431 for (defHeaderName in defHeaders) {
10432 lowercaseDefHeaderName = lowercase(defHeaderName);
10434 for (reqHeaderName in reqHeaders) {
10435 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
10436 continue defaultHeadersIteration;
10440 reqHeaders[defHeaderName] = defHeaders[defHeaderName];
10443 // execute if header value is a function for merged headers
10444 return executeHeaderFns(reqHeaders, shallowCopy(config));
10448 $http.pendingRequests = [];
10455 * Shortcut method to perform `GET` request.
10457 * @param {string} url Relative or absolute URL specifying the destination of the request
10458 * @param {Object=} config Optional configuration object
10459 * @returns {HttpPromise} Future object
10464 * @name $http#delete
10467 * Shortcut method to perform `DELETE` request.
10469 * @param {string} url Relative or absolute URL specifying the destination of the request
10470 * @param {Object=} config Optional configuration object
10471 * @returns {HttpPromise} Future object
10479 * Shortcut method to perform `HEAD` request.
10481 * @param {string} url Relative or absolute URL specifying the destination of the request
10482 * @param {Object=} config Optional configuration object
10483 * @returns {HttpPromise} Future object
10488 * @name $http#jsonp
10491 * Shortcut method to perform `JSONP` request.
10493 * @param {string} url Relative or absolute URL specifying the destination of the request.
10494 * The name of the callback should be the string `JSON_CALLBACK`.
10495 * @param {Object=} config Optional configuration object
10496 * @returns {HttpPromise} Future object
10498 createShortMethods('get', 'delete', 'head', 'jsonp');
10505 * Shortcut method to perform `POST` request.
10507 * @param {string} url Relative or absolute URL specifying the destination of the request
10508 * @param {*} data Request content
10509 * @param {Object=} config Optional configuration object
10510 * @returns {HttpPromise} Future object
10518 * Shortcut method to perform `PUT` request.
10520 * @param {string} url Relative or absolute URL specifying the destination of the request
10521 * @param {*} data Request content
10522 * @param {Object=} config Optional configuration object
10523 * @returns {HttpPromise} Future object
10528 * @name $http#patch
10531 * Shortcut method to perform `PATCH` request.
10533 * @param {string} url Relative or absolute URL specifying the destination of the request
10534 * @param {*} data Request content
10535 * @param {Object=} config Optional configuration object
10536 * @returns {HttpPromise} Future object
10538 createShortMethodsWithData('post', 'put', 'patch');
10542 * @name $http#defaults
10545 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
10546 * default headers, withCredentials as well as request and response transformations.
10548 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
10550 $http.defaults = defaults;
10556 function createShortMethods(names) {
10557 forEach(arguments, function(name) {
10558 $http[name] = function(url, config) {
10559 return $http(extend({}, config || {}, {
10568 function createShortMethodsWithData(name) {
10569 forEach(arguments, function(name) {
10570 $http[name] = function(url, data, config) {
10571 return $http(extend({}, config || {}, {
10582 * Makes the request.
10584 * !!! ACCESSES CLOSURE VARS:
10585 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
10587 function sendReq(config, reqData) {
10588 var deferred = $q.defer(),
10589 promise = deferred.promise,
10592 reqHeaders = config.headers,
10593 url = buildUrl(config.url, config.paramSerializer(config.params));
10595 $http.pendingRequests.push(config);
10596 promise.then(removePendingReq, removePendingReq);
10599 if ((config.cache || defaults.cache) && config.cache !== false &&
10600 (config.method === 'GET' || config.method === 'JSONP')) {
10601 cache = isObject(config.cache) ? config.cache
10602 : isObject(defaults.cache) ? defaults.cache
10607 cachedResp = cache.get(url);
10608 if (isDefined(cachedResp)) {
10609 if (isPromiseLike(cachedResp)) {
10610 // cached request has already been sent, but there is no response yet
10611 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
10613 // serving from cache
10614 if (isArray(cachedResp)) {
10615 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
10617 resolvePromise(cachedResp, 200, {}, 'OK');
10621 // put the promise for the non-transformed response into cache as a placeholder
10622 cache.put(url, promise);
10627 // if we won't have the response in cache, set the xsrf headers and
10628 // send the request to the backend
10629 if (isUndefined(cachedResp)) {
10630 var xsrfValue = urlIsSameOrigin(config.url)
10631 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
10634 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
10637 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
10638 config.withCredentials, config.responseType);
10645 * Callback registered to $httpBackend():
10646 * - caches the response if desired
10647 * - resolves the raw $http promise
10650 function done(status, response, headersString, statusText) {
10652 if (isSuccess(status)) {
10653 cache.put(url, [status, response, parseHeaders(headersString), statusText]);
10655 // remove promise from the cache
10660 function resolveHttpPromise() {
10661 resolvePromise(response, status, headersString, statusText);
10664 if (useApplyAsync) {
10665 $rootScope.$applyAsync(resolveHttpPromise);
10667 resolveHttpPromise();
10668 if (!$rootScope.$$phase) $rootScope.$apply();
10674 * Resolves the raw $http promise.
10676 function resolvePromise(response, status, headers, statusText) {
10677 //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
10678 status = status >= -1 ? status : 0;
10680 (isSuccess(status) ? deferred.resolve : deferred.reject)({
10683 headers: headersGetter(headers),
10685 statusText: statusText
10689 function resolvePromiseWithResult(result) {
10690 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
10693 function removePendingReq() {
10694 var idx = $http.pendingRequests.indexOf(config);
10695 if (idx !== -1) $http.pendingRequests.splice(idx, 1);
10700 function buildUrl(url, serializedParams) {
10701 if (serializedParams.length > 0) {
10702 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
10711 * @name $xhrFactory
10714 * Factory function used to create XMLHttpRequest objects.
10716 * Replace or decorate this service to create your own custom XMLHttpRequest objects.
10719 * angular.module('myApp', [])
10720 * .factory('$xhrFactory', function() {
10721 * return function createXhr(method, url) {
10722 * return new window.XMLHttpRequest({mozSystem: true});
10727 * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
10728 * @param {string} url URL of the request.
10730 function $xhrFactoryProvider() {
10731 this.$get = function() {
10732 return function createXhr() {
10733 return new window.XMLHttpRequest();
10740 * @name $httpBackend
10741 * @requires $window
10742 * @requires $document
10743 * @requires $xhrFactory
10746 * HTTP backend used by the {@link ng.$http service} that delegates to
10747 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
10749 * You should never need to use this service directly, instead use the higher-level abstractions:
10750 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
10752 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
10753 * $httpBackend} which can be trained with responses.
10755 function $HttpBackendProvider() {
10756 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
10757 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
10761 function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
10762 // TODO(vojta): fix the signature
10763 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
10764 $browser.$$incOutstandingRequestCount();
10765 url = url || $browser.url();
10767 if (lowercase(method) == 'jsonp') {
10768 var callbackId = '_' + (callbacks.counter++).toString(36);
10769 callbacks[callbackId] = function(data) {
10770 callbacks[callbackId].data = data;
10771 callbacks[callbackId].called = true;
10774 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
10775 callbackId, function(status, text) {
10776 completeRequest(callback, status, callbacks[callbackId].data, "", text);
10777 callbacks[callbackId] = noop;
10781 var xhr = createXhr(method, url);
10783 xhr.open(method, url, true);
10784 forEach(headers, function(value, key) {
10785 if (isDefined(value)) {
10786 xhr.setRequestHeader(key, value);
10790 xhr.onload = function requestLoaded() {
10791 var statusText = xhr.statusText || '';
10793 // responseText is the old-school way of retrieving response (supported by IE9)
10794 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
10795 var response = ('response' in xhr) ? xhr.response : xhr.responseText;
10797 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
10798 var status = xhr.status === 1223 ? 204 : xhr.status;
10800 // fix status code when it is 0 (0 status is undocumented).
10801 // Occurs when accessing file resources or on Android 4.1 stock browser
10802 // while retrieving files from application cache.
10803 if (status === 0) {
10804 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
10807 completeRequest(callback,
10810 xhr.getAllResponseHeaders(),
10814 var requestError = function() {
10815 // The response is always empty
10816 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
10817 completeRequest(callback, -1, null, null, '');
10820 xhr.onerror = requestError;
10821 xhr.onabort = requestError;
10823 if (withCredentials) {
10824 xhr.withCredentials = true;
10827 if (responseType) {
10829 xhr.responseType = responseType;
10831 // WebKit added support for the json responseType value on 09/03/2013
10832 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
10833 // known to throw when setting the value "json" as the response type. Other older
10834 // browsers implementing the responseType
10836 // The json response type can be ignored if not supported, because JSON payloads are
10837 // parsed on the client-side regardless.
10838 if (responseType !== 'json') {
10844 xhr.send(isUndefined(post) ? null : post);
10848 var timeoutId = $browserDefer(timeoutRequest, timeout);
10849 } else if (isPromiseLike(timeout)) {
10850 timeout.then(timeoutRequest);
10854 function timeoutRequest() {
10855 jsonpDone && jsonpDone();
10856 xhr && xhr.abort();
10859 function completeRequest(callback, status, response, headersString, statusText) {
10860 // cancel timeout and subsequent timeout promise resolution
10861 if (isDefined(timeoutId)) {
10862 $browserDefer.cancel(timeoutId);
10864 jsonpDone = xhr = null;
10866 callback(status, response, headersString, statusText);
10867 $browser.$$completeOutstandingRequest(noop);
10871 function jsonpReq(url, callbackId, done) {
10872 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
10873 // - fetches local scripts via XHR and evals them
10874 // - adds and immediately removes script elements from the document
10875 var script = rawDocument.createElement('script'), callback = null;
10876 script.type = "text/javascript";
10878 script.async = true;
10880 callback = function(event) {
10881 removeEventListenerFn(script, "load", callback);
10882 removeEventListenerFn(script, "error", callback);
10883 rawDocument.body.removeChild(script);
10886 var text = "unknown";
10889 if (event.type === "load" && !callbacks[callbackId].called) {
10890 event = { type: "error" };
10893 status = event.type === "error" ? 404 : 200;
10897 done(status, text);
10901 addEventListenerFn(script, "load", callback);
10902 addEventListenerFn(script, "error", callback);
10903 rawDocument.body.appendChild(script);
10908 var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
10909 $interpolateMinErr.throwNoconcat = function(text) {
10910 throw $interpolateMinErr('noconcat',
10911 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
10912 "interpolations that concatenate multiple expressions when a trusted value is " +
10913 "required. See http://docs.angularjs.org/api/ng.$sce", text);
10916 $interpolateMinErr.interr = function(text, err) {
10917 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
10922 * @name $interpolateProvider
10926 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
10929 <example module="customInterpolationApp">
10930 <file name="index.html">
10932 var customInterpolationApp = angular.module('customInterpolationApp', []);
10934 customInterpolationApp.config(function($interpolateProvider) {
10935 $interpolateProvider.startSymbol('//');
10936 $interpolateProvider.endSymbol('//');
10940 customInterpolationApp.controller('DemoController', function() {
10941 this.label = "This binding is brought you by // interpolation symbols.";
10944 <div ng-app="App" ng-controller="DemoController as demo">
10948 <file name="protractor.js" type="protractor">
10949 it('should interpolate binding with custom symbols', function() {
10950 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
10955 function $InterpolateProvider() {
10956 var startSymbol = '{{';
10957 var endSymbol = '}}';
10961 * @name $interpolateProvider#startSymbol
10963 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
10965 * @param {string=} value new value to set the starting symbol to.
10966 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10968 this.startSymbol = function(value) {
10970 startSymbol = value;
10973 return startSymbol;
10979 * @name $interpolateProvider#endSymbol
10981 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
10983 * @param {string=} value new value to set the ending symbol to.
10984 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10986 this.endSymbol = function(value) {
10996 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
10997 var startSymbolLength = startSymbol.length,
10998 endSymbolLength = endSymbol.length,
10999 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
11000 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
11002 function escape(ch) {
11003 return '\\\\\\' + ch;
11006 function unescapeText(text) {
11007 return text.replace(escapedStartRegexp, startSymbol).
11008 replace(escapedEndRegexp, endSymbol);
11011 function stringify(value) {
11012 if (value == null) { // null || undefined
11015 switch (typeof value) {
11019 value = '' + value;
11022 value = toJson(value);
11030 * @name $interpolate
11038 * Compiles a string with markup into an interpolation function. This service is used by the
11039 * HTML {@link ng.$compile $compile} service for data binding. See
11040 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
11041 * interpolation markup.
11045 * var $interpolate = ...; // injected
11046 * var exp = $interpolate('Hello {{name | uppercase}}!');
11047 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
11050 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
11051 * `true`, the interpolation function will return `undefined` unless all embedded expressions
11052 * evaluate to a value other than `undefined`.
11055 * var $interpolate = ...; // injected
11056 * var context = {greeting: 'Hello', name: undefined };
11058 * // default "forgiving" mode
11059 * var exp = $interpolate('{{greeting}} {{name}}!');
11060 * expect(exp(context)).toEqual('Hello !');
11062 * // "allOrNothing" mode
11063 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
11064 * expect(exp(context)).toBeUndefined();
11065 * context.name = 'Angular';
11066 * expect(exp(context)).toEqual('Hello Angular!');
11069 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
11071 * ####Escaped Interpolation
11072 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
11073 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
11074 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
11077 * This enables web-servers to prevent script injection attacks and defacing attacks, to some
11078 * degree, while also enabling code examples to work without relying on the
11079 * {@link ng.directive:ngNonBindable ngNonBindable} directive.
11081 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
11082 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all
11083 * interpolation start/end markers with their escaped counterparts.**
11085 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
11086 * output when the $interpolate service processes the text. So, for HTML elements interpolated
11087 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
11088 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
11089 * this is typically useful only when user-data is used in rendering a template from the server, or
11090 * when otherwise untrusted data is used by a directive.
11093 * <file name="index.html">
11094 * <div ng-init="username='A user'">
11095 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
11097 * <p><strong>{{username}}</strong> attempts to inject code which will deface the
11098 * application, but fails to accomplish their task, because the server has correctly
11099 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
11101 * <p>Instead, the result of the attempted script injection is visible, and can be removed
11102 * from the database by an administrator.</p>
11107 * @param {string} text The text with markup to interpolate.
11108 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
11109 * embedded expression in order to return an interpolation function. Strings with no
11110 * embedded expression will return null for the interpolation function.
11111 * @param {string=} trustedContext when provided, the returned function passes the interpolated
11112 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
11113 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
11114 * provides Strict Contextual Escaping for details.
11115 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
11116 * unless all embedded expressions evaluate to a value other than `undefined`.
11117 * @returns {function(context)} an interpolation function which is used to compute the
11118 * interpolated string. The function has these parameters:
11120 * - `context`: evaluation context for all expressions embedded in the interpolated text
11122 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
11123 allOrNothing = !!allOrNothing;
11129 textLength = text.length,
11132 expressionPositions = [];
11134 while (index < textLength) {
11135 if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
11136 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
11137 if (index !== startIndex) {
11138 concat.push(unescapeText(text.substring(index, startIndex)));
11140 exp = text.substring(startIndex + startSymbolLength, endIndex);
11141 expressions.push(exp);
11142 parseFns.push($parse(exp, parseStringifyInterceptor));
11143 index = endIndex + endSymbolLength;
11144 expressionPositions.push(concat.length);
11147 // we did not find an interpolation, so we have to add the remainder to the separators array
11148 if (index !== textLength) {
11149 concat.push(unescapeText(text.substring(index)));
11155 // Concatenating expressions makes it hard to reason about whether some combination of
11156 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
11157 // single expression be used for iframe[src], object[src], etc., we ensure that the value
11158 // that's used is assigned or constructed by some JS code somewhere that is more testable or
11159 // make it obvious that you bound the value to some user controlled value. This helps reduce
11160 // the load when auditing for XSS issues.
11161 if (trustedContext && concat.length > 1) {
11162 $interpolateMinErr.throwNoconcat(text);
11165 if (!mustHaveExpression || expressions.length) {
11166 var compute = function(values) {
11167 for (var i = 0, ii = expressions.length; i < ii; i++) {
11168 if (allOrNothing && isUndefined(values[i])) return;
11169 concat[expressionPositions[i]] = values[i];
11171 return concat.join('');
11174 var getValue = function(value) {
11175 return trustedContext ?
11176 $sce.getTrusted(trustedContext, value) :
11177 $sce.valueOf(value);
11180 return extend(function interpolationFn(context) {
11182 var ii = expressions.length;
11183 var values = new Array(ii);
11186 for (; i < ii; i++) {
11187 values[i] = parseFns[i](context);
11190 return compute(values);
11192 $exceptionHandler($interpolateMinErr.interr(text, err));
11196 // all of these properties are undocumented for now
11197 exp: text, //just for compatibility with regular watchers created via $watch
11198 expressions: expressions,
11199 $$watchDelegate: function(scope, listener) {
11201 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
11202 var currValue = compute(values);
11203 if (isFunction(listener)) {
11204 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
11206 lastValue = currValue;
11212 function parseStringifyInterceptor(value) {
11214 value = getValue(value);
11215 return allOrNothing && !isDefined(value) ? value : stringify(value);
11217 $exceptionHandler($interpolateMinErr.interr(text, err));
11225 * @name $interpolate#startSymbol
11227 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
11229 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
11232 * @returns {string} start symbol.
11234 $interpolate.startSymbol = function() {
11235 return startSymbol;
11241 * @name $interpolate#endSymbol
11243 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
11245 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
11248 * @returns {string} end symbol.
11250 $interpolate.endSymbol = function() {
11254 return $interpolate;
11258 function $IntervalProvider() {
11259 this.$get = ['$rootScope', '$window', '$q', '$$q',
11260 function($rootScope, $window, $q, $$q) {
11261 var intervals = {};
11269 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
11272 * The return value of registering an interval function is a promise. This promise will be
11273 * notified upon each tick of the interval, and will be resolved after `count` iterations, or
11274 * run indefinitely if `count` is not defined. The value of the notification will be the
11275 * number of iterations that have run.
11276 * To cancel an interval, call `$interval.cancel(promise)`.
11278 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
11279 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
11282 * <div class="alert alert-warning">
11283 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
11284 * with them. In particular they are not automatically destroyed when a controller's scope or a
11285 * directive's element are destroyed.
11286 * You should take this into consideration and make sure to always cancel the interval at the
11287 * appropriate moment. See the example below for more details on how and when to do this.
11290 * @param {function()} fn A function that should be called repeatedly.
11291 * @param {number} delay Number of milliseconds between each function call.
11292 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
11294 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
11295 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
11296 * @param {...*=} Pass additional parameters to the executed function.
11297 * @returns {promise} A promise which will be notified on each iteration.
11300 * <example module="intervalExample">
11301 * <file name="index.html">
11303 * angular.module('intervalExample', [])
11304 * .controller('ExampleController', ['$scope', '$interval',
11305 * function($scope, $interval) {
11306 * $scope.format = 'M/d/yy h:mm:ss a';
11307 * $scope.blood_1 = 100;
11308 * $scope.blood_2 = 120;
11311 * $scope.fight = function() {
11312 * // Don't start a new fight if we are already fighting
11313 * if ( angular.isDefined(stop) ) return;
11315 * stop = $interval(function() {
11316 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
11317 * $scope.blood_1 = $scope.blood_1 - 3;
11318 * $scope.blood_2 = $scope.blood_2 - 4;
11320 * $scope.stopFight();
11325 * $scope.stopFight = function() {
11326 * if (angular.isDefined(stop)) {
11327 * $interval.cancel(stop);
11328 * stop = undefined;
11332 * $scope.resetFight = function() {
11333 * $scope.blood_1 = 100;
11334 * $scope.blood_2 = 120;
11337 * $scope.$on('$destroy', function() {
11338 * // Make sure that the interval is destroyed too
11339 * $scope.stopFight();
11342 * // Register the 'myCurrentTime' directive factory method.
11343 * // We inject $interval and dateFilter service since the factory method is DI.
11344 * .directive('myCurrentTime', ['$interval', 'dateFilter',
11345 * function($interval, dateFilter) {
11346 * // return the directive link function. (compile function not needed)
11347 * return function(scope, element, attrs) {
11348 * var format, // date format
11349 * stopTime; // so that we can cancel the time updates
11351 * // used to update the UI
11352 * function updateTime() {
11353 * element.text(dateFilter(new Date(), format));
11356 * // watch the expression, and update the UI on change.
11357 * scope.$watch(attrs.myCurrentTime, function(value) {
11362 * stopTime = $interval(updateTime, 1000);
11364 * // listen on DOM destroy (removal) event, and cancel the next UI update
11365 * // to prevent updating time after the DOM element was removed.
11366 * element.on('$destroy', function() {
11367 * $interval.cancel(stopTime);
11374 * <div ng-controller="ExampleController">
11375 * <label>Date format: <input ng-model="format"></label> <hr/>
11376 * Current time is: <span my-current-time="format"></span>
11378 * Blood 1 : <font color='red'>{{blood_1}}</font>
11379 * Blood 2 : <font color='red'>{{blood_2}}</font>
11380 * <button type="button" data-ng-click="fight()">Fight</button>
11381 * <button type="button" data-ng-click="stopFight()">StopFight</button>
11382 * <button type="button" data-ng-click="resetFight()">resetFight</button>
11389 function interval(fn, delay, count, invokeApply) {
11390 var hasParams = arguments.length > 4,
11391 args = hasParams ? sliceArgs(arguments, 4) : [],
11392 setInterval = $window.setInterval,
11393 clearInterval = $window.clearInterval,
11395 skipApply = (isDefined(invokeApply) && !invokeApply),
11396 deferred = (skipApply ? $$q : $q).defer(),
11397 promise = deferred.promise;
11399 count = isDefined(count) ? count : 0;
11401 promise.then(null, null, (!hasParams) ? fn : function() {
11402 fn.apply(null, args);
11405 promise.$$intervalId = setInterval(function tick() {
11406 deferred.notify(iteration++);
11408 if (count > 0 && iteration >= count) {
11409 deferred.resolve(iteration);
11410 clearInterval(promise.$$intervalId);
11411 delete intervals[promise.$$intervalId];
11414 if (!skipApply) $rootScope.$apply();
11418 intervals[promise.$$intervalId] = deferred;
11426 * @name $interval#cancel
11429 * Cancels a task associated with the `promise`.
11431 * @param {Promise=} promise returned by the `$interval` function.
11432 * @returns {boolean} Returns `true` if the task was successfully canceled.
11434 interval.cancel = function(promise) {
11435 if (promise && promise.$$intervalId in intervals) {
11436 intervals[promise.$$intervalId].reject('canceled');
11437 $window.clearInterval(promise.$$intervalId);
11438 delete intervals[promise.$$intervalId];
11453 * $locale service provides localization rules for various Angular components. As of right now the
11454 * only public api is:
11456 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
11459 var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/,
11460 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
11461 var $locationMinErr = minErr('$location');
11465 * Encode path using encodeUriSegment, ignoring forward slashes
11467 * @param {string} path Path to encode
11468 * @returns {string}
11470 function encodePath(path) {
11471 var segments = path.split('/'),
11472 i = segments.length;
11475 segments[i] = encodeUriSegment(segments[i]);
11478 return segments.join('/');
11481 function parseAbsoluteUrl(absoluteUrl, locationObj) {
11482 var parsedUrl = urlResolve(absoluteUrl);
11484 locationObj.$$protocol = parsedUrl.protocol;
11485 locationObj.$$host = parsedUrl.hostname;
11486 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
11490 function parseAppUrl(relativeUrl, locationObj) {
11491 var prefixed = (relativeUrl.charAt(0) !== '/');
11493 relativeUrl = '/' + relativeUrl;
11495 var match = urlResolve(relativeUrl);
11496 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
11497 match.pathname.substring(1) : match.pathname);
11498 locationObj.$$search = parseKeyValue(match.search);
11499 locationObj.$$hash = decodeURIComponent(match.hash);
11501 // make sure path starts with '/';
11502 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') {
11503 locationObj.$$path = '/' + locationObj.$$path;
11510 * @param {string} begin
11511 * @param {string} whole
11512 * @returns {string} returns text from whole after begin or undefined if it does not begin with
11515 function beginsWith(begin, whole) {
11516 if (whole.indexOf(begin) === 0) {
11517 return whole.substr(begin.length);
11522 function stripHash(url) {
11523 var index = url.indexOf('#');
11524 return index == -1 ? url : url.substr(0, index);
11527 function trimEmptyHash(url) {
11528 return url.replace(/(#.+)|#$/, '$1');
11532 function stripFile(url) {
11533 return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
11536 /* return the server only (scheme://host:port) */
11537 function serverBase(url) {
11538 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
11543 * LocationHtml5Url represents an url
11544 * This object is exposed as $location service when HTML5 mode is enabled and supported
11547 * @param {string} appBase application base URL
11548 * @param {string} appBaseNoFile application base URL stripped of any filename
11549 * @param {string} basePrefix url path prefix
11551 function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
11552 this.$$html5 = true;
11553 basePrefix = basePrefix || '';
11554 parseAbsoluteUrl(appBase, this);
11558 * Parse given html5 (regular) url string into properties
11559 * @param {string} url HTML5 url
11562 this.$$parse = function(url) {
11563 var pathUrl = beginsWith(appBaseNoFile, url);
11564 if (!isString(pathUrl)) {
11565 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
11569 parseAppUrl(pathUrl, this);
11571 if (!this.$$path) {
11579 * Compose url and update `absUrl` property
11582 this.$$compose = function() {
11583 var search = toKeyValue(this.$$search),
11584 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11586 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11587 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
11590 this.$$parseLinkUrl = function(url, relHref) {
11591 if (relHref && relHref[0] === '#') {
11592 // special case for links to hash fragments:
11593 // keep the old url and only replace the hash fragment
11594 this.hash(relHref.slice(1));
11597 var appUrl, prevAppUrl;
11600 if (isDefined(appUrl = beginsWith(appBase, url))) {
11601 prevAppUrl = appUrl;
11602 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
11603 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
11605 rewrittenUrl = appBase + prevAppUrl;
11607 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
11608 rewrittenUrl = appBaseNoFile + appUrl;
11609 } else if (appBaseNoFile == url + '/') {
11610 rewrittenUrl = appBaseNoFile;
11612 if (rewrittenUrl) {
11613 this.$$parse(rewrittenUrl);
11615 return !!rewrittenUrl;
11621 * LocationHashbangUrl represents url
11622 * This object is exposed as $location service when developer doesn't opt into html5 mode.
11623 * It also serves as the base class for html5 mode fallback on legacy browsers.
11626 * @param {string} appBase application base URL
11627 * @param {string} appBaseNoFile application base URL stripped of any filename
11628 * @param {string} hashPrefix hashbang prefix
11630 function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
11632 parseAbsoluteUrl(appBase, this);
11636 * Parse given hashbang url into properties
11637 * @param {string} url Hashbang url
11640 this.$$parse = function(url) {
11641 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
11642 var withoutHashUrl;
11644 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
11646 // The rest of the url starts with a hash so we have
11647 // got either a hashbang path or a plain hash fragment
11648 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
11649 if (isUndefined(withoutHashUrl)) {
11650 // There was no hashbang prefix so we just have a hash fragment
11651 withoutHashUrl = withoutBaseUrl;
11655 // There was no hashbang path nor hash fragment:
11656 // If we are in HTML5 mode we use what is left as the path;
11657 // Otherwise we ignore what is left
11658 if (this.$$html5) {
11659 withoutHashUrl = withoutBaseUrl;
11661 withoutHashUrl = '';
11662 if (isUndefined(withoutBaseUrl)) {
11669 parseAppUrl(withoutHashUrl, this);
11671 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
11676 * In Windows, on an anchor node on documents loaded from
11677 * the filesystem, the browser will return a pathname
11678 * prefixed with the drive name ('/C:/path') when a
11679 * pathname without a drive is set:
11680 * * a.setAttribute('href', '/foo')
11681 * * a.pathname === '/C:/foo' //true
11683 * Inside of Angular, we're always using pathnames that
11684 * do not include drive names for routing.
11686 function removeWindowsDriveName(path, url, base) {
11688 Matches paths for file protocol on windows,
11689 such as /C:/foo/bar, and captures only /foo/bar.
11691 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
11693 var firstPathSegmentMatch;
11695 //Get the relative path from the input URL.
11696 if (url.indexOf(base) === 0) {
11697 url = url.replace(base, '');
11700 // The input URL intentionally contains a first path segment that ends with a colon.
11701 if (windowsFilePathExp.exec(url)) {
11705 firstPathSegmentMatch = windowsFilePathExp.exec(path);
11706 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
11711 * Compose hashbang url and update `absUrl` property
11714 this.$$compose = function() {
11715 var search = toKeyValue(this.$$search),
11716 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11718 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11719 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
11722 this.$$parseLinkUrl = function(url, relHref) {
11723 if (stripHash(appBase) == stripHash(url)) {
11733 * LocationHashbangUrl represents url
11734 * This object is exposed as $location service when html5 history api is enabled but the browser
11735 * does not support it.
11738 * @param {string} appBase application base URL
11739 * @param {string} appBaseNoFile application base URL stripped of any filename
11740 * @param {string} hashPrefix hashbang prefix
11742 function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
11743 this.$$html5 = true;
11744 LocationHashbangUrl.apply(this, arguments);
11746 this.$$parseLinkUrl = function(url, relHref) {
11747 if (relHref && relHref[0] === '#') {
11748 // special case for links to hash fragments:
11749 // keep the old url and only replace the hash fragment
11750 this.hash(relHref.slice(1));
11757 if (appBase == stripHash(url)) {
11758 rewrittenUrl = url;
11759 } else if ((appUrl = beginsWith(appBaseNoFile, url))) {
11760 rewrittenUrl = appBase + hashPrefix + appUrl;
11761 } else if (appBaseNoFile === url + '/') {
11762 rewrittenUrl = appBaseNoFile;
11764 if (rewrittenUrl) {
11765 this.$$parse(rewrittenUrl);
11767 return !!rewrittenUrl;
11770 this.$$compose = function() {
11771 var search = toKeyValue(this.$$search),
11772 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11774 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11775 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
11776 this.$$absUrl = appBase + hashPrefix + this.$$url;
11782 var locationPrototype = {
11785 * Are we in html5 mode?
11791 * Has any change been replacing?
11798 * @name $location#absUrl
11801 * This method is getter only.
11803 * Return full url representation with all segments encoded according to rules specified in
11804 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
11808 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11809 * var absUrl = $location.absUrl();
11810 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
11813 * @return {string} full url
11815 absUrl: locationGetter('$$absUrl'),
11819 * @name $location#url
11822 * This method is getter / setter.
11824 * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
11826 * Change path, search and hash, when called with parameter and return `$location`.
11830 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11831 * var url = $location.url();
11832 * // => "/some/path?foo=bar&baz=xoxo"
11835 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
11836 * @return {string} url
11838 url: function(url) {
11839 if (isUndefined(url)) {
11843 var match = PATH_MATCH.exec(url);
11844 if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
11845 if (match[2] || match[1] || url === '') this.search(match[3] || '');
11846 this.hash(match[5] || '');
11853 * @name $location#protocol
11856 * This method is getter only.
11858 * Return protocol of current url.
11862 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11863 * var protocol = $location.protocol();
11867 * @return {string} protocol of current url
11869 protocol: locationGetter('$$protocol'),
11873 * @name $location#host
11876 * This method is getter only.
11878 * Return host of current url.
11880 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
11884 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11885 * var host = $location.host();
11886 * // => "example.com"
11888 * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
11889 * host = $location.host();
11890 * // => "example.com"
11891 * host = location.host;
11892 * // => "example.com:8080"
11895 * @return {string} host of current url.
11897 host: locationGetter('$$host'),
11901 * @name $location#port
11904 * This method is getter only.
11906 * Return port of current url.
11910 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11911 * var port = $location.port();
11915 * @return {Number} port
11917 port: locationGetter('$$port'),
11921 * @name $location#path
11924 * This method is getter / setter.
11926 * Return path of current url when called without any parameter.
11928 * Change path when called with parameter and return `$location`.
11930 * Note: Path should always begin with forward slash (/), this method will add the forward slash
11931 * if it is missing.
11935 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11936 * var path = $location.path();
11937 * // => "/some/path"
11940 * @param {(string|number)=} path New path
11941 * @return {string} path
11943 path: locationGetterSetter('$$path', function(path) {
11944 path = path !== null ? path.toString() : '';
11945 return path.charAt(0) == '/' ? path : '/' + path;
11950 * @name $location#search
11953 * This method is getter / setter.
11955 * Return search part (as object) of current url when called without any parameter.
11957 * Change search part when called with parameter and return `$location`.
11961 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11962 * var searchObject = $location.search();
11963 * // => {foo: 'bar', baz: 'xoxo'}
11965 * // set foo to 'yipee'
11966 * $location.search('foo', 'yipee');
11967 * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
11970 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
11973 * When called with a single argument the method acts as a setter, setting the `search` component
11974 * of `$location` to the specified value.
11976 * If the argument is a hash object containing an array of values, these values will be encoded
11977 * as duplicate search parameters in the url.
11979 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
11980 * will override only a single search property.
11982 * If `paramValue` is an array, it will override the property of the `search` component of
11983 * `$location` specified via the first argument.
11985 * If `paramValue` is `null`, the property specified via the first argument will be deleted.
11987 * If `paramValue` is `true`, the property specified via the first argument will be added with no
11988 * value nor trailing equal sign.
11990 * @return {Object} If called with no arguments returns the parsed `search` object. If called with
11991 * one or more arguments returns `$location` object itself.
11993 search: function(search, paramValue) {
11994 switch (arguments.length) {
11996 return this.$$search;
11998 if (isString(search) || isNumber(search)) {
11999 search = search.toString();
12000 this.$$search = parseKeyValue(search);
12001 } else if (isObject(search)) {
12002 search = copy(search, {});
12003 // remove object undefined or null properties
12004 forEach(search, function(value, key) {
12005 if (value == null) delete search[key];
12008 this.$$search = search;
12010 throw $locationMinErr('isrcharg',
12011 'The first argument of the `$location#search()` call must be a string or an object.');
12015 if (isUndefined(paramValue) || paramValue === null) {
12016 delete this.$$search[search];
12018 this.$$search[search] = paramValue;
12028 * @name $location#hash
12031 * This method is getter / setter.
12033 * Returns the hash fragment when called without any parameters.
12035 * Changes the hash fragment when called with a parameter and returns `$location`.
12039 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
12040 * var hash = $location.hash();
12041 * // => "hashValue"
12044 * @param {(string|number)=} hash New hash fragment
12045 * @return {string} hash
12047 hash: locationGetterSetter('$$hash', function(hash) {
12048 return hash !== null ? hash.toString() : '';
12053 * @name $location#replace
12056 * If called, all changes to $location during the current `$digest` will replace the current history
12057 * record, instead of adding a new one.
12059 replace: function() {
12060 this.$$replace = true;
12065 forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
12066 Location.prototype = Object.create(locationPrototype);
12070 * @name $location#state
12073 * This method is getter / setter.
12075 * Return the history state object when called without any parameter.
12077 * Change the history state object when called with one parameter and return `$location`.
12078 * The state object is later passed to `pushState` or `replaceState`.
12080 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
12081 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
12082 * older browsers (like IE9 or Android < 4.0), don't use this method.
12084 * @param {object=} state State object for pushState or replaceState
12085 * @return {object} state
12087 Location.prototype.state = function(state) {
12088 if (!arguments.length) {
12089 return this.$$state;
12092 if (Location !== LocationHtml5Url || !this.$$html5) {
12093 throw $locationMinErr('nostate', 'History API state support is available only ' +
12094 'in HTML5 mode and only in browsers supporting HTML5 History API');
12096 // The user might modify `stateObject` after invoking `$location.state(stateObject)`
12097 // but we're changing the $$state reference to $browser.state() during the $digest
12098 // so the modification window is narrow.
12099 this.$$state = isUndefined(state) ? null : state;
12106 function locationGetter(property) {
12107 return function() {
12108 return this[property];
12113 function locationGetterSetter(property, preprocess) {
12114 return function(value) {
12115 if (isUndefined(value)) {
12116 return this[property];
12119 this[property] = preprocess(value);
12131 * @requires $rootElement
12134 * The $location service parses the URL in the browser address bar (based on the
12135 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
12136 * available to your application. Changes to the URL in the address bar are reflected into
12137 * $location service and changes to $location are reflected into the browser address bar.
12139 * **The $location service:**
12141 * - Exposes the current URL in the browser address bar, so you can
12142 * - Watch and observe the URL.
12143 * - Change the URL.
12144 * - Synchronizes the URL with the browser when the user
12145 * - Changes the address bar.
12146 * - Clicks the back or forward button (or clicks a History link).
12147 * - Clicks on a link.
12148 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
12150 * For more information see {@link guide/$location Developer Guide: Using $location}
12155 * @name $locationProvider
12157 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
12159 function $LocationProvider() {
12160 var hashPrefix = '',
12169 * @name $locationProvider#hashPrefix
12171 * @param {string=} prefix Prefix for hash part (containing path and search)
12172 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12174 this.hashPrefix = function(prefix) {
12175 if (isDefined(prefix)) {
12176 hashPrefix = prefix;
12185 * @name $locationProvider#html5Mode
12187 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
12188 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
12190 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
12191 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
12192 * support `pushState`.
12193 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
12194 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
12195 * true, and a base tag is not present, an error will be thrown when `$location` is injected.
12196 * See the {@link guide/$location $location guide for more information}
12197 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
12198 * enables/disables url rewriting for relative links.
12200 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
12202 this.html5Mode = function(mode) {
12203 if (isBoolean(mode)) {
12204 html5Mode.enabled = mode;
12206 } else if (isObject(mode)) {
12208 if (isBoolean(mode.enabled)) {
12209 html5Mode.enabled = mode.enabled;
12212 if (isBoolean(mode.requireBase)) {
12213 html5Mode.requireBase = mode.requireBase;
12216 if (isBoolean(mode.rewriteLinks)) {
12217 html5Mode.rewriteLinks = mode.rewriteLinks;
12228 * @name $location#$locationChangeStart
12229 * @eventType broadcast on root scope
12231 * Broadcasted before a URL will change.
12233 * This change can be prevented by calling
12234 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
12235 * details about event object. Upon successful change
12236 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
12238 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12239 * the browser supports the HTML5 History API.
12241 * @param {Object} angularEvent Synthetic event object.
12242 * @param {string} newUrl New URL
12243 * @param {string=} oldUrl URL that was before it was changed.
12244 * @param {string=} newState New history state object
12245 * @param {string=} oldState History state object that was before it was changed.
12250 * @name $location#$locationChangeSuccess
12251 * @eventType broadcast on root scope
12253 * Broadcasted after a URL was changed.
12255 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12256 * the browser supports the HTML5 History API.
12258 * @param {Object} angularEvent Synthetic event object.
12259 * @param {string} newUrl New URL
12260 * @param {string=} oldUrl URL that was before it was changed.
12261 * @param {string=} newState New history state object
12262 * @param {string=} oldState History state object that was before it was changed.
12265 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
12266 function($rootScope, $browser, $sniffer, $rootElement, $window) {
12269 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
12270 initialUrl = $browser.url(),
12273 if (html5Mode.enabled) {
12274 if (!baseHref && html5Mode.requireBase) {
12275 throw $locationMinErr('nobase',
12276 "$location in HTML5 mode requires a <base> tag to be present!");
12278 appBase = serverBase(initialUrl) + (baseHref || '/');
12279 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
12281 appBase = stripHash(initialUrl);
12282 LocationMode = LocationHashbangUrl;
12284 var appBaseNoFile = stripFile(appBase);
12286 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
12287 $location.$$parseLinkUrl(initialUrl, initialUrl);
12289 $location.$$state = $browser.state();
12291 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
12293 function setBrowserUrlWithFallback(url, replace, state) {
12294 var oldUrl = $location.url();
12295 var oldState = $location.$$state;
12297 $browser.url(url, replace, state);
12299 // Make sure $location.state() returns referentially identical (not just deeply equal)
12300 // state object; this makes possible quick checking if the state changed in the digest
12301 // loop. Checking deep equality would be too expensive.
12302 $location.$$state = $browser.state();
12304 // Restore old values if pushState fails
12305 $location.url(oldUrl);
12306 $location.$$state = oldState;
12312 $rootElement.on('click', function(event) {
12313 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
12314 // currently we open nice url link and redirect then
12316 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
12318 var elm = jqLite(event.target);
12320 // traverse the DOM up to find first A tag
12321 while (nodeName_(elm[0]) !== 'a') {
12322 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
12323 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
12326 var absHref = elm.prop('href');
12327 // get the actual href attribute - see
12328 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
12329 var relHref = elm.attr('href') || elm.attr('xlink:href');
12331 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
12332 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
12334 absHref = urlResolve(absHref.animVal).href;
12337 // Ignore when url is started with javascript: or mailto:
12338 if (IGNORE_URI_REGEXP.test(absHref)) return;
12340 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
12341 if ($location.$$parseLinkUrl(absHref, relHref)) {
12342 // We do a preventDefault for all urls that are part of the angular application,
12343 // in html5mode and also without, so that we are able to abort navigation without
12344 // getting double entries in the location history.
12345 event.preventDefault();
12346 // update location manually
12347 if ($location.absUrl() != $browser.url()) {
12348 $rootScope.$apply();
12349 // hack to work around FF6 bug 684208 when scenario runner clicks on links
12350 $window.angular['ff-684208-preventDefault'] = true;
12357 // rewrite hashbang url <> html5 url
12358 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
12359 $browser.url($location.absUrl(), true);
12362 var initializing = true;
12364 // update $location when $browser url changes
12365 $browser.onUrlChange(function(newUrl, newState) {
12367 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) {
12368 // If we are navigating outside of the app then force a reload
12369 $window.location.href = newUrl;
12373 $rootScope.$evalAsync(function() {
12374 var oldUrl = $location.absUrl();
12375 var oldState = $location.$$state;
12376 var defaultPrevented;
12377 newUrl = trimEmptyHash(newUrl);
12378 $location.$$parse(newUrl);
12379 $location.$$state = newState;
12381 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12382 newState, oldState).defaultPrevented;
12384 // if the location was changed by a `$locationChangeStart` handler then stop
12385 // processing this location change
12386 if ($location.absUrl() !== newUrl) return;
12388 if (defaultPrevented) {
12389 $location.$$parse(oldUrl);
12390 $location.$$state = oldState;
12391 setBrowserUrlWithFallback(oldUrl, false, oldState);
12393 initializing = false;
12394 afterLocationChange(oldUrl, oldState);
12397 if (!$rootScope.$$phase) $rootScope.$digest();
12401 $rootScope.$watch(function $locationWatch() {
12402 var oldUrl = trimEmptyHash($browser.url());
12403 var newUrl = trimEmptyHash($location.absUrl());
12404 var oldState = $browser.state();
12405 var currentReplace = $location.$$replace;
12406 var urlOrStateChanged = oldUrl !== newUrl ||
12407 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
12409 if (initializing || urlOrStateChanged) {
12410 initializing = false;
12412 $rootScope.$evalAsync(function() {
12413 var newUrl = $location.absUrl();
12414 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12415 $location.$$state, oldState).defaultPrevented;
12417 // if the location was changed by a `$locationChangeStart` handler then stop
12418 // processing this location change
12419 if ($location.absUrl() !== newUrl) return;
12421 if (defaultPrevented) {
12422 $location.$$parse(oldUrl);
12423 $location.$$state = oldState;
12425 if (urlOrStateChanged) {
12426 setBrowserUrlWithFallback(newUrl, currentReplace,
12427 oldState === $location.$$state ? null : $location.$$state);
12429 afterLocationChange(oldUrl, oldState);
12434 $location.$$replace = false;
12436 // we don't need to return anything because $evalAsync will make the digest loop dirty when
12437 // there is a change
12442 function afterLocationChange(oldUrl, oldState) {
12443 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
12444 $location.$$state, oldState);
12452 * @requires $window
12455 * Simple service for logging. Default implementation safely writes the message
12456 * into the browser's console (if present).
12458 * The main purpose of this service is to simplify debugging and troubleshooting.
12460 * The default is to log `debug` messages. You can use
12461 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
12464 <example module="logExample">
12465 <file name="script.js">
12466 angular.module('logExample', [])
12467 .controller('LogController', ['$scope', '$log', function($scope, $log) {
12468 $scope.$log = $log;
12469 $scope.message = 'Hello World!';
12472 <file name="index.html">
12473 <div ng-controller="LogController">
12474 <p>Reload this page with open console, enter text and hit the log button...</p>
12476 <input type="text" ng-model="message" /></label>
12477 <button ng-click="$log.log(message)">log</button>
12478 <button ng-click="$log.warn(message)">warn</button>
12479 <button ng-click="$log.info(message)">info</button>
12480 <button ng-click="$log.error(message)">error</button>
12481 <button ng-click="$log.debug(message)">debug</button>
12489 * @name $logProvider
12491 * Use the `$logProvider` to configure how the application logs messages
12493 function $LogProvider() {
12499 * @name $logProvider#debugEnabled
12501 * @param {boolean=} flag enable or disable debug level messages
12502 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12504 this.debugEnabled = function(flag) {
12505 if (isDefined(flag)) {
12513 this.$get = ['$window', function($window) {
12520 * Write a log message
12522 log: consoleLog('log'),
12529 * Write an information message
12531 info: consoleLog('info'),
12538 * Write a warning message
12540 warn: consoleLog('warn'),
12547 * Write an error message
12549 error: consoleLog('error'),
12556 * Write a debug message
12558 debug: (function() {
12559 var fn = consoleLog('debug');
12561 return function() {
12563 fn.apply(self, arguments);
12569 function formatError(arg) {
12570 if (arg instanceof Error) {
12572 arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
12573 ? 'Error: ' + arg.message + '\n' + arg.stack
12575 } else if (arg.sourceURL) {
12576 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
12582 function consoleLog(type) {
12583 var console = $window.console || {},
12584 logFn = console[type] || console.log || noop,
12587 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
12588 // The reason behind this is that console.log has type "object" in IE8...
12590 hasApply = !!logFn.apply;
12594 return function() {
12596 forEach(arguments, function(arg) {
12597 args.push(formatError(arg));
12599 return logFn.apply(console, args);
12603 // we are IE which either doesn't have window.console => this is noop and we do nothing,
12604 // or we are IE where console.log doesn't have apply so we log at least first 2 args
12605 return function(arg1, arg2) {
12606 logFn(arg1, arg2 == null ? '' : arg2);
12612 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
12613 * Any commits to this file should be reviewed with security in mind. *
12614 * Changes to this file can potentially create security vulnerabilities. *
12615 * An approval from 2 Core members with history of modifying *
12616 * this file is required. *
12618 * Does the change somehow allow for arbitrary javascript to be executed? *
12619 * Or allows for someone to change the prototype of built-in objects? *
12620 * Or gives undesired access to variables likes document or window? *
12621 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
12623 var $parseMinErr = minErr('$parse');
12625 // Sandboxing Angular Expressions
12626 // ------------------------------
12627 // Angular expressions are generally considered safe because these expressions only have direct
12628 // access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
12629 // obtaining a reference to native JS functions such as the Function constructor.
12631 // As an example, consider the following Angular expression:
12633 // {}.toString.constructor('alert("evil JS code")')
12635 // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
12636 // against the expression language, but not to prevent exploits that were enabled by exposing
12637 // sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
12638 // practice and therefore we are not even trying to protect against interaction with an object
12639 // explicitly exposed in this way.
12641 // In general, it is not possible to access a Window object from an angular expression unless a
12642 // window or some DOM object that has a reference to window is published onto a Scope.
12643 // Similarly we prevent invocations of function known to be dangerous, as well as assignments to
12646 // See https://docs.angularjs.org/guide/security
12649 function ensureSafeMemberName(name, fullExpression) {
12650 if (name === "__defineGetter__" || name === "__defineSetter__"
12651 || name === "__lookupGetter__" || name === "__lookupSetter__"
12652 || name === "__proto__") {
12653 throw $parseMinErr('isecfld',
12654 'Attempting to access a disallowed field in Angular expressions! '
12655 + 'Expression: {0}', fullExpression);
12660 function getStringValue(name, fullExpression) {
12661 // From the JavaScript docs:
12662 // Property names must be strings. This means that non-string objects cannot be used
12663 // as keys in an object. Any non-string object, including a number, is typecasted
12664 // into a string via the toString method.
12666 // So, to ensure that we are checking the same `name` that JavaScript would use,
12667 // we cast it to a string, if possible.
12668 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
12669 // this is, this will handle objects that misbehave.
12671 if (!isString(name)) {
12672 throw $parseMinErr('iseccst',
12673 'Cannot convert object to primitive value! '
12674 + 'Expression: {0}', fullExpression);
12679 function ensureSafeObject(obj, fullExpression) {
12680 // nifty check if obj is Function that is fast and works across iframes and other contexts
12682 if (obj.constructor === obj) {
12683 throw $parseMinErr('isecfn',
12684 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12686 } else if (// isWindow(obj)
12687 obj.window === obj) {
12688 throw $parseMinErr('isecwindow',
12689 'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
12691 } else if (// isElement(obj)
12692 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
12693 throw $parseMinErr('isecdom',
12694 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
12696 } else if (// block Object so that we can't get hold of dangerous Object.* methods
12698 throw $parseMinErr('isecobj',
12699 'Referencing Object in Angular expressions is disallowed! Expression: {0}',
12706 var CALL = Function.prototype.call;
12707 var APPLY = Function.prototype.apply;
12708 var BIND = Function.prototype.bind;
12710 function ensureSafeFunction(obj, fullExpression) {
12712 if (obj.constructor === obj) {
12713 throw $parseMinErr('isecfn',
12714 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12716 } else if (obj === CALL || obj === APPLY || obj === BIND) {
12717 throw $parseMinErr('isecff',
12718 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
12724 function ensureSafeAssignContext(obj, fullExpression) {
12726 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
12727 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
12728 throw $parseMinErr('isecaf',
12729 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
12734 var OPERATORS = createMap();
12735 forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
12736 var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
12739 /////////////////////////////////////////
12745 var Lexer = function(options) {
12746 this.options = options;
12749 Lexer.prototype = {
12750 constructor: Lexer,
12752 lex: function(text) {
12757 while (this.index < this.text.length) {
12758 var ch = this.text.charAt(this.index);
12759 if (ch === '"' || ch === "'") {
12760 this.readString(ch);
12761 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
12763 } else if (this.isIdent(ch)) {
12765 } else if (this.is(ch, '(){}[].,;:?')) {
12766 this.tokens.push({index: this.index, text: ch});
12768 } else if (this.isWhitespace(ch)) {
12771 var ch2 = ch + this.peek();
12772 var ch3 = ch2 + this.peek(2);
12773 var op1 = OPERATORS[ch];
12774 var op2 = OPERATORS[ch2];
12775 var op3 = OPERATORS[ch3];
12776 if (op1 || op2 || op3) {
12777 var token = op3 ? ch3 : (op2 ? ch2 : ch);
12778 this.tokens.push({index: this.index, text: token, operator: true});
12779 this.index += token.length;
12781 this.throwError('Unexpected next character ', this.index, this.index + 1);
12785 return this.tokens;
12788 is: function(ch, chars) {
12789 return chars.indexOf(ch) !== -1;
12792 peek: function(i) {
12794 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
12797 isNumber: function(ch) {
12798 return ('0' <= ch && ch <= '9') && typeof ch === "string";
12801 isWhitespace: function(ch) {
12802 // IE treats non-breaking space as \u00A0
12803 return (ch === ' ' || ch === '\r' || ch === '\t' ||
12804 ch === '\n' || ch === '\v' || ch === '\u00A0');
12807 isIdent: function(ch) {
12808 return ('a' <= ch && ch <= 'z' ||
12809 'A' <= ch && ch <= 'Z' ||
12810 '_' === ch || ch === '$');
12813 isExpOperator: function(ch) {
12814 return (ch === '-' || ch === '+' || this.isNumber(ch));
12817 throwError: function(error, start, end) {
12818 end = end || this.index;
12819 var colStr = (isDefined(start)
12820 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
12822 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
12823 error, colStr, this.text);
12826 readNumber: function() {
12828 var start = this.index;
12829 while (this.index < this.text.length) {
12830 var ch = lowercase(this.text.charAt(this.index));
12831 if (ch == '.' || this.isNumber(ch)) {
12834 var peekCh = this.peek();
12835 if (ch == 'e' && this.isExpOperator(peekCh)) {
12837 } else if (this.isExpOperator(ch) &&
12838 peekCh && this.isNumber(peekCh) &&
12839 number.charAt(number.length - 1) == 'e') {
12841 } else if (this.isExpOperator(ch) &&
12842 (!peekCh || !this.isNumber(peekCh)) &&
12843 number.charAt(number.length - 1) == 'e') {
12844 this.throwError('Invalid exponent');
12855 value: Number(number)
12859 readIdent: function() {
12860 var start = this.index;
12861 while (this.index < this.text.length) {
12862 var ch = this.text.charAt(this.index);
12863 if (!(this.isIdent(ch) || this.isNumber(ch))) {
12870 text: this.text.slice(start, this.index),
12875 readString: function(quote) {
12876 var start = this.index;
12879 var rawString = quote;
12880 var escape = false;
12881 while (this.index < this.text.length) {
12882 var ch = this.text.charAt(this.index);
12886 var hex = this.text.substring(this.index + 1, this.index + 5);
12887 if (!hex.match(/[\da-f]{4}/i)) {
12888 this.throwError('Invalid unicode escape [\\u' + hex + ']');
12891 string += String.fromCharCode(parseInt(hex, 16));
12893 var rep = ESCAPE[ch];
12894 string = string + (rep || ch);
12897 } else if (ch === '\\') {
12899 } else if (ch === quote) {
12913 this.throwError('Unterminated quote', start);
12917 var AST = function(lexer, options) {
12918 this.lexer = lexer;
12919 this.options = options;
12922 AST.Program = 'Program';
12923 AST.ExpressionStatement = 'ExpressionStatement';
12924 AST.AssignmentExpression = 'AssignmentExpression';
12925 AST.ConditionalExpression = 'ConditionalExpression';
12926 AST.LogicalExpression = 'LogicalExpression';
12927 AST.BinaryExpression = 'BinaryExpression';
12928 AST.UnaryExpression = 'UnaryExpression';
12929 AST.CallExpression = 'CallExpression';
12930 AST.MemberExpression = 'MemberExpression';
12931 AST.Identifier = 'Identifier';
12932 AST.Literal = 'Literal';
12933 AST.ArrayExpression = 'ArrayExpression';
12934 AST.Property = 'Property';
12935 AST.ObjectExpression = 'ObjectExpression';
12936 AST.ThisExpression = 'ThisExpression';
12938 // Internal use only
12939 AST.NGValueParameter = 'NGValueParameter';
12942 ast: function(text) {
12944 this.tokens = this.lexer.lex(text);
12946 var value = this.program();
12948 if (this.tokens.length !== 0) {
12949 this.throwError('is an unexpected token', this.tokens[0]);
12955 program: function() {
12958 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
12959 body.push(this.expressionStatement());
12960 if (!this.expect(';')) {
12961 return { type: AST.Program, body: body};
12966 expressionStatement: function() {
12967 return { type: AST.ExpressionStatement, expression: this.filterChain() };
12970 filterChain: function() {
12971 var left = this.expression();
12973 while ((token = this.expect('|'))) {
12974 left = this.filter(left);
12979 expression: function() {
12980 return this.assignment();
12983 assignment: function() {
12984 var result = this.ternary();
12985 if (this.expect('=')) {
12986 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
12991 ternary: function() {
12992 var test = this.logicalOR();
12995 if (this.expect('?')) {
12996 alternate = this.expression();
12997 if (this.consume(':')) {
12998 consequent = this.expression();
12999 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
13005 logicalOR: function() {
13006 var left = this.logicalAND();
13007 while (this.expect('||')) {
13008 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
13013 logicalAND: function() {
13014 var left = this.equality();
13015 while (this.expect('&&')) {
13016 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
13021 equality: function() {
13022 var left = this.relational();
13024 while ((token = this.expect('==','!=','===','!=='))) {
13025 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
13030 relational: function() {
13031 var left = this.additive();
13033 while ((token = this.expect('<', '>', '<=', '>='))) {
13034 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
13039 additive: function() {
13040 var left = this.multiplicative();
13042 while ((token = this.expect('+','-'))) {
13043 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
13048 multiplicative: function() {
13049 var left = this.unary();
13051 while ((token = this.expect('*','/','%'))) {
13052 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
13057 unary: function() {
13059 if ((token = this.expect('+', '-', '!'))) {
13060 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
13062 return this.primary();
13066 primary: function() {
13068 if (this.expect('(')) {
13069 primary = this.filterChain();
13071 } else if (this.expect('[')) {
13072 primary = this.arrayDeclaration();
13073 } else if (this.expect('{')) {
13074 primary = this.object();
13075 } else if (this.constants.hasOwnProperty(this.peek().text)) {
13076 primary = copy(this.constants[this.consume().text]);
13077 } else if (this.peek().identifier) {
13078 primary = this.identifier();
13079 } else if (this.peek().constant) {
13080 primary = this.constant();
13082 this.throwError('not a primary expression', this.peek());
13086 while ((next = this.expect('(', '[', '.'))) {
13087 if (next.text === '(') {
13088 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
13090 } else if (next.text === '[') {
13091 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
13093 } else if (next.text === '.') {
13094 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
13096 this.throwError('IMPOSSIBLE');
13102 filter: function(baseExpression) {
13103 var args = [baseExpression];
13104 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
13106 while (this.expect(':')) {
13107 args.push(this.expression());
13113 parseArguments: function() {
13115 if (this.peekToken().text !== ')') {
13117 args.push(this.expression());
13118 } while (this.expect(','));
13123 identifier: function() {
13124 var token = this.consume();
13125 if (!token.identifier) {
13126 this.throwError('is not a valid identifier', token);
13128 return { type: AST.Identifier, name: token.text };
13131 constant: function() {
13132 // TODO check that it is a constant
13133 return { type: AST.Literal, value: this.consume().value };
13136 arrayDeclaration: function() {
13138 if (this.peekToken().text !== ']') {
13140 if (this.peek(']')) {
13141 // Support trailing commas per ES5.1.
13144 elements.push(this.expression());
13145 } while (this.expect(','));
13149 return { type: AST.ArrayExpression, elements: elements };
13152 object: function() {
13153 var properties = [], property;
13154 if (this.peekToken().text !== '}') {
13156 if (this.peek('}')) {
13157 // Support trailing commas per ES5.1.
13160 property = {type: AST.Property, kind: 'init'};
13161 if (this.peek().constant) {
13162 property.key = this.constant();
13163 } else if (this.peek().identifier) {
13164 property.key = this.identifier();
13166 this.throwError("invalid key", this.peek());
13169 property.value = this.expression();
13170 properties.push(property);
13171 } while (this.expect(','));
13175 return {type: AST.ObjectExpression, properties: properties };
13178 throwError: function(msg, token) {
13179 throw $parseMinErr('syntax',
13180 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
13181 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
13184 consume: function(e1) {
13185 if (this.tokens.length === 0) {
13186 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13189 var token = this.expect(e1);
13191 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
13196 peekToken: function() {
13197 if (this.tokens.length === 0) {
13198 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13200 return this.tokens[0];
13203 peek: function(e1, e2, e3, e4) {
13204 return this.peekAhead(0, e1, e2, e3, e4);
13207 peekAhead: function(i, e1, e2, e3, e4) {
13208 if (this.tokens.length > i) {
13209 var token = this.tokens[i];
13210 var t = token.text;
13211 if (t === e1 || t === e2 || t === e3 || t === e4 ||
13212 (!e1 && !e2 && !e3 && !e4)) {
13219 expect: function(e1, e2, e3, e4) {
13220 var token = this.peek(e1, e2, e3, e4);
13222 this.tokens.shift();
13229 /* `undefined` is not a constant, it is an identifier,
13230 * but using it as an identifier is not supported
13233 'true': { type: AST.Literal, value: true },
13234 'false': { type: AST.Literal, value: false },
13235 'null': { type: AST.Literal, value: null },
13236 'undefined': {type: AST.Literal, value: undefined },
13237 'this': {type: AST.ThisExpression }
13241 function ifDefined(v, d) {
13242 return typeof v !== 'undefined' ? v : d;
13245 function plusFn(l, r) {
13246 if (typeof l === 'undefined') return r;
13247 if (typeof r === 'undefined') return l;
13251 function isStateless($filter, filterName) {
13252 var fn = $filter(filterName);
13253 return !fn.$stateful;
13256 function findConstantAndWatchExpressions(ast, $filter) {
13259 switch (ast.type) {
13261 allConstants = true;
13262 forEach(ast.body, function(expr) {
13263 findConstantAndWatchExpressions(expr.expression, $filter);
13264 allConstants = allConstants && expr.expression.constant;
13266 ast.constant = allConstants;
13269 ast.constant = true;
13272 case AST.UnaryExpression:
13273 findConstantAndWatchExpressions(ast.argument, $filter);
13274 ast.constant = ast.argument.constant;
13275 ast.toWatch = ast.argument.toWatch;
13277 case AST.BinaryExpression:
13278 findConstantAndWatchExpressions(ast.left, $filter);
13279 findConstantAndWatchExpressions(ast.right, $filter);
13280 ast.constant = ast.left.constant && ast.right.constant;
13281 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
13283 case AST.LogicalExpression:
13284 findConstantAndWatchExpressions(ast.left, $filter);
13285 findConstantAndWatchExpressions(ast.right, $filter);
13286 ast.constant = ast.left.constant && ast.right.constant;
13287 ast.toWatch = ast.constant ? [] : [ast];
13289 case AST.ConditionalExpression:
13290 findConstantAndWatchExpressions(ast.test, $filter);
13291 findConstantAndWatchExpressions(ast.alternate, $filter);
13292 findConstantAndWatchExpressions(ast.consequent, $filter);
13293 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
13294 ast.toWatch = ast.constant ? [] : [ast];
13296 case AST.Identifier:
13297 ast.constant = false;
13298 ast.toWatch = [ast];
13300 case AST.MemberExpression:
13301 findConstantAndWatchExpressions(ast.object, $filter);
13302 if (ast.computed) {
13303 findConstantAndWatchExpressions(ast.property, $filter);
13305 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
13306 ast.toWatch = [ast];
13308 case AST.CallExpression:
13309 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false;
13311 forEach(ast.arguments, function(expr) {
13312 findConstantAndWatchExpressions(expr, $filter);
13313 allConstants = allConstants && expr.constant;
13314 if (!expr.constant) {
13315 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13318 ast.constant = allConstants;
13319 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast];
13321 case AST.AssignmentExpression:
13322 findConstantAndWatchExpressions(ast.left, $filter);
13323 findConstantAndWatchExpressions(ast.right, $filter);
13324 ast.constant = ast.left.constant && ast.right.constant;
13325 ast.toWatch = [ast];
13327 case AST.ArrayExpression:
13328 allConstants = true;
13330 forEach(ast.elements, function(expr) {
13331 findConstantAndWatchExpressions(expr, $filter);
13332 allConstants = allConstants && expr.constant;
13333 if (!expr.constant) {
13334 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13337 ast.constant = allConstants;
13338 ast.toWatch = argsToWatch;
13340 case AST.ObjectExpression:
13341 allConstants = true;
13343 forEach(ast.properties, function(property) {
13344 findConstantAndWatchExpressions(property.value, $filter);
13345 allConstants = allConstants && property.value.constant;
13346 if (!property.value.constant) {
13347 argsToWatch.push.apply(argsToWatch, property.value.toWatch);
13350 ast.constant = allConstants;
13351 ast.toWatch = argsToWatch;
13353 case AST.ThisExpression:
13354 ast.constant = false;
13360 function getInputs(body) {
13361 if (body.length != 1) return;
13362 var lastExpression = body[0].expression;
13363 var candidate = lastExpression.toWatch;
13364 if (candidate.length !== 1) return candidate;
13365 return candidate[0] !== lastExpression ? candidate : undefined;
13368 function isAssignable(ast) {
13369 return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
13372 function assignableAST(ast) {
13373 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
13374 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
13378 function isLiteral(ast) {
13379 return ast.body.length === 0 ||
13380 ast.body.length === 1 && (
13381 ast.body[0].expression.type === AST.Literal ||
13382 ast.body[0].expression.type === AST.ArrayExpression ||
13383 ast.body[0].expression.type === AST.ObjectExpression);
13386 function isConstant(ast) {
13387 return ast.constant;
13390 function ASTCompiler(astBuilder, $filter) {
13391 this.astBuilder = astBuilder;
13392 this.$filter = $filter;
13395 ASTCompiler.prototype = {
13396 compile: function(expression, expensiveChecks) {
13398 var ast = this.astBuilder.ast(expression);
13402 expensiveChecks: expensiveChecks,
13403 fn: {vars: [], body: [], own: {}},
13404 assign: {vars: [], body: [], own: {}},
13407 findConstantAndWatchExpressions(ast, self.$filter);
13410 this.stage = 'assign';
13411 if ((assignable = assignableAST(ast))) {
13412 this.state.computing = 'assign';
13413 var result = this.nextId();
13414 this.recurse(assignable, result);
13415 this.return_(result);
13416 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
13418 var toWatch = getInputs(ast.body);
13419 self.stage = 'inputs';
13420 forEach(toWatch, function(watch, key) {
13421 var fnKey = 'fn' + key;
13422 self.state[fnKey] = {vars: [], body: [], own: {}};
13423 self.state.computing = fnKey;
13424 var intoId = self.nextId();
13425 self.recurse(watch, intoId);
13426 self.return_(intoId);
13427 self.state.inputs.push(fnKey);
13428 watch.watchId = key;
13430 this.state.computing = 'fn';
13431 this.stage = 'main';
13434 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
13435 // This is a workaround for this until we do a better job at only removing the prefix only when we should.
13436 '"' + this.USE + ' ' + this.STRICT + '";\n' +
13437 this.filterPrefix() +
13438 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
13444 var fn = (new Function('$filter',
13445 'ensureSafeMemberName',
13446 'ensureSafeObject',
13447 'ensureSafeFunction',
13449 'ensureSafeAssignContext',
13455 ensureSafeMemberName,
13457 ensureSafeFunction,
13459 ensureSafeAssignContext,
13464 this.state = this.stage = undefined;
13465 fn.literal = isLiteral(ast);
13466 fn.constant = isConstant(ast);
13474 watchFns: function() {
13476 var fns = this.state.inputs;
13478 forEach(fns, function(name) {
13479 result.push('var ' + name + '=' + self.generateFunction(name, 's'));
13482 result.push('fn.inputs=[' + fns.join(',') + '];');
13484 return result.join('');
13487 generateFunction: function(name, params) {
13488 return 'function(' + params + '){' +
13489 this.varsPrefix(name) +
13494 filterPrefix: function() {
13497 forEach(this.state.filters, function(id, filter) {
13498 parts.push(id + '=$filter(' + self.escape(filter) + ')');
13500 if (parts.length) return 'var ' + parts.join(',') + ';';
13504 varsPrefix: function(section) {
13505 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
13508 body: function(section) {
13509 return this.state[section].body.join('');
13512 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13513 var left, right, self = this, args, expression;
13514 recursionFn = recursionFn || noop;
13515 if (!skipWatchIdCheck && isDefined(ast.watchId)) {
13516 intoId = intoId || this.nextId();
13518 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
13519 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
13523 switch (ast.type) {
13525 forEach(ast.body, function(expression, pos) {
13526 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
13527 if (pos !== ast.body.length - 1) {
13528 self.current().body.push(right, ';');
13530 self.return_(right);
13535 expression = this.escape(ast.value);
13536 this.assign(intoId, expression);
13537 recursionFn(expression);
13539 case AST.UnaryExpression:
13540 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
13541 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
13542 this.assign(intoId, expression);
13543 recursionFn(expression);
13545 case AST.BinaryExpression:
13546 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
13547 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
13548 if (ast.operator === '+') {
13549 expression = this.plus(left, right);
13550 } else if (ast.operator === '-') {
13551 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
13553 expression = '(' + left + ')' + ast.operator + '(' + right + ')';
13555 this.assign(intoId, expression);
13556 recursionFn(expression);
13558 case AST.LogicalExpression:
13559 intoId = intoId || this.nextId();
13560 self.recurse(ast.left, intoId);
13561 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
13562 recursionFn(intoId);
13564 case AST.ConditionalExpression:
13565 intoId = intoId || this.nextId();
13566 self.recurse(ast.test, intoId);
13567 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
13568 recursionFn(intoId);
13570 case AST.Identifier:
13571 intoId = intoId || this.nextId();
13573 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
13574 nameId.computed = false;
13575 nameId.name = ast.name;
13577 ensureSafeMemberName(ast.name);
13578 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
13580 self.if_(self.stage === 'inputs' || 's', function() {
13581 if (create && create !== 1) {
13583 self.not(self.nonComputedMember('s', ast.name)),
13584 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
13586 self.assign(intoId, self.nonComputedMember('s', ast.name));
13588 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
13590 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) {
13591 self.addEnsureSafeObject(intoId);
13593 recursionFn(intoId);
13595 case AST.MemberExpression:
13596 left = nameId && (nameId.context = this.nextId()) || this.nextId();
13597 intoId = intoId || this.nextId();
13598 self.recurse(ast.object, left, undefined, function() {
13599 self.if_(self.notNull(left), function() {
13600 if (ast.computed) {
13601 right = self.nextId();
13602 self.recurse(ast.property, right);
13603 self.getStringValue(right);
13604 self.addEnsureSafeMemberName(right);
13605 if (create && create !== 1) {
13606 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
13608 expression = self.ensureSafeObject(self.computedMember(left, right));
13609 self.assign(intoId, expression);
13611 nameId.computed = true;
13612 nameId.name = right;
13615 ensureSafeMemberName(ast.property.name);
13616 if (create && create !== 1) {
13617 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
13619 expression = self.nonComputedMember(left, ast.property.name);
13620 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
13621 expression = self.ensureSafeObject(expression);
13623 self.assign(intoId, expression);
13625 nameId.computed = false;
13626 nameId.name = ast.property.name;
13630 self.assign(intoId, 'undefined');
13632 recursionFn(intoId);
13635 case AST.CallExpression:
13636 intoId = intoId || this.nextId();
13638 right = self.filter(ast.callee.name);
13640 forEach(ast.arguments, function(expr) {
13641 var argument = self.nextId();
13642 self.recurse(expr, argument);
13643 args.push(argument);
13645 expression = right + '(' + args.join(',') + ')';
13646 self.assign(intoId, expression);
13647 recursionFn(intoId);
13649 right = self.nextId();
13652 self.recurse(ast.callee, right, left, function() {
13653 self.if_(self.notNull(right), function() {
13654 self.addEnsureSafeFunction(right);
13655 forEach(ast.arguments, function(expr) {
13656 self.recurse(expr, self.nextId(), undefined, function(argument) {
13657 args.push(self.ensureSafeObject(argument));
13661 if (!self.state.expensiveChecks) {
13662 self.addEnsureSafeObject(left.context);
13664 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
13666 expression = right + '(' + args.join(',') + ')';
13668 expression = self.ensureSafeObject(expression);
13669 self.assign(intoId, expression);
13671 self.assign(intoId, 'undefined');
13673 recursionFn(intoId);
13677 case AST.AssignmentExpression:
13678 right = this.nextId();
13680 if (!isAssignable(ast.left)) {
13681 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value');
13683 this.recurse(ast.left, undefined, left, function() {
13684 self.if_(self.notNull(left.context), function() {
13685 self.recurse(ast.right, right);
13686 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
13687 self.addEnsureSafeAssignContext(left.context);
13688 expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
13689 self.assign(intoId, expression);
13690 recursionFn(intoId || expression);
13694 case AST.ArrayExpression:
13696 forEach(ast.elements, function(expr) {
13697 self.recurse(expr, self.nextId(), undefined, function(argument) {
13698 args.push(argument);
13701 expression = '[' + args.join(',') + ']';
13702 this.assign(intoId, expression);
13703 recursionFn(expression);
13705 case AST.ObjectExpression:
13707 forEach(ast.properties, function(property) {
13708 self.recurse(property.value, self.nextId(), undefined, function(expr) {
13709 args.push(self.escape(
13710 property.key.type === AST.Identifier ? property.key.name :
13711 ('' + property.key.value)) +
13715 expression = '{' + args.join(',') + '}';
13716 this.assign(intoId, expression);
13717 recursionFn(expression);
13719 case AST.ThisExpression:
13720 this.assign(intoId, 's');
13723 case AST.NGValueParameter:
13724 this.assign(intoId, 'v');
13730 getHasOwnProperty: function(element, property) {
13731 var key = element + '.' + property;
13732 var own = this.current().own;
13733 if (!own.hasOwnProperty(key)) {
13734 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
13739 assign: function(id, value) {
13741 this.current().body.push(id, '=', value, ';');
13745 filter: function(filterName) {
13746 if (!this.state.filters.hasOwnProperty(filterName)) {
13747 this.state.filters[filterName] = this.nextId(true);
13749 return this.state.filters[filterName];
13752 ifDefined: function(id, defaultValue) {
13753 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
13756 plus: function(left, right) {
13757 return 'plus(' + left + ',' + right + ')';
13760 return_: function(id) {
13761 this.current().body.push('return ', id, ';');
13764 if_: function(test, alternate, consequent) {
13765 if (test === true) {
13768 var body = this.current().body;
13769 body.push('if(', test, '){');
13773 body.push('else{');
13780 not: function(expression) {
13781 return '!(' + expression + ')';
13784 notNull: function(expression) {
13785 return expression + '!=null';
13788 nonComputedMember: function(left, right) {
13789 return left + '.' + right;
13792 computedMember: function(left, right) {
13793 return left + '[' + right + ']';
13796 member: function(left, right, computed) {
13797 if (computed) return this.computedMember(left, right);
13798 return this.nonComputedMember(left, right);
13801 addEnsureSafeObject: function(item) {
13802 this.current().body.push(this.ensureSafeObject(item), ';');
13805 addEnsureSafeMemberName: function(item) {
13806 this.current().body.push(this.ensureSafeMemberName(item), ';');
13809 addEnsureSafeFunction: function(item) {
13810 this.current().body.push(this.ensureSafeFunction(item), ';');
13813 addEnsureSafeAssignContext: function(item) {
13814 this.current().body.push(this.ensureSafeAssignContext(item), ';');
13817 ensureSafeObject: function(item) {
13818 return 'ensureSafeObject(' + item + ',text)';
13821 ensureSafeMemberName: function(item) {
13822 return 'ensureSafeMemberName(' + item + ',text)';
13825 ensureSafeFunction: function(item) {
13826 return 'ensureSafeFunction(' + item + ',text)';
13829 getStringValue: function(item) {
13830 this.assign(item, 'getStringValue(' + item + ',text)');
13833 ensureSafeAssignContext: function(item) {
13834 return 'ensureSafeAssignContext(' + item + ',text)';
13837 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13839 return function() {
13840 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
13844 lazyAssign: function(id, value) {
13846 return function() {
13847 self.assign(id, value);
13851 stringEscapeRegex: /[^ a-zA-Z0-9]/g,
13853 stringEscapeFn: function(c) {
13854 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
13857 escape: function(value) {
13858 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'";
13859 if (isNumber(value)) return value.toString();
13860 if (value === true) return 'true';
13861 if (value === false) return 'false';
13862 if (value === null) return 'null';
13863 if (typeof value === 'undefined') return 'undefined';
13865 throw $parseMinErr('esc', 'IMPOSSIBLE');
13868 nextId: function(skip, init) {
13869 var id = 'v' + (this.state.nextId++);
13871 this.current().vars.push(id + (init ? '=' + init : ''));
13876 current: function() {
13877 return this.state[this.state.computing];
13882 function ASTInterpreter(astBuilder, $filter) {
13883 this.astBuilder = astBuilder;
13884 this.$filter = $filter;
13887 ASTInterpreter.prototype = {
13888 compile: function(expression, expensiveChecks) {
13890 var ast = this.astBuilder.ast(expression);
13891 this.expression = expression;
13892 this.expensiveChecks = expensiveChecks;
13893 findConstantAndWatchExpressions(ast, self.$filter);
13896 if ((assignable = assignableAST(ast))) {
13897 assign = this.recurse(assignable);
13899 var toWatch = getInputs(ast.body);
13903 forEach(toWatch, function(watch, key) {
13904 var input = self.recurse(watch);
13905 watch.input = input;
13906 inputs.push(input);
13907 watch.watchId = key;
13910 var expressions = [];
13911 forEach(ast.body, function(expression) {
13912 expressions.push(self.recurse(expression.expression));
13914 var fn = ast.body.length === 0 ? function() {} :
13915 ast.body.length === 1 ? expressions[0] :
13916 function(scope, locals) {
13918 forEach(expressions, function(exp) {
13919 lastValue = exp(scope, locals);
13924 fn.assign = function(scope, value, locals) {
13925 return assign(scope, locals, value);
13929 fn.inputs = inputs;
13931 fn.literal = isLiteral(ast);
13932 fn.constant = isConstant(ast);
13936 recurse: function(ast, context, create) {
13937 var left, right, self = this, args, expression;
13939 return this.inputs(ast.input, ast.watchId);
13941 switch (ast.type) {
13943 return this.value(ast.value, context);
13944 case AST.UnaryExpression:
13945 right = this.recurse(ast.argument);
13946 return this['unary' + ast.operator](right, context);
13947 case AST.BinaryExpression:
13948 left = this.recurse(ast.left);
13949 right = this.recurse(ast.right);
13950 return this['binary' + ast.operator](left, right, context);
13951 case AST.LogicalExpression:
13952 left = this.recurse(ast.left);
13953 right = this.recurse(ast.right);
13954 return this['binary' + ast.operator](left, right, context);
13955 case AST.ConditionalExpression:
13956 return this['ternary?:'](
13957 this.recurse(ast.test),
13958 this.recurse(ast.alternate),
13959 this.recurse(ast.consequent),
13962 case AST.Identifier:
13963 ensureSafeMemberName(ast.name, self.expression);
13964 return self.identifier(ast.name,
13965 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name),
13966 context, create, self.expression);
13967 case AST.MemberExpression:
13968 left = this.recurse(ast.object, false, !!create);
13969 if (!ast.computed) {
13970 ensureSafeMemberName(ast.property.name, self.expression);
13971 right = ast.property.name;
13973 if (ast.computed) right = this.recurse(ast.property);
13974 return ast.computed ?
13975 this.computedMember(left, right, context, create, self.expression) :
13976 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression);
13977 case AST.CallExpression:
13979 forEach(ast.arguments, function(expr) {
13980 args.push(self.recurse(expr));
13982 if (ast.filter) right = this.$filter(ast.callee.name);
13983 if (!ast.filter) right = this.recurse(ast.callee, true);
13984 return ast.filter ?
13985 function(scope, locals, assign, inputs) {
13987 for (var i = 0; i < args.length; ++i) {
13988 values.push(args[i](scope, locals, assign, inputs));
13990 var value = right.apply(undefined, values, inputs);
13991 return context ? {context: undefined, name: undefined, value: value} : value;
13993 function(scope, locals, assign, inputs) {
13994 var rhs = right(scope, locals, assign, inputs);
13996 if (rhs.value != null) {
13997 ensureSafeObject(rhs.context, self.expression);
13998 ensureSafeFunction(rhs.value, self.expression);
14000 for (var i = 0; i < args.length; ++i) {
14001 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression));
14003 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression);
14005 return context ? {value: value} : value;
14007 case AST.AssignmentExpression:
14008 left = this.recurse(ast.left, true, 1);
14009 right = this.recurse(ast.right);
14010 return function(scope, locals, assign, inputs) {
14011 var lhs = left(scope, locals, assign, inputs);
14012 var rhs = right(scope, locals, assign, inputs);
14013 ensureSafeObject(lhs.value, self.expression);
14014 ensureSafeAssignContext(lhs.context);
14015 lhs.context[lhs.name] = rhs;
14016 return context ? {value: rhs} : rhs;
14018 case AST.ArrayExpression:
14020 forEach(ast.elements, function(expr) {
14021 args.push(self.recurse(expr));
14023 return function(scope, locals, assign, inputs) {
14025 for (var i = 0; i < args.length; ++i) {
14026 value.push(args[i](scope, locals, assign, inputs));
14028 return context ? {value: value} : value;
14030 case AST.ObjectExpression:
14032 forEach(ast.properties, function(property) {
14033 args.push({key: property.key.type === AST.Identifier ?
14034 property.key.name :
14035 ('' + property.key.value),
14036 value: self.recurse(property.value)
14039 return function(scope, locals, assign, inputs) {
14041 for (var i = 0; i < args.length; ++i) {
14042 value[args[i].key] = args[i].value(scope, locals, assign, inputs);
14044 return context ? {value: value} : value;
14046 case AST.ThisExpression:
14047 return function(scope) {
14048 return context ? {value: scope} : scope;
14050 case AST.NGValueParameter:
14051 return function(scope, locals, assign, inputs) {
14052 return context ? {value: assign} : assign;
14057 'unary+': function(argument, context) {
14058 return function(scope, locals, assign, inputs) {
14059 var arg = argument(scope, locals, assign, inputs);
14060 if (isDefined(arg)) {
14065 return context ? {value: arg} : arg;
14068 'unary-': function(argument, context) {
14069 return function(scope, locals, assign, inputs) {
14070 var arg = argument(scope, locals, assign, inputs);
14071 if (isDefined(arg)) {
14076 return context ? {value: arg} : arg;
14079 'unary!': function(argument, context) {
14080 return function(scope, locals, assign, inputs) {
14081 var arg = !argument(scope, locals, assign, inputs);
14082 return context ? {value: arg} : arg;
14085 'binary+': function(left, right, context) {
14086 return function(scope, locals, assign, inputs) {
14087 var lhs = left(scope, locals, assign, inputs);
14088 var rhs = right(scope, locals, assign, inputs);
14089 var arg = plusFn(lhs, rhs);
14090 return context ? {value: arg} : arg;
14093 'binary-': function(left, right, context) {
14094 return function(scope, locals, assign, inputs) {
14095 var lhs = left(scope, locals, assign, inputs);
14096 var rhs = right(scope, locals, assign, inputs);
14097 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
14098 return context ? {value: arg} : arg;
14101 'binary*': function(left, right, context) {
14102 return function(scope, locals, assign, inputs) {
14103 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
14104 return context ? {value: arg} : arg;
14107 'binary/': function(left, right, context) {
14108 return function(scope, locals, assign, inputs) {
14109 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
14110 return context ? {value: arg} : arg;
14113 'binary%': function(left, right, context) {
14114 return function(scope, locals, assign, inputs) {
14115 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
14116 return context ? {value: arg} : arg;
14119 'binary===': function(left, right, context) {
14120 return function(scope, locals, assign, inputs) {
14121 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
14122 return context ? {value: arg} : arg;
14125 'binary!==': function(left, right, context) {
14126 return function(scope, locals, assign, inputs) {
14127 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
14128 return context ? {value: arg} : arg;
14131 'binary==': function(left, right, context) {
14132 return function(scope, locals, assign, inputs) {
14133 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
14134 return context ? {value: arg} : arg;
14137 'binary!=': function(left, right, context) {
14138 return function(scope, locals, assign, inputs) {
14139 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
14140 return context ? {value: arg} : arg;
14143 'binary<': function(left, right, context) {
14144 return function(scope, locals, assign, inputs) {
14145 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
14146 return context ? {value: arg} : arg;
14149 'binary>': function(left, right, context) {
14150 return function(scope, locals, assign, inputs) {
14151 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
14152 return context ? {value: arg} : arg;
14155 'binary<=': function(left, right, context) {
14156 return function(scope, locals, assign, inputs) {
14157 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
14158 return context ? {value: arg} : arg;
14161 'binary>=': function(left, right, context) {
14162 return function(scope, locals, assign, inputs) {
14163 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
14164 return context ? {value: arg} : arg;
14167 'binary&&': function(left, right, context) {
14168 return function(scope, locals, assign, inputs) {
14169 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
14170 return context ? {value: arg} : arg;
14173 'binary||': function(left, right, context) {
14174 return function(scope, locals, assign, inputs) {
14175 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
14176 return context ? {value: arg} : arg;
14179 'ternary?:': function(test, alternate, consequent, context) {
14180 return function(scope, locals, assign, inputs) {
14181 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
14182 return context ? {value: arg} : arg;
14185 value: function(value, context) {
14186 return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
14188 identifier: function(name, expensiveChecks, context, create, expression) {
14189 return function(scope, locals, assign, inputs) {
14190 var base = locals && (name in locals) ? locals : scope;
14191 if (create && create !== 1 && base && !(base[name])) {
14194 var value = base ? base[name] : undefined;
14195 if (expensiveChecks) {
14196 ensureSafeObject(value, expression);
14199 return {context: base, name: name, value: value};
14205 computedMember: function(left, right, context, create, expression) {
14206 return function(scope, locals, assign, inputs) {
14207 var lhs = left(scope, locals, assign, inputs);
14211 rhs = right(scope, locals, assign, inputs);
14212 rhs = getStringValue(rhs);
14213 ensureSafeMemberName(rhs, expression);
14214 if (create && create !== 1 && lhs && !(lhs[rhs])) {
14218 ensureSafeObject(value, expression);
14221 return {context: lhs, name: rhs, value: value};
14227 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
14228 return function(scope, locals, assign, inputs) {
14229 var lhs = left(scope, locals, assign, inputs);
14230 if (create && create !== 1 && lhs && !(lhs[right])) {
14233 var value = lhs != null ? lhs[right] : undefined;
14234 if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
14235 ensureSafeObject(value, expression);
14238 return {context: lhs, name: right, value: value};
14244 inputs: function(input, watchId) {
14245 return function(scope, value, locals, inputs) {
14246 if (inputs) return inputs[watchId];
14247 return input(scope, value, locals);
14255 var Parser = function(lexer, $filter, options) {
14256 this.lexer = lexer;
14257 this.$filter = $filter;
14258 this.options = options;
14259 this.ast = new AST(this.lexer);
14260 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) :
14261 new ASTCompiler(this.ast, $filter);
14264 Parser.prototype = {
14265 constructor: Parser,
14267 parse: function(text) {
14268 return this.astCompiler.compile(text, this.options.expensiveChecks);
14272 var getterFnCacheDefault = createMap();
14273 var getterFnCacheExpensive = createMap();
14275 function isPossiblyDangerousMemberName(name) {
14276 return name == 'constructor';
14279 var objectValueOf = Object.prototype.valueOf;
14281 function getValueOf(value) {
14282 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
14285 ///////////////////////////////////
14294 * Converts Angular {@link guide/expression expression} into a function.
14297 * var getter = $parse('user.name');
14298 * var setter = getter.assign;
14299 * var context = {user:{name:'angular'}};
14300 * var locals = {user:{name:'local'}};
14302 * expect(getter(context)).toEqual('angular');
14303 * setter(context, 'newValue');
14304 * expect(context.user.name).toEqual('newValue');
14305 * expect(getter(context, locals)).toEqual('local');
14309 * @param {string} expression String expression to compile.
14310 * @returns {function(context, locals)} a function which represents the compiled expression:
14312 * * `context` – `{object}` – an object against which any expressions embedded in the strings
14313 * are evaluated against (typically a scope object).
14314 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
14317 * The returned function also has the following properties:
14318 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
14320 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
14321 * constant literals.
14322 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
14323 * set to a function to change its value on the given context.
14330 * @name $parseProvider
14333 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
14336 function $ParseProvider() {
14337 var cacheDefault = createMap();
14338 var cacheExpensive = createMap();
14340 this.$get = ['$filter', function($filter) {
14341 var noUnsafeEval = csp().noUnsafeEval;
14342 var $parseOptions = {
14344 expensiveChecks: false
14346 $parseOptionsExpensive = {
14348 expensiveChecks: true
14351 return function $parse(exp, interceptorFn, expensiveChecks) {
14352 var parsedExpression, oneTime, cacheKey;
14354 switch (typeof exp) {
14359 var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
14360 parsedExpression = cache[cacheKey];
14362 if (!parsedExpression) {
14363 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
14365 exp = exp.substring(2);
14367 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
14368 var lexer = new Lexer(parseOptions);
14369 var parser = new Parser(lexer, $filter, parseOptions);
14370 parsedExpression = parser.parse(exp);
14371 if (parsedExpression.constant) {
14372 parsedExpression.$$watchDelegate = constantWatchDelegate;
14373 } else if (oneTime) {
14374 parsedExpression.$$watchDelegate = parsedExpression.literal ?
14375 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
14376 } else if (parsedExpression.inputs) {
14377 parsedExpression.$$watchDelegate = inputsWatchDelegate;
14379 cache[cacheKey] = parsedExpression;
14381 return addInterceptor(parsedExpression, interceptorFn);
14384 return addInterceptor(exp, interceptorFn);
14391 function expressionInputDirtyCheck(newValue, oldValueOfValue) {
14393 if (newValue == null || oldValueOfValue == null) { // null/undefined
14394 return newValue === oldValueOfValue;
14397 if (typeof newValue === 'object') {
14399 // attempt to convert the value to a primitive type
14400 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
14401 // be cheaply dirty-checked
14402 newValue = getValueOf(newValue);
14404 if (typeof newValue === 'object') {
14405 // objects/arrays are not supported - deep-watching them would be too expensive
14409 // fall-through to the primitive equality check
14413 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
14416 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
14417 var inputExpressions = parsedExpression.inputs;
14420 if (inputExpressions.length === 1) {
14421 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
14422 inputExpressions = inputExpressions[0];
14423 return scope.$watch(function expressionInputWatch(scope) {
14424 var newInputValue = inputExpressions(scope);
14425 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
14426 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
14427 oldInputValueOf = newInputValue && getValueOf(newInputValue);
14430 }, listener, objectEquality, prettyPrintExpression);
14433 var oldInputValueOfValues = [];
14434 var oldInputValues = [];
14435 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14436 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
14437 oldInputValues[i] = null;
14440 return scope.$watch(function expressionInputsWatch(scope) {
14441 var changed = false;
14443 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14444 var newInputValue = inputExpressions[i](scope);
14445 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
14446 oldInputValues[i] = newInputValue;
14447 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
14452 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
14456 }, listener, objectEquality, prettyPrintExpression);
14459 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14460 var unwatch, lastValue;
14461 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14462 return parsedExpression(scope);
14463 }, function oneTimeListener(value, old, scope) {
14465 if (isFunction(listener)) {
14466 listener.apply(this, arguments);
14468 if (isDefined(value)) {
14469 scope.$$postDigest(function() {
14470 if (isDefined(lastValue)) {
14475 }, objectEquality);
14478 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14479 var unwatch, lastValue;
14480 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14481 return parsedExpression(scope);
14482 }, function oneTimeListener(value, old, scope) {
14484 if (isFunction(listener)) {
14485 listener.call(this, value, old, scope);
14487 if (isAllDefined(value)) {
14488 scope.$$postDigest(function() {
14489 if (isAllDefined(lastValue)) unwatch();
14492 }, objectEquality);
14494 function isAllDefined(value) {
14495 var allDefined = true;
14496 forEach(value, function(val) {
14497 if (!isDefined(val)) allDefined = false;
14503 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14505 return unwatch = scope.$watch(function constantWatch(scope) {
14506 return parsedExpression(scope);
14507 }, function constantListener(value, old, scope) {
14508 if (isFunction(listener)) {
14509 listener.apply(this, arguments);
14512 }, objectEquality);
14515 function addInterceptor(parsedExpression, interceptorFn) {
14516 if (!interceptorFn) return parsedExpression;
14517 var watchDelegate = parsedExpression.$$watchDelegate;
14518 var useInputs = false;
14521 watchDelegate !== oneTimeLiteralWatchDelegate &&
14522 watchDelegate !== oneTimeWatchDelegate;
14524 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
14525 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
14526 return interceptorFn(value, scope, locals);
14527 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
14528 var value = parsedExpression(scope, locals, assign, inputs);
14529 var result = interceptorFn(value, scope, locals);
14530 // we only return the interceptor's result if the
14531 // initial value is defined (for bind-once)
14532 return isDefined(value) ? result : value;
14535 // Propagate $$watchDelegates other then inputsWatchDelegate
14536 if (parsedExpression.$$watchDelegate &&
14537 parsedExpression.$$watchDelegate !== inputsWatchDelegate) {
14538 fn.$$watchDelegate = parsedExpression.$$watchDelegate;
14539 } else if (!interceptorFn.$stateful) {
14540 // If there is an interceptor, but no watchDelegate then treat the interceptor like
14541 // we treat filters - it is assumed to be a pure function unless flagged with $stateful
14542 fn.$$watchDelegate = inputsWatchDelegate;
14543 useInputs = !parsedExpression.inputs;
14544 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
14555 * @requires $rootScope
14558 * A service that helps you run functions asynchronously, and use their return values (or exceptions)
14559 * when they are done processing.
14561 * This is an implementation of promises/deferred objects inspired by
14562 * [Kris Kowal's Q](https://github.com/kriskowal/q).
14564 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
14565 * implementations, and the other which resembles ES6 promises to some degree.
14569 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
14570 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony,
14571 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
14573 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are
14576 * It can be used like so:
14579 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14580 * // are available in the current lexical scope (they could have been injected or passed in).
14582 * function asyncGreet(name) {
14583 * // perform some asynchronous operation, resolve or reject the promise when appropriate.
14584 * return $q(function(resolve, reject) {
14585 * setTimeout(function() {
14586 * if (okToGreet(name)) {
14587 * resolve('Hello, ' + name + '!');
14589 * reject('Greeting ' + name + ' is not allowed.');
14595 * var promise = asyncGreet('Robin Hood');
14596 * promise.then(function(greeting) {
14597 * alert('Success: ' + greeting);
14598 * }, function(reason) {
14599 * alert('Failed: ' + reason);
14603 * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
14605 * Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise.
14607 * However, the more traditional CommonJS-style usage is still available, and documented below.
14609 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
14610 * interface for interacting with an object that represents the result of an action that is
14611 * performed asynchronously, and may or may not be finished at any given point in time.
14613 * From the perspective of dealing with error handling, deferred and promise APIs are to
14614 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
14617 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14618 * // are available in the current lexical scope (they could have been injected or passed in).
14620 * function asyncGreet(name) {
14621 * var deferred = $q.defer();
14623 * setTimeout(function() {
14624 * deferred.notify('About to greet ' + name + '.');
14626 * if (okToGreet(name)) {
14627 * deferred.resolve('Hello, ' + name + '!');
14629 * deferred.reject('Greeting ' + name + ' is not allowed.');
14633 * return deferred.promise;
14636 * var promise = asyncGreet('Robin Hood');
14637 * promise.then(function(greeting) {
14638 * alert('Success: ' + greeting);
14639 * }, function(reason) {
14640 * alert('Failed: ' + reason);
14641 * }, function(update) {
14642 * alert('Got notification: ' + update);
14646 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
14647 * comes in the way of guarantees that promise and deferred APIs make, see
14648 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
14650 * Additionally the promise api allows for composition that is very hard to do with the
14651 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
14652 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
14653 * section on serial or parallel joining of promises.
14655 * # The Deferred API
14657 * A new instance of deferred is constructed by calling `$q.defer()`.
14659 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
14660 * that can be used for signaling the successful or unsuccessful completion, as well as the status
14665 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
14666 * constructed via `$q.reject`, the promise will be rejected instead.
14667 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
14668 * resolving it with a rejection constructed via `$q.reject`.
14669 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
14670 * multiple times before the promise is either resolved or rejected.
14674 * - promise – `{Promise}` – promise object associated with this deferred.
14677 * # The Promise API
14679 * A new promise instance is created when a deferred instance is created and can be retrieved by
14680 * calling `deferred.promise`.
14682 * The purpose of the promise object is to allow for interested parties to get access to the result
14683 * of the deferred task when it completes.
14687 * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or
14688 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
14689 * as soon as the result is available. The callbacks are called with a single argument: the result
14690 * or rejection reason. Additionally, the notify callback may be called zero or more times to
14691 * provide a progress indication, before the promise is resolved or rejected.
14693 * This method *returns a new promise* which is resolved or rejected via the return value of the
14694 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14695 * with the value which is resolved in that promise using
14696 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
14697 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
14698 * resolved or rejected from the notifyCallback method.
14700 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
14702 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
14703 * but to do so without modifying the final value. This is useful to release resources or do some
14704 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
14705 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
14706 * more information.
14708 * # Chaining promises
14710 * Because calling the `then` method of a promise returns a new derived promise, it is easily
14711 * possible to create a chain of promises:
14714 * promiseB = promiseA.then(function(result) {
14715 * return result + 1;
14718 * // promiseB will be resolved immediately after promiseA is resolved and its value
14719 * // will be the result of promiseA incremented by 1
14722 * It is possible to create chains of any length and since a promise can be resolved with another
14723 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
14724 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
14725 * $http's response interceptors.
14728 * # Differences between Kris Kowal's Q and $q
14730 * There are two main differences:
14732 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
14733 * mechanism in angular, which means faster propagation of resolution or rejection into your
14734 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
14735 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14736 * all the important functionality needed for common async tasks.
14741 * it('should simulate promise', inject(function($q, $rootScope) {
14742 * var deferred = $q.defer();
14743 * var promise = deferred.promise;
14744 * var resolvedValue;
14746 * promise.then(function(value) { resolvedValue = value; });
14747 * expect(resolvedValue).toBeUndefined();
14749 * // Simulate resolving of promise
14750 * deferred.resolve(123);
14751 * // Note that the 'then' function does not get called synchronously.
14752 * // This is because we want the promise API to always be async, whether or not
14753 * // it got called synchronously or asynchronously.
14754 * expect(resolvedValue).toBeUndefined();
14756 * // Propagate promise resolution to 'then' functions using $apply().
14757 * $rootScope.$apply();
14758 * expect(resolvedValue).toEqual(123);
14762 * @param {function(function, function)} resolver Function which is responsible for resolving or
14763 * rejecting the newly created promise. The first parameter is a function which resolves the
14764 * promise, the second parameter is a function which rejects the promise.
14766 * @returns {Promise} The newly created promise.
14768 function $QProvider() {
14770 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
14771 return qFactory(function(callback) {
14772 $rootScope.$evalAsync(callback);
14773 }, $exceptionHandler);
14777 function $$QProvider() {
14778 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
14779 return qFactory(function(callback) {
14780 $browser.defer(callback);
14781 }, $exceptionHandler);
14786 * Constructs a promise manager.
14788 * @param {function(function)} nextTick Function for executing functions in the next turn.
14789 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
14790 * debugging purposes.
14791 * @returns {object} Promise manager.
14793 function qFactory(nextTick, exceptionHandler) {
14794 var $qMinErr = minErr('$q', TypeError);
14795 function callOnce(self, resolveFn, rejectFn) {
14796 var called = false;
14797 function wrap(fn) {
14798 return function(value) {
14799 if (called) return;
14801 fn.call(self, value);
14805 return [wrap(resolveFn), wrap(rejectFn)];
14810 * @name ng.$q#defer
14814 * Creates a `Deferred` object which represents a task which will finish in the future.
14816 * @returns {Deferred} Returns a new instance of deferred.
14818 var defer = function() {
14819 return new Deferred();
14822 function Promise() {
14823 this.$$state = { status: 0 };
14826 extend(Promise.prototype, {
14827 then: function(onFulfilled, onRejected, progressBack) {
14828 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
14831 var result = new Deferred();
14833 this.$$state.pending = this.$$state.pending || [];
14834 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
14835 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
14837 return result.promise;
14840 "catch": function(callback) {
14841 return this.then(null, callback);
14844 "finally": function(callback, progressBack) {
14845 return this.then(function(value) {
14846 return handleCallback(value, true, callback);
14847 }, function(error) {
14848 return handleCallback(error, false, callback);
14853 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native
14854 function simpleBind(context, fn) {
14855 return function(value) {
14856 fn.call(context, value);
14860 function processQueue(state) {
14861 var fn, deferred, pending;
14863 pending = state.pending;
14864 state.processScheduled = false;
14865 state.pending = undefined;
14866 for (var i = 0, ii = pending.length; i < ii; ++i) {
14867 deferred = pending[i][0];
14868 fn = pending[i][state.status];
14870 if (isFunction(fn)) {
14871 deferred.resolve(fn(state.value));
14872 } else if (state.status === 1) {
14873 deferred.resolve(state.value);
14875 deferred.reject(state.value);
14878 deferred.reject(e);
14879 exceptionHandler(e);
14884 function scheduleProcessQueue(state) {
14885 if (state.processScheduled || !state.pending) return;
14886 state.processScheduled = true;
14887 nextTick(function() { processQueue(state); });
14890 function Deferred() {
14891 this.promise = new Promise();
14892 //Necessary to support unbound execution :/
14893 this.resolve = simpleBind(this, this.resolve);
14894 this.reject = simpleBind(this, this.reject);
14895 this.notify = simpleBind(this, this.notify);
14898 extend(Deferred.prototype, {
14899 resolve: function(val) {
14900 if (this.promise.$$state.status) return;
14901 if (val === this.promise) {
14902 this.$$reject($qMinErr(
14904 "Expected promise to be resolved with value other than itself '{0}'",
14907 this.$$resolve(val);
14912 $$resolve: function(val) {
14915 fns = callOnce(this, this.$$resolve, this.$$reject);
14917 if ((isObject(val) || isFunction(val))) then = val && val.then;
14918 if (isFunction(then)) {
14919 this.promise.$$state.status = -1;
14920 then.call(val, fns[0], fns[1], this.notify);
14922 this.promise.$$state.value = val;
14923 this.promise.$$state.status = 1;
14924 scheduleProcessQueue(this.promise.$$state);
14928 exceptionHandler(e);
14932 reject: function(reason) {
14933 if (this.promise.$$state.status) return;
14934 this.$$reject(reason);
14937 $$reject: function(reason) {
14938 this.promise.$$state.value = reason;
14939 this.promise.$$state.status = 2;
14940 scheduleProcessQueue(this.promise.$$state);
14943 notify: function(progress) {
14944 var callbacks = this.promise.$$state.pending;
14946 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) {
14947 nextTick(function() {
14948 var callback, result;
14949 for (var i = 0, ii = callbacks.length; i < ii; i++) {
14950 result = callbacks[i][0];
14951 callback = callbacks[i][3];
14953 result.notify(isFunction(callback) ? callback(progress) : progress);
14955 exceptionHandler(e);
14969 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
14970 * used to forward rejection in a chain of promises. If you are dealing with the last promise in
14971 * a promise chain, you don't need to worry about it.
14973 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
14974 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
14975 * a promise error callback and you want to forward the error to the promise derived from the
14976 * current promise, you have to "rethrow" the error by returning a rejection constructed via
14980 * promiseB = promiseA.then(function(result) {
14981 * // success: do something and resolve promiseB
14982 * // with the old or a new result
14984 * }, function(reason) {
14985 * // error: handle the error if possible and
14986 * // resolve promiseB with newPromiseOrValue,
14987 * // otherwise forward the rejection to promiseB
14988 * if (canHandle(reason)) {
14989 * // handle the error and recover
14990 * return newPromiseOrValue;
14992 * return $q.reject(reason);
14996 * @param {*} reason Constant, message, exception or an object representing the rejection reason.
14997 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
14999 var reject = function(reason) {
15000 var result = new Deferred();
15001 result.reject(reason);
15002 return result.promise;
15005 var makePromise = function makePromise(value, resolved) {
15006 var result = new Deferred();
15008 result.resolve(value);
15010 result.reject(value);
15012 return result.promise;
15015 var handleCallback = function handleCallback(value, isResolved, callback) {
15016 var callbackOutput = null;
15018 if (isFunction(callback)) callbackOutput = callback();
15020 return makePromise(e, false);
15022 if (isPromiseLike(callbackOutput)) {
15023 return callbackOutput.then(function() {
15024 return makePromise(value, isResolved);
15025 }, function(error) {
15026 return makePromise(error, false);
15029 return makePromise(value, isResolved);
15039 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
15040 * This is useful when you are dealing with an object that might or might not be a promise, or if
15041 * the promise comes from a source that can't be trusted.
15043 * @param {*} value Value or a promise
15044 * @param {Function=} successCallback
15045 * @param {Function=} errorCallback
15046 * @param {Function=} progressCallback
15047 * @returns {Promise} Returns a promise of the passed value or promise
15051 var when = function(value, callback, errback, progressBack) {
15052 var result = new Deferred();
15053 result.resolve(value);
15054 return result.promise.then(callback, errback, progressBack);
15063 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
15065 * @param {*} value Value or a promise
15066 * @param {Function=} successCallback
15067 * @param {Function=} errorCallback
15068 * @param {Function=} progressCallback
15069 * @returns {Promise} Returns a promise of the passed value or promise
15071 var resolve = when;
15079 * Combines multiple promises into a single promise that is resolved when all of the input
15080 * promises are resolved.
15082 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
15083 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
15084 * each value corresponding to the promise at the same index/key in the `promises` array/hash.
15085 * If any of the promises is resolved with a rejection, this resulting promise will be rejected
15086 * with the same rejection value.
15089 function all(promises) {
15090 var deferred = new Deferred(),
15092 results = isArray(promises) ? [] : {};
15094 forEach(promises, function(promise, key) {
15096 when(promise).then(function(value) {
15097 if (results.hasOwnProperty(key)) return;
15098 results[key] = value;
15099 if (!(--counter)) deferred.resolve(results);
15100 }, function(reason) {
15101 if (results.hasOwnProperty(key)) return;
15102 deferred.reject(reason);
15106 if (counter === 0) {
15107 deferred.resolve(results);
15110 return deferred.promise;
15113 var $Q = function Q(resolver) {
15114 if (!isFunction(resolver)) {
15115 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
15118 if (!(this instanceof Q)) {
15119 // More useful when $Q is the Promise itself.
15120 return new Q(resolver);
15123 var deferred = new Deferred();
15125 function resolveFn(value) {
15126 deferred.resolve(value);
15129 function rejectFn(reason) {
15130 deferred.reject(reason);
15133 resolver(resolveFn, rejectFn);
15135 return deferred.promise;
15139 $Q.reject = reject;
15141 $Q.resolve = resolve;
15147 function $$RAFProvider() { //rAF
15148 this.$get = ['$window', '$timeout', function($window, $timeout) {
15149 var requestAnimationFrame = $window.requestAnimationFrame ||
15150 $window.webkitRequestAnimationFrame;
15152 var cancelAnimationFrame = $window.cancelAnimationFrame ||
15153 $window.webkitCancelAnimationFrame ||
15154 $window.webkitCancelRequestAnimationFrame;
15156 var rafSupported = !!requestAnimationFrame;
15157 var raf = rafSupported
15159 var id = requestAnimationFrame(fn);
15160 return function() {
15161 cancelAnimationFrame(id);
15165 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
15166 return function() {
15167 $timeout.cancel(timer);
15171 raf.supported = rafSupported;
15180 * The design decisions behind the scope are heavily favored for speed and memory consumption.
15182 * The typical use of scope is to watch the expressions, which most of the time return the same
15183 * value as last time so we optimize the operation.
15185 * Closures construction is expensive in terms of speed as well as memory:
15186 * - No closures, instead use prototypical inheritance for API
15187 * - Internal state needs to be stored on scope directly, which means that private state is
15188 * exposed as $$____ properties
15190 * Loop operations are optimized by using while(count--) { ... }
15191 * - This means that in order to keep the same order of execution as addition we have to add
15192 * items to the array at the beginning (unshift) instead of at the end (push)
15194 * Child scopes are created and removed often
15195 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
15197 * There are fewer watches than observers. This is why you don't want the observer to be implemented
15198 * in the same way as watch. Watch requires return of the initialization function which is expensive
15205 * @name $rootScopeProvider
15208 * Provider for the $rootScope service.
15213 * @name $rootScopeProvider#digestTtl
15216 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
15217 * assuming that the model is unstable.
15219 * The current default is 10 iterations.
15221 * In complex applications it's possible that the dependencies between `$watch`s will result in
15222 * several digest iterations. However if an application needs more than the default 10 digest
15223 * iterations for its model to stabilize then you should investigate what is causing the model to
15224 * continuously change during the digest.
15226 * Increasing the TTL could have performance implications, so you should not change it without
15227 * proper justification.
15229 * @param {number} limit The number of digest iterations.
15238 * Every application has a single root {@link ng.$rootScope.Scope scope}.
15239 * All other scopes are descendant scopes of the root scope. Scopes provide separation
15240 * between the model and the view, via a mechanism for watching the model for changes.
15241 * They also provide event emission/broadcast and subscription facility. See the
15242 * {@link guide/scope developer guide on scopes}.
15244 function $RootScopeProvider() {
15246 var $rootScopeMinErr = minErr('$rootScope');
15247 var lastDirtyWatch = null;
15248 var applyAsyncId = null;
15250 this.digestTtl = function(value) {
15251 if (arguments.length) {
15257 function createChildScopeClass(parent) {
15258 function ChildScope() {
15259 this.$$watchers = this.$$nextSibling =
15260 this.$$childHead = this.$$childTail = null;
15261 this.$$listeners = {};
15262 this.$$listenerCount = {};
15263 this.$$watchersCount = 0;
15264 this.$id = nextUid();
15265 this.$$ChildScope = null;
15267 ChildScope.prototype = parent;
15271 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
15272 function($injector, $exceptionHandler, $parse, $browser) {
15274 function destroyChildScope($event) {
15275 $event.currentScope.$$destroyed = true;
15278 function cleanUpScope($scope) {
15281 // There is a memory leak in IE9 if all child scopes are not disconnected
15282 // completely when a scope is destroyed. So this code will recurse up through
15283 // all this scopes children
15285 // See issue https://github.com/angular/angular.js/issues/10706
15286 $scope.$$childHead && cleanUpScope($scope.$$childHead);
15287 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling);
15290 // The code below works around IE9 and V8's memory leaks
15293 // - https://code.google.com/p/v8/issues/detail?id=2073#c26
15294 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
15295 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
15297 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
15298 $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
15303 * @name $rootScope.Scope
15306 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
15307 * {@link auto.$injector $injector}. Child scopes are created using the
15308 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
15309 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
15310 * an in-depth introduction and usage examples.
15314 * A scope can inherit from a parent scope, as in this example:
15316 var parent = $rootScope;
15317 var child = parent.$new();
15319 parent.salutation = "Hello";
15320 expect(child.salutation).toEqual('Hello');
15322 child.salutation = "Welcome";
15323 expect(child.salutation).toEqual('Welcome');
15324 expect(parent.salutation).toEqual('Hello');
15327 * When interacting with `Scope` in tests, additional helper methods are available on the
15328 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
15332 * @param {Object.<string, function()>=} providers Map of service factory which need to be
15333 * provided for the current scope. Defaults to {@link ng}.
15334 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
15335 * append/override services provided by `providers`. This is handy
15336 * when unit-testing and having the need to override a default
15338 * @returns {Object} Newly created scope.
15342 this.$id = nextUid();
15343 this.$$phase = this.$parent = this.$$watchers =
15344 this.$$nextSibling = this.$$prevSibling =
15345 this.$$childHead = this.$$childTail = null;
15347 this.$$destroyed = false;
15348 this.$$listeners = {};
15349 this.$$listenerCount = {};
15350 this.$$watchersCount = 0;
15351 this.$$isolateBindings = null;
15356 * @name $rootScope.Scope#$id
15359 * Unique scope ID (monotonically increasing) useful for debugging.
15364 * @name $rootScope.Scope#$parent
15367 * Reference to the parent scope.
15372 * @name $rootScope.Scope#$root
15375 * Reference to the root scope.
15378 Scope.prototype = {
15379 constructor: Scope,
15382 * @name $rootScope.Scope#$new
15386 * Creates a new child {@link ng.$rootScope.Scope scope}.
15388 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
15389 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
15391 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
15392 * desired for the scope and its child scopes to be permanently detached from the parent and
15393 * thus stop participating in model change detection and listener notification by invoking.
15395 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
15396 * parent scope. The scope is isolated, as it can not see parent scope properties.
15397 * When creating widgets, it is useful for the widget to not accidentally read parent
15400 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
15401 * of the newly created scope. Defaults to `this` scope if not provided.
15402 * This is used when creating a transclude scope to correctly place it
15403 * in the scope hierarchy while maintaining the correct prototypical
15406 * @returns {Object} The newly created child scope.
15409 $new: function(isolate, parent) {
15412 parent = parent || this;
15415 child = new Scope();
15416 child.$root = this.$root;
15418 // Only create a child scope class if somebody asks for one,
15419 // but cache it to allow the VM to optimize lookups.
15420 if (!this.$$ChildScope) {
15421 this.$$ChildScope = createChildScopeClass(this);
15423 child = new this.$$ChildScope();
15425 child.$parent = parent;
15426 child.$$prevSibling = parent.$$childTail;
15427 if (parent.$$childHead) {
15428 parent.$$childTail.$$nextSibling = child;
15429 parent.$$childTail = child;
15431 parent.$$childHead = parent.$$childTail = child;
15434 // When the new scope is not isolated or we inherit from `this`, and
15435 // the parent scope is destroyed, the property `$$destroyed` is inherited
15436 // prototypically. In all other cases, this property needs to be set
15437 // when the parent scope is destroyed.
15438 // The listener needs to be added after the parent is set
15439 if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
15446 * @name $rootScope.Scope#$watch
15450 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
15452 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
15453 * $digest()} and should return the value that will be watched. (`watchExpression` should not change
15454 * its value when executed multiple times with the same input because it may be executed multiple
15455 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
15456 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).
15457 * - The `listener` is called only when the value from the current `watchExpression` and the
15458 * previous call to `watchExpression` are not equal (with the exception of the initial run,
15459 * see below). Inequality is determined according to reference inequality,
15460 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
15461 * via the `!==` Javascript operator, unless `objectEquality == true`
15463 * - When `objectEquality == true`, inequality of the `watchExpression` is determined
15464 * according to the {@link angular.equals} function. To save the value of the object for
15465 * later comparison, the {@link angular.copy} function is used. This therefore means that
15466 * watching complex objects will have adverse memory and performance implications.
15467 * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
15468 * This is achieved by rerunning the watchers until no changes are detected. The rerun
15469 * iteration limit is 10 to prevent an infinite loop deadlock.
15472 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
15473 * you can register a `watchExpression` function with no `listener`. (Be prepared for
15474 * multiple calls to your `watchExpression` because it will execute multiple times in a
15475 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
15477 * After a watcher is registered with the scope, the `listener` fn is called asynchronously
15478 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
15479 * watcher. In rare cases, this is undesirable because the listener is called when the result
15480 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
15481 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
15482 * listener was called due to initialization.
15488 // let's assume that scope was dependency injected as the $rootScope
15489 var scope = $rootScope;
15490 scope.name = 'misko';
15493 expect(scope.counter).toEqual(0);
15494 scope.$watch('name', function(newValue, oldValue) {
15495 scope.counter = scope.counter + 1;
15497 expect(scope.counter).toEqual(0);
15500 // the listener is always called during the first $digest loop after it was registered
15501 expect(scope.counter).toEqual(1);
15504 // but now it will not be called unless the value changes
15505 expect(scope.counter).toEqual(1);
15507 scope.name = 'adam';
15509 expect(scope.counter).toEqual(2);
15513 // Using a function as a watchExpression
15515 scope.foodCounter = 0;
15516 expect(scope.foodCounter).toEqual(0);
15518 // This function returns the value being watched. It is called for each turn of the $digest loop
15519 function() { return food; },
15520 // This is the change listener, called when the value returned from the above function changes
15521 function(newValue, oldValue) {
15522 if ( newValue !== oldValue ) {
15523 // Only increment the counter if the value changed
15524 scope.foodCounter = scope.foodCounter + 1;
15528 // No digest has been run so the counter will be zero
15529 expect(scope.foodCounter).toEqual(0);
15531 // Run the digest but since food has not changed count will still be zero
15533 expect(scope.foodCounter).toEqual(0);
15535 // Update food and run digest. Now the counter will increment
15536 food = 'cheeseburger';
15538 expect(scope.foodCounter).toEqual(1);
15544 * @param {(function()|string)} watchExpression Expression that is evaluated on each
15545 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
15546 * a call to the `listener`.
15548 * - `string`: Evaluated as {@link guide/expression expression}
15549 * - `function(scope)`: called with current `scope` as a parameter.
15550 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
15551 * of `watchExpression` changes.
15553 * - `newVal` contains the current value of the `watchExpression`
15554 * - `oldVal` contains the previous value of the `watchExpression`
15555 * - `scope` refers to the current scope
15556 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of
15557 * comparing for reference equality.
15558 * @returns {function()} Returns a deregistration function for this listener.
15560 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
15561 var get = $parse(watchExp);
15563 if (get.$$watchDelegate) {
15564 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
15567 array = scope.$$watchers,
15570 last: initWatchVal,
15572 exp: prettyPrintExpression || watchExp,
15573 eq: !!objectEquality
15576 lastDirtyWatch = null;
15578 if (!isFunction(listener)) {
15583 array = scope.$$watchers = [];
15585 // we use unshift since we use a while loop in $digest for speed.
15586 // the while loop reads in reverse order.
15587 array.unshift(watcher);
15588 incrementWatchersCount(this, 1);
15590 return function deregisterWatch() {
15591 if (arrayRemove(array, watcher) >= 0) {
15592 incrementWatchersCount(scope, -1);
15594 lastDirtyWatch = null;
15600 * @name $rootScope.Scope#$watchGroup
15604 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
15605 * If any one expression in the collection changes the `listener` is executed.
15607 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every
15608 * call to $digest() to see if any items changes.
15609 * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
15611 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
15612 * watched using {@link ng.$rootScope.Scope#$watch $watch()}
15614 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
15615 * expression in `watchExpressions` changes
15616 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
15617 * those of `watchExpression`
15618 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
15619 * those of `watchExpression`
15620 * The `scope` refers to the current scope.
15621 * @returns {function()} Returns a de-registration function for all listeners.
15623 $watchGroup: function(watchExpressions, listener) {
15624 var oldValues = new Array(watchExpressions.length);
15625 var newValues = new Array(watchExpressions.length);
15626 var deregisterFns = [];
15628 var changeReactionScheduled = false;
15629 var firstRun = true;
15631 if (!watchExpressions.length) {
15632 // No expressions means we call the listener ASAP
15633 var shouldCall = true;
15634 self.$evalAsync(function() {
15635 if (shouldCall) listener(newValues, newValues, self);
15637 return function deregisterWatchGroup() {
15638 shouldCall = false;
15642 if (watchExpressions.length === 1) {
15643 // Special case size of one
15644 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
15645 newValues[0] = value;
15646 oldValues[0] = oldValue;
15647 listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
15651 forEach(watchExpressions, function(expr, i) {
15652 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
15653 newValues[i] = value;
15654 oldValues[i] = oldValue;
15655 if (!changeReactionScheduled) {
15656 changeReactionScheduled = true;
15657 self.$evalAsync(watchGroupAction);
15660 deregisterFns.push(unwatchFn);
15663 function watchGroupAction() {
15664 changeReactionScheduled = false;
15668 listener(newValues, newValues, self);
15670 listener(newValues, oldValues, self);
15674 return function deregisterWatchGroup() {
15675 while (deregisterFns.length) {
15676 deregisterFns.shift()();
15684 * @name $rootScope.Scope#$watchCollection
15688 * Shallow watches the properties of an object and fires whenever any of the properties change
15689 * (for arrays, this implies watching the array items; for object maps, this implies watching
15690 * the properties). If a change is detected, the `listener` callback is fired.
15692 * - The `obj` collection is observed via standard $watch operation and is examined on every
15693 * call to $digest() to see if any items have been added, removed, or moved.
15694 * - The `listener` is called whenever anything within the `obj` has changed. Examples include
15695 * adding, removing, and moving items belonging to an object or array.
15700 $scope.names = ['igor', 'matias', 'misko', 'james'];
15701 $scope.dataCount = 4;
15703 $scope.$watchCollection('names', function(newNames, oldNames) {
15704 $scope.dataCount = newNames.length;
15707 expect($scope.dataCount).toEqual(4);
15710 //still at 4 ... no changes
15711 expect($scope.dataCount).toEqual(4);
15713 $scope.names.pop();
15716 //now there's been a change
15717 expect($scope.dataCount).toEqual(3);
15721 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
15722 * expression value should evaluate to an object or an array which is observed on each
15723 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
15724 * collection will trigger a call to the `listener`.
15726 * @param {function(newCollection, oldCollection, scope)} listener a callback function called
15727 * when a change is detected.
15728 * - The `newCollection` object is the newly modified data obtained from the `obj` expression
15729 * - The `oldCollection` object is a copy of the former collection data.
15730 * Due to performance considerations, the`oldCollection` value is computed only if the
15731 * `listener` function declares two or more arguments.
15732 * - The `scope` argument refers to the current scope.
15734 * @returns {function()} Returns a de-registration function for this listener. When the
15735 * de-registration function is executed, the internal watch operation is terminated.
15737 $watchCollection: function(obj, listener) {
15738 $watchCollectionInterceptor.$stateful = true;
15741 // the current value, updated on each dirty-check run
15743 // a shallow copy of the newValue from the last dirty-check run,
15744 // updated to match newValue during dirty-check run
15746 // a shallow copy of the newValue from when the last change happened
15748 // only track veryOldValue if the listener is asking for it
15749 var trackVeryOldValue = (listener.length > 1);
15750 var changeDetected = 0;
15751 var changeDetector = $parse(obj, $watchCollectionInterceptor);
15752 var internalArray = [];
15753 var internalObject = {};
15754 var initRun = true;
15757 function $watchCollectionInterceptor(_value) {
15759 var newLength, key, bothNaN, newItem, oldItem;
15761 // If the new value is undefined, then return undefined as the watch may be a one-time watch
15762 if (isUndefined(newValue)) return;
15764 if (!isObject(newValue)) { // if primitive
15765 if (oldValue !== newValue) {
15766 oldValue = newValue;
15769 } else if (isArrayLike(newValue)) {
15770 if (oldValue !== internalArray) {
15771 // we are transitioning from something which was not an array into array.
15772 oldValue = internalArray;
15773 oldLength = oldValue.length = 0;
15777 newLength = newValue.length;
15779 if (oldLength !== newLength) {
15780 // if lengths do not match we need to trigger change notification
15782 oldValue.length = oldLength = newLength;
15784 // copy the items to oldValue and look for changes.
15785 for (var i = 0; i < newLength; i++) {
15786 oldItem = oldValue[i];
15787 newItem = newValue[i];
15789 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15790 if (!bothNaN && (oldItem !== newItem)) {
15792 oldValue[i] = newItem;
15796 if (oldValue !== internalObject) {
15797 // we are transitioning from something which was not an object into object.
15798 oldValue = internalObject = {};
15802 // copy the items to oldValue and look for changes.
15804 for (key in newValue) {
15805 if (hasOwnProperty.call(newValue, key)) {
15807 newItem = newValue[key];
15808 oldItem = oldValue[key];
15810 if (key in oldValue) {
15811 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15812 if (!bothNaN && (oldItem !== newItem)) {
15814 oldValue[key] = newItem;
15818 oldValue[key] = newItem;
15823 if (oldLength > newLength) {
15824 // we used to have more keys, need to find them and destroy them.
15826 for (key in oldValue) {
15827 if (!hasOwnProperty.call(newValue, key)) {
15829 delete oldValue[key];
15834 return changeDetected;
15837 function $watchCollectionAction() {
15840 listener(newValue, newValue, self);
15842 listener(newValue, veryOldValue, self);
15845 // make a copy for the next time a collection is changed
15846 if (trackVeryOldValue) {
15847 if (!isObject(newValue)) {
15849 veryOldValue = newValue;
15850 } else if (isArrayLike(newValue)) {
15851 veryOldValue = new Array(newValue.length);
15852 for (var i = 0; i < newValue.length; i++) {
15853 veryOldValue[i] = newValue[i];
15855 } else { // if object
15857 for (var key in newValue) {
15858 if (hasOwnProperty.call(newValue, key)) {
15859 veryOldValue[key] = newValue[key];
15866 return this.$watch(changeDetector, $watchCollectionAction);
15871 * @name $rootScope.Scope#$digest
15875 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
15876 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
15877 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
15878 * until no more listeners are firing. This means that it is possible to get into an infinite
15879 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
15880 * iterations exceeds 10.
15882 * Usually, you don't call `$digest()` directly in
15883 * {@link ng.directive:ngController controllers} or in
15884 * {@link ng.$compileProvider#directive directives}.
15885 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
15886 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
15888 * If you want to be notified whenever `$digest()` is called,
15889 * you can register a `watchExpression` function with
15890 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
15892 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
15897 scope.name = 'misko';
15900 expect(scope.counter).toEqual(0);
15901 scope.$watch('name', function(newValue, oldValue) {
15902 scope.counter = scope.counter + 1;
15904 expect(scope.counter).toEqual(0);
15907 // the listener is always called during the first $digest loop after it was registered
15908 expect(scope.counter).toEqual(1);
15911 // but now it will not be called unless the value changes
15912 expect(scope.counter).toEqual(1);
15914 scope.name = 'adam';
15916 expect(scope.counter).toEqual(2);
15920 $digest: function() {
15921 var watch, value, last,
15925 next, current, target = this,
15927 logIdx, logMsg, asyncTask;
15929 beginPhase('$digest');
15930 // Check for changes to browser url that happened in sync before the call to $digest
15931 $browser.$$checkUrlChange();
15933 if (this === $rootScope && applyAsyncId !== null) {
15934 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
15935 // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
15936 $browser.defer.cancel(applyAsyncId);
15940 lastDirtyWatch = null;
15942 do { // "while dirty" loop
15946 while (asyncQueue.length) {
15948 asyncTask = asyncQueue.shift();
15949 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
15951 $exceptionHandler(e);
15953 lastDirtyWatch = null;
15956 traverseScopesLoop:
15957 do { // "traverse the scopes" loop
15958 if ((watchers = current.$$watchers)) {
15959 // process our watches
15960 length = watchers.length;
15963 watch = watchers[length];
15964 // Most common watches are on primitives, in which case we can short
15965 // circuit it with === operator, only when === fails do we use .equals
15967 if ((value = watch.get(current)) !== (last = watch.last) &&
15969 ? equals(value, last)
15970 : (typeof value === 'number' && typeof last === 'number'
15971 && isNaN(value) && isNaN(last)))) {
15973 lastDirtyWatch = watch;
15974 watch.last = watch.eq ? copy(value, null) : value;
15975 watch.fn(value, ((last === initWatchVal) ? value : last), current);
15978 if (!watchLog[logIdx]) watchLog[logIdx] = [];
15979 watchLog[logIdx].push({
15980 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
15985 } else if (watch === lastDirtyWatch) {
15986 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
15987 // have already been tested.
15989 break traverseScopesLoop;
15993 $exceptionHandler(e);
15998 // Insanity Warning: scope depth-first traversal
15999 // yes, this code is a bit crazy, but it works and we have tests to prove it!
16000 // this piece should be kept in sync with the traversal in $broadcast
16001 if (!(next = ((current.$$watchersCount && current.$$childHead) ||
16002 (current !== target && current.$$nextSibling)))) {
16003 while (current !== target && !(next = current.$$nextSibling)) {
16004 current = current.$parent;
16007 } while ((current = next));
16009 // `break traverseScopesLoop;` takes us to here
16011 if ((dirty || asyncQueue.length) && !(ttl--)) {
16013 throw $rootScopeMinErr('infdig',
16014 '{0} $digest() iterations reached. Aborting!\n' +
16015 'Watchers fired in the last 5 iterations: {1}',
16019 } while (dirty || asyncQueue.length);
16023 while (postDigestQueue.length) {
16025 postDigestQueue.shift()();
16027 $exceptionHandler(e);
16035 * @name $rootScope.Scope#$destroy
16036 * @eventType broadcast on scope being destroyed
16039 * Broadcasted when a scope and its children are being destroyed.
16041 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16042 * clean up DOM bindings before an element is removed from the DOM.
16047 * @name $rootScope.Scope#$destroy
16051 * Removes the current scope (and all of its children) from the parent scope. Removal implies
16052 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
16053 * propagate to the current scope and its children. Removal also implies that the current
16054 * scope is eligible for garbage collection.
16056 * The `$destroy()` is usually used by directives such as
16057 * {@link ng.directive:ngRepeat ngRepeat} for managing the
16058 * unrolling of the loop.
16060 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
16061 * Application code can register a `$destroy` event handler that will give it a chance to
16062 * perform any necessary cleanup.
16064 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16065 * clean up DOM bindings before an element is removed from the DOM.
16067 $destroy: function() {
16068 // We can't destroy a scope that has been already destroyed.
16069 if (this.$$destroyed) return;
16070 var parent = this.$parent;
16072 this.$broadcast('$destroy');
16073 this.$$destroyed = true;
16075 if (this === $rootScope) {
16076 //Remove handlers attached to window when $rootScope is removed
16077 $browser.$$applicationDestroyed();
16080 incrementWatchersCount(this, -this.$$watchersCount);
16081 for (var eventName in this.$$listenerCount) {
16082 decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
16085 // sever all the references to parent scopes (after this cleanup, the current scope should
16086 // not be retained by any of our references and should be eligible for garbage collection)
16087 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
16088 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
16089 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
16090 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
16092 // Disable listeners, watchers and apply/digest methods
16093 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
16094 this.$on = this.$watch = this.$watchGroup = function() { return noop; };
16095 this.$$listeners = {};
16097 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
16098 this.$$nextSibling = null;
16099 cleanUpScope(this);
16104 * @name $rootScope.Scope#$eval
16108 * Executes the `expression` on the current scope and returns the result. Any exceptions in
16109 * the expression are propagated (uncaught). This is useful when evaluating Angular
16114 var scope = ng.$rootScope.Scope();
16118 expect(scope.$eval('a+b')).toEqual(3);
16119 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
16122 * @param {(string|function())=} expression An angular expression to be executed.
16124 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16125 * - `function(scope)`: execute the function with the current `scope` parameter.
16127 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16128 * @returns {*} The result of evaluating the expression.
16130 $eval: function(expr, locals) {
16131 return $parse(expr)(this, locals);
16136 * @name $rootScope.Scope#$evalAsync
16140 * Executes the expression on the current scope at a later point in time.
16142 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
16145 * - it will execute after the function that scheduled the evaluation (preferably before DOM
16147 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
16148 * `expression` execution.
16150 * Any exceptions from the execution of the expression are forwarded to the
16151 * {@link ng.$exceptionHandler $exceptionHandler} service.
16153 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
16154 * will be scheduled. However, it is encouraged to always call code that changes the model
16155 * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
16157 * @param {(string|function())=} expression An angular expression to be executed.
16159 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16160 * - `function(scope)`: execute the function with the current `scope` parameter.
16162 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16164 $evalAsync: function(expr, locals) {
16165 // if we are outside of an $digest loop and this is the first time we are scheduling async
16166 // task also schedule async auto-flush
16167 if (!$rootScope.$$phase && !asyncQueue.length) {
16168 $browser.defer(function() {
16169 if (asyncQueue.length) {
16170 $rootScope.$digest();
16175 asyncQueue.push({scope: this, expression: expr, locals: locals});
16178 $$postDigest: function(fn) {
16179 postDigestQueue.push(fn);
16184 * @name $rootScope.Scope#$apply
16188 * `$apply()` is used to execute an expression in angular from outside of the angular
16189 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
16190 * Because we are calling into the angular framework we need to perform proper scope life
16191 * cycle of {@link ng.$exceptionHandler exception handling},
16192 * {@link ng.$rootScope.Scope#$digest executing watches}.
16196 * # Pseudo-Code of `$apply()`
16198 function $apply(expr) {
16200 return $eval(expr);
16202 $exceptionHandler(e);
16210 * Scope's `$apply()` method transitions through the following stages:
16212 * 1. The {@link guide/expression expression} is executed using the
16213 * {@link ng.$rootScope.Scope#$eval $eval()} method.
16214 * 2. Any exceptions from the execution of the expression are forwarded to the
16215 * {@link ng.$exceptionHandler $exceptionHandler} service.
16216 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
16217 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
16220 * @param {(string|function())=} exp An angular expression to be executed.
16222 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16223 * - `function(scope)`: execute the function with current `scope` parameter.
16225 * @returns {*} The result of evaluating the expression.
16227 $apply: function(expr) {
16229 beginPhase('$apply');
16231 return this.$eval(expr);
16236 $exceptionHandler(e);
16239 $rootScope.$digest();
16241 $exceptionHandler(e);
16249 * @name $rootScope.Scope#$applyAsync
16253 * Schedule the invocation of $apply to occur at a later time. The actual time difference
16254 * varies across browsers, but is typically around ~10 milliseconds.
16256 * This can be used to queue up multiple expressions which need to be evaluated in the same
16259 * @param {(string|function())=} exp An angular expression to be executed.
16261 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16262 * - `function(scope)`: execute the function with current `scope` parameter.
16264 $applyAsync: function(expr) {
16266 expr && applyAsyncQueue.push($applyAsyncExpression);
16267 scheduleApplyAsync();
16269 function $applyAsyncExpression() {
16276 * @name $rootScope.Scope#$on
16280 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
16281 * discussion of event life cycle.
16283 * The event listener function format is: `function(event, args...)`. The `event` object
16284 * passed into the listener has the following attributes:
16286 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
16288 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
16289 * event propagates through the scope hierarchy, this property is set to null.
16290 * - `name` - `{string}`: name of the event.
16291 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
16292 * further event propagation (available only for events that were `$emit`-ed).
16293 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
16295 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
16297 * @param {string} name Event name to listen on.
16298 * @param {function(event, ...args)} listener Function to call when the event is emitted.
16299 * @returns {function()} Returns a deregistration function for this listener.
16301 $on: function(name, listener) {
16302 var namedListeners = this.$$listeners[name];
16303 if (!namedListeners) {
16304 this.$$listeners[name] = namedListeners = [];
16306 namedListeners.push(listener);
16308 var current = this;
16310 if (!current.$$listenerCount[name]) {
16311 current.$$listenerCount[name] = 0;
16313 current.$$listenerCount[name]++;
16314 } while ((current = current.$parent));
16317 return function() {
16318 var indexOfListener = namedListeners.indexOf(listener);
16319 if (indexOfListener !== -1) {
16320 namedListeners[indexOfListener] = null;
16321 decrementListenerCount(self, 1, name);
16329 * @name $rootScope.Scope#$emit
16333 * Dispatches an event `name` upwards through the scope hierarchy notifying the
16334 * registered {@link ng.$rootScope.Scope#$on} listeners.
16336 * The event life cycle starts at the scope on which `$emit` was called. All
16337 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16338 * notified. Afterwards, the event traverses upwards toward the root scope and calls all
16339 * registered listeners along the way. The event will stop propagating if one of the listeners
16342 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16343 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16345 * @param {string} name Event name to emit.
16346 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16347 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
16349 $emit: function(name, args) {
16353 stopPropagation = false,
16356 targetScope: scope,
16357 stopPropagation: function() {stopPropagation = true;},
16358 preventDefault: function() {
16359 event.defaultPrevented = true;
16361 defaultPrevented: false
16363 listenerArgs = concat([event], arguments, 1),
16367 namedListeners = scope.$$listeners[name] || empty;
16368 event.currentScope = scope;
16369 for (i = 0, length = namedListeners.length; i < length; i++) {
16371 // if listeners were deregistered, defragment the array
16372 if (!namedListeners[i]) {
16373 namedListeners.splice(i, 1);
16379 //allow all listeners attached to the current scope to run
16380 namedListeners[i].apply(null, listenerArgs);
16382 $exceptionHandler(e);
16385 //if any listener on the current scope stops propagation, prevent bubbling
16386 if (stopPropagation) {
16387 event.currentScope = null;
16391 scope = scope.$parent;
16394 event.currentScope = null;
16402 * @name $rootScope.Scope#$broadcast
16406 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
16407 * registered {@link ng.$rootScope.Scope#$on} listeners.
16409 * The event life cycle starts at the scope on which `$broadcast` was called. All
16410 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16411 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
16412 * scope and calls all registered listeners along the way. The event cannot be canceled.
16414 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16415 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16417 * @param {string} name Event name to broadcast.
16418 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16419 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
16421 $broadcast: function(name, args) {
16427 targetScope: target,
16428 preventDefault: function() {
16429 event.defaultPrevented = true;
16431 defaultPrevented: false
16434 if (!target.$$listenerCount[name]) return event;
16436 var listenerArgs = concat([event], arguments, 1),
16437 listeners, i, length;
16439 //down while you can, then up and next sibling or up and next sibling until back at root
16440 while ((current = next)) {
16441 event.currentScope = current;
16442 listeners = current.$$listeners[name] || [];
16443 for (i = 0, length = listeners.length; i < length; i++) {
16444 // if listeners were deregistered, defragment the array
16445 if (!listeners[i]) {
16446 listeners.splice(i, 1);
16453 listeners[i].apply(null, listenerArgs);
16455 $exceptionHandler(e);
16459 // Insanity Warning: scope depth-first traversal
16460 // yes, this code is a bit crazy, but it works and we have tests to prove it!
16461 // this piece should be kept in sync with the traversal in $digest
16462 // (though it differs due to having the extra check for $$listenerCount)
16463 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
16464 (current !== target && current.$$nextSibling)))) {
16465 while (current !== target && !(next = current.$$nextSibling)) {
16466 current = current.$parent;
16471 event.currentScope = null;
16476 var $rootScope = new Scope();
16478 //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
16479 var asyncQueue = $rootScope.$$asyncQueue = [];
16480 var postDigestQueue = $rootScope.$$postDigestQueue = [];
16481 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
16486 function beginPhase(phase) {
16487 if ($rootScope.$$phase) {
16488 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
16491 $rootScope.$$phase = phase;
16494 function clearPhase() {
16495 $rootScope.$$phase = null;
16498 function incrementWatchersCount(current, count) {
16500 current.$$watchersCount += count;
16501 } while ((current = current.$parent));
16504 function decrementListenerCount(current, count, name) {
16506 current.$$listenerCount[name] -= count;
16508 if (current.$$listenerCount[name] === 0) {
16509 delete current.$$listenerCount[name];
16511 } while ((current = current.$parent));
16515 * function used as an initial value for watchers.
16516 * because it's unique we can easily tell it apart from other values
16518 function initWatchVal() {}
16520 function flushApplyAsync() {
16521 while (applyAsyncQueue.length) {
16523 applyAsyncQueue.shift()();
16525 $exceptionHandler(e);
16528 applyAsyncId = null;
16531 function scheduleApplyAsync() {
16532 if (applyAsyncId === null) {
16533 applyAsyncId = $browser.defer(function() {
16534 $rootScope.$apply(flushApplyAsync);
16543 * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
16545 function $$SanitizeUriProvider() {
16546 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
16547 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
16551 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16552 * urls during a[href] sanitization.
16554 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16556 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
16557 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
16558 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16559 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16561 * @param {RegExp=} regexp New regexp to whitelist urls with.
16562 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16563 * chaining otherwise.
16565 this.aHrefSanitizationWhitelist = function(regexp) {
16566 if (isDefined(regexp)) {
16567 aHrefSanitizationWhitelist = regexp;
16570 return aHrefSanitizationWhitelist;
16576 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16577 * urls during img[src] sanitization.
16579 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16581 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
16582 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
16583 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16584 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16586 * @param {RegExp=} regexp New regexp to whitelist urls with.
16587 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16588 * chaining otherwise.
16590 this.imgSrcSanitizationWhitelist = function(regexp) {
16591 if (isDefined(regexp)) {
16592 imgSrcSanitizationWhitelist = regexp;
16595 return imgSrcSanitizationWhitelist;
16598 this.$get = function() {
16599 return function sanitizeUri(uri, isImage) {
16600 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
16602 normalizedVal = urlResolve(uri).href;
16603 if (normalizedVal !== '' && !normalizedVal.match(regex)) {
16604 return 'unsafe:' + normalizedVal;
16611 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16612 * Any commits to this file should be reviewed with security in mind. *
16613 * Changes to this file can potentially create security vulnerabilities. *
16614 * An approval from 2 Core members with history of modifying *
16615 * this file is required. *
16617 * Does the change somehow allow for arbitrary javascript to be executed? *
16618 * Or allows for someone to change the prototype of built-in objects? *
16619 * Or gives undesired access to variables likes document or window? *
16620 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16622 var $sceMinErr = minErr('$sce');
16624 var SCE_CONTEXTS = {
16628 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a
16629 // url. (e.g. ng-include, script src, templateUrl)
16630 RESOURCE_URL: 'resourceUrl',
16634 // Helper functions follow.
16636 function adjustMatcher(matcher) {
16637 if (matcher === 'self') {
16639 } else if (isString(matcher)) {
16640 // Strings match exactly except for 2 wildcards - '*' and '**'.
16641 // '*' matches any character except those from the set ':/.?&'.
16642 // '**' matches any character (like .* in a RegExp).
16643 // More than 2 *'s raises an error as it's ill defined.
16644 if (matcher.indexOf('***') > -1) {
16645 throw $sceMinErr('iwcard',
16646 'Illegal sequence *** in string matcher. String: {0}', matcher);
16648 matcher = escapeForRegexp(matcher).
16649 replace('\\*\\*', '.*').
16650 replace('\\*', '[^:/.?&;]*');
16651 return new RegExp('^' + matcher + '$');
16652 } else if (isRegExp(matcher)) {
16653 // The only other type of matcher allowed is a Regexp.
16654 // Match entire URL / disallow partial matches.
16655 // Flags are reset (i.e. no global, ignoreCase or multiline)
16656 return new RegExp('^' + matcher.source + '$');
16658 throw $sceMinErr('imatcher',
16659 'Matchers may only be "self", string patterns or RegExp objects');
16664 function adjustMatchers(matchers) {
16665 var adjustedMatchers = [];
16666 if (isDefined(matchers)) {
16667 forEach(matchers, function(matcher) {
16668 adjustedMatchers.push(adjustMatcher(matcher));
16671 return adjustedMatchers;
16677 * @name $sceDelegate
16682 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
16683 * Contextual Escaping (SCE)} services to AngularJS.
16685 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
16686 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
16687 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
16688 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
16689 * work because `$sce` delegates to `$sceDelegate` for these operations.
16691 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
16693 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
16694 * can override it completely to change the behavior of `$sce`, the common case would
16695 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
16696 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
16697 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
16698 * $sceDelegateProvider.resourceUrlWhitelist} and {@link
16699 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16704 * @name $sceDelegateProvider
16707 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
16708 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure
16709 * that the URLs used for sourcing Angular templates are safe. Refer {@link
16710 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and
16711 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16713 * For the general details about this service in Angular, read the main page for {@link ng.$sce
16714 * Strict Contextual Escaping (SCE)}.
16716 * **Example**: Consider the following case. <a name="example"></a>
16718 * - your app is hosted at url `http://myapp.example.com/`
16719 * - but some of your templates are hosted on other domains you control such as
16720 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc.
16721 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
16723 * Here is what a secure configuration for this scenario might look like:
16726 * angular.module('myApp', []).config(function($sceDelegateProvider) {
16727 * $sceDelegateProvider.resourceUrlWhitelist([
16728 * // Allow same origin resource loads.
16730 * // Allow loading from our assets domain. Notice the difference between * and **.
16731 * 'http://srv*.assets.example.com/**'
16734 * // The blacklist overrides the whitelist so the open redirect here is blocked.
16735 * $sceDelegateProvider.resourceUrlBlacklist([
16736 * 'http://myapp.example.com/clickThru**'
16742 function $SceDelegateProvider() {
16743 this.SCE_CONTEXTS = SCE_CONTEXTS;
16745 // Resource URLs can also be trusted by policy.
16746 var resourceUrlWhitelist = ['self'],
16747 resourceUrlBlacklist = [];
16751 * @name $sceDelegateProvider#resourceUrlWhitelist
16754 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
16755 * provided. This must be an array or null. A snapshot of this array is used so further
16756 * changes to the array are ignored.
16758 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16759 * allowed in this array.
16761 * Note: **an empty whitelist array will block all URLs**!
16763 * @return {Array} the currently set whitelist array.
16765 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only
16766 * same origin resource requests.
16769 * Sets/Gets the whitelist of trusted resource URLs.
16771 this.resourceUrlWhitelist = function(value) {
16772 if (arguments.length) {
16773 resourceUrlWhitelist = adjustMatchers(value);
16775 return resourceUrlWhitelist;
16780 * @name $sceDelegateProvider#resourceUrlBlacklist
16783 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
16784 * provided. This must be an array or null. A snapshot of this array is used so further
16785 * changes to the array are ignored.
16787 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16788 * allowed in this array.
16790 * The typical usage for the blacklist is to **block
16791 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
16792 * these would otherwise be trusted but actually return content from the redirected domain.
16794 * Finally, **the blacklist overrides the whitelist** and has the final say.
16796 * @return {Array} the currently set blacklist array.
16798 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
16799 * is no blacklist.)
16802 * Sets/Gets the blacklist of trusted resource URLs.
16805 this.resourceUrlBlacklist = function(value) {
16806 if (arguments.length) {
16807 resourceUrlBlacklist = adjustMatchers(value);
16809 return resourceUrlBlacklist;
16812 this.$get = ['$injector', function($injector) {
16814 var htmlSanitizer = function htmlSanitizer(html) {
16815 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16818 if ($injector.has('$sanitize')) {
16819 htmlSanitizer = $injector.get('$sanitize');
16823 function matchUrl(matcher, parsedUrl) {
16824 if (matcher === 'self') {
16825 return urlIsSameOrigin(parsedUrl);
16827 // definitely a regex. See adjustMatchers()
16828 return !!matcher.exec(parsedUrl.href);
16832 function isResourceUrlAllowedByPolicy(url) {
16833 var parsedUrl = urlResolve(url.toString());
16834 var i, n, allowed = false;
16835 // Ensure that at least one item from the whitelist allows this url.
16836 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) {
16837 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) {
16843 // Ensure that no item from the blacklist blocked this url.
16844 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) {
16845 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) {
16854 function generateHolderType(Base) {
16855 var holderType = function TrustedValueHolderType(trustedValue) {
16856 this.$$unwrapTrustedValue = function() {
16857 return trustedValue;
16861 holderType.prototype = new Base();
16863 holderType.prototype.valueOf = function sceValueOf() {
16864 return this.$$unwrapTrustedValue();
16866 holderType.prototype.toString = function sceToString() {
16867 return this.$$unwrapTrustedValue().toString();
16872 var trustedValueHolderBase = generateHolderType(),
16875 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
16876 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
16877 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
16878 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
16879 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
16883 * @name $sceDelegate#trustAs
16886 * Returns an object that is trusted by angular for use in specified strict
16887 * contextual escaping contexts (such as ng-bind-html, ng-include, any src
16888 * attribute interpolation, any dom event binding attribute interpolation
16889 * such as for onclick, etc.) that uses the provided value.
16890 * See {@link ng.$sce $sce} for enabling strict contextual escaping.
16892 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
16893 * resourceUrl, html, js and css.
16894 * @param {*} value The value that that should be considered trusted/safe.
16895 * @returns {*} A value that can be used to stand in for the provided `value` in places
16896 * where Angular expects a $sce.trustAs() return value.
16898 function trustAs(type, trustedValue) {
16899 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16900 if (!Constructor) {
16901 throw $sceMinErr('icontext',
16902 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
16903 type, trustedValue);
16905 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
16906 return trustedValue;
16908 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
16909 // mutable objects, we ensure here that the value passed in is actually a string.
16910 if (typeof trustedValue !== 'string') {
16911 throw $sceMinErr('itype',
16912 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
16915 return new Constructor(trustedValue);
16920 * @name $sceDelegate#valueOf
16923 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
16924 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
16925 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
16927 * If the passed parameter is not a value that had been returned by {@link
16928 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is.
16930 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
16931 * call or anything else.
16932 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
16933 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
16934 * `value` unchanged.
16936 function valueOf(maybeTrusted) {
16937 if (maybeTrusted instanceof trustedValueHolderBase) {
16938 return maybeTrusted.$$unwrapTrustedValue();
16940 return maybeTrusted;
16946 * @name $sceDelegate#getTrusted
16949 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and
16950 * returns the originally supplied value if the queried context type is a supertype of the
16951 * created type. If this condition isn't satisfied, throws an exception.
16953 * @param {string} type The kind of context in which this value is to be used.
16954 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
16955 * `$sceDelegate.trustAs`} call.
16956 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs
16957 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
16959 function getTrusted(type, maybeTrusted) {
16960 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
16961 return maybeTrusted;
16963 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16964 if (constructor && maybeTrusted instanceof constructor) {
16965 return maybeTrusted.$$unwrapTrustedValue();
16967 // If we get here, then we may only take one of two actions.
16968 // 1. sanitize the value for the requested type, or
16969 // 2. throw an exception.
16970 if (type === SCE_CONTEXTS.RESOURCE_URL) {
16971 if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
16972 return maybeTrusted;
16974 throw $sceMinErr('insecurl',
16975 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
16976 maybeTrusted.toString());
16978 } else if (type === SCE_CONTEXTS.HTML) {
16979 return htmlSanitizer(maybeTrusted);
16981 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16984 return { trustAs: trustAs,
16985 getTrusted: getTrusted,
16986 valueOf: valueOf };
16993 * @name $sceProvider
16996 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
16997 * - enable/disable Strict Contextual Escaping (SCE) in a module
16998 * - override the default implementation with a custom delegate
17000 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
17003 /* jshint maxlen: false*/
17012 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
17014 * # Strict Contextual Escaping
17016 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain
17017 * contexts to result in a value that is marked as safe to use for that context. One example of
17018 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer
17019 * to these contexts as privileged or SCE contexts.
17021 * As of version 1.2, Angular ships with SCE enabled by default.
17023 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow
17024 * one to execute arbitrary javascript by the use of the expression() syntax. Refer
17025 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
17026 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>`
17027 * to the top of your HTML document.
17029 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for
17030 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
17032 * Here's an example of a binding in a privileged context:
17035 * <input ng-model="userHtml" aria-label="User input">
17036 * <div ng-bind-html="userHtml"></div>
17039 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
17040 * disabled, this application allows the user to render arbitrary HTML into the DIV.
17041 * In a more realistic example, one may be rendering user comments, blog articles, etc. via
17042 * bindings. (HTML is just one example of a context where rendering user controlled input creates
17043 * security vulnerabilities.)
17045 * For the case of HTML, you might use a library, either on the client side, or on the server side,
17046 * to sanitize unsafe HTML before binding to the value and rendering it in the document.
17048 * How would you ensure that every place that used these types of bindings was bound to a value that
17049 * was sanitized by your library (or returned as safe for rendering by your server?) How can you
17050 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
17051 * properties/fields and forgot to update the binding to the sanitized value?
17053 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can
17054 * determine that something explicitly says it's safe to use a value for binding in that
17055 * context. You can then audit your code (a simple grep would do) to ensure that this is only done
17056 * for those values that you can easily tell are safe - because they were received from your server,
17057 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps
17058 * allowing only the files in a specific directory to do this. Ensuring that the internal API
17059 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
17061 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
17062 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
17063 * obtain values that will be accepted by SCE / privileged contexts.
17066 * ## How does it work?
17068 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
17069 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link
17070 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
17071 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
17073 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
17074 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
17078 * var ngBindHtmlDirective = ['$sce', function($sce) {
17079 * return function(scope, element, attr) {
17080 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
17081 * element.html(value || '');
17087 * ## Impact on loading templates
17089 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
17090 * `templateUrl`'s specified by {@link guide/directive directives}.
17092 * By default, Angular only loads templates from the same domain and protocol as the application
17093 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
17094 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
17095 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
17096 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
17100 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
17101 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
17102 * policy apply in addition to this and may further restrict whether the template is successfully
17103 * loaded. This means that without the right CORS policy, loading templates from a different domain
17104 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
17107 * ## This feels like too much overhead
17109 * It's important to remember that SCE only applies to interpolation expressions.
17111 * If your expressions are constant literals, they're automatically trusted and you don't need to
17112 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g.
17113 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works.
17115 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
17116 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here.
17118 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
17119 * templates in `ng-include` from your application's domain without having to even know about SCE.
17120 * It blocks loading templates from other domains or loading templates over http from an https
17121 * served document. You can change these by setting your own custom {@link
17122 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
17123 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
17125 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
17126 * application that's secure and can be audited to verify that with much more ease than bolting
17127 * security onto an application later.
17129 * <a name="contexts"></a>
17130 * ## What trusted context types are supported?
17132 * | Context | Notes |
17133 * |---------------------|----------------|
17134 * | `$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. |
17135 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
17136 * | `$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. |
17137 * | `$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. |
17138 * | `$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. |
17140 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
17142 * Each element in these arrays must be one of the following:
17145 * - The special **string**, `'self'`, can be used to match against all URLs of the **same
17146 * domain** as the application document using the **same protocol**.
17147 * - **String** (except the special value `'self'`)
17148 * - The string is matched against the full *normalized / absolute URL* of the resource
17149 * being tested (substring matches are not good enough.)
17150 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
17151 * match themselves.
17152 * - `*`: matches zero or more occurrences of any character other than one of the following 6
17153 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
17155 * - `**`: matches zero or more occurrences of *any* character. As such, it's not
17156 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
17157 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
17158 * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
17159 * http://foo.example.com/templates/**).
17160 * - **RegExp** (*see caveat below*)
17161 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
17162 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
17163 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
17164 * have good test coverage). For instance, the use of `.` in the regex is correct only in a
17165 * small number of cases. A `.` character in the regex used when matching the scheme or a
17166 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
17167 * is highly recommended to use the string patterns and only fall back to regular expressions
17168 * as a last resort.
17169 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
17170 * matched against the **entire** *normalized / absolute URL* of the resource being tested
17171 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
17172 * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
17173 * - If you are generating your JavaScript from some other templating engine (not
17174 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
17175 * remember to escape your regular expression (and be aware that you might need more than
17176 * one level of escaping depending on your templating engine and the way you interpolated
17177 * the value.) Do make use of your platform's escaping mechanism as it might be good
17178 * enough before coding your own. E.g. Ruby has
17179 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
17180 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
17181 * Javascript lacks a similar built in function for escaping. Take a look at Google
17182 * Closure library's [goog.string.regExpEscape(s)](
17183 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
17185 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
17187 * ## Show me an example using SCE.
17189 * <example module="mySceApp" deps="angular-sanitize.js">
17190 * <file name="index.html">
17191 * <div ng-controller="AppController as myCtrl">
17192 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
17193 * <b>User comments</b><br>
17194 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
17195 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
17197 * <div class="well">
17198 * <div ng-repeat="userComment in myCtrl.userComments">
17199 * <b>{{userComment.name}}</b>:
17200 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
17207 * <file name="script.js">
17208 * angular.module('mySceApp', ['ngSanitize'])
17209 * .controller('AppController', ['$http', '$templateCache', '$sce',
17210 * function($http, $templateCache, $sce) {
17212 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) {
17213 * self.userComments = userComments;
17215 * self.explicitlyTrustedHtml = $sce.trustAsHtml(
17216 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17217 * 'sanitization."">Hover over this text.</span>');
17221 * <file name="test_data.json">
17223 * { "name": "Alice",
17225 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
17228 * "htmlComment": "<i>Yes!</i> Am I the only other one?"
17233 * <file name="protractor.js" type="protractor">
17234 * describe('SCE doc demo', function() {
17235 * it('should sanitize untrusted values', function() {
17236 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml())
17237 * .toBe('<span>Is <i>anyone</i> reading this?</span>');
17240 * it('should NOT sanitize explicitly trusted values', function() {
17241 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
17242 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17243 * 'sanitization."">Hover over this text.</span>');
17251 * ## Can I disable SCE completely?
17253 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
17254 * for little coding overhead. It will be much harder to take an SCE disabled application and
17255 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
17256 * for cases where you have a lot of existing code that was written before SCE was introduced and
17257 * you're migrating them a module at a time.
17259 * That said, here's how you can completely disable SCE:
17262 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
17263 * // Completely disable SCE. For demonstration purposes only!
17264 * // Do not use in new projects.
17265 * $sceProvider.enabled(false);
17270 /* jshint maxlen: 100 */
17272 function $SceProvider() {
17273 var enabled = true;
17277 * @name $sceProvider#enabled
17280 * @param {boolean=} value If provided, then enables/disables SCE.
17281 * @return {boolean} true if SCE is enabled, false otherwise.
17284 * Enables/disables SCE and returns the current value.
17286 this.enabled = function(value) {
17287 if (arguments.length) {
17294 /* Design notes on the default implementation for SCE.
17296 * The API contract for the SCE delegate
17297 * -------------------------------------
17298 * The SCE delegate object must provide the following 3 methods:
17300 * - trustAs(contextEnum, value)
17301 * This method is used to tell the SCE service that the provided value is OK to use in the
17302 * contexts specified by contextEnum. It must return an object that will be accepted by
17303 * getTrusted() for a compatible contextEnum and return this value.
17306 * For values that were not produced by trustAs(), return them as is. For values that were
17307 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
17308 * trustAs is wrapping the given values into some type, this operation unwraps it when given
17311 * - getTrusted(contextEnum, value)
17312 * This function should return the a value that is safe to use in the context specified by
17313 * contextEnum or throw and exception otherwise.
17315 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
17316 * opaque or wrapped in some holder object. That happens to be an implementation detail. For
17317 * instance, an implementation could maintain a registry of all trusted objects by context. In
17318 * such a case, trustAs() would return the same object that was passed in. getTrusted() would
17319 * return the same object passed in if it was found in the registry under a compatible context or
17320 * throw an exception otherwise. An implementation might only wrap values some of the time based
17321 * on some criteria. getTrusted() might return a value and not throw an exception for special
17322 * constants or objects even if not wrapped. All such implementations fulfill this contract.
17325 * A note on the inheritance model for SCE contexts
17326 * ------------------------------------------------
17327 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
17328 * is purely an implementation details.
17330 * The contract is simply this:
17332 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
17333 * will also succeed.
17335 * Inheritance happens to capture this in a natural way. In some future, we
17336 * may not use inheritance anymore. That is OK because no code outside of
17337 * sce.js and sceSpecs.js would need to be aware of this detail.
17340 this.$get = ['$parse', '$sceDelegate', function(
17341 $parse, $sceDelegate) {
17342 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
17343 // the "expression(javascript expression)" syntax which is insecure.
17344 if (enabled && msie < 8) {
17345 throw $sceMinErr('iequirks',
17346 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
17347 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
17348 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
17351 var sce = shallowCopy(SCE_CONTEXTS);
17355 * @name $sce#isEnabled
17358 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you
17359 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
17362 * Returns a boolean indicating if SCE is enabled.
17364 sce.isEnabled = function() {
17367 sce.trustAs = $sceDelegate.trustAs;
17368 sce.getTrusted = $sceDelegate.getTrusted;
17369 sce.valueOf = $sceDelegate.valueOf;
17372 sce.trustAs = sce.getTrusted = function(type, value) { return value; };
17373 sce.valueOf = identity;
17378 * @name $sce#parseAs
17381 * Converts Angular {@link guide/expression expression} into a function. This is like {@link
17382 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
17383 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
17386 * @param {string} type The kind of SCE context in which this result will be used.
17387 * @param {string} expression String expression to compile.
17388 * @returns {function(context, locals)} a function which represents the compiled expression:
17390 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17391 * are evaluated against (typically a scope object).
17392 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17395 sce.parseAs = function sceParseAs(type, expr) {
17396 var parsed = $parse(expr);
17397 if (parsed.literal && parsed.constant) {
17400 return $parse(expr, function(value) {
17401 return sce.getTrusted(type, value);
17408 * @name $sce#trustAs
17411 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such,
17412 * returns an object that is trusted by angular for use in specified strict contextual
17413 * escaping contexts (such as ng-bind-html, ng-include, any src attribute
17414 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.)
17415 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual
17418 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
17419 * resourceUrl, html, js and css.
17420 * @param {*} value The value that that should be considered trusted/safe.
17421 * @returns {*} A value that can be used to stand in for the provided `value` in places
17422 * where Angular expects a $sce.trustAs() return value.
17427 * @name $sce#trustAsHtml
17430 * Shorthand method. `$sce.trustAsHtml(value)` →
17431 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
17433 * @param {*} value The value to trustAs.
17434 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml
17435 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives
17436 * only accept expressions that are either literal constants or are the
17437 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17442 * @name $sce#trustAsUrl
17445 * Shorthand method. `$sce.trustAsUrl(value)` →
17446 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
17448 * @param {*} value The value to trustAs.
17449 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl
17450 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives
17451 * only accept expressions that are either literal constants or are the
17452 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17457 * @name $sce#trustAsResourceUrl
17460 * Shorthand method. `$sce.trustAsResourceUrl(value)` →
17461 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
17463 * @param {*} value The value to trustAs.
17464 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl
17465 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives
17466 * only accept expressions that are either literal constants or are the return
17467 * value of {@link ng.$sce#trustAs $sce.trustAs}.)
17472 * @name $sce#trustAsJs
17475 * Shorthand method. `$sce.trustAsJs(value)` →
17476 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
17478 * @param {*} value The value to trustAs.
17479 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs
17480 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives
17481 * only accept expressions that are either literal constants or are the
17482 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17487 * @name $sce#getTrusted
17490 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
17491 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the
17492 * originally supplied value if the queried context type is a supertype of the created type.
17493 * If this condition isn't satisfied, throws an exception.
17495 * @param {string} type The kind of context in which this value is to be used.
17496 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`}
17498 * @returns {*} The value the was originally provided to
17499 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context.
17500 * Otherwise, throws an exception.
17505 * @name $sce#getTrustedHtml
17508 * Shorthand method. `$sce.getTrustedHtml(value)` →
17509 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
17511 * @param {*} value The value to pass to `$sce.getTrusted`.
17512 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)`
17517 * @name $sce#getTrustedCss
17520 * Shorthand method. `$sce.getTrustedCss(value)` →
17521 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
17523 * @param {*} value The value to pass to `$sce.getTrusted`.
17524 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)`
17529 * @name $sce#getTrustedUrl
17532 * Shorthand method. `$sce.getTrustedUrl(value)` →
17533 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
17535 * @param {*} value The value to pass to `$sce.getTrusted`.
17536 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)`
17541 * @name $sce#getTrustedResourceUrl
17544 * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
17545 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
17547 * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
17548 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
17553 * @name $sce#getTrustedJs
17556 * Shorthand method. `$sce.getTrustedJs(value)` →
17557 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
17559 * @param {*} value The value to pass to `$sce.getTrusted`.
17560 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)`
17565 * @name $sce#parseAsHtml
17568 * Shorthand method. `$sce.parseAsHtml(expression string)` →
17569 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
17571 * @param {string} expression String expression to compile.
17572 * @returns {function(context, locals)} a function which represents the compiled expression:
17574 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17575 * are evaluated against (typically a scope object).
17576 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17582 * @name $sce#parseAsCss
17585 * Shorthand method. `$sce.parseAsCss(value)` →
17586 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
17588 * @param {string} expression String expression to compile.
17589 * @returns {function(context, locals)} a function which represents the compiled expression:
17591 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17592 * are evaluated against (typically a scope object).
17593 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17599 * @name $sce#parseAsUrl
17602 * Shorthand method. `$sce.parseAsUrl(value)` →
17603 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
17605 * @param {string} expression String expression to compile.
17606 * @returns {function(context, locals)} a function which represents the compiled expression:
17608 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17609 * are evaluated against (typically a scope object).
17610 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17616 * @name $sce#parseAsResourceUrl
17619 * Shorthand method. `$sce.parseAsResourceUrl(value)` →
17620 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
17622 * @param {string} expression String expression to compile.
17623 * @returns {function(context, locals)} a function which represents the compiled expression:
17625 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17626 * are evaluated against (typically a scope object).
17627 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17633 * @name $sce#parseAsJs
17636 * Shorthand method. `$sce.parseAsJs(value)` →
17637 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
17639 * @param {string} expression String expression to compile.
17640 * @returns {function(context, locals)} a function which represents the compiled expression:
17642 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17643 * are evaluated against (typically a scope object).
17644 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17648 // Shorthand delegations.
17649 var parse = sce.parseAs,
17650 getTrusted = sce.getTrusted,
17651 trustAs = sce.trustAs;
17653 forEach(SCE_CONTEXTS, function(enumValue, name) {
17654 var lName = lowercase(name);
17655 sce[camelCase("parse_as_" + lName)] = function(expr) {
17656 return parse(enumValue, expr);
17658 sce[camelCase("get_trusted_" + lName)] = function(value) {
17659 return getTrusted(enumValue, value);
17661 sce[camelCase("trust_as_" + lName)] = function(value) {
17662 return trustAs(enumValue, value);
17671 * !!! This is an undocumented "private" service !!!
17674 * @requires $window
17675 * @requires $document
17677 * @property {boolean} history Does the browser support html5 history api ?
17678 * @property {boolean} transitions Does the browser support CSS transition events ?
17679 * @property {boolean} animations Does the browser support CSS animation events ?
17682 * This is very simple implementation of testing browser's features.
17684 function $SnifferProvider() {
17685 this.$get = ['$window', '$document', function($window, $document) {
17686 var eventSupport = {},
17688 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
17689 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
17690 document = $document[0] || {},
17692 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/,
17693 bodyStyle = document.body && document.body.style,
17694 transitions = false,
17695 animations = false,
17699 for (var prop in bodyStyle) {
17700 if (match = vendorRegex.exec(prop)) {
17701 vendorPrefix = match[0];
17702 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
17707 if (!vendorPrefix) {
17708 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
17711 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
17712 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
17714 if (android && (!transitions || !animations)) {
17715 transitions = isString(bodyStyle.webkitTransition);
17716 animations = isString(bodyStyle.webkitAnimation);
17722 // Android has history.pushState, but it does not update location correctly
17723 // so let's not use the history API at all.
17724 // http://code.google.com/p/android/issues/detail?id=17471
17725 // https://github.com/angular/angular.js/issues/904
17727 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
17728 // so let's not use the history API also
17729 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
17731 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
17733 hasEvent: function(event) {
17734 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
17735 // it. In particular the event is not fired when backspace or delete key are pressed or
17736 // when cut operation is performed.
17737 // IE10+ implements 'input' event but it erroneously fires under various situations,
17738 // e.g. when placeholder changes, or a form is focused.
17739 if (event === 'input' && msie <= 11) return false;
17741 if (isUndefined(eventSupport[event])) {
17742 var divElm = document.createElement('div');
17743 eventSupport[event] = 'on' + event in divElm;
17746 return eventSupport[event];
17749 vendorPrefix: vendorPrefix,
17750 transitions: transitions,
17751 animations: animations,
17757 var $compileMinErr = minErr('$compile');
17761 * @name $templateRequest
17764 * The `$templateRequest` service runs security checks then downloads the provided template using
17765 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
17766 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
17767 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
17768 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
17769 * when `tpl` is of type string and `$templateCache` has the matching entry.
17771 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
17772 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
17774 * @return {Promise} a promise for the HTTP response data of the given URL.
17776 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
17778 function $TemplateRequestProvider() {
17779 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
17780 function handleRequestFn(tpl, ignoreRequestError) {
17781 handleRequestFn.totalPendingRequests++;
17783 // We consider the template cache holds only trusted templates, so
17784 // there's no need to go through whitelisting again for keys that already
17785 // are included in there. This also makes Angular accept any script
17786 // directive, no matter its name. However, we still need to unwrap trusted
17788 if (!isString(tpl) || !$templateCache.get(tpl)) {
17789 tpl = $sce.getTrustedResourceUrl(tpl);
17792 var transformResponse = $http.defaults && $http.defaults.transformResponse;
17794 if (isArray(transformResponse)) {
17795 transformResponse = transformResponse.filter(function(transformer) {
17796 return transformer !== defaultHttpResponseTransform;
17798 } else if (transformResponse === defaultHttpResponseTransform) {
17799 transformResponse = null;
17802 var httpOptions = {
17803 cache: $templateCache,
17804 transformResponse: transformResponse
17807 return $http.get(tpl, httpOptions)
17808 ['finally'](function() {
17809 handleRequestFn.totalPendingRequests--;
17811 .then(function(response) {
17812 $templateCache.put(tpl, response.data);
17813 return response.data;
17816 function handleError(resp) {
17817 if (!ignoreRequestError) {
17818 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})',
17819 tpl, resp.status, resp.statusText);
17821 return $q.reject(resp);
17825 handleRequestFn.totalPendingRequests = 0;
17827 return handleRequestFn;
17831 function $$TestabilityProvider() {
17832 this.$get = ['$rootScope', '$browser', '$location',
17833 function($rootScope, $browser, $location) {
17836 * @name $testability
17839 * The private $$testability service provides a collection of methods for use when debugging
17840 * or by automated test and debugging tools.
17842 var testability = {};
17845 * @name $$testability#findBindings
17848 * Returns an array of elements that are bound (via ng-bind or {{}})
17849 * to expressions matching the input.
17851 * @param {Element} element The element root to search from.
17852 * @param {string} expression The binding expression to match.
17853 * @param {boolean} opt_exactMatch If true, only returns exact matches
17854 * for the expression. Filters and whitespace are ignored.
17856 testability.findBindings = function(element, expression, opt_exactMatch) {
17857 var bindings = element.getElementsByClassName('ng-binding');
17859 forEach(bindings, function(binding) {
17860 var dataBinding = angular.element(binding).data('$binding');
17862 forEach(dataBinding, function(bindingName) {
17863 if (opt_exactMatch) {
17864 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
17865 if (matcher.test(bindingName)) {
17866 matches.push(binding);
17869 if (bindingName.indexOf(expression) != -1) {
17870 matches.push(binding);
17880 * @name $$testability#findModels
17883 * Returns an array of elements that are two-way found via ng-model to
17884 * expressions matching the input.
17886 * @param {Element} element The element root to search from.
17887 * @param {string} expression The model expression to match.
17888 * @param {boolean} opt_exactMatch If true, only returns exact matches
17889 * for the expression.
17891 testability.findModels = function(element, expression, opt_exactMatch) {
17892 var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
17893 for (var p = 0; p < prefixes.length; ++p) {
17894 var attributeEquals = opt_exactMatch ? '=' : '*=';
17895 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
17896 var elements = element.querySelectorAll(selector);
17897 if (elements.length) {
17904 * @name $$testability#getLocation
17907 * Shortcut for getting the location in a browser agnostic way. Returns
17908 * the path, search, and hash. (e.g. /path?a=b#hash)
17910 testability.getLocation = function() {
17911 return $location.url();
17915 * @name $$testability#setLocation
17918 * Shortcut for navigating to a location without doing a full page reload.
17920 * @param {string} url The location url (path, search and hash,
17921 * e.g. /path?a=b#hash) to go to.
17923 testability.setLocation = function(url) {
17924 if (url !== $location.url()) {
17925 $location.url(url);
17926 $rootScope.$digest();
17931 * @name $$testability#whenStable
17934 * Calls the callback when $timeout and $http requests are completed.
17936 * @param {function} callback
17938 testability.whenStable = function(callback) {
17939 $browser.notifyWhenNoOutstandingRequests(callback);
17942 return testability;
17946 function $TimeoutProvider() {
17947 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
17948 function($rootScope, $browser, $q, $$q, $exceptionHandler) {
17950 var deferreds = {};
17958 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
17959 * block and delegates any exceptions to
17960 * {@link ng.$exceptionHandler $exceptionHandler} service.
17962 * The return value of calling `$timeout` is a promise, which will be resolved when
17963 * the delay has passed and the timeout function, if provided, is executed.
17965 * To cancel a timeout request, call `$timeout.cancel(promise)`.
17967 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
17968 * synchronously flush the queue of deferred functions.
17970 * If you only want a promise that will be resolved after some specified delay
17971 * then you can call `$timeout` without the `fn` function.
17973 * @param {function()=} fn A function, whose execution should be delayed.
17974 * @param {number=} [delay=0] Delay in milliseconds.
17975 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
17976 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
17977 * @param {...*=} Pass additional parameters to the executed function.
17978 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
17979 * promise will be resolved with is the return value of the `fn` function.
17982 function timeout(fn, delay, invokeApply) {
17983 if (!isFunction(fn)) {
17984 invokeApply = delay;
17989 var args = sliceArgs(arguments, 3),
17990 skipApply = (isDefined(invokeApply) && !invokeApply),
17991 deferred = (skipApply ? $$q : $q).defer(),
17992 promise = deferred.promise,
17995 timeoutId = $browser.defer(function() {
17997 deferred.resolve(fn.apply(null, args));
17999 deferred.reject(e);
18000 $exceptionHandler(e);
18003 delete deferreds[promise.$$timeoutId];
18006 if (!skipApply) $rootScope.$apply();
18009 promise.$$timeoutId = timeoutId;
18010 deferreds[timeoutId] = deferred;
18018 * @name $timeout#cancel
18021 * Cancels a task associated with the `promise`. As a result of this, the promise will be
18022 * resolved with a rejection.
18024 * @param {Promise=} promise Promise returned by the `$timeout` function.
18025 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
18028 timeout.cancel = function(promise) {
18029 if (promise && promise.$$timeoutId in deferreds) {
18030 deferreds[promise.$$timeoutId].reject('canceled');
18031 delete deferreds[promise.$$timeoutId];
18032 return $browser.defer.cancel(promise.$$timeoutId);
18041 // NOTE: The usage of window and document instead of $window and $document here is
18042 // deliberate. This service depends on the specific behavior of anchor nodes created by the
18043 // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
18044 // cause us to break tests. In addition, when the browser resolves a URL for XHR, it
18045 // doesn't know about mocked locations and resolves URLs to the real document - which is
18046 // exactly the behavior needed here. There is little value is mocking these out for this
18048 var urlParsingNode = document.createElement("a");
18049 var originUrl = urlResolve(window.location.href);
18054 * Implementation Notes for non-IE browsers
18055 * ----------------------------------------
18056 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
18057 * results both in the normalizing and parsing of the URL. Normalizing means that a relative
18058 * URL will be resolved into an absolute URL in the context of the application document.
18059 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
18060 * properties are all populated to reflect the normalized URL. This approach has wide
18061 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See
18062 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18064 * Implementation Notes for IE
18065 * ---------------------------
18066 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
18067 * browsers. However, the parsed components will not be set if the URL assigned did not specify
18068 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
18069 * work around that by performing the parsing in a 2nd step by taking a previously normalized
18070 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
18071 * properties such as protocol, hostname, port, etc.
18074 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
18075 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18076 * http://url.spec.whatwg.org/#urlutils
18077 * https://github.com/angular/angular.js/pull/2902
18078 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
18081 * @param {string} url The URL to be parsed.
18082 * @description Normalizes and parses a URL.
18083 * @returns {object} Returns the normalized URL as a dictionary.
18085 * | member name | Description |
18086 * |---------------|----------------|
18087 * | href | A normalized version of the provided URL if it was not an absolute URL |
18088 * | protocol | The protocol including the trailing colon |
18089 * | host | The host and port (if the port is non-default) of the normalizedUrl |
18090 * | search | The search params, minus the question mark |
18091 * | hash | The hash string, minus the hash symbol
18092 * | hostname | The hostname
18093 * | port | The port, without ":"
18094 * | pathname | The pathname, beginning with "/"
18097 function urlResolve(url) {
18101 // Normalize before parse. Refer Implementation Notes on why this is
18102 // done in two steps on IE.
18103 urlParsingNode.setAttribute("href", href);
18104 href = urlParsingNode.href;
18107 urlParsingNode.setAttribute('href', href);
18109 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
18111 href: urlParsingNode.href,
18112 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
18113 host: urlParsingNode.host,
18114 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
18115 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
18116 hostname: urlParsingNode.hostname,
18117 port: urlParsingNode.port,
18118 pathname: (urlParsingNode.pathname.charAt(0) === '/')
18119 ? urlParsingNode.pathname
18120 : '/' + urlParsingNode.pathname
18125 * Parse a request URL and determine whether this is a same-origin request as the application document.
18127 * @param {string|object} requestUrl The url of the request as a string that will be resolved
18128 * or a parsed URL object.
18129 * @returns {boolean} Whether the request is for the same origin as the application document.
18131 function urlIsSameOrigin(requestUrl) {
18132 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
18133 return (parsed.protocol === originUrl.protocol &&
18134 parsed.host === originUrl.host);
18142 * A reference to the browser's `window` object. While `window`
18143 * is globally available in JavaScript, it causes testability problems, because
18144 * it is a global variable. In angular we always refer to it through the
18145 * `$window` service, so it may be overridden, removed or mocked for testing.
18147 * Expressions, like the one defined for the `ngClick` directive in the example
18148 * below, are evaluated with respect to the current scope. Therefore, there is
18149 * no risk of inadvertently coding in a dependency on a global value in such an
18153 <example module="windowExample">
18154 <file name="index.html">
18156 angular.module('windowExample', [])
18157 .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
18158 $scope.greeting = 'Hello, World!';
18159 $scope.doGreeting = function(greeting) {
18160 $window.alert(greeting);
18164 <div ng-controller="ExampleController">
18165 <input type="text" ng-model="greeting" aria-label="greeting" />
18166 <button ng-click="doGreeting(greeting)">ALERT</button>
18169 <file name="protractor.js" type="protractor">
18170 it('should display the greeting in the input box', function() {
18171 element(by.model('greeting')).sendKeys('Hello, E2E Tests');
18172 // If we click the button it will block the test runner
18173 // element(':button').click();
18178 function $WindowProvider() {
18179 this.$get = valueFn(window);
18183 * @name $$cookieReader
18184 * @requires $document
18187 * This is a private service for reading cookies used by $http and ngCookies
18189 * @return {Object} a key/value map of the current cookies
18191 function $$CookieReader($document) {
18192 var rawDocument = $document[0] || {};
18193 var lastCookies = {};
18194 var lastCookieString = '';
18196 function safeDecodeURIComponent(str) {
18198 return decodeURIComponent(str);
18204 return function() {
18205 var cookieArray, cookie, i, index, name;
18206 var currentCookieString = rawDocument.cookie || '';
18208 if (currentCookieString !== lastCookieString) {
18209 lastCookieString = currentCookieString;
18210 cookieArray = lastCookieString.split('; ');
18213 for (i = 0; i < cookieArray.length; i++) {
18214 cookie = cookieArray[i];
18215 index = cookie.indexOf('=');
18216 if (index > 0) { //ignore nameless cookies
18217 name = safeDecodeURIComponent(cookie.substring(0, index));
18218 // the first value that is seen for a cookie is the most
18219 // specific one. values for the same cookie name that
18220 // follow are for less specific paths.
18221 if (isUndefined(lastCookies[name])) {
18222 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
18227 return lastCookies;
18231 $$CookieReader.$inject = ['$document'];
18233 function $$CookieReaderProvider() {
18234 this.$get = $$CookieReader;
18237 /* global currencyFilter: true,
18239 filterFilter: true,
18241 limitToFilter: true,
18242 lowercaseFilter: true,
18243 numberFilter: true,
18244 orderByFilter: true,
18245 uppercaseFilter: true,
18250 * @name $filterProvider
18253 * Filters are just functions which transform input to an output. However filters need to be
18254 * Dependency Injected. To achieve this a filter definition consists of a factory function which is
18255 * annotated with dependencies and is responsible for creating a filter function.
18257 * <div class="alert alert-warning">
18258 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18259 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18260 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18261 * (`myapp_subsection_filterx`).
18265 * // Filter registration
18266 * function MyModule($provide, $filterProvider) {
18267 * // create a service to demonstrate injection (not always needed)
18268 * $provide.value('greet', function(name){
18269 * return 'Hello ' + name + '!';
18272 * // register a filter factory which uses the
18273 * // greet service to demonstrate DI.
18274 * $filterProvider.register('greet', function(greet){
18275 * // return the filter function which uses the greet service
18276 * // to generate salutation
18277 * return function(text) {
18278 * // filters need to be forgiving so check input validity
18279 * return text && greet(text) || text;
18285 * The filter function is registered with the `$injector` under the filter name suffix with
18289 * it('should be the same instance', inject(
18290 * function($filterProvider) {
18291 * $filterProvider.register('reverse', function(){
18295 * function($filter, reverseFilter) {
18296 * expect($filter('reverse')).toBe(reverseFilter);
18301 * For more information about how angular filters work, and how to create your own filters, see
18302 * {@link guide/filter Filters} in the Angular Developer Guide.
18310 * Filters are used for formatting data displayed to the user.
18312 * The general syntax in templates is as follows:
18314 * {{ expression [| filter_name[:parameter_value] ... ] }}
18316 * @param {String} name Name of the filter function to retrieve
18317 * @return {Function} the filter function
18319 <example name="$filter" module="filterExample">
18320 <file name="index.html">
18321 <div ng-controller="MainCtrl">
18322 <h3>{{ originalText }}</h3>
18323 <h3>{{ filteredText }}</h3>
18327 <file name="script.js">
18328 angular.module('filterExample', [])
18329 .controller('MainCtrl', function($scope, $filter) {
18330 $scope.originalText = 'hello';
18331 $scope.filteredText = $filter('uppercase')($scope.originalText);
18336 $FilterProvider.$inject = ['$provide'];
18337 function $FilterProvider($provide) {
18338 var suffix = 'Filter';
18342 * @name $filterProvider#register
18343 * @param {string|Object} name Name of the filter function, or an object map of filters where
18344 * the keys are the filter names and the values are the filter factories.
18346 * <div class="alert alert-warning">
18347 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18348 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18349 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18350 * (`myapp_subsection_filterx`).
18352 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
18353 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
18354 * of the registered filter instances.
18356 function register(name, factory) {
18357 if (isObject(name)) {
18359 forEach(name, function(filter, key) {
18360 filters[key] = register(key, filter);
18364 return $provide.factory(name + suffix, factory);
18367 this.register = register;
18369 this.$get = ['$injector', function($injector) {
18370 return function(name) {
18371 return $injector.get(name + suffix);
18375 ////////////////////////////////////////
18378 currencyFilter: false,
18380 filterFilter: false,
18382 limitToFilter: false,
18383 lowercaseFilter: false,
18384 numberFilter: false,
18385 orderByFilter: false,
18386 uppercaseFilter: false,
18389 register('currency', currencyFilter);
18390 register('date', dateFilter);
18391 register('filter', filterFilter);
18392 register('json', jsonFilter);
18393 register('limitTo', limitToFilter);
18394 register('lowercase', lowercaseFilter);
18395 register('number', numberFilter);
18396 register('orderBy', orderByFilter);
18397 register('uppercase', uppercaseFilter);
18406 * Selects a subset of items from `array` and returns it as a new array.
18408 * @param {Array} array The source array.
18409 * @param {string|Object|function()} expression The predicate to be used for selecting items from
18414 * - `string`: The string is used for matching against the contents of the `array`. All strings or
18415 * objects with string properties in `array` that match this string will be returned. This also
18416 * applies to nested object properties.
18417 * The predicate can be negated by prefixing the string with `!`.
18419 * - `Object`: A pattern object can be used to filter specific properties on objects contained
18420 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
18421 * which have property `name` containing "M" and property `phone` containing "1". A special
18422 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
18423 * property of the object or its nested object properties. That's equivalent to the simple
18424 * substring match with a `string` as described above. The predicate can be negated by prefixing
18425 * the string with `!`.
18426 * For example `{name: "!M"}` predicate will return an array of items which have property `name`
18427 * not containing "M".
18429 * Note that a named property will match properties on the same level only, while the special
18430 * `$` property will match properties on the same level or deeper. E.g. an array item like
18431 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
18432 * **will** be matched by `{$: 'John'}`.
18434 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
18435 * The function is called for each element of the array, with the element, its index, and
18436 * the entire array itself as arguments.
18438 * The final result is an array of those elements that the predicate returned true for.
18440 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
18441 * determining if the expected value (from the filter expression) and actual value (from
18442 * the object in the array) should be considered a match.
18446 * - `function(actual, expected)`:
18447 * The function will be given the object value and the predicate value to compare and
18448 * should return true if both values should be considered equal.
18450 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
18451 * This is essentially strict comparison of expected and actual.
18453 * - `false|undefined`: A short hand for a function which will look for a substring match in case
18456 * Primitive values are converted to strings. Objects are not compared against primitives,
18457 * unless they have a custom `toString` method (e.g. `Date` objects).
18461 <file name="index.html">
18462 <div ng-init="friends = [{name:'John', phone:'555-1276'},
18463 {name:'Mary', phone:'800-BIG-MARY'},
18464 {name:'Mike', phone:'555-4321'},
18465 {name:'Adam', phone:'555-5678'},
18466 {name:'Julie', phone:'555-8765'},
18467 {name:'Juliette', phone:'555-5678'}]"></div>
18469 <label>Search: <input ng-model="searchText"></label>
18470 <table id="searchTextResults">
18471 <tr><th>Name</th><th>Phone</th></tr>
18472 <tr ng-repeat="friend in friends | filter:searchText">
18473 <td>{{friend.name}}</td>
18474 <td>{{friend.phone}}</td>
18478 <label>Any: <input ng-model="search.$"></label> <br>
18479 <label>Name only <input ng-model="search.name"></label><br>
18480 <label>Phone only <input ng-model="search.phone"></label><br>
18481 <label>Equality <input type="checkbox" ng-model="strict"></label><br>
18482 <table id="searchObjResults">
18483 <tr><th>Name</th><th>Phone</th></tr>
18484 <tr ng-repeat="friendObj in friends | filter:search:strict">
18485 <td>{{friendObj.name}}</td>
18486 <td>{{friendObj.phone}}</td>
18490 <file name="protractor.js" type="protractor">
18491 var expectFriendNames = function(expectedNames, key) {
18492 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
18493 arr.forEach(function(wd, i) {
18494 expect(wd.getText()).toMatch(expectedNames[i]);
18499 it('should search across all fields when filtering with a string', function() {
18500 var searchText = element(by.model('searchText'));
18501 searchText.clear();
18502 searchText.sendKeys('m');
18503 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
18505 searchText.clear();
18506 searchText.sendKeys('76');
18507 expectFriendNames(['John', 'Julie'], 'friend');
18510 it('should search in specific fields when filtering with a predicate object', function() {
18511 var searchAny = element(by.model('search.$'));
18513 searchAny.sendKeys('i');
18514 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
18516 it('should use a equal comparison when comparator is true', function() {
18517 var searchName = element(by.model('search.name'));
18518 var strict = element(by.model('strict'));
18519 searchName.clear();
18520 searchName.sendKeys('Julie');
18522 expectFriendNames(['Julie'], 'friendObj');
18527 function filterFilter() {
18528 return function(array, expression, comparator) {
18529 if (!isArrayLike(array)) {
18530 if (array == null) {
18533 throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
18537 var expressionType = getTypeForFilter(expression);
18539 var matchAgainstAnyProp;
18541 switch (expressionType) {
18543 predicateFn = expression;
18549 matchAgainstAnyProp = true;
18553 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
18559 return Array.prototype.filter.call(array, predicateFn);
18563 // Helper functions for `filterFilter`
18564 function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
18565 var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
18568 if (comparator === true) {
18569 comparator = equals;
18570 } else if (!isFunction(comparator)) {
18571 comparator = function(actual, expected) {
18572 if (isUndefined(actual)) {
18573 // No substring matching against `undefined`
18576 if ((actual === null) || (expected === null)) {
18577 // No substring matching against `null`; only match against `null`
18578 return actual === expected;
18580 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
18581 // Should not compare primitives against objects, unless they have custom `toString` method
18585 actual = lowercase('' + actual);
18586 expected = lowercase('' + expected);
18587 return actual.indexOf(expected) !== -1;
18591 predicateFn = function(item) {
18592 if (shouldMatchPrimitives && !isObject(item)) {
18593 return deepCompare(item, expression.$, comparator, false);
18595 return deepCompare(item, expression, comparator, matchAgainstAnyProp);
18598 return predicateFn;
18601 function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
18602 var actualType = getTypeForFilter(actual);
18603 var expectedType = getTypeForFilter(expected);
18605 if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
18606 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
18607 } else if (isArray(actual)) {
18608 // In case `actual` is an array, consider it a match
18609 // if ANY of it's items matches `expected`
18610 return actual.some(function(item) {
18611 return deepCompare(item, expected, comparator, matchAgainstAnyProp);
18615 switch (actualType) {
18618 if (matchAgainstAnyProp) {
18619 for (key in actual) {
18620 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
18624 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
18625 } else if (expectedType === 'object') {
18626 for (key in expected) {
18627 var expectedVal = expected[key];
18628 if (isFunction(expectedVal) || isUndefined(expectedVal)) {
18632 var matchAnyProperty = key === '$';
18633 var actualVal = matchAnyProperty ? actual : actual[key];
18634 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
18640 return comparator(actual, expected);
18646 return comparator(actual, expected);
18650 // Used for easily differentiating between `null` and actual `object`
18651 function getTypeForFilter(val) {
18652 return (val === null) ? 'null' : typeof val;
18661 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
18662 * symbol for current locale is used.
18664 * @param {number} amount Input to filter.
18665 * @param {string=} symbol Currency symbol or identifier to be displayed.
18666 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
18667 * @returns {string} Formatted number.
18671 <example module="currencyExample">
18672 <file name="index.html">
18674 angular.module('currencyExample', [])
18675 .controller('ExampleController', ['$scope', function($scope) {
18676 $scope.amount = 1234.56;
18679 <div ng-controller="ExampleController">
18680 <input type="number" ng-model="amount" aria-label="amount"> <br>
18681 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
18682 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
18683 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
18686 <file name="protractor.js" type="protractor">
18687 it('should init with 1234.56', function() {
18688 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
18689 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
18690 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
18692 it('should update', function() {
18693 if (browser.params.browser == 'safari') {
18694 // Safari does not understand the minus key. See
18695 // https://github.com/angular/protractor/issues/481
18698 element(by.model('amount')).clear();
18699 element(by.model('amount')).sendKeys('-1234');
18700 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
18701 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
18702 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
18707 currencyFilter.$inject = ['$locale'];
18708 function currencyFilter($locale) {
18709 var formats = $locale.NUMBER_FORMATS;
18710 return function(amount, currencySymbol, fractionSize) {
18711 if (isUndefined(currencySymbol)) {
18712 currencySymbol = formats.CURRENCY_SYM;
18715 if (isUndefined(fractionSize)) {
18716 fractionSize = formats.PATTERNS[1].maxFrac;
18719 // if null or undefined pass it through
18720 return (amount == null)
18722 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
18723 replace(/\u00A4/g, currencySymbol);
18733 * Formats a number as text.
18735 * If the input is null or undefined, it will just be returned.
18736 * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
18737 * If the input is not a number an empty string is returned.
18740 * @param {number|string} number Number to format.
18741 * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
18742 * If this is not provided then the fraction size is computed from the current locale's number
18743 * formatting pattern. In the case of the default locale, it will be 3.
18744 * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
18747 <example module="numberFilterExample">
18748 <file name="index.html">
18750 angular.module('numberFilterExample', [])
18751 .controller('ExampleController', ['$scope', function($scope) {
18752 $scope.val = 1234.56789;
18755 <div ng-controller="ExampleController">
18756 <label>Enter number: <input ng-model='val'></label><br>
18757 Default formatting: <span id='number-default'>{{val | number}}</span><br>
18758 No fractions: <span>{{val | number:0}}</span><br>
18759 Negative number: <span>{{-val | number:4}}</span>
18762 <file name="protractor.js" type="protractor">
18763 it('should format numbers', function() {
18764 expect(element(by.id('number-default')).getText()).toBe('1,234.568');
18765 expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
18766 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
18769 it('should update', function() {
18770 element(by.model('val')).clear();
18771 element(by.model('val')).sendKeys('3374.333');
18772 expect(element(by.id('number-default')).getText()).toBe('3,374.333');
18773 expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
18774 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
18781 numberFilter.$inject = ['$locale'];
18782 function numberFilter($locale) {
18783 var formats = $locale.NUMBER_FORMATS;
18784 return function(number, fractionSize) {
18786 // if null or undefined pass it through
18787 return (number == null)
18789 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
18794 var DECIMAL_SEP = '.';
18795 function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
18796 if (isObject(number)) return '';
18798 var isNegative = number < 0;
18799 number = Math.abs(number);
18801 var isInfinity = number === Infinity;
18802 if (!isInfinity && !isFinite(number)) return '';
18804 var numStr = number + '',
18806 hasExponent = false,
18809 if (isInfinity) formatedText = '\u221e';
18811 if (!isInfinity && numStr.indexOf('e') !== -1) {
18812 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
18813 if (match && match[2] == '-' && match[3] > fractionSize + 1) {
18816 formatedText = numStr;
18817 hasExponent = true;
18821 if (!isInfinity && !hasExponent) {
18822 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
18824 // determine fractionSize if it is not specified
18825 if (isUndefined(fractionSize)) {
18826 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
18829 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics
18831 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
18832 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
18834 var fraction = ('' + number).split(DECIMAL_SEP);
18835 var whole = fraction[0];
18836 fraction = fraction[1] || '';
18839 lgroup = pattern.lgSize,
18840 group = pattern.gSize;
18842 if (whole.length >= (lgroup + group)) {
18843 pos = whole.length - lgroup;
18844 for (i = 0; i < pos; i++) {
18845 if ((pos - i) % group === 0 && i !== 0) {
18846 formatedText += groupSep;
18848 formatedText += whole.charAt(i);
18852 for (i = pos; i < whole.length; i++) {
18853 if ((whole.length - i) % lgroup === 0 && i !== 0) {
18854 formatedText += groupSep;
18856 formatedText += whole.charAt(i);
18859 // format fraction part.
18860 while (fraction.length < fractionSize) {
18864 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
18866 if (fractionSize > 0 && number < 1) {
18867 formatedText = number.toFixed(fractionSize);
18868 number = parseFloat(formatedText);
18869 formatedText = formatedText.replace(DECIMAL_SEP, decimalSep);
18873 if (number === 0) {
18874 isNegative = false;
18877 parts.push(isNegative ? pattern.negPre : pattern.posPre,
18879 isNegative ? pattern.negSuf : pattern.posSuf);
18880 return parts.join('');
18883 function padNumber(num, digits, trim) {
18890 while (num.length < digits) num = '0' + num;
18892 num = num.substr(num.length - digits);
18898 function dateGetter(name, size, offset, trim) {
18899 offset = offset || 0;
18900 return function(date) {
18901 var value = date['get' + name]();
18902 if (offset > 0 || value > -offset) {
18905 if (value === 0 && offset == -12) value = 12;
18906 return padNumber(value, size, trim);
18910 function dateStrGetter(name, shortForm) {
18911 return function(date, formats) {
18912 var value = date['get' + name]();
18913 var get = uppercase(shortForm ? ('SHORT' + name) : name);
18915 return formats[get][value];
18919 function timeZoneGetter(date, formats, offset) {
18920 var zone = -1 * offset;
18921 var paddedZone = (zone >= 0) ? "+" : "";
18923 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
18924 padNumber(Math.abs(zone % 60), 2);
18929 function getFirstThursdayOfYear(year) {
18930 // 0 = index of January
18931 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
18932 // 4 = index of Thursday (+1 to account for 1st = 5)
18933 // 11 = index of *next* Thursday (+1 account for 1st = 12)
18934 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
18937 function getThursdayThisWeek(datetime) {
18938 return new Date(datetime.getFullYear(), datetime.getMonth(),
18939 // 4 = index of Thursday
18940 datetime.getDate() + (4 - datetime.getDay()));
18943 function weekGetter(size) {
18944 return function(date) {
18945 var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
18946 thisThurs = getThursdayThisWeek(date);
18948 var diff = +thisThurs - +firstThurs,
18949 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
18951 return padNumber(result, size);
18955 function ampmGetter(date, formats) {
18956 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
18959 function eraGetter(date, formats) {
18960 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
18963 function longEraGetter(date, formats) {
18964 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
18967 var DATE_FORMATS = {
18968 yyyy: dateGetter('FullYear', 4),
18969 yy: dateGetter('FullYear', 2, 0, true),
18970 y: dateGetter('FullYear', 1),
18971 MMMM: dateStrGetter('Month'),
18972 MMM: dateStrGetter('Month', true),
18973 MM: dateGetter('Month', 2, 1),
18974 M: dateGetter('Month', 1, 1),
18975 dd: dateGetter('Date', 2),
18976 d: dateGetter('Date', 1),
18977 HH: dateGetter('Hours', 2),
18978 H: dateGetter('Hours', 1),
18979 hh: dateGetter('Hours', 2, -12),
18980 h: dateGetter('Hours', 1, -12),
18981 mm: dateGetter('Minutes', 2),
18982 m: dateGetter('Minutes', 1),
18983 ss: dateGetter('Seconds', 2),
18984 s: dateGetter('Seconds', 1),
18985 // while ISO 8601 requires fractions to be prefixed with `.` or `,`
18986 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
18987 sss: dateGetter('Milliseconds', 3),
18988 EEEE: dateStrGetter('Day'),
18989 EEE: dateStrGetter('Day', true),
18997 GGGG: longEraGetter
19000 var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
19001 NUMBER_STRING = /^\-?\d+$/;
19009 * Formats `date` to a string based on the requested `format`.
19011 * `format` string can be composed of the following elements:
19013 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
19014 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
19015 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
19016 * * `'MMMM'`: Month in year (January-December)
19017 * * `'MMM'`: Month in year (Jan-Dec)
19018 * * `'MM'`: Month in year, padded (01-12)
19019 * * `'M'`: Month in year (1-12)
19020 * * `'dd'`: Day in month, padded (01-31)
19021 * * `'d'`: Day in month (1-31)
19022 * * `'EEEE'`: Day in Week,(Sunday-Saturday)
19023 * * `'EEE'`: Day in Week, (Sun-Sat)
19024 * * `'HH'`: Hour in day, padded (00-23)
19025 * * `'H'`: Hour in day (0-23)
19026 * * `'hh'`: Hour in AM/PM, padded (01-12)
19027 * * `'h'`: Hour in AM/PM, (1-12)
19028 * * `'mm'`: Minute in hour, padded (00-59)
19029 * * `'m'`: Minute in hour (0-59)
19030 * * `'ss'`: Second in minute, padded (00-59)
19031 * * `'s'`: Second in minute (0-59)
19032 * * `'sss'`: Millisecond in second, padded (000-999)
19033 * * `'a'`: AM/PM marker
19034 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
19035 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
19036 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
19037 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
19038 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
19040 * `format` string can also be one of the following predefined
19041 * {@link guide/i18n localizable formats}:
19043 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
19044 * (e.g. Sep 3, 2010 12:05:08 PM)
19045 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
19046 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
19047 * (e.g. Friday, September 3, 2010)
19048 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
19049 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
19050 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
19051 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
19052 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
19054 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
19055 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
19056 * (e.g. `"h 'o''clock'"`).
19058 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
19059 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
19060 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
19061 * specified in the string input, the time is considered to be in the local timezone.
19062 * @param {string=} format Formatting rules (see Description). If not specified,
19063 * `mediumDate` is used.
19064 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
19065 * continental US time zone abbreviations, but for general use, use a time zone offset, for
19066 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
19067 * If not specified, the timezone of the browser will be used.
19068 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
19072 <file name="index.html">
19073 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
19074 <span>{{1288323623006 | date:'medium'}}</span><br>
19075 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
19076 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
19077 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
19078 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
19079 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
19080 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
19082 <file name="protractor.js" type="protractor">
19083 it('should format date', function() {
19084 expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
19085 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
19086 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
19087 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
19088 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
19089 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
19090 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
19091 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
19096 dateFilter.$inject = ['$locale'];
19097 function dateFilter($locale) {
19100 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
19101 // 1 2 3 4 5 6 7 8 9 10 11
19102 function jsonStringToDate(string) {
19104 if (match = string.match(R_ISO8601_STR)) {
19105 var date = new Date(0),
19108 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
19109 timeSetter = match[8] ? date.setUTCHours : date.setHours;
19112 tzHour = toInt(match[9] + match[10]);
19113 tzMin = toInt(match[9] + match[11]);
19115 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
19116 var h = toInt(match[4] || 0) - tzHour;
19117 var m = toInt(match[5] || 0) - tzMin;
19118 var s = toInt(match[6] || 0);
19119 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
19120 timeSetter.call(date, h, m, s, ms);
19127 return function(date, format, timezone) {
19132 format = format || 'mediumDate';
19133 format = $locale.DATETIME_FORMATS[format] || format;
19134 if (isString(date)) {
19135 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
19138 if (isNumber(date)) {
19139 date = new Date(date);
19142 if (!isDate(date) || !isFinite(date.getTime())) {
19147 match = DATE_FORMATS_SPLIT.exec(format);
19149 parts = concat(parts, match, 1);
19150 format = parts.pop();
19152 parts.push(format);
19157 var dateTimezoneOffset = date.getTimezoneOffset();
19159 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
19160 date = convertTimezoneToLocal(date, timezone, true);
19162 forEach(parts, function(value) {
19163 fn = DATE_FORMATS[value];
19164 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
19165 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
19179 * Allows you to convert a JavaScript object into JSON string.
19181 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
19182 * the binding is automatically converted to JSON.
19184 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
19185 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
19186 * @returns {string} JSON string.
19191 <file name="index.html">
19192 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
19193 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
19195 <file name="protractor.js" type="protractor">
19196 it('should jsonify filtered objects', function() {
19197 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19198 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19204 function jsonFilter() {
19205 return function(object, spacing) {
19206 if (isUndefined(spacing)) {
19209 return toJson(object, spacing);
19219 * Converts string to lowercase.
19220 * @see angular.lowercase
19222 var lowercaseFilter = valueFn(lowercase);
19230 * Converts string to uppercase.
19231 * @see angular.uppercase
19233 var uppercaseFilter = valueFn(uppercase);
19241 * Creates a new array or string containing only a specified number of elements. The elements
19242 * are taken from either the beginning or the end of the source array, string or number, as specified by
19243 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is
19244 * converted to a string.
19246 * @param {Array|string|number} input Source array, string or number to be limited.
19247 * @param {string|number} limit The length of the returned array or string. If the `limit` number
19248 * is positive, `limit` number of items from the beginning of the source array/string are copied.
19249 * If the number is negative, `limit` number of items from the end of the source array/string
19250 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
19251 * the input will be returned unchanged.
19252 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
19253 * indicates an offset from the end of `input`. Defaults to `0`.
19254 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
19255 * had less than `limit` elements.
19258 <example module="limitToExample">
19259 <file name="index.html">
19261 angular.module('limitToExample', [])
19262 .controller('ExampleController', ['$scope', function($scope) {
19263 $scope.numbers = [1,2,3,4,5,6,7,8,9];
19264 $scope.letters = "abcdefghi";
19265 $scope.longNumber = 2345432342;
19266 $scope.numLimit = 3;
19267 $scope.letterLimit = 3;
19268 $scope.longNumberLimit = 3;
19271 <div ng-controller="ExampleController">
19273 Limit {{numbers}} to:
19274 <input type="number" step="1" ng-model="numLimit">
19276 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
19278 Limit {{letters}} to:
19279 <input type="number" step="1" ng-model="letterLimit">
19281 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
19283 Limit {{longNumber}} to:
19284 <input type="number" step="1" ng-model="longNumberLimit">
19286 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
19289 <file name="protractor.js" type="protractor">
19290 var numLimitInput = element(by.model('numLimit'));
19291 var letterLimitInput = element(by.model('letterLimit'));
19292 var longNumberLimitInput = element(by.model('longNumberLimit'));
19293 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
19294 var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
19295 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
19297 it('should limit the number array to first three items', function() {
19298 expect(numLimitInput.getAttribute('value')).toBe('3');
19299 expect(letterLimitInput.getAttribute('value')).toBe('3');
19300 expect(longNumberLimitInput.getAttribute('value')).toBe('3');
19301 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
19302 expect(limitedLetters.getText()).toEqual('Output letters: abc');
19303 expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
19306 // There is a bug in safari and protractor that doesn't like the minus key
19307 // it('should update the output when -3 is entered', function() {
19308 // numLimitInput.clear();
19309 // numLimitInput.sendKeys('-3');
19310 // letterLimitInput.clear();
19311 // letterLimitInput.sendKeys('-3');
19312 // longNumberLimitInput.clear();
19313 // longNumberLimitInput.sendKeys('-3');
19314 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
19315 // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
19316 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
19319 it('should not exceed the maximum size of input array', function() {
19320 numLimitInput.clear();
19321 numLimitInput.sendKeys('100');
19322 letterLimitInput.clear();
19323 letterLimitInput.sendKeys('100');
19324 longNumberLimitInput.clear();
19325 longNumberLimitInput.sendKeys('100');
19326 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
19327 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
19328 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
19333 function limitToFilter() {
19334 return function(input, limit, begin) {
19335 if (Math.abs(Number(limit)) === Infinity) {
19336 limit = Number(limit);
19338 limit = toInt(limit);
19340 if (isNaN(limit)) return input;
19342 if (isNumber(input)) input = input.toString();
19343 if (!isArray(input) && !isString(input)) return input;
19345 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
19346 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
19349 return input.slice(begin, begin + limit);
19352 return input.slice(limit, input.length);
19354 return input.slice(Math.max(0, begin + limit), begin);
19366 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
19367 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted
19368 * as expected, make sure they are actually being saved as numbers and not strings.
19370 * @param {Array} array The array to sort.
19371 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
19372 * used by the comparator to determine the order of elements.
19376 * - `function`: Getter function. The result of this function will be sorted using the
19377 * `<`, `===`, `>` operator.
19378 * - `string`: An Angular expression. The result of this expression is used to compare elements
19379 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
19380 * 3 first characters of a property called `name`). The result of a constant expression
19381 * is interpreted as a property name to be used in comparisons (for example `"special name"`
19382 * to sort object by the value of their `special name` property). An expression can be
19383 * optionally prefixed with `+` or `-` to control ascending or descending sort order
19384 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
19385 * element itself is used to compare where sorting.
19386 * - `Array`: An array of function or string predicates. The first predicate in the array
19387 * is used for sorting, but when two items are equivalent, the next predicate is used.
19389 * If the predicate is missing or empty then it defaults to `'+'`.
19391 * @param {boolean=} reverse Reverse the order of the array.
19392 * @returns {Array} Sorted copy of the source array.
19396 * The example below demonstrates a simple ngRepeat, where the data is sorted
19397 * by age in descending order (predicate is set to `'-age'`).
19398 * `reverse` is not set, which means it defaults to `false`.
19399 <example module="orderByExample">
19400 <file name="index.html">
19402 angular.module('orderByExample', [])
19403 .controller('ExampleController', ['$scope', function($scope) {
19405 [{name:'John', phone:'555-1212', age:10},
19406 {name:'Mary', phone:'555-9876', age:19},
19407 {name:'Mike', phone:'555-4321', age:21},
19408 {name:'Adam', phone:'555-5678', age:35},
19409 {name:'Julie', phone:'555-8765', age:29}];
19412 <div ng-controller="ExampleController">
19413 <table class="friend">
19416 <th>Phone Number</th>
19419 <tr ng-repeat="friend in friends | orderBy:'-age'">
19420 <td>{{friend.name}}</td>
19421 <td>{{friend.phone}}</td>
19422 <td>{{friend.age}}</td>
19429 * The predicate and reverse parameters can be controlled dynamically through scope properties,
19430 * as shown in the next example.
19432 <example module="orderByExample">
19433 <file name="index.html">
19435 angular.module('orderByExample', [])
19436 .controller('ExampleController', ['$scope', function($scope) {
19438 [{name:'John', phone:'555-1212', age:10},
19439 {name:'Mary', phone:'555-9876', age:19},
19440 {name:'Mike', phone:'555-4321', age:21},
19441 {name:'Adam', phone:'555-5678', age:35},
19442 {name:'Julie', phone:'555-8765', age:29}];
19443 $scope.predicate = 'age';
19444 $scope.reverse = true;
19445 $scope.order = function(predicate) {
19446 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
19447 $scope.predicate = predicate;
19451 <style type="text/css">
19455 .sortorder.reverse:after {
19459 <div ng-controller="ExampleController">
19460 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
19462 [ <a href="" ng-click="predicate=''">unsorted</a> ]
19463 <table class="friend">
19466 <a href="" ng-click="order('name')">Name</a>
19467 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
19470 <a href="" ng-click="order('phone')">Phone Number</a>
19471 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
19474 <a href="" ng-click="order('age')">Age</a>
19475 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
19478 <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
19479 <td>{{friend.name}}</td>
19480 <td>{{friend.phone}}</td>
19481 <td>{{friend.age}}</td>
19488 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
19489 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the
19490 * desired parameters.
19495 <example module="orderByExample">
19496 <file name="index.html">
19497 <div ng-controller="ExampleController">
19498 <table class="friend">
19500 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a>
19501 (<a href="" ng-click="order('-name',false)">^</a>)</th>
19502 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th>
19503 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th>
19505 <tr ng-repeat="friend in friends">
19506 <td>{{friend.name}}</td>
19507 <td>{{friend.phone}}</td>
19508 <td>{{friend.age}}</td>
19514 <file name="script.js">
19515 angular.module('orderByExample', [])
19516 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) {
19517 var orderBy = $filter('orderBy');
19519 { name: 'John', phone: '555-1212', age: 10 },
19520 { name: 'Mary', phone: '555-9876', age: 19 },
19521 { name: 'Mike', phone: '555-4321', age: 21 },
19522 { name: 'Adam', phone: '555-5678', age: 35 },
19523 { name: 'Julie', phone: '555-8765', age: 29 }
19525 $scope.order = function(predicate, reverse) {
19526 $scope.friends = orderBy($scope.friends, predicate, reverse);
19528 $scope.order('-age',false);
19533 orderByFilter.$inject = ['$parse'];
19534 function orderByFilter($parse) {
19535 return function(array, sortPredicate, reverseOrder) {
19537 if (!(isArrayLike(array))) return array;
19539 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
19540 if (sortPredicate.length === 0) { sortPredicate = ['+']; }
19542 var predicates = processPredicates(sortPredicate, reverseOrder);
19543 // Add a predicate at the end that evaluates to the element index. This makes the
19544 // sort stable as it works as a tie-breaker when all the input predicates cannot
19545 // distinguish between two elements.
19546 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1});
19548 // The next three lines are a version of a Swartzian Transform idiom from Perl
19549 // (sometimes called the Decorate-Sort-Undecorate idiom)
19550 // See https://en.wikipedia.org/wiki/Schwartzian_transform
19551 var compareValues = Array.prototype.map.call(array, getComparisonObject);
19552 compareValues.sort(doComparison);
19553 array = compareValues.map(function(item) { return item.value; });
19557 function getComparisonObject(value, index) {
19560 predicateValues: predicates.map(function(predicate) {
19561 return getPredicateValue(predicate.get(value), index);
19566 function doComparison(v1, v2) {
19568 for (var index=0, length = predicates.length; index < length; ++index) {
19569 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending;
19576 function processPredicates(sortPredicate, reverseOrder) {
19577 reverseOrder = reverseOrder ? -1 : 1;
19578 return sortPredicate.map(function(predicate) {
19579 var descending = 1, get = identity;
19581 if (isFunction(predicate)) {
19583 } else if (isString(predicate)) {
19584 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
19585 descending = predicate.charAt(0) == '-' ? -1 : 1;
19586 predicate = predicate.substring(1);
19588 if (predicate !== '') {
19589 get = $parse(predicate);
19590 if (get.constant) {
19592 get = function(value) { return value[key]; };
19596 return { get: get, descending: descending * reverseOrder };
19600 function isPrimitive(value) {
19601 switch (typeof value) {
19602 case 'number': /* falls through */
19603 case 'boolean': /* falls through */
19611 function objectValue(value, index) {
19612 // If `valueOf` is a valid function use that
19613 if (typeof value.valueOf === 'function') {
19614 value = value.valueOf();
19615 if (isPrimitive(value)) return value;
19617 // If `toString` is a valid function and not the one from `Object.prototype` use that
19618 if (hasCustomToString(value)) {
19619 value = value.toString();
19620 if (isPrimitive(value)) return value;
19622 // We have a basic object so we use the position of the object in the collection
19626 function getPredicateValue(value, index) {
19627 var type = typeof value;
19628 if (value === null) {
19631 } else if (type === 'string') {
19632 value = value.toLowerCase();
19633 } else if (type === 'object') {
19634 value = objectValue(value, index);
19636 return { value: value, type: type };
19639 function compare(v1, v2) {
19641 if (v1.type === v2.type) {
19642 if (v1.value !== v2.value) {
19643 result = v1.value < v2.value ? -1 : 1;
19646 result = v1.type < v2.type ? -1 : 1;
19652 function ngDirective(directive) {
19653 if (isFunction(directive)) {
19658 directive.restrict = directive.restrict || 'AC';
19659 return valueFn(directive);
19668 * Modifies the default behavior of the html A tag so that the default action is prevented when
19669 * the href attribute is empty.
19671 * This change permits the easy creation of action links with the `ngClick` directive
19672 * without changing the location or causing page reloads, e.g.:
19673 * `<a href="" ng-click="list.addItem()">Add Item</a>`
19675 var htmlAnchorDirective = valueFn({
19677 compile: function(element, attr) {
19678 if (!attr.href && !attr.xlinkHref) {
19679 return function(scope, element) {
19680 // If the linked element is not an anchor tag anymore, do nothing
19681 if (element[0].nodeName.toLowerCase() !== 'a') return;
19683 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
19684 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
19685 'xlink:href' : 'href';
19686 element.on('click', function(event) {
19687 // if we have no href url, then don't navigate anywhere.
19688 if (!element.attr(href)) {
19689 event.preventDefault();
19704 * Using Angular markup like `{{hash}}` in an href attribute will
19705 * make the link go to the wrong URL if the user clicks it before
19706 * Angular has a chance to replace the `{{hash}}` markup with its
19707 * value. Until Angular replaces the markup the link will be broken
19708 * and will most likely return a 404 error. The `ngHref` directive
19709 * solves this problem.
19711 * The wrong way to write it:
19713 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19716 * The correct way to write it:
19718 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19722 * @param {template} ngHref any string which can contain `{{}}` markup.
19725 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
19726 * in links and their different behaviors:
19728 <file name="index.html">
19729 <input ng-model="value" /><br />
19730 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
19731 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
19732 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
19733 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
19734 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
19735 <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
19737 <file name="protractor.js" type="protractor">
19738 it('should execute ng-click but not reload when href without value', function() {
19739 element(by.id('link-1')).click();
19740 expect(element(by.model('value')).getAttribute('value')).toEqual('1');
19741 expect(element(by.id('link-1')).getAttribute('href')).toBe('');
19744 it('should execute ng-click but not reload when href empty string', function() {
19745 element(by.id('link-2')).click();
19746 expect(element(by.model('value')).getAttribute('value')).toEqual('2');
19747 expect(element(by.id('link-2')).getAttribute('href')).toBe('');
19750 it('should execute ng-click and change url when ng-href specified', function() {
19751 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
19753 element(by.id('link-3')).click();
19755 // At this point, we navigate away from an Angular page, so we need
19756 // to use browser.driver to get the base webdriver.
19758 browser.wait(function() {
19759 return browser.driver.getCurrentUrl().then(function(url) {
19760 return url.match(/\/123$/);
19762 }, 5000, 'page should navigate to /123');
19765 it('should execute ng-click but not reload when href empty string and name specified', function() {
19766 element(by.id('link-4')).click();
19767 expect(element(by.model('value')).getAttribute('value')).toEqual('4');
19768 expect(element(by.id('link-4')).getAttribute('href')).toBe('');
19771 it('should execute ng-click but not reload when no href but name specified', function() {
19772 element(by.id('link-5')).click();
19773 expect(element(by.model('value')).getAttribute('value')).toEqual('5');
19774 expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
19777 it('should only change url when only ng-href', function() {
19778 element(by.model('value')).clear();
19779 element(by.model('value')).sendKeys('6');
19780 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
19782 element(by.id('link-6')).click();
19784 // At this point, we navigate away from an Angular page, so we need
19785 // to use browser.driver to get the base webdriver.
19786 browser.wait(function() {
19787 return browser.driver.getCurrentUrl().then(function(url) {
19788 return url.match(/\/6$/);
19790 }, 5000, 'page should navigate to /6');
19803 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
19804 * work right: The browser will fetch from the URL with the literal
19805 * text `{{hash}}` until Angular replaces the expression inside
19806 * `{{hash}}`. The `ngSrc` directive solves this problem.
19808 * The buggy way to write it:
19810 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
19813 * The correct way to write it:
19815 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
19819 * @param {template} ngSrc any string which can contain `{{}}` markup.
19829 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
19830 * work right: The browser will fetch from the URL with the literal
19831 * text `{{hash}}` until Angular replaces the expression inside
19832 * `{{hash}}`. The `ngSrcset` directive solves this problem.
19834 * The buggy way to write it:
19836 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
19839 * The correct way to write it:
19841 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
19845 * @param {template} ngSrcset any string which can contain `{{}}` markup.
19856 * This directive sets the `disabled` attribute on the element if the
19857 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
19859 * A special directive is necessary because we cannot use interpolation inside the `disabled`
19860 * attribute. The following example would make the button enabled on Chrome/Firefox
19861 * but not on older IEs:
19864 * <!-- See below for an example of ng-disabled being used correctly -->
19865 * <div ng-init="isDisabled = false">
19866 * <button disabled="{{isDisabled}}">Disabled</button>
19870 * This is because the HTML specification does not require browsers to preserve the values of
19871 * boolean attributes such as `disabled` (Their presence means true and their absence means false.)
19872 * If we put an Angular interpolation expression into such an attribute then the
19873 * binding information would be lost when the browser removes the attribute.
19877 <file name="index.html">
19878 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
19879 <button ng-model="button" ng-disabled="checked">Button</button>
19881 <file name="protractor.js" type="protractor">
19882 it('should toggle button', function() {
19883 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
19884 element(by.model('checked')).click();
19885 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
19891 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
19892 * then the `disabled` attribute will be set on the element
19903 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
19905 * Note that this directive should not be used together with {@link ngModel `ngModel`},
19906 * as this can lead to unexpected behavior.
19908 * ### Why do we need `ngChecked`?
19910 * The HTML specification does not require browsers to preserve the values of boolean attributes
19911 * such as checked. (Their presence means true and their absence means false.)
19912 * If we put an Angular interpolation expression into such an attribute then the
19913 * binding information would be lost when the browser removes the attribute.
19914 * The `ngChecked` directive solves this problem for the `checked` attribute.
19915 * This complementary directive is not removed by the browser and so provides
19916 * a permanent reliable place to store the binding information.
19919 <file name="index.html">
19920 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/>
19921 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input">
19923 <file name="protractor.js" type="protractor">
19924 it('should check both checkBoxes', function() {
19925 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
19926 element(by.model('master')).click();
19927 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
19933 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
19934 * then the `checked` attribute will be set on the element
19945 * The HTML specification does not require browsers to preserve the values of boolean attributes
19946 * such as readonly. (Their presence means true and their absence means false.)
19947 * If we put an Angular interpolation expression into such an attribute then the
19948 * binding information would be lost when the browser removes the attribute.
19949 * The `ngReadonly` directive solves this problem for the `readonly` attribute.
19950 * This complementary directive is not removed by the browser and so provides
19951 * a permanent reliable place to store the binding information.
19954 <file name="index.html">
19955 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
19956 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" />
19958 <file name="protractor.js" type="protractor">
19959 it('should toggle readonly attr', function() {
19960 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
19961 element(by.model('checked')).click();
19962 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
19968 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
19969 * then special attribute "readonly" will be set on the element
19980 * The HTML specification does not require browsers to preserve the values of boolean attributes
19981 * such as selected. (Their presence means true and their absence means false.)
19982 * If we put an Angular interpolation expression into such an attribute then the
19983 * binding information would be lost when the browser removes the attribute.
19984 * The `ngSelected` directive solves this problem for the `selected` attribute.
19985 * This complementary directive is not removed by the browser and so provides
19986 * a permanent reliable place to store the binding information.
19990 <file name="index.html">
19991 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
19992 <select aria-label="ngSelected demo">
19993 <option>Hello!</option>
19994 <option id="greet" ng-selected="selected">Greetings!</option>
19997 <file name="protractor.js" type="protractor">
19998 it('should select Greetings!', function() {
19999 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
20000 element(by.model('selected')).click();
20001 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
20007 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
20008 * then special attribute "selected" will be set on the element
20018 * The HTML specification does not require browsers to preserve the values of boolean attributes
20019 * such as open. (Their presence means true and their absence means false.)
20020 * If we put an Angular interpolation expression into such an attribute then the
20021 * binding information would be lost when the browser removes the attribute.
20022 * The `ngOpen` directive solves this problem for the `open` attribute.
20023 * This complementary directive is not removed by the browser and so provides
20024 * a permanent reliable place to store the binding information.
20027 <file name="index.html">
20028 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/>
20029 <details id="details" ng-open="open">
20030 <summary>Show/Hide me</summary>
20033 <file name="protractor.js" type="protractor">
20034 it('should toggle open', function() {
20035 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
20036 element(by.model('open')).click();
20037 expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
20043 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
20044 * then special attribute "open" will be set on the element
20047 var ngAttributeAliasDirectives = {};
20049 // boolean attrs are evaluated
20050 forEach(BOOLEAN_ATTR, function(propName, attrName) {
20051 // binding to multiple is not supported
20052 if (propName == "multiple") return;
20054 function defaultLinkFn(scope, element, attr) {
20055 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
20056 attr.$set(attrName, !!value);
20060 var normalized = directiveNormalize('ng-' + attrName);
20061 var linkFn = defaultLinkFn;
20063 if (propName === 'checked') {
20064 linkFn = function(scope, element, attr) {
20065 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
20066 if (attr.ngModel !== attr[normalized]) {
20067 defaultLinkFn(scope, element, attr);
20072 ngAttributeAliasDirectives[normalized] = function() {
20081 // aliased input attrs are evaluated
20082 forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
20083 ngAttributeAliasDirectives[ngAttr] = function() {
20086 link: function(scope, element, attr) {
20087 //special case ngPattern when a literal regular expression value
20088 //is used as the expression (this way we don't have to watch anything).
20089 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") {
20090 var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
20092 attr.$set("ngPattern", new RegExp(match[1], match[2]));
20097 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
20098 attr.$set(ngAttr, value);
20105 // ng-src, ng-srcset, ng-href are interpolated
20106 forEach(['src', 'srcset', 'href'], function(attrName) {
20107 var normalized = directiveNormalize('ng-' + attrName);
20108 ngAttributeAliasDirectives[normalized] = function() {
20110 priority: 99, // it needs to run after the attributes are interpolated
20111 link: function(scope, element, attr) {
20112 var propName = attrName,
20115 if (attrName === 'href' &&
20116 toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
20117 name = 'xlinkHref';
20118 attr.$attr[name] = 'xlink:href';
20122 attr.$observe(normalized, function(value) {
20124 if (attrName === 'href') {
20125 attr.$set(name, null);
20130 attr.$set(name, value);
20132 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
20133 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
20134 // to set the property as well to achieve the desired effect.
20135 // we use attr[attrName] value since $set can sanitize the url.
20136 if (msie && propName) element.prop(propName, attr[name]);
20143 /* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true
20145 var nullFormCtrl = {
20147 $$renameControl: nullFormRenameControl,
20148 $removeControl: noop,
20149 $setValidity: noop,
20151 $setPristine: noop,
20152 $setSubmitted: noop
20154 SUBMITTED_CLASS = 'ng-submitted';
20156 function nullFormRenameControl(control, name) {
20157 control.$name = name;
20162 * @name form.FormController
20164 * @property {boolean} $pristine True if user has not interacted with the form yet.
20165 * @property {boolean} $dirty True if user has already interacted with the form.
20166 * @property {boolean} $valid True if all of the containing forms and controls are valid.
20167 * @property {boolean} $invalid True if at least one containing control or form is invalid.
20168 * @property {boolean} $pending True if at least one containing control or form is pending.
20169 * @property {boolean} $submitted True if user has submitted the form even if its invalid.
20171 * @property {Object} $error Is an object hash, containing references to controls or
20172 * forms with failing validators, where:
20174 * - keys are validation tokens (error names),
20175 * - values are arrays of controls or forms that have a failing validator for given error name.
20177 * Built-in validation tokens:
20189 * - `datetimelocal`
20195 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
20196 * such as being valid/invalid or dirty/pristine.
20198 * Each {@link ng.directive:form form} directive creates an instance
20199 * of `FormController`.
20202 //asks for $scope to fool the BC controller module
20203 FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
20204 function FormController(element, attrs, $scope, $animate, $interpolate) {
20210 form.$$success = {};
20211 form.$pending = undefined;
20212 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope);
20213 form.$dirty = false;
20214 form.$pristine = true;
20215 form.$valid = true;
20216 form.$invalid = false;
20217 form.$submitted = false;
20218 form.$$parentForm = nullFormCtrl;
20222 * @name form.FormController#$rollbackViewValue
20225 * Rollback all form controls pending updates to the `$modelValue`.
20227 * Updates may be pending by a debounced event or because the input is waiting for a some future
20228 * event defined in `ng-model-options`. This method is typically needed by the reset button of
20229 * a form that uses `ng-model-options` to pend updates.
20231 form.$rollbackViewValue = function() {
20232 forEach(controls, function(control) {
20233 control.$rollbackViewValue();
20239 * @name form.FormController#$commitViewValue
20242 * Commit all form controls pending updates to the `$modelValue`.
20244 * Updates may be pending by a debounced event or because the input is waiting for a some future
20245 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
20246 * usually handles calling this in response to input events.
20248 form.$commitViewValue = function() {
20249 forEach(controls, function(control) {
20250 control.$commitViewValue();
20256 * @name form.FormController#$addControl
20257 * @param {object} control control object, either a {@link form.FormController} or an
20258 * {@link ngModel.NgModelController}
20261 * Register a control with the form. Input elements using ngModelController do this automatically
20262 * when they are linked.
20264 * Note that the current state of the control will not be reflected on the new parent form. This
20265 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
20268 * However, if the method is used programmatically, for example by adding dynamically created controls,
20269 * or controls that have been previously removed without destroying their corresponding DOM element,
20270 * it's the developers responsiblity to make sure the current state propagates to the parent form.
20272 * For example, if an input control is added that is already `$dirty` and has `$error` properties,
20273 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
20275 form.$addControl = function(control) {
20276 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
20277 // and not added to the scope. Now we throw an error.
20278 assertNotHasOwnProperty(control.$name, 'input');
20279 controls.push(control);
20281 if (control.$name) {
20282 form[control.$name] = control;
20285 control.$$parentForm = form;
20288 // Private API: rename a form control
20289 form.$$renameControl = function(control, newName) {
20290 var oldName = control.$name;
20292 if (form[oldName] === control) {
20293 delete form[oldName];
20295 form[newName] = control;
20296 control.$name = newName;
20301 * @name form.FormController#$removeControl
20302 * @param {object} control control object, either a {@link form.FormController} or an
20303 * {@link ngModel.NgModelController}
20306 * Deregister a control from the form.
20308 * Input elements using ngModelController do this automatically when they are destroyed.
20310 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
20311 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
20312 * different from case to case. For example, removing the only `$dirty` control from a form may or
20313 * may not mean that the form is still `$dirty`.
20315 form.$removeControl = function(control) {
20316 if (control.$name && form[control.$name] === control) {
20317 delete form[control.$name];
20319 forEach(form.$pending, function(value, name) {
20320 form.$setValidity(name, null, control);
20322 forEach(form.$error, function(value, name) {
20323 form.$setValidity(name, null, control);
20325 forEach(form.$$success, function(value, name) {
20326 form.$setValidity(name, null, control);
20329 arrayRemove(controls, control);
20330 control.$$parentForm = nullFormCtrl;
20336 * @name form.FormController#$setValidity
20339 * Sets the validity of a form control.
20341 * This method will also propagate to parent forms.
20343 addSetValidityMethod({
20346 set: function(object, property, controller) {
20347 var list = object[property];
20349 object[property] = [controller];
20351 var index = list.indexOf(controller);
20352 if (index === -1) {
20353 list.push(controller);
20357 unset: function(object, property, controller) {
20358 var list = object[property];
20362 arrayRemove(list, controller);
20363 if (list.length === 0) {
20364 delete object[property];
20372 * @name form.FormController#$setDirty
20375 * Sets the form to a dirty state.
20377 * This method can be called to add the 'ng-dirty' class and set the form to a dirty
20378 * state (ng-dirty class). This method will also propagate to parent forms.
20380 form.$setDirty = function() {
20381 $animate.removeClass(element, PRISTINE_CLASS);
20382 $animate.addClass(element, DIRTY_CLASS);
20383 form.$dirty = true;
20384 form.$pristine = false;
20385 form.$$parentForm.$setDirty();
20390 * @name form.FormController#$setPristine
20393 * Sets the form to its pristine state.
20395 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
20396 * state (ng-pristine class). This method will also propagate to all the controls contained
20399 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
20400 * saving or resetting it.
20402 form.$setPristine = function() {
20403 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
20404 form.$dirty = false;
20405 form.$pristine = true;
20406 form.$submitted = false;
20407 forEach(controls, function(control) {
20408 control.$setPristine();
20414 * @name form.FormController#$setUntouched
20417 * Sets the form to its untouched state.
20419 * This method can be called to remove the 'ng-touched' class and set the form controls to their
20420 * untouched state (ng-untouched class).
20422 * Setting a form controls back to their untouched state is often useful when setting the form
20423 * back to its pristine state.
20425 form.$setUntouched = function() {
20426 forEach(controls, function(control) {
20427 control.$setUntouched();
20433 * @name form.FormController#$setSubmitted
20436 * Sets the form to its submitted state.
20438 form.$setSubmitted = function() {
20439 $animate.addClass(element, SUBMITTED_CLASS);
20440 form.$submitted = true;
20441 form.$$parentForm.$setSubmitted();
20451 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
20452 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
20453 * sub-group of controls needs to be determined.
20455 * Note: the purpose of `ngForm` is to group controls,
20456 * but not to be a replacement for the `<form>` tag with all of its capabilities
20457 * (e.g. posting to the server, ...).
20459 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
20460 * related scope, under this name.
20470 * Directive that instantiates
20471 * {@link form.FormController FormController}.
20473 * If the `name` attribute is specified, the form controller is published onto the current scope under
20476 * # Alias: {@link ng.directive:ngForm `ngForm`}
20478 * In Angular, forms can be nested. This means that the outer form is valid when all of the child
20479 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
20480 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
20481 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when
20482 * using Angular validation directives in forms that are dynamically generated using the
20483 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name`
20484 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an
20485 * `ngForm` directive and nest these in an outer `form` element.
20489 * - `ng-valid` is set if the form is valid.
20490 * - `ng-invalid` is set if the form is invalid.
20491 * - `ng-pending` is set if the form is pending.
20492 * - `ng-pristine` is set if the form is pristine.
20493 * - `ng-dirty` is set if the form is dirty.
20494 * - `ng-submitted` is set if the form was submitted.
20496 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
20499 * # Submitting a form and preventing the default action
20501 * Since the role of forms in client-side Angular applications is different than in classical
20502 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
20503 * page reload that sends the data to the server. Instead some javascript logic should be triggered
20504 * to handle the form submission in an application-specific way.
20506 * For this reason, Angular prevents the default action (form submission to the server) unless the
20507 * `<form>` element has an `action` attribute specified.
20509 * You can use one of the following two ways to specify what javascript method should be called when
20510 * a form is submitted:
20512 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
20513 * - {@link ng.directive:ngClick ngClick} directive on the first
20514 * button or input field of type submit (input[type=submit])
20516 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
20517 * or {@link ng.directive:ngClick ngClick} directives.
20518 * This is because of the following form submission rules in the HTML specification:
20520 * - If a form has only one input field then hitting enter in this field triggers form submit
20522 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
20523 * doesn't trigger submit
20524 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
20525 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
20526 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
20528 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
20529 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
20530 * to have access to the updated model.
20532 * ## Animation Hooks
20534 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
20535 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
20536 * other validations that are performed within the form. Animations in ngForm are similar to how
20537 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
20538 * as JS animations.
20540 * The following example shows a simple way to utilize CSS transitions to style a form element
20541 * that has been rendered as invalid after it has been validated:
20544 * //be sure to include ngAnimate as a module to hook into more
20545 * //advanced animations
20547 * transition:0.5s linear all;
20548 * background: white;
20550 * .my-form.ng-invalid {
20557 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
20558 <file name="index.html">
20560 angular.module('formExample', [])
20561 .controller('FormController', ['$scope', function($scope) {
20562 $scope.userType = 'guest';
20567 transition:all linear 0.5s;
20568 background: transparent;
20570 .my-form.ng-invalid {
20574 <form name="myForm" ng-controller="FormController" class="my-form">
20575 userType: <input name="input" ng-model="userType" required>
20576 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
20577 <code>userType = {{userType}}</code><br>
20578 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
20579 <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
20580 <code>myForm.$valid = {{myForm.$valid}}</code><br>
20581 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
20584 <file name="protractor.js" type="protractor">
20585 it('should initialize to model', function() {
20586 var userType = element(by.binding('userType'));
20587 var valid = element(by.binding('myForm.input.$valid'));
20589 expect(userType.getText()).toContain('guest');
20590 expect(valid.getText()).toContain('true');
20593 it('should be invalid if empty', function() {
20594 var userType = element(by.binding('userType'));
20595 var valid = element(by.binding('myForm.input.$valid'));
20596 var userInput = element(by.model('userType'));
20599 userInput.sendKeys('');
20601 expect(userType.getText()).toEqual('userType =');
20602 expect(valid.getText()).toContain('false');
20607 * @param {string=} name Name of the form. If specified, the form controller will be published into
20608 * related scope, under this name.
20610 var formDirectiveFactory = function(isNgForm) {
20611 return ['$timeout', '$parse', function($timeout, $parse) {
20612 var formDirective = {
20614 restrict: isNgForm ? 'EAC' : 'E',
20615 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
20616 controller: FormController,
20617 compile: function ngFormCompile(formElement, attr) {
20618 // Setup initial state of the control
20619 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
20621 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
20624 pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
20625 var controller = ctrls[0];
20627 // if `action` attr is not present on the form, prevent the default action (submission)
20628 if (!('action' in attr)) {
20629 // we can't use jq events because if a form is destroyed during submission the default
20630 // action is not prevented. see #1238
20632 // IE 9 is not affected because it doesn't fire a submit event and try to do a full
20633 // page reload if the form was destroyed by submission of the form via a click handler
20634 // on a button in the form. Looks like an IE9 specific bug.
20635 var handleFormSubmission = function(event) {
20636 scope.$apply(function() {
20637 controller.$commitViewValue();
20638 controller.$setSubmitted();
20641 event.preventDefault();
20644 addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20646 // unregister the preventDefault listener so that we don't not leak memory but in a
20647 // way that will achieve the prevention of the default action.
20648 formElement.on('$destroy', function() {
20649 $timeout(function() {
20650 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20655 var parentFormCtrl = ctrls[1] || controller.$$parentForm;
20656 parentFormCtrl.$addControl(controller);
20658 var setter = nameAttr ? getSetter(controller.$name) : noop;
20661 setter(scope, controller);
20662 attr.$observe(nameAttr, function(newValue) {
20663 if (controller.$name === newValue) return;
20664 setter(scope, undefined);
20665 controller.$$parentForm.$$renameControl(controller, newValue);
20666 setter = getSetter(controller.$name);
20667 setter(scope, controller);
20670 formElement.on('$destroy', function() {
20671 controller.$$parentForm.$removeControl(controller);
20672 setter(scope, undefined);
20673 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
20680 return formDirective;
20682 function getSetter(expression) {
20683 if (expression === '') {
20684 //create an assignable expression, so forms with an empty name can be renamed later
20685 return $parse('this[""]').assign;
20687 return $parse(expression).assign || noop;
20692 var formDirective = formDirectiveFactory();
20693 var ngFormDirective = formDirectiveFactory(true);
20695 /* global VALID_CLASS: false,
20696 INVALID_CLASS: false,
20697 PRISTINE_CLASS: false,
20698 DIRTY_CLASS: false,
20699 UNTOUCHED_CLASS: false,
20700 TOUCHED_CLASS: false,
20701 ngModelMinErr: false,
20704 // Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
20705 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)/;
20706 // See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
20707 var URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/;
20708 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;
20709 var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
20710 var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
20711 var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20712 var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
20713 var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
20714 var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20720 * @name input[text]
20723 * Standard HTML text input with angular data binding, inherited by most of the `input` elements.
20726 * @param {string} ngModel Assignable angular expression to data-bind to.
20727 * @param {string=} name Property name of the form under which the control is published.
20728 * @param {string=} required Adds `required` validation error key if the value is not entered.
20729 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20730 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20731 * `required` when you want to data-bind to the `required` attribute.
20732 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
20734 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
20735 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
20737 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
20738 * that contains the regular expression body that will be converted to a regular expression
20739 * as in the ngPattern directive.
20740 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
20741 * a RegExp found by evaluating the Angular expression given in the attribute value.
20742 * If the expression evaluates to a RegExp object, then this is used directly.
20743 * If the expression evaluates to a string, then it will be converted to a RegExp
20744 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
20745 * `new RegExp('^abc$')`.<br />
20746 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
20747 * start at the index of the last search's match, thus not taking the whole input value into
20749 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20750 * interaction with the input element.
20751 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
20752 * This parameter is ignored for input[type=password] controls, which will never trim the
20756 <example name="text-input-directive" module="textInputExample">
20757 <file name="index.html">
20759 angular.module('textInputExample', [])
20760 .controller('ExampleController', ['$scope', function($scope) {
20763 word: /^\s*\w*\s*$/
20767 <form name="myForm" ng-controller="ExampleController">
20768 <label>Single word:
20769 <input type="text" name="input" ng-model="example.text"
20770 ng-pattern="example.word" required ng-trim="false">
20773 <span class="error" ng-show="myForm.input.$error.required">
20775 <span class="error" ng-show="myForm.input.$error.pattern">
20776 Single word only!</span>
20778 <tt>text = {{example.text}}</tt><br/>
20779 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20780 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20781 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20782 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20785 <file name="protractor.js" type="protractor">
20786 var text = element(by.binding('example.text'));
20787 var valid = element(by.binding('myForm.input.$valid'));
20788 var input = element(by.model('example.text'));
20790 it('should initialize to model', function() {
20791 expect(text.getText()).toContain('guest');
20792 expect(valid.getText()).toContain('true');
20795 it('should be invalid if empty', function() {
20797 input.sendKeys('');
20799 expect(text.getText()).toEqual('text =');
20800 expect(valid.getText()).toContain('false');
20803 it('should be invalid if multi word', function() {
20805 input.sendKeys('hello world');
20807 expect(valid.getText()).toContain('false');
20812 'text': textInputType,
20816 * @name input[date]
20819 * Input with date validation and transformation. In browsers that do not yet support
20820 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
20821 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
20822 * modern browsers do not yet support this input type, it is important to provide cues to users on the
20823 * expected input format via a placeholder or label.
20825 * The model must always be a Date object, otherwise Angular will throw an error.
20826 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20828 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20829 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20831 * @param {string} ngModel Assignable angular expression to data-bind to.
20832 * @param {string=} name Property name of the form under which the control is published.
20833 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
20834 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20835 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
20836 * constraint validation.
20837 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
20838 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20839 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
20840 * constraint validation.
20841 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
20842 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20843 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
20844 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20845 * @param {string=} required Sets `required` validation error key if the value is not entered.
20846 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20847 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20848 * `required` when you want to data-bind to the `required` attribute.
20849 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20850 * interaction with the input element.
20853 <example name="date-input-directive" module="dateInputExample">
20854 <file name="index.html">
20856 angular.module('dateInputExample', [])
20857 .controller('DateController', ['$scope', function($scope) {
20859 value: new Date(2013, 9, 22)
20863 <form name="myForm" ng-controller="DateController as dateCtrl">
20864 <label for="exampleInput">Pick a date in 2013:</label>
20865 <input type="date" id="exampleInput" name="input" ng-model="example.value"
20866 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
20868 <span class="error" ng-show="myForm.input.$error.required">
20870 <span class="error" ng-show="myForm.input.$error.date">
20871 Not a valid date!</span>
20873 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
20874 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20875 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20876 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20877 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20880 <file name="protractor.js" type="protractor">
20881 var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
20882 var valid = element(by.binding('myForm.input.$valid'));
20883 var input = element(by.model('example.value'));
20885 // currently protractor/webdriver does not support
20886 // sending keys to all known HTML5 input controls
20887 // for various browsers (see https://github.com/angular/protractor/issues/562).
20888 function setInput(val) {
20889 // set the value of the element and force validation.
20890 var scr = "var ipt = document.getElementById('exampleInput'); " +
20891 "ipt.value = '" + val + "';" +
20892 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20893 browser.executeScript(scr);
20896 it('should initialize to model', function() {
20897 expect(value.getText()).toContain('2013-10-22');
20898 expect(valid.getText()).toContain('myForm.input.$valid = true');
20901 it('should be invalid if empty', function() {
20903 expect(value.getText()).toEqual('value =');
20904 expect(valid.getText()).toContain('myForm.input.$valid = false');
20907 it('should be invalid if over max', function() {
20908 setInput('2015-01-01');
20909 expect(value.getText()).toContain('');
20910 expect(valid.getText()).toContain('myForm.input.$valid = false');
20915 'date': createDateInputType('date', DATE_REGEXP,
20916 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
20921 * @name input[datetime-local]
20924 * Input with datetime validation and transformation. In browsers that do not yet support
20925 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
20926 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
20928 * The model must always be a Date object, otherwise Angular will throw an error.
20929 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20931 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20932 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20934 * @param {string} ngModel Assignable angular expression to data-bind to.
20935 * @param {string=} name Property name of the form under which the control is published.
20936 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
20937 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20938 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20939 * Note that `min` will also add native HTML5 constraint validation.
20940 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
20941 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20942 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20943 * Note that `max` will also add native HTML5 constraint validation.
20944 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
20945 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20946 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
20947 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20948 * @param {string=} required Sets `required` validation error key if the value is not entered.
20949 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20950 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20951 * `required` when you want to data-bind to the `required` attribute.
20952 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20953 * interaction with the input element.
20956 <example name="datetimelocal-input-directive" module="dateExample">
20957 <file name="index.html">
20959 angular.module('dateExample', [])
20960 .controller('DateController', ['$scope', function($scope) {
20962 value: new Date(2010, 11, 28, 14, 57)
20966 <form name="myForm" ng-controller="DateController as dateCtrl">
20967 <label for="exampleInput">Pick a date between in 2013:</label>
20968 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
20969 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
20971 <span class="error" ng-show="myForm.input.$error.required">
20973 <span class="error" ng-show="myForm.input.$error.datetimelocal">
20974 Not a valid date!</span>
20976 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
20977 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20978 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20979 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20980 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20983 <file name="protractor.js" type="protractor">
20984 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
20985 var valid = element(by.binding('myForm.input.$valid'));
20986 var input = element(by.model('example.value'));
20988 // currently protractor/webdriver does not support
20989 // sending keys to all known HTML5 input controls
20990 // for various browsers (https://github.com/angular/protractor/issues/562).
20991 function setInput(val) {
20992 // set the value of the element and force validation.
20993 var scr = "var ipt = document.getElementById('exampleInput'); " +
20994 "ipt.value = '" + val + "';" +
20995 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20996 browser.executeScript(scr);
20999 it('should initialize to model', function() {
21000 expect(value.getText()).toContain('2010-12-28T14:57:00');
21001 expect(valid.getText()).toContain('myForm.input.$valid = true');
21004 it('should be invalid if empty', function() {
21006 expect(value.getText()).toEqual('value =');
21007 expect(valid.getText()).toContain('myForm.input.$valid = false');
21010 it('should be invalid if over max', function() {
21011 setInput('2015-01-01T23:59:00');
21012 expect(value.getText()).toContain('');
21013 expect(valid.getText()).toContain('myForm.input.$valid = false');
21018 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
21019 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
21020 'yyyy-MM-ddTHH:mm:ss.sss'),
21024 * @name input[time]
21027 * Input with time validation and transformation. In browsers that do not yet support
21028 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21029 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
21030 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
21032 * The model must always be a Date object, otherwise Angular will throw an error.
21033 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21035 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21036 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21038 * @param {string} ngModel Assignable angular expression to data-bind to.
21039 * @param {string=} name Property name of the form under which the control is published.
21040 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21041 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21042 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
21043 * native HTML5 constraint validation.
21044 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21045 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21046 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
21047 * native HTML5 constraint validation.
21048 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
21049 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21050 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
21051 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21052 * @param {string=} required Sets `required` validation error key if the value is not entered.
21053 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21054 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21055 * `required` when you want to data-bind to the `required` attribute.
21056 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21057 * interaction with the input element.
21060 <example name="time-input-directive" module="timeExample">
21061 <file name="index.html">
21063 angular.module('timeExample', [])
21064 .controller('DateController', ['$scope', function($scope) {
21066 value: new Date(1970, 0, 1, 14, 57, 0)
21070 <form name="myForm" ng-controller="DateController as dateCtrl">
21071 <label for="exampleInput">Pick a between 8am and 5pm:</label>
21072 <input type="time" id="exampleInput" name="input" ng-model="example.value"
21073 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
21075 <span class="error" ng-show="myForm.input.$error.required">
21077 <span class="error" ng-show="myForm.input.$error.time">
21078 Not a valid date!</span>
21080 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
21081 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21082 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21083 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21084 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21087 <file name="protractor.js" type="protractor">
21088 var value = element(by.binding('example.value | date: "HH:mm:ss"'));
21089 var valid = element(by.binding('myForm.input.$valid'));
21090 var input = element(by.model('example.value'));
21092 // currently protractor/webdriver does not support
21093 // sending keys to all known HTML5 input controls
21094 // for various browsers (https://github.com/angular/protractor/issues/562).
21095 function setInput(val) {
21096 // set the value of the element and force validation.
21097 var scr = "var ipt = document.getElementById('exampleInput'); " +
21098 "ipt.value = '" + val + "';" +
21099 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21100 browser.executeScript(scr);
21103 it('should initialize to model', function() {
21104 expect(value.getText()).toContain('14:57:00');
21105 expect(valid.getText()).toContain('myForm.input.$valid = true');
21108 it('should be invalid if empty', function() {
21110 expect(value.getText()).toEqual('value =');
21111 expect(valid.getText()).toContain('myForm.input.$valid = false');
21114 it('should be invalid if over max', function() {
21115 setInput('23:59:00');
21116 expect(value.getText()).toContain('');
21117 expect(valid.getText()).toContain('myForm.input.$valid = false');
21122 'time': createDateInputType('time', TIME_REGEXP,
21123 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
21128 * @name input[week]
21131 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
21132 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21133 * week format (yyyy-W##), for example: `2013-W02`.
21135 * The model must always be a Date object, otherwise Angular will throw an error.
21136 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21138 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21139 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21141 * @param {string} ngModel Assignable angular expression to data-bind to.
21142 * @param {string=} name Property name of the form under which the control is published.
21143 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21144 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21145 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
21146 * native HTML5 constraint validation.
21147 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21148 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21149 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
21150 * native HTML5 constraint validation.
21151 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21152 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21153 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21154 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21155 * @param {string=} required Sets `required` validation error key if the value is not entered.
21156 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21157 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21158 * `required` when you want to data-bind to the `required` attribute.
21159 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21160 * interaction with the input element.
21163 <example name="week-input-directive" module="weekExample">
21164 <file name="index.html">
21166 angular.module('weekExample', [])
21167 .controller('DateController', ['$scope', function($scope) {
21169 value: new Date(2013, 0, 3)
21173 <form name="myForm" ng-controller="DateController as dateCtrl">
21174 <label>Pick a date between in 2013:
21175 <input id="exampleInput" type="week" name="input" ng-model="example.value"
21176 placeholder="YYYY-W##" min="2012-W32"
21177 max="2013-W52" required />
21180 <span class="error" ng-show="myForm.input.$error.required">
21182 <span class="error" ng-show="myForm.input.$error.week">
21183 Not a valid date!</span>
21185 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
21186 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21187 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21188 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21189 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21192 <file name="protractor.js" type="protractor">
21193 var value = element(by.binding('example.value | date: "yyyy-Www"'));
21194 var valid = element(by.binding('myForm.input.$valid'));
21195 var input = element(by.model('example.value'));
21197 // currently protractor/webdriver does not support
21198 // sending keys to all known HTML5 input controls
21199 // for various browsers (https://github.com/angular/protractor/issues/562).
21200 function setInput(val) {
21201 // set the value of the element and force validation.
21202 var scr = "var ipt = document.getElementById('exampleInput'); " +
21203 "ipt.value = '" + val + "';" +
21204 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21205 browser.executeScript(scr);
21208 it('should initialize to model', function() {
21209 expect(value.getText()).toContain('2013-W01');
21210 expect(valid.getText()).toContain('myForm.input.$valid = true');
21213 it('should be invalid if empty', function() {
21215 expect(value.getText()).toEqual('value =');
21216 expect(valid.getText()).toContain('myForm.input.$valid = false');
21219 it('should be invalid if over max', function() {
21220 setInput('2015-W01');
21221 expect(value.getText()).toContain('');
21222 expect(valid.getText()).toContain('myForm.input.$valid = false');
21227 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),
21231 * @name input[month]
21234 * Input with month validation and transformation. In browsers that do not yet support
21235 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21236 * month format (yyyy-MM), for example: `2009-01`.
21238 * The model must always be a Date object, otherwise Angular will throw an error.
21239 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21240 * If the model is not set to the first of the month, the next view to model update will set it
21241 * to the first of the month.
21243 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21244 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21246 * @param {string} ngModel Assignable angular expression to data-bind to.
21247 * @param {string=} name Property name of the form under which the control is published.
21248 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21249 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21250 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
21251 * native HTML5 constraint validation.
21252 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21253 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21254 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
21255 * native HTML5 constraint validation.
21256 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21257 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21258 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21259 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21261 * @param {string=} required Sets `required` validation error key if the value is not entered.
21262 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21263 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21264 * `required` when you want to data-bind to the `required` attribute.
21265 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21266 * interaction with the input element.
21269 <example name="month-input-directive" module="monthExample">
21270 <file name="index.html">
21272 angular.module('monthExample', [])
21273 .controller('DateController', ['$scope', function($scope) {
21275 value: new Date(2013, 9, 1)
21279 <form name="myForm" ng-controller="DateController as dateCtrl">
21280 <label for="exampleInput">Pick a month in 2013:</label>
21281 <input id="exampleInput" type="month" name="input" ng-model="example.value"
21282 placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
21284 <span class="error" ng-show="myForm.input.$error.required">
21286 <span class="error" ng-show="myForm.input.$error.month">
21287 Not a valid month!</span>
21289 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
21290 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21291 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21292 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21293 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21296 <file name="protractor.js" type="protractor">
21297 var value = element(by.binding('example.value | date: "yyyy-MM"'));
21298 var valid = element(by.binding('myForm.input.$valid'));
21299 var input = element(by.model('example.value'));
21301 // currently protractor/webdriver does not support
21302 // sending keys to all known HTML5 input controls
21303 // for various browsers (https://github.com/angular/protractor/issues/562).
21304 function setInput(val) {
21305 // set the value of the element and force validation.
21306 var scr = "var ipt = document.getElementById('exampleInput'); " +
21307 "ipt.value = '" + val + "';" +
21308 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21309 browser.executeScript(scr);
21312 it('should initialize to model', function() {
21313 expect(value.getText()).toContain('2013-10');
21314 expect(valid.getText()).toContain('myForm.input.$valid = true');
21317 it('should be invalid if empty', function() {
21319 expect(value.getText()).toEqual('value =');
21320 expect(valid.getText()).toContain('myForm.input.$valid = false');
21323 it('should be invalid if over max', function() {
21324 setInput('2015-01');
21325 expect(value.getText()).toContain('');
21326 expect(valid.getText()).toContain('myForm.input.$valid = false');
21331 'month': createDateInputType('month', MONTH_REGEXP,
21332 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
21337 * @name input[number]
21340 * Text input with number validation and transformation. Sets the `number` validation
21341 * error if not a valid number.
21343 * <div class="alert alert-warning">
21344 * The model must always be of type `number` otherwise Angular will throw an error.
21345 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
21346 * error docs for more information and an example of how to convert your model if necessary.
21349 * ## Issues with HTML5 constraint validation
21351 * In browsers that follow the
21352 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
21353 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
21354 * If a non-number is entered in the input, the browser will report the value as an empty string,
21355 * which means the view / model values in `ngModel` and subsequently the scope value
21356 * will also be an empty string.
21359 * @param {string} ngModel Assignable angular expression to data-bind to.
21360 * @param {string=} name Property name of the form under which the control is published.
21361 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21362 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21363 * @param {string=} required Sets `required` validation error key if the value is not entered.
21364 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21365 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21366 * `required` when you want to data-bind to the `required` attribute.
21367 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21369 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21370 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21372 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21373 * that contains the regular expression body that will be converted to a regular expression
21374 * as in the ngPattern directive.
21375 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21376 * a RegExp found by evaluating the Angular expression given in the attribute value.
21377 * If the expression evaluates to a RegExp object, then this is used directly.
21378 * If the expression evaluates to a string, then it will be converted to a RegExp
21379 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21380 * `new RegExp('^abc$')`.<br />
21381 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21382 * start at the index of the last search's match, thus not taking the whole input value into
21384 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21385 * interaction with the input element.
21388 <example name="number-input-directive" module="numberExample">
21389 <file name="index.html">
21391 angular.module('numberExample', [])
21392 .controller('ExampleController', ['$scope', function($scope) {
21398 <form name="myForm" ng-controller="ExampleController">
21400 <input type="number" name="input" ng-model="example.value"
21401 min="0" max="99" required>
21404 <span class="error" ng-show="myForm.input.$error.required">
21406 <span class="error" ng-show="myForm.input.$error.number">
21407 Not valid number!</span>
21409 <tt>value = {{example.value}}</tt><br/>
21410 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21411 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21412 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21413 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21416 <file name="protractor.js" type="protractor">
21417 var value = element(by.binding('example.value'));
21418 var valid = element(by.binding('myForm.input.$valid'));
21419 var input = element(by.model('example.value'));
21421 it('should initialize to model', function() {
21422 expect(value.getText()).toContain('12');
21423 expect(valid.getText()).toContain('true');
21426 it('should be invalid if empty', function() {
21428 input.sendKeys('');
21429 expect(value.getText()).toEqual('value =');
21430 expect(valid.getText()).toContain('false');
21433 it('should be invalid if over max', function() {
21435 input.sendKeys('123');
21436 expect(value.getText()).toEqual('value =');
21437 expect(valid.getText()).toContain('false');
21442 'number': numberInputType,
21450 * Text input with URL validation. Sets the `url` validation error key if the content is not a
21453 * <div class="alert alert-warning">
21454 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
21455 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
21456 * the built-in validators (see the {@link guide/forms Forms guide})
21459 * @param {string} ngModel Assignable angular expression to data-bind to.
21460 * @param {string=} name Property name of the form under which the control is published.
21461 * @param {string=} required Sets `required` validation error key if the value is not entered.
21462 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21463 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21464 * `required` when you want to data-bind to the `required` attribute.
21465 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21467 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21468 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21470 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21471 * that contains the regular expression body that will be converted to a regular expression
21472 * as in the ngPattern directive.
21473 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21474 * a RegExp found by evaluating the Angular expression given in the attribute value.
21475 * If the expression evaluates to a RegExp object, then this is used directly.
21476 * If the expression evaluates to a string, then it will be converted to a RegExp
21477 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21478 * `new RegExp('^abc$')`.<br />
21479 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21480 * start at the index of the last search's match, thus not taking the whole input value into
21482 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21483 * interaction with the input element.
21486 <example name="url-input-directive" module="urlExample">
21487 <file name="index.html">
21489 angular.module('urlExample', [])
21490 .controller('ExampleController', ['$scope', function($scope) {
21492 text: 'http://google.com'
21496 <form name="myForm" ng-controller="ExampleController">
21498 <input type="url" name="input" ng-model="url.text" required>
21501 <span class="error" ng-show="myForm.input.$error.required">
21503 <span class="error" ng-show="myForm.input.$error.url">
21504 Not valid url!</span>
21506 <tt>text = {{url.text}}</tt><br/>
21507 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21508 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21509 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21510 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21511 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
21514 <file name="protractor.js" type="protractor">
21515 var text = element(by.binding('url.text'));
21516 var valid = element(by.binding('myForm.input.$valid'));
21517 var input = element(by.model('url.text'));
21519 it('should initialize to model', function() {
21520 expect(text.getText()).toContain('http://google.com');
21521 expect(valid.getText()).toContain('true');
21524 it('should be invalid if empty', function() {
21526 input.sendKeys('');
21528 expect(text.getText()).toEqual('text =');
21529 expect(valid.getText()).toContain('false');
21532 it('should be invalid if not url', function() {
21534 input.sendKeys('box');
21536 expect(valid.getText()).toContain('false');
21541 'url': urlInputType,
21546 * @name input[email]
21549 * Text input with email validation. Sets the `email` validation error key if not a valid email
21552 * <div class="alert alert-warning">
21553 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
21554 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
21555 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
21558 * @param {string} ngModel Assignable angular expression to data-bind to.
21559 * @param {string=} name Property name of the form under which the control is published.
21560 * @param {string=} required Sets `required` validation error key if the value is not entered.
21561 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21562 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21563 * `required` when you want to data-bind to the `required` attribute.
21564 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21566 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21567 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21569 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21570 * that contains the regular expression body that will be converted to a regular expression
21571 * as in the ngPattern directive.
21572 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21573 * a RegExp found by evaluating the Angular expression given in the attribute value.
21574 * If the expression evaluates to a RegExp object, then this is used directly.
21575 * If the expression evaluates to a string, then it will be converted to a RegExp
21576 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21577 * `new RegExp('^abc$')`.<br />
21578 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21579 * start at the index of the last search's match, thus not taking the whole input value into
21581 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21582 * interaction with the input element.
21585 <example name="email-input-directive" module="emailExample">
21586 <file name="index.html">
21588 angular.module('emailExample', [])
21589 .controller('ExampleController', ['$scope', function($scope) {
21591 text: 'me@example.com'
21595 <form name="myForm" ng-controller="ExampleController">
21597 <input type="email" name="input" ng-model="email.text" required>
21600 <span class="error" ng-show="myForm.input.$error.required">
21602 <span class="error" ng-show="myForm.input.$error.email">
21603 Not valid email!</span>
21605 <tt>text = {{email.text}}</tt><br/>
21606 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21607 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21608 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21609 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21610 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
21613 <file name="protractor.js" type="protractor">
21614 var text = element(by.binding('email.text'));
21615 var valid = element(by.binding('myForm.input.$valid'));
21616 var input = element(by.model('email.text'));
21618 it('should initialize to model', function() {
21619 expect(text.getText()).toContain('me@example.com');
21620 expect(valid.getText()).toContain('true');
21623 it('should be invalid if empty', function() {
21625 input.sendKeys('');
21626 expect(text.getText()).toEqual('text =');
21627 expect(valid.getText()).toContain('false');
21630 it('should be invalid if not email', function() {
21632 input.sendKeys('xxx');
21634 expect(valid.getText()).toContain('false');
21639 'email': emailInputType,
21644 * @name input[radio]
21647 * HTML radio button.
21649 * @param {string} ngModel Assignable angular expression to data-bind to.
21650 * @param {string} value The value to which the `ngModel` expression should be set when selected.
21651 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
21652 * too. Use `ngValue` if you need complex models (`number`, `object`, ...).
21653 * @param {string=} name Property name of the form under which the control is published.
21654 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21655 * interaction with the input element.
21656 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio
21657 * is selected. Should be used instead of the `value` attribute if you need
21658 * a non-string `ngModel` (`boolean`, `array`, ...).
21661 <example name="radio-input-directive" module="radioExample">
21662 <file name="index.html">
21664 angular.module('radioExample', [])
21665 .controller('ExampleController', ['$scope', function($scope) {
21669 $scope.specialValue = {
21675 <form name="myForm" ng-controller="ExampleController">
21677 <input type="radio" ng-model="color.name" value="red">
21681 <input type="radio" ng-model="color.name" ng-value="specialValue">
21685 <input type="radio" ng-model="color.name" value="blue">
21688 <tt>color = {{color.name | json}}</tt><br/>
21690 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
21692 <file name="protractor.js" type="protractor">
21693 it('should change state', function() {
21694 var color = element(by.binding('color.name'));
21696 expect(color.getText()).toContain('blue');
21698 element.all(by.model('color.name')).get(0).click();
21700 expect(color.getText()).toContain('red');
21705 'radio': radioInputType,
21710 * @name input[checkbox]
21715 * @param {string} ngModel Assignable angular expression to data-bind to.
21716 * @param {string=} name Property name of the form under which the control is published.
21717 * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
21718 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
21719 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21720 * interaction with the input element.
21723 <example name="checkbox-input-directive" module="checkboxExample">
21724 <file name="index.html">
21726 angular.module('checkboxExample', [])
21727 .controller('ExampleController', ['$scope', function($scope) {
21728 $scope.checkboxModel = {
21734 <form name="myForm" ng-controller="ExampleController">
21736 <input type="checkbox" ng-model="checkboxModel.value1">
21739 <input type="checkbox" ng-model="checkboxModel.value2"
21740 ng-true-value="'YES'" ng-false-value="'NO'">
21742 <tt>value1 = {{checkboxModel.value1}}</tt><br/>
21743 <tt>value2 = {{checkboxModel.value2}}</tt><br/>
21746 <file name="protractor.js" type="protractor">
21747 it('should change state', function() {
21748 var value1 = element(by.binding('checkboxModel.value1'));
21749 var value2 = element(by.binding('checkboxModel.value2'));
21751 expect(value1.getText()).toContain('true');
21752 expect(value2.getText()).toContain('YES');
21754 element(by.model('checkboxModel.value1')).click();
21755 element(by.model('checkboxModel.value2')).click();
21757 expect(value1.getText()).toContain('false');
21758 expect(value2.getText()).toContain('NO');
21763 'checkbox': checkboxInputType,
21772 function stringBasedInputType(ctrl) {
21773 ctrl.$formatters.push(function(value) {
21774 return ctrl.$isEmpty(value) ? value : value.toString();
21778 function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21779 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21780 stringBasedInputType(ctrl);
21783 function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21784 var type = lowercase(element[0].type);
21786 // In composition mode, users are still inputing intermediate text buffer,
21787 // hold the listener until composition is done.
21788 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
21789 if (!$sniffer.android) {
21790 var composing = false;
21792 element.on('compositionstart', function(data) {
21796 element.on('compositionend', function() {
21802 var listener = function(ev) {
21804 $browser.defer.cancel(timeout);
21807 if (composing) return;
21808 var value = element.val(),
21809 event = ev && ev.type;
21811 // By default we will trim the value
21812 // If the attribute ng-trim exists we will avoid trimming
21813 // If input type is 'password', the value is never trimmed
21814 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
21815 value = trim(value);
21818 // If a control is suffering from bad input (due to native validators), browsers discard its
21819 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
21820 // control's value is the same empty value twice in a row.
21821 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
21822 ctrl.$setViewValue(value, event);
21826 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
21827 // input event on backspace, delete or cut
21828 if ($sniffer.hasEvent('input')) {
21829 element.on('input', listener);
21833 var deferListener = function(ev, input, origValue) {
21835 timeout = $browser.defer(function() {
21837 if (!input || input.value !== origValue) {
21844 element.on('keydown', function(event) {
21845 var key = event.keyCode;
21848 // command modifiers arrows
21849 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
21851 deferListener(event, this, this.value);
21854 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
21855 if ($sniffer.hasEvent('paste')) {
21856 element.on('paste cut', deferListener);
21860 // if user paste into input using mouse on older browser
21861 // or form autocomplete on newer browser, we need "change" event to catch it
21862 element.on('change', listener);
21864 ctrl.$render = function() {
21865 // Workaround for Firefox validation #12102.
21866 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
21867 if (element.val() !== value) {
21868 element.val(value);
21873 function weekParser(isoWeek, existingDate) {
21874 if (isDate(isoWeek)) {
21878 if (isString(isoWeek)) {
21879 WEEK_REGEXP.lastIndex = 0;
21880 var parts = WEEK_REGEXP.exec(isoWeek);
21882 var year = +parts[1],
21888 firstThurs = getFirstThursdayOfYear(year),
21889 addDays = (week - 1) * 7;
21891 if (existingDate) {
21892 hours = existingDate.getHours();
21893 minutes = existingDate.getMinutes();
21894 seconds = existingDate.getSeconds();
21895 milliseconds = existingDate.getMilliseconds();
21898 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
21905 function createDateParser(regexp, mapping) {
21906 return function(iso, date) {
21913 if (isString(iso)) {
21914 // When a date is JSON'ified to wraps itself inside of an extra
21915 // set of double quotes. This makes the date parsing code unable
21916 // to match the date string and parse it as a date.
21917 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
21918 iso = iso.substring(1, iso.length - 1);
21920 if (ISO_DATE_REGEXP.test(iso)) {
21921 return new Date(iso);
21923 regexp.lastIndex = 0;
21924 parts = regexp.exec(iso);
21930 yyyy: date.getFullYear(),
21931 MM: date.getMonth() + 1,
21932 dd: date.getDate(),
21933 HH: date.getHours(),
21934 mm: date.getMinutes(),
21935 ss: date.getSeconds(),
21936 sss: date.getMilliseconds() / 1000
21939 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
21942 forEach(parts, function(part, index) {
21943 if (index < mapping.length) {
21944 map[mapping[index]] = +part;
21947 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
21955 function createDateInputType(type, regexp, parseDate, format) {
21956 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
21957 badInputChecker(scope, element, attr, ctrl);
21958 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21959 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
21962 ctrl.$$parserName = type;
21963 ctrl.$parsers.push(function(value) {
21964 if (ctrl.$isEmpty(value)) return null;
21965 if (regexp.test(value)) {
21966 // Note: We cannot read ctrl.$modelValue, as there might be a different
21967 // parser/formatter in the processing chain so that the model
21968 // contains some different data format!
21969 var parsedDate = parseDate(value, previousDate);
21971 parsedDate = convertTimezoneToLocal(parsedDate, timezone);
21978 ctrl.$formatters.push(function(value) {
21979 if (value && !isDate(value)) {
21980 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
21982 if (isValidDate(value)) {
21983 previousDate = value;
21984 if (previousDate && timezone) {
21985 previousDate = convertTimezoneToLocal(previousDate, timezone, true);
21987 return $filter('date')(value, format, timezone);
21989 previousDate = null;
21994 if (isDefined(attr.min) || attr.ngMin) {
21996 ctrl.$validators.min = function(value) {
21997 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
21999 attr.$observe('min', function(val) {
22000 minVal = parseObservedDateValue(val);
22005 if (isDefined(attr.max) || attr.ngMax) {
22007 ctrl.$validators.max = function(value) {
22008 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
22010 attr.$observe('max', function(val) {
22011 maxVal = parseObservedDateValue(val);
22016 function isValidDate(value) {
22017 // Invalid Date: getTime() returns NaN
22018 return value && !(value.getTime && value.getTime() !== value.getTime());
22021 function parseObservedDateValue(val) {
22022 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
22027 function badInputChecker(scope, element, attr, ctrl) {
22028 var node = element[0];
22029 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
22030 if (nativeValidation) {
22031 ctrl.$parsers.push(function(value) {
22032 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
22033 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
22034 // - also sets validity.badInput (should only be validity.typeMismatch).
22035 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
22036 // - can ignore this case as we can still read out the erroneous email...
22037 return validity.badInput && !validity.typeMismatch ? undefined : value;
22042 function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22043 badInputChecker(scope, element, attr, ctrl);
22044 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22046 ctrl.$$parserName = 'number';
22047 ctrl.$parsers.push(function(value) {
22048 if (ctrl.$isEmpty(value)) return null;
22049 if (NUMBER_REGEXP.test(value)) return parseFloat(value);
22053 ctrl.$formatters.push(function(value) {
22054 if (!ctrl.$isEmpty(value)) {
22055 if (!isNumber(value)) {
22056 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
22058 value = value.toString();
22063 if (isDefined(attr.min) || attr.ngMin) {
22065 ctrl.$validators.min = function(value) {
22066 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
22069 attr.$observe('min', function(val) {
22070 if (isDefined(val) && !isNumber(val)) {
22071 val = parseFloat(val, 10);
22073 minVal = isNumber(val) && !isNaN(val) ? val : undefined;
22074 // TODO(matsko): implement validateLater to reduce number of validations
22079 if (isDefined(attr.max) || attr.ngMax) {
22081 ctrl.$validators.max = function(value) {
22082 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
22085 attr.$observe('max', function(val) {
22086 if (isDefined(val) && !isNumber(val)) {
22087 val = parseFloat(val, 10);
22089 maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
22090 // TODO(matsko): implement validateLater to reduce number of validations
22096 function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22097 // Note: no badInputChecker here by purpose as `url` is only a validation
22098 // in browsers, i.e. we can always read out input.value even if it is not valid!
22099 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22100 stringBasedInputType(ctrl);
22102 ctrl.$$parserName = 'url';
22103 ctrl.$validators.url = function(modelValue, viewValue) {
22104 var value = modelValue || viewValue;
22105 return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
22109 function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22110 // Note: no badInputChecker here by purpose as `url` is only a validation
22111 // in browsers, i.e. we can always read out input.value even if it is not valid!
22112 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22113 stringBasedInputType(ctrl);
22115 ctrl.$$parserName = 'email';
22116 ctrl.$validators.email = function(modelValue, viewValue) {
22117 var value = modelValue || viewValue;
22118 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
22122 function radioInputType(scope, element, attr, ctrl) {
22123 // make the name unique, if not defined
22124 if (isUndefined(attr.name)) {
22125 element.attr('name', nextUid());
22128 var listener = function(ev) {
22129 if (element[0].checked) {
22130 ctrl.$setViewValue(attr.value, ev && ev.type);
22134 element.on('click', listener);
22136 ctrl.$render = function() {
22137 var value = attr.value;
22138 element[0].checked = (value == ctrl.$viewValue);
22141 attr.$observe('value', ctrl.$render);
22144 function parseConstantExpr($parse, context, name, expression, fallback) {
22146 if (isDefined(expression)) {
22147 parseFn = $parse(expression);
22148 if (!parseFn.constant) {
22149 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
22150 '`{1}`.', name, expression);
22152 return parseFn(context);
22157 function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
22158 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
22159 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
22161 var listener = function(ev) {
22162 ctrl.$setViewValue(element[0].checked, ev && ev.type);
22165 element.on('click', listener);
22167 ctrl.$render = function() {
22168 element[0].checked = ctrl.$viewValue;
22171 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
22172 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
22173 // it to a boolean.
22174 ctrl.$isEmpty = function(value) {
22175 return value === false;
22178 ctrl.$formatters.push(function(value) {
22179 return equals(value, trueValue);
22182 ctrl.$parsers.push(function(value) {
22183 return value ? trueValue : falseValue;
22194 * HTML textarea element control with angular data-binding. The data-binding and validation
22195 * properties of this element are exactly the same as those of the
22196 * {@link ng.directive:input input element}.
22198 * @param {string} ngModel Assignable angular expression to data-bind to.
22199 * @param {string=} name Property name of the form under which the control is published.
22200 * @param {string=} required Sets `required` validation error key if the value is not entered.
22201 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
22202 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
22203 * `required` when you want to data-bind to the `required` attribute.
22204 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22206 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22207 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22209 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22210 * a RegExp found by evaluating the Angular expression given in the attribute value.
22211 * If the expression evaluates to a RegExp object, then this is used directly.
22212 * If the expression evaluates to a string, then it will be converted to a RegExp
22213 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22214 * `new RegExp('^abc$')`.<br />
22215 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22216 * start at the index of the last search's match, thus not taking the whole input value into
22218 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22219 * interaction with the input element.
22220 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22230 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
22231 * input state control, and validation.
22232 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
22234 * <div class="alert alert-warning">
22235 * **Note:** Not every feature offered is available for all input types.
22236 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
22239 * @param {string} ngModel Assignable angular expression to data-bind to.
22240 * @param {string=} name Property name of the form under which the control is published.
22241 * @param {string=} required Sets `required` validation error key if the value is not entered.
22242 * @param {boolean=} ngRequired Sets `required` attribute if set to true
22243 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22245 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22246 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22248 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22249 * a RegExp found by evaluating the Angular expression given in the attribute value.
22250 * If the expression evaluates to a RegExp object, then this is used directly.
22251 * If the expression evaluates to a string, then it will be converted to a RegExp
22252 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22253 * `new RegExp('^abc$')`.<br />
22254 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22255 * start at the index of the last search's match, thus not taking the whole input value into
22257 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22258 * interaction with the input element.
22259 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22260 * This parameter is ignored for input[type=password] controls, which will never trim the
22264 <example name="input-directive" module="inputExample">
22265 <file name="index.html">
22267 angular.module('inputExample', [])
22268 .controller('ExampleController', ['$scope', function($scope) {
22269 $scope.user = {name: 'guest', last: 'visitor'};
22272 <div ng-controller="ExampleController">
22273 <form name="myForm">
22276 <input type="text" name="userName" ng-model="user.name" required>
22279 <span class="error" ng-show="myForm.userName.$error.required">
22284 <input type="text" name="lastName" ng-model="user.last"
22285 ng-minlength="3" ng-maxlength="10">
22288 <span class="error" ng-show="myForm.lastName.$error.minlength">
22290 <span class="error" ng-show="myForm.lastName.$error.maxlength">
22295 <tt>user = {{user}}</tt><br/>
22296 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
22297 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
22298 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
22299 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
22300 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
22301 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
22302 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
22303 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
22306 <file name="protractor.js" type="protractor">
22307 var user = element(by.exactBinding('user'));
22308 var userNameValid = element(by.binding('myForm.userName.$valid'));
22309 var lastNameValid = element(by.binding('myForm.lastName.$valid'));
22310 var lastNameError = element(by.binding('myForm.lastName.$error'));
22311 var formValid = element(by.binding('myForm.$valid'));
22312 var userNameInput = element(by.model('user.name'));
22313 var userLastInput = element(by.model('user.last'));
22315 it('should initialize to model', function() {
22316 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
22317 expect(userNameValid.getText()).toContain('true');
22318 expect(formValid.getText()).toContain('true');
22321 it('should be invalid if empty when required', function() {
22322 userNameInput.clear();
22323 userNameInput.sendKeys('');
22325 expect(user.getText()).toContain('{"last":"visitor"}');
22326 expect(userNameValid.getText()).toContain('false');
22327 expect(formValid.getText()).toContain('false');
22330 it('should be valid if empty when min length is set', function() {
22331 userLastInput.clear();
22332 userLastInput.sendKeys('');
22334 expect(user.getText()).toContain('{"name":"guest","last":""}');
22335 expect(lastNameValid.getText()).toContain('true');
22336 expect(formValid.getText()).toContain('true');
22339 it('should be invalid if less than required min length', function() {
22340 userLastInput.clear();
22341 userLastInput.sendKeys('xx');
22343 expect(user.getText()).toContain('{"name":"guest"}');
22344 expect(lastNameValid.getText()).toContain('false');
22345 expect(lastNameError.getText()).toContain('minlength');
22346 expect(formValid.getText()).toContain('false');
22349 it('should be invalid if longer than max length', function() {
22350 userLastInput.clear();
22351 userLastInput.sendKeys('some ridiculously long name');
22353 expect(user.getText()).toContain('{"name":"guest"}');
22354 expect(lastNameValid.getText()).toContain('false');
22355 expect(lastNameError.getText()).toContain('maxlength');
22356 expect(formValid.getText()).toContain('false');
22361 var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
22362 function($browser, $sniffer, $filter, $parse) {
22365 require: ['?ngModel'],
22367 pre: function(scope, element, attr, ctrls) {
22369 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
22370 $browser, $filter, $parse);
22379 var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
22385 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
22386 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
22389 * `ngValue` is useful when dynamically generating lists of radio buttons using
22390 * {@link ngRepeat `ngRepeat`}, as shown below.
22392 * Likewise, `ngValue` can be used to generate `<option>` elements for
22393 * the {@link select `select`} element. In that case however, only strings are supported
22394 * for the `value `attribute, so the resulting `ngModel` will always be a string.
22395 * Support for `select` models with non-string values is available via `ngOptions`.
22398 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
22399 * of the `input` element
22402 <example name="ngValue-directive" module="valueExample">
22403 <file name="index.html">
22405 angular.module('valueExample', [])
22406 .controller('ExampleController', ['$scope', function($scope) {
22407 $scope.names = ['pizza', 'unicorns', 'robots'];
22408 $scope.my = { favorite: 'unicorns' };
22411 <form ng-controller="ExampleController">
22412 <h2>Which is your favorite?</h2>
22413 <label ng-repeat="name in names" for="{{name}}">
22415 <input type="radio"
22416 ng-model="my.favorite"
22421 <div>You chose {{my.favorite}}</div>
22424 <file name="protractor.js" type="protractor">
22425 var favorite = element(by.binding('my.favorite'));
22427 it('should initialize to model', function() {
22428 expect(favorite.getText()).toContain('unicorns');
22430 it('should bind the values to the inputs', function() {
22431 element.all(by.model('my.favorite')).get(0).click();
22432 expect(favorite.getText()).toContain('pizza');
22437 var ngValueDirective = function() {
22441 compile: function(tpl, tplAttr) {
22442 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
22443 return function ngValueConstantLink(scope, elm, attr) {
22444 attr.$set('value', scope.$eval(attr.ngValue));
22447 return function ngValueLink(scope, elm, attr) {
22448 scope.$watch(attr.ngValue, function valueWatchAction(value) {
22449 attr.$set('value', value);
22463 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
22464 * with the value of a given expression, and to update the text content when the value of that
22465 * expression changes.
22467 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
22468 * `{{ expression }}` which is similar but less verbose.
22470 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
22471 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an
22472 * element attribute, it makes the bindings invisible to the user while the page is loading.
22474 * An alternative solution to this problem would be using the
22475 * {@link ng.directive:ngCloak ngCloak} directive.
22479 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
22482 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
22483 <example module="bindExample">
22484 <file name="index.html">
22486 angular.module('bindExample', [])
22487 .controller('ExampleController', ['$scope', function($scope) {
22488 $scope.name = 'Whirled';
22491 <div ng-controller="ExampleController">
22492 <label>Enter name: <input type="text" ng-model="name"></label><br>
22493 Hello <span ng-bind="name"></span>!
22496 <file name="protractor.js" type="protractor">
22497 it('should check ng-bind', function() {
22498 var nameInput = element(by.model('name'));
22500 expect(element(by.binding('name')).getText()).toBe('Whirled');
22502 nameInput.sendKeys('world');
22503 expect(element(by.binding('name')).getText()).toBe('world');
22508 var ngBindDirective = ['$compile', function($compile) {
22511 compile: function ngBindCompile(templateElement) {
22512 $compile.$$addBindingClass(templateElement);
22513 return function ngBindLink(scope, element, attr) {
22514 $compile.$$addBindingInfo(element, attr.ngBind);
22515 element = element[0];
22516 scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
22517 element.textContent = isUndefined(value) ? '' : value;
22527 * @name ngBindTemplate
22530 * The `ngBindTemplate` directive specifies that the element
22531 * text content should be replaced with the interpolation of the template
22532 * in the `ngBindTemplate` attribute.
22533 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
22534 * expressions. This directive is needed since some HTML elements
22535 * (such as TITLE and OPTION) cannot contain SPAN elements.
22538 * @param {string} ngBindTemplate template of form
22539 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
22542 * Try it here: enter text in text box and watch the greeting change.
22543 <example module="bindExample">
22544 <file name="index.html">
22546 angular.module('bindExample', [])
22547 .controller('ExampleController', ['$scope', function($scope) {
22548 $scope.salutation = 'Hello';
22549 $scope.name = 'World';
22552 <div ng-controller="ExampleController">
22553 <label>Salutation: <input type="text" ng-model="salutation"></label><br>
22554 <label>Name: <input type="text" ng-model="name"></label><br>
22555 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
22558 <file name="protractor.js" type="protractor">
22559 it('should check ng-bind', function() {
22560 var salutationElem = element(by.binding('salutation'));
22561 var salutationInput = element(by.model('salutation'));
22562 var nameInput = element(by.model('name'));
22564 expect(salutationElem.getText()).toBe('Hello World!');
22566 salutationInput.clear();
22567 salutationInput.sendKeys('Greetings');
22569 nameInput.sendKeys('user');
22571 expect(salutationElem.getText()).toBe('Greetings user!');
22576 var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
22578 compile: function ngBindTemplateCompile(templateElement) {
22579 $compile.$$addBindingClass(templateElement);
22580 return function ngBindTemplateLink(scope, element, attr) {
22581 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
22582 $compile.$$addBindingInfo(element, interpolateFn.expressions);
22583 element = element[0];
22584 attr.$observe('ngBindTemplate', function(value) {
22585 element.textContent = isUndefined(value) ? '' : value;
22598 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
22599 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
22600 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
22601 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
22602 * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
22604 * You may also bypass sanitization for values you know are safe. To do so, bind to
22605 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
22606 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
22608 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
22609 * will have an exception (instead of an exploit.)
22612 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
22616 <example module="bindHtmlExample" deps="angular-sanitize.js">
22617 <file name="index.html">
22618 <div ng-controller="ExampleController">
22619 <p ng-bind-html="myHTML"></p>
22623 <file name="script.js">
22624 angular.module('bindHtmlExample', ['ngSanitize'])
22625 .controller('ExampleController', ['$scope', function($scope) {
22627 'I am an <code>HTML</code>string with ' +
22628 '<a href="#">links!</a> and other <em>stuff</em>';
22632 <file name="protractor.js" type="protractor">
22633 it('should check ng-bind-html', function() {
22634 expect(element(by.binding('myHTML')).getText()).toBe(
22635 'I am an HTMLstring with links! and other stuff');
22640 var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
22643 compile: function ngBindHtmlCompile(tElement, tAttrs) {
22644 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
22645 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
22646 return (value || '').toString();
22648 $compile.$$addBindingClass(tElement);
22650 return function ngBindHtmlLink(scope, element, attr) {
22651 $compile.$$addBindingInfo(element, attr.ngBindHtml);
22653 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
22654 // we re-evaluate the expr because we want a TrustedValueHolderType
22655 // for $sce, not a string
22656 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
22668 * Evaluate the given expression when the user changes the input.
22669 * The expression is evaluated immediately, unlike the JavaScript onchange event
22670 * which only triggers at the end of a change (usually, when the user leaves the
22671 * form element or presses the return key).
22673 * The `ngChange` expression is only evaluated when a change in the input value causes
22674 * a new value to be committed to the model.
22676 * It will not be evaluated:
22677 * * if the value returned from the `$parsers` transformation pipeline has not changed
22678 * * if the input has continued to be invalid since the model will stay `null`
22679 * * if the model is changed programmatically and not by a change to the input value
22682 * Note, this directive requires `ngModel` to be present.
22685 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
22689 * <example name="ngChange-directive" module="changeExample">
22690 * <file name="index.html">
22692 * angular.module('changeExample', [])
22693 * .controller('ExampleController', ['$scope', function($scope) {
22694 * $scope.counter = 0;
22695 * $scope.change = function() {
22696 * $scope.counter++;
22700 * <div ng-controller="ExampleController">
22701 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
22702 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
22703 * <label for="ng-change-example2">Confirmed</label><br />
22704 * <tt>debug = {{confirmed}}</tt><br/>
22705 * <tt>counter = {{counter}}</tt><br/>
22708 * <file name="protractor.js" type="protractor">
22709 * var counter = element(by.binding('counter'));
22710 * var debug = element(by.binding('confirmed'));
22712 * it('should evaluate the expression if changing from view', function() {
22713 * expect(counter.getText()).toContain('0');
22715 * element(by.id('ng-change-example1')).click();
22717 * expect(counter.getText()).toContain('1');
22718 * expect(debug.getText()).toContain('true');
22721 * it('should not evaluate the expression if changing from model', function() {
22722 * element(by.id('ng-change-example2')).click();
22724 * expect(counter.getText()).toContain('0');
22725 * expect(debug.getText()).toContain('true');
22730 var ngChangeDirective = valueFn({
22732 require: 'ngModel',
22733 link: function(scope, element, attr, ctrl) {
22734 ctrl.$viewChangeListeners.push(function() {
22735 scope.$eval(attr.ngChange);
22740 function classDirective(name, selector) {
22741 name = 'ngClass' + name;
22742 return ['$animate', function($animate) {
22745 link: function(scope, element, attr) {
22748 scope.$watch(attr[name], ngClassWatchAction, true);
22750 attr.$observe('class', function(value) {
22751 ngClassWatchAction(scope.$eval(attr[name]));
22755 if (name !== 'ngClass') {
22756 scope.$watch('$index', function($index, old$index) {
22757 // jshint bitwise: false
22758 var mod = $index & 1;
22759 if (mod !== (old$index & 1)) {
22760 var classes = arrayClasses(scope.$eval(attr[name]));
22762 addClasses(classes) :
22763 removeClasses(classes);
22768 function addClasses(classes) {
22769 var newClasses = digestClassCounts(classes, 1);
22770 attr.$addClass(newClasses);
22773 function removeClasses(classes) {
22774 var newClasses = digestClassCounts(classes, -1);
22775 attr.$removeClass(newClasses);
22778 function digestClassCounts(classes, count) {
22779 // Use createMap() to prevent class assumptions involving property
22780 // names in Object.prototype
22781 var classCounts = element.data('$classCounts') || createMap();
22782 var classesToUpdate = [];
22783 forEach(classes, function(className) {
22784 if (count > 0 || classCounts[className]) {
22785 classCounts[className] = (classCounts[className] || 0) + count;
22786 if (classCounts[className] === +(count > 0)) {
22787 classesToUpdate.push(className);
22791 element.data('$classCounts', classCounts);
22792 return classesToUpdate.join(' ');
22795 function updateClasses(oldClasses, newClasses) {
22796 var toAdd = arrayDifference(newClasses, oldClasses);
22797 var toRemove = arrayDifference(oldClasses, newClasses);
22798 toAdd = digestClassCounts(toAdd, 1);
22799 toRemove = digestClassCounts(toRemove, -1);
22800 if (toAdd && toAdd.length) {
22801 $animate.addClass(element, toAdd);
22803 if (toRemove && toRemove.length) {
22804 $animate.removeClass(element, toRemove);
22808 function ngClassWatchAction(newVal) {
22809 if (selector === true || scope.$index % 2 === selector) {
22810 var newClasses = arrayClasses(newVal || []);
22812 addClasses(newClasses);
22813 } else if (!equals(newVal,oldVal)) {
22814 var oldClasses = arrayClasses(oldVal);
22815 updateClasses(oldClasses, newClasses);
22818 oldVal = shallowCopy(newVal);
22823 function arrayDifference(tokens1, tokens2) {
22827 for (var i = 0; i < tokens1.length; i++) {
22828 var token = tokens1[i];
22829 for (var j = 0; j < tokens2.length; j++) {
22830 if (token == tokens2[j]) continue outer;
22832 values.push(token);
22837 function arrayClasses(classVal) {
22839 if (isArray(classVal)) {
22840 forEach(classVal, function(v) {
22841 classes = classes.concat(arrayClasses(v));
22844 } else if (isString(classVal)) {
22845 return classVal.split(' ');
22846 } else if (isObject(classVal)) {
22847 forEach(classVal, function(v, k) {
22849 classes = classes.concat(k.split(' '));
22865 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
22866 * an expression that represents all classes to be added.
22868 * The directive operates in three different ways, depending on which of three types the expression
22871 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
22874 * 2. If the expression evaluates to an object, then for each key-value pair of the
22875 * object with a truthy value the corresponding key is used as a class name.
22877 * 3. If the expression evaluates to an array, each element of the array should either be a string as in
22878 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
22879 * to give you more control over what CSS classes appear. See the code below for an example of this.
22882 * The directive won't add duplicate classes if a particular class was already set.
22884 * When the expression changes, the previously added classes are removed and only then are the
22885 * new classes added.
22888 * **add** - happens just before the class is applied to the elements
22890 * **remove** - happens just before the class is removed from the element
22893 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
22894 * of the evaluation can be a string representing space delimited class
22895 * names, an array, or a map of class names to boolean values. In the case of a map, the
22896 * names of the properties whose values are truthy will be added as css classes to the
22899 * @example Example that demonstrates basic bindings via ngClass directive.
22901 <file name="index.html">
22902 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
22904 <input type="checkbox" ng-model="deleted">
22905 deleted (apply "strike" class)
22908 <input type="checkbox" ng-model="important">
22909 important (apply "bold" class)
22912 <input type="checkbox" ng-model="error">
22913 error (apply "has-error" class)
22916 <p ng-class="style">Using String Syntax</p>
22917 <input type="text" ng-model="style"
22918 placeholder="Type: bold strike red" aria-label="Type: bold strike red">
22920 <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
22921 <input ng-model="style1"
22922 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
22923 <input ng-model="style2"
22924 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
22925 <input ng-model="style3"
22926 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
22928 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
22929 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
22930 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
22932 <file name="style.css">
22934 text-decoration: line-through;
22944 background-color: yellow;
22950 <file name="protractor.js" type="protractor">
22951 var ps = element.all(by.css('p'));
22953 it('should let you toggle the class', function() {
22955 expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
22956 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
22958 element(by.model('important')).click();
22959 expect(ps.first().getAttribute('class')).toMatch(/bold/);
22961 element(by.model('error')).click();
22962 expect(ps.first().getAttribute('class')).toMatch(/has-error/);
22965 it('should let you toggle string example', function() {
22966 expect(ps.get(1).getAttribute('class')).toBe('');
22967 element(by.model('style')).clear();
22968 element(by.model('style')).sendKeys('red');
22969 expect(ps.get(1).getAttribute('class')).toBe('red');
22972 it('array example should have 3 classes', function() {
22973 expect(ps.get(2).getAttribute('class')).toBe('');
22974 element(by.model('style1')).sendKeys('bold');
22975 element(by.model('style2')).sendKeys('strike');
22976 element(by.model('style3')).sendKeys('red');
22977 expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
22980 it('array with map example should have 2 classes', function() {
22981 expect(ps.last().getAttribute('class')).toBe('');
22982 element(by.model('style4')).sendKeys('bold');
22983 element(by.model('warning')).click();
22984 expect(ps.last().getAttribute('class')).toBe('bold orange');
22991 The example below demonstrates how to perform animations using ngClass.
22993 <example module="ngAnimate" deps="angular-animate.js" animations="true">
22994 <file name="index.html">
22995 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
22996 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
22998 <span class="base-class" ng-class="myVar">Sample Text</span>
23000 <file name="style.css">
23002 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
23005 .base-class.my-class {
23010 <file name="protractor.js" type="protractor">
23011 it('should check ng-class', function() {
23012 expect(element(by.css('.base-class')).getAttribute('class')).not.
23013 toMatch(/my-class/);
23015 element(by.id('setbtn')).click();
23017 expect(element(by.css('.base-class')).getAttribute('class')).
23018 toMatch(/my-class/);
23020 element(by.id('clearbtn')).click();
23022 expect(element(by.css('.base-class')).getAttribute('class')).not.
23023 toMatch(/my-class/);
23029 ## ngClass and pre-existing CSS3 Transitions/Animations
23030 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
23031 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
23032 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
23033 to view the step by step details of {@link $animate#addClass $animate.addClass} and
23034 {@link $animate#removeClass $animate.removeClass}.
23036 var ngClassDirective = classDirective('', true);
23044 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23045 * {@link ng.directive:ngClass ngClass}, except they work in
23046 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23048 * This directive can be applied only within the scope of an
23049 * {@link ng.directive:ngRepeat ngRepeat}.
23052 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
23053 * of the evaluation can be a string representing space delimited class names or an array.
23057 <file name="index.html">
23058 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23059 <li ng-repeat="name in names">
23060 <span ng-class-odd="'odd'" ng-class-even="'even'">
23066 <file name="style.css">
23074 <file name="protractor.js" type="protractor">
23075 it('should check ng-class-odd and ng-class-even', function() {
23076 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23078 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23084 var ngClassOddDirective = classDirective('Odd', 0);
23088 * @name ngClassEven
23092 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23093 * {@link ng.directive:ngClass ngClass}, except they work in
23094 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23096 * This directive can be applied only within the scope of an
23097 * {@link ng.directive:ngRepeat ngRepeat}.
23100 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
23101 * result of the evaluation can be a string representing space delimited class names or an array.
23105 <file name="index.html">
23106 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23107 <li ng-repeat="name in names">
23108 <span ng-class-odd="'odd'" ng-class-even="'even'">
23109 {{name}}
23114 <file name="style.css">
23122 <file name="protractor.js" type="protractor">
23123 it('should check ng-class-odd and ng-class-even', function() {
23124 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23126 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23132 var ngClassEvenDirective = classDirective('Even', 1);
23140 * The `ngCloak` directive is used to prevent the Angular html template from being briefly
23141 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
23142 * directive to avoid the undesirable flicker effect caused by the html template display.
23144 * The directive can be applied to the `<body>` element, but the preferred usage is to apply
23145 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
23146 * of the browser view.
23148 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
23149 * `angular.min.js`.
23150 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
23153 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
23154 * display: none !important;
23158 * When this css rule is loaded by the browser, all html elements (including their children) that
23159 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive
23160 * during the compilation of the template it deletes the `ngCloak` element attribute, making
23161 * the compiled element visible.
23163 * For the best result, the `angular.js` script must be loaded in the head section of the html
23164 * document; alternatively, the css rule above must be included in the external stylesheet of the
23171 <file name="index.html">
23172 <div id="template1" ng-cloak>{{ 'hello' }}</div>
23173 <div id="template2" class="ng-cloak">{{ 'world' }}</div>
23175 <file name="protractor.js" type="protractor">
23176 it('should remove the template directive and css class', function() {
23177 expect($('#template1').getAttribute('ng-cloak')).
23179 expect($('#template2').getAttribute('ng-cloak')).
23186 var ngCloakDirective = ngDirective({
23187 compile: function(element, attr) {
23188 attr.$set('ngCloak', undefined);
23189 element.removeClass('ng-cloak');
23195 * @name ngController
23198 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
23199 * supports the principles behind the Model-View-Controller design pattern.
23201 * MVC components in angular:
23203 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
23204 * are accessed through bindings.
23205 * * View — The template (HTML with data bindings) that is rendered into the View.
23206 * * Controller — The `ngController` directive specifies a Controller class; the class contains business
23207 * logic behind the application to decorate the scope with functions and values
23209 * Note that you can also attach controllers to the DOM by declaring it in a route definition
23210 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
23211 * again using `ng-controller` in the template itself. This will cause the controller to be attached
23212 * and executed twice.
23217 * @param {expression} ngController Name of a constructor function registered with the current
23218 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
23219 * that on the current scope evaluates to a constructor function.
23221 * The controller instance can be published into a scope property by specifying
23222 * `ng-controller="as propertyName"`.
23224 * If the current `$controllerProvider` is configured to use globals (via
23225 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may
23226 * also be the name of a globally accessible constructor function (not recommended).
23229 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
23230 * greeting are methods declared on the controller (see source tab). These methods can
23231 * easily be called from the angular markup. Any changes to the data are automatically reflected
23232 * in the View without the need for a manual update.
23234 * Two different declaration styles are included below:
23236 * * one binds methods and properties directly onto the controller using `this`:
23237 * `ng-controller="SettingsController1 as settings"`
23238 * * one injects `$scope` into the controller:
23239 * `ng-controller="SettingsController2"`
23241 * The second option is more common in the Angular community, and is generally used in boilerplates
23242 * and in this guide. However, there are advantages to binding properties directly to the controller
23243 * and avoiding scope.
23245 * * Using `controller as` makes it obvious which controller you are accessing in the template when
23246 * multiple controllers apply to an element.
23247 * * If you are writing your controllers as classes you have easier access to the properties and
23248 * methods, which will appear on the scope, from inside the controller code.
23249 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
23250 * inheritance masking primitives.
23252 * This example demonstrates the `controller as` syntax.
23254 * <example name="ngControllerAs" module="controllerAsExample">
23255 * <file name="index.html">
23256 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
23257 * <label>Name: <input type="text" ng-model="settings.name"/></label>
23258 * <button ng-click="settings.greet()">greet</button><br/>
23261 * <li ng-repeat="contact in settings.contacts">
23262 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
23263 * <option>phone</option>
23264 * <option>email</option>
23266 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23267 * <button ng-click="settings.clearContact(contact)">clear</button>
23268 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
23270 * <li><button ng-click="settings.addContact()">add</button></li>
23274 * <file name="app.js">
23275 * angular.module('controllerAsExample', [])
23276 * .controller('SettingsController1', SettingsController1);
23278 * function SettingsController1() {
23279 * this.name = "John Smith";
23280 * this.contacts = [
23281 * {type: 'phone', value: '408 555 1212'},
23282 * {type: 'email', value: 'john.smith@example.org'} ];
23285 * SettingsController1.prototype.greet = function() {
23286 * alert(this.name);
23289 * SettingsController1.prototype.addContact = function() {
23290 * this.contacts.push({type: 'email', value: 'yourname@example.org'});
23293 * SettingsController1.prototype.removeContact = function(contactToRemove) {
23294 * var index = this.contacts.indexOf(contactToRemove);
23295 * this.contacts.splice(index, 1);
23298 * SettingsController1.prototype.clearContact = function(contact) {
23299 * contact.type = 'phone';
23300 * contact.value = '';
23303 * <file name="protractor.js" type="protractor">
23304 * it('should check controller as', function() {
23305 * var container = element(by.id('ctrl-as-exmpl'));
23306 * expect(container.element(by.model('settings.name'))
23307 * .getAttribute('value')).toBe('John Smith');
23309 * var firstRepeat =
23310 * container.element(by.repeater('contact in settings.contacts').row(0));
23311 * var secondRepeat =
23312 * container.element(by.repeater('contact in settings.contacts').row(1));
23314 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23315 * .toBe('408 555 1212');
23317 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23318 * .toBe('john.smith@example.org');
23320 * firstRepeat.element(by.buttonText('clear')).click();
23322 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23325 * container.element(by.buttonText('add')).click();
23327 * expect(container.element(by.repeater('contact in settings.contacts').row(2))
23328 * .element(by.model('contact.value'))
23329 * .getAttribute('value'))
23330 * .toBe('yourname@example.org');
23335 * This example demonstrates the "attach to `$scope`" style of controller.
23337 * <example name="ngController" module="controllerExample">
23338 * <file name="index.html">
23339 * <div id="ctrl-exmpl" ng-controller="SettingsController2">
23340 * <label>Name: <input type="text" ng-model="name"/></label>
23341 * <button ng-click="greet()">greet</button><br/>
23344 * <li ng-repeat="contact in contacts">
23345 * <select ng-model="contact.type" id="select_{{$index}}">
23346 * <option>phone</option>
23347 * <option>email</option>
23349 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23350 * <button ng-click="clearContact(contact)">clear</button>
23351 * <button ng-click="removeContact(contact)">X</button>
23353 * <li>[ <button ng-click="addContact()">add</button> ]</li>
23357 * <file name="app.js">
23358 * angular.module('controllerExample', [])
23359 * .controller('SettingsController2', ['$scope', SettingsController2]);
23361 * function SettingsController2($scope) {
23362 * $scope.name = "John Smith";
23363 * $scope.contacts = [
23364 * {type:'phone', value:'408 555 1212'},
23365 * {type:'email', value:'john.smith@example.org'} ];
23367 * $scope.greet = function() {
23368 * alert($scope.name);
23371 * $scope.addContact = function() {
23372 * $scope.contacts.push({type:'email', value:'yourname@example.org'});
23375 * $scope.removeContact = function(contactToRemove) {
23376 * var index = $scope.contacts.indexOf(contactToRemove);
23377 * $scope.contacts.splice(index, 1);
23380 * $scope.clearContact = function(contact) {
23381 * contact.type = 'phone';
23382 * contact.value = '';
23386 * <file name="protractor.js" type="protractor">
23387 * it('should check controller', function() {
23388 * var container = element(by.id('ctrl-exmpl'));
23390 * expect(container.element(by.model('name'))
23391 * .getAttribute('value')).toBe('John Smith');
23393 * var firstRepeat =
23394 * container.element(by.repeater('contact in contacts').row(0));
23395 * var secondRepeat =
23396 * container.element(by.repeater('contact in contacts').row(1));
23398 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23399 * .toBe('408 555 1212');
23400 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23401 * .toBe('john.smith@example.org');
23403 * firstRepeat.element(by.buttonText('clear')).click();
23405 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23408 * container.element(by.buttonText('add')).click();
23410 * expect(container.element(by.repeater('contact in contacts').row(2))
23411 * .element(by.model('contact.value'))
23412 * .getAttribute('value'))
23413 * .toBe('yourname@example.org');
23419 var ngControllerDirective = [function() {
23435 * Angular has some features that can break certain
23436 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
23438 * If you intend to implement these rules then you must tell Angular not to use these features.
23440 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
23443 * The following rules affect Angular:
23445 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions
23446 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30%
23447 * increase in the speed of evaluating Angular expressions.
23449 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular
23450 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}).
23451 * To make these directives work when a CSP rule is blocking inline styles, you must link to the
23452 * `angular-csp.css` in your HTML manually.
23454 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval
23455 * and automatically deactivates this feature in the {@link $parse} service. This autodetection,
23456 * however, triggers a CSP error to be logged in the console:
23459 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
23460 * script in the following Content Security Policy directive: "default-src 'self'". Note that
23461 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
23464 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
23465 * directive on an element of the HTML document that appears before the `<script>` tag that loads
23466 * the `angular.js` file.
23468 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
23470 * You can specify which of the CSP related Angular features should be deactivated by providing
23471 * a value for the `ng-csp` attribute. The options are as follows:
23473 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM
23475 * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings
23477 * You can use these values in the following combinations:
23480 * * No declaration means that Angular will assume that you can do inline styles, but it will do
23481 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions
23484 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline
23485 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions
23488 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject
23489 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
23491 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
23492 * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
23494 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
23495 * styles nor use eval, which is the same as an empty: ng-csp.
23496 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
23499 * This example shows how to apply the `ngCsp` directive to the `html` tag.
23502 <html ng-app ng-csp>
23508 // Note: the suffix `.csp` in the example name triggers
23509 // csp mode in our http server!
23510 <example name="example.csp" module="cspExample" ng-csp="true">
23511 <file name="index.html">
23512 <div ng-controller="MainController as ctrl">
23514 <button ng-click="ctrl.inc()" id="inc">Increment</button>
23515 <span id="counter">
23521 <button ng-click="ctrl.evil()" id="evil">Evil</button>
23522 <span id="evilError">
23528 <file name="script.js">
23529 angular.module('cspExample', [])
23530 .controller('MainController', function() {
23532 this.inc = function() {
23535 this.evil = function() {
23536 // jshint evil:true
23540 this.evilError = e.message;
23545 <file name="protractor.js" type="protractor">
23546 var util, webdriver;
23548 var incBtn = element(by.id('inc'));
23549 var counter = element(by.id('counter'));
23550 var evilBtn = element(by.id('evil'));
23551 var evilError = element(by.id('evilError'));
23553 function getAndClearSevereErrors() {
23554 return browser.manage().logs().get('browser').then(function(browserLog) {
23555 return browserLog.filter(function(logEntry) {
23556 return logEntry.level.value > webdriver.logging.Level.WARNING.value;
23561 function clearErrors() {
23562 getAndClearSevereErrors();
23565 function expectNoErrors() {
23566 getAndClearSevereErrors().then(function(filteredLog) {
23567 expect(filteredLog.length).toEqual(0);
23568 if (filteredLog.length) {
23569 console.log('browser console errors: ' + util.inspect(filteredLog));
23574 function expectError(regex) {
23575 getAndClearSevereErrors().then(function(filteredLog) {
23577 filteredLog.forEach(function(log) {
23578 if (log.message.match(regex)) {
23583 throw new Error('expected an error that matches ' + regex);
23588 beforeEach(function() {
23589 util = require('util');
23590 webdriver = require('protractor/node_modules/selenium-webdriver');
23593 // For now, we only test on Chrome,
23594 // as Safari does not load the page with Protractor's injected scripts,
23595 // and Firefox webdriver always disables content security policy (#6358)
23596 if (browser.params.browser !== 'chrome') {
23600 it('should not report errors when the page is loaded', function() {
23601 // clear errors so we are not dependent on previous tests
23603 // Need to reload the page as the page is already loaded when
23605 browser.driver.getCurrentUrl().then(function(url) {
23611 it('should evaluate expressions', function() {
23612 expect(counter.getText()).toEqual('0');
23614 expect(counter.getText()).toEqual('1');
23618 it('should throw and report an error when using "eval"', function() {
23620 expect(evilError.getText()).toMatch(/Content Security Policy/);
23621 expectError(/Content Security Policy/);
23627 // ngCsp is not implemented as a proper directive any more, because we need it be processed while we
23628 // bootstrap the system (before $parse is instantiated), for this reason we just have
23629 // the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc
23636 * The ngClick directive allows you to specify custom behavior when
23637 * an element is clicked.
23641 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
23642 * click. ({@link guide/expression#-event- Event object is available as `$event`})
23646 <file name="index.html">
23647 <button ng-click="count = count + 1" ng-init="count=0">
23654 <file name="protractor.js" type="protractor">
23655 it('should check ng-click', function() {
23656 expect(element(by.binding('count')).getText()).toMatch('0');
23657 element(by.css('button')).click();
23658 expect(element(by.binding('count')).getText()).toMatch('1');
23664 * A collection of directives that allows creation of custom event handlers that are defined as
23665 * angular expressions and are compiled and executed within the current scope.
23667 var ngEventDirectives = {};
23669 // For events that might fire synchronously during DOM manipulation
23670 // we need to execute their event handlers asynchronously using $evalAsync,
23671 // so that they are not executed in an inconsistent state.
23672 var forceAsyncEvents = {
23677 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
23678 function(eventName) {
23679 var directiveName = directiveNormalize('ng-' + eventName);
23680 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
23683 compile: function($element, attr) {
23684 // We expose the powerful $event object on the scope that provides access to the Window,
23685 // etc. that isn't protected by the fast paths in $parse. We explicitly request better
23686 // checks at the cost of speed since event handler expressions are not executed as
23687 // frequently as regular change detection.
23688 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
23689 return function ngEventHandler(scope, element) {
23690 element.on(eventName, function(event) {
23691 var callback = function() {
23692 fn(scope, {$event:event});
23694 if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
23695 scope.$evalAsync(callback);
23697 scope.$apply(callback);
23712 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
23716 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
23717 * a dblclick. (The Event object is available as `$event`)
23721 <file name="index.html">
23722 <button ng-dblclick="count = count + 1" ng-init="count=0">
23723 Increment (on double click)
23733 * @name ngMousedown
23736 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
23740 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
23741 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
23745 <file name="index.html">
23746 <button ng-mousedown="count = count + 1" ng-init="count=0">
23747 Increment (on mouse down)
23760 * Specify custom behavior on mouseup event.
23764 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
23765 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
23769 <file name="index.html">
23770 <button ng-mouseup="count = count + 1" ng-init="count=0">
23771 Increment (on mouse up)
23780 * @name ngMouseover
23783 * Specify custom behavior on mouseover event.
23787 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
23788 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
23792 <file name="index.html">
23793 <button ng-mouseover="count = count + 1" ng-init="count=0">
23794 Increment (when mouse is over)
23804 * @name ngMouseenter
23807 * Specify custom behavior on mouseenter event.
23811 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
23812 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
23816 <file name="index.html">
23817 <button ng-mouseenter="count = count + 1" ng-init="count=0">
23818 Increment (when mouse enters)
23828 * @name ngMouseleave
23831 * Specify custom behavior on mouseleave event.
23835 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
23836 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
23840 <file name="index.html">
23841 <button ng-mouseleave="count = count + 1" ng-init="count=0">
23842 Increment (when mouse leaves)
23852 * @name ngMousemove
23855 * Specify custom behavior on mousemove event.
23859 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
23860 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
23864 <file name="index.html">
23865 <button ng-mousemove="count = count + 1" ng-init="count=0">
23866 Increment (when mouse moves)
23879 * Specify custom behavior on keydown event.
23883 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
23884 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23888 <file name="index.html">
23889 <input ng-keydown="count = count + 1" ng-init="count=0">
23890 key down count: {{count}}
23901 * Specify custom behavior on keyup event.
23905 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
23906 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23910 <file name="index.html">
23911 <p>Typing in the input box below updates the key count</p>
23912 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}
23914 <p>Typing in the input box below updates the keycode</p>
23915 <input ng-keyup="event=$event">
23916 <p>event keyCode: {{ event.keyCode }}</p>
23917 <p>event altKey: {{ event.altKey }}</p>
23928 * Specify custom behavior on keypress event.
23931 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
23932 * keypress. ({@link guide/expression#-event- Event object is available as `$event`}
23933 * and can be interrogated for keyCode, altKey, etc.)
23937 <file name="index.html">
23938 <input ng-keypress="count = count + 1" ng-init="count=0">
23939 key press count: {{count}}
23950 * Enables binding angular expressions to onsubmit events.
23952 * Additionally it prevents the default action (which for form means sending the request to the
23953 * server and reloading the current page), but only if the form does not contain `action`,
23954 * `data-action`, or `x-action` attributes.
23956 * <div class="alert alert-warning">
23957 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and
23958 * `ngSubmit` handlers together. See the
23959 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation}
23960 * for a detailed discussion of when `ngSubmit` may be triggered.
23965 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
23966 * ({@link guide/expression#-event- Event object is available as `$event`})
23969 <example module="submitExample">
23970 <file name="index.html">
23972 angular.module('submitExample', [])
23973 .controller('ExampleController', ['$scope', function($scope) {
23975 $scope.text = 'hello';
23976 $scope.submit = function() {
23978 $scope.list.push(this.text);
23984 <form ng-submit="submit()" ng-controller="ExampleController">
23985 Enter text and hit enter:
23986 <input type="text" ng-model="text" name="text" />
23987 <input type="submit" id="submit" value="Submit" />
23988 <pre>list={{list}}</pre>
23991 <file name="protractor.js" type="protractor">
23992 it('should check ng-submit', function() {
23993 expect(element(by.binding('list')).getText()).toBe('list=[]');
23994 element(by.css('#submit')).click();
23995 expect(element(by.binding('list')).getText()).toContain('hello');
23996 expect(element(by.model('text')).getAttribute('value')).toBe('');
23998 it('should ignore empty strings', function() {
23999 expect(element(by.binding('list')).getText()).toBe('list=[]');
24000 element(by.css('#submit')).click();
24001 element(by.css('#submit')).click();
24002 expect(element(by.binding('list')).getText()).toContain('hello');
24013 * Specify custom behavior on focus event.
24015 * Note: As the `focus` event is executed synchronously when calling `input.focus()`
24016 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24017 * during an `$apply` to ensure a consistent state.
24019 * @element window, input, select, textarea, a
24021 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
24022 * focus. ({@link guide/expression#-event- Event object is available as `$event`})
24025 * See {@link ng.directive:ngClick ngClick}
24033 * Specify custom behavior on blur event.
24035 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
24036 * an element has lost focus.
24038 * Note: As the `blur` event is executed synchronously also during DOM manipulations
24039 * (e.g. removing a focussed input),
24040 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24041 * during an `$apply` to ensure a consistent state.
24043 * @element window, input, select, textarea, a
24045 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
24046 * blur. ({@link guide/expression#-event- Event object is available as `$event`})
24049 * See {@link ng.directive:ngClick ngClick}
24057 * Specify custom behavior on copy event.
24059 * @element window, input, select, textarea, a
24061 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
24062 * copy. ({@link guide/expression#-event- Event object is available as `$event`})
24066 <file name="index.html">
24067 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
24078 * Specify custom behavior on cut event.
24080 * @element window, input, select, textarea, a
24082 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
24083 * cut. ({@link guide/expression#-event- Event object is available as `$event`})
24087 <file name="index.html">
24088 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
24099 * Specify custom behavior on paste event.
24101 * @element window, input, select, textarea, a
24103 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
24104 * paste. ({@link guide/expression#-event- Event object is available as `$event`})
24108 <file name="index.html">
24109 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
24122 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an
24123 * {expression}. If the expression assigned to `ngIf` evaluates to a false
24124 * value then the element is removed from the DOM, otherwise a clone of the
24125 * element is reinserted into the DOM.
24127 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the
24128 * element in the DOM rather than changing its visibility via the `display` css property. A common
24129 * case when this difference is significant is when using css selectors that rely on an element's
24130 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes.
24132 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
24133 * is created when the element is restored. The scope created within `ngIf` inherits from
24134 * its parent scope using
24135 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
24136 * An important implication of this is if `ngModel` is used within `ngIf` to bind to
24137 * a javascript primitive defined in the parent scope. In this case any modifications made to the
24138 * variable within the child scope will override (hide) the value in the parent scope.
24140 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior
24141 * is if an element's class attribute is directly modified after it's compiled, using something like
24142 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
24143 * the added class will be lost because the original compiled state is used to regenerate the element.
24145 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter`
24146 * and `leave` effects.
24149 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
24150 * leave - happens just before the `ngIf` contents are removed from the DOM
24155 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then
24156 * the element is removed from the DOM tree. If it is truthy a copy of the compiled
24157 * element is added to the DOM tree.
24160 <example module="ngAnimate" deps="angular-animate.js" animations="true">
24161 <file name="index.html">
24162 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
24164 <span ng-if="checked" class="animate-if">
24165 This is removed when the checkbox is unchecked.
24168 <file name="animations.css">
24171 border:1px solid black;
24175 .animate-if.ng-enter, .animate-if.ng-leave {
24176 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24179 .animate-if.ng-enter,
24180 .animate-if.ng-leave.ng-leave-active {
24184 .animate-if.ng-leave,
24185 .animate-if.ng-enter.ng-enter-active {
24191 var ngIfDirective = ['$animate', function($animate) {
24193 multiElement: true,
24194 transclude: 'element',
24199 link: function($scope, $element, $attr, ctrl, $transclude) {
24200 var block, childScope, previousElements;
24201 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
24205 $transclude(function(clone, newScope) {
24206 childScope = newScope;
24207 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
24208 // Note: We only need the first/last node of the cloned nodes.
24209 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
24210 // by a directive with templateUrl when its template arrives.
24214 $animate.enter(clone, $element.parent(), $element);
24218 if (previousElements) {
24219 previousElements.remove();
24220 previousElements = null;
24223 childScope.$destroy();
24227 previousElements = getBlockNodes(block.clone);
24228 $animate.leave(previousElements).then(function() {
24229 previousElements = null;
24245 * Fetches, compiles and includes an external HTML fragment.
24247 * By default, the template URL is restricted to the same domain and protocol as the
24248 * application document. This is done by calling {@link $sce#getTrustedResourceUrl
24249 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
24250 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
24251 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link
24252 * ng.$sce Strict Contextual Escaping}.
24254 * In addition, the browser's
24255 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
24256 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
24257 * policy may further restrict whether the template is successfully loaded.
24258 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://`
24259 * access on some browsers.
24262 * enter - animation is used to bring new content into the browser.
24263 * leave - animation is used to animate existing content away.
24265 * The enter and leave animation occur concurrently.
24270 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
24271 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
24272 * @param {string=} onload Expression to evaluate when a new partial is loaded.
24273 * <div class="alert alert-warning">
24274 * **Note:** When using onload on SVG elements in IE11, the browser will try to call
24275 * a function with the name on the window element, which will usually throw a
24276 * "function is undefined" error. To fix this, you can instead use `data-onload` or a
24277 * different form that {@link guide/directive#normalization matches} `onload`.
24280 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
24281 * $anchorScroll} to scroll the viewport after the content is loaded.
24283 * - If the attribute is not set, disable scrolling.
24284 * - If the attribute is set without value, enable scrolling.
24285 * - Otherwise enable scrolling only if the expression evaluates to truthy value.
24288 <example module="includeExample" deps="angular-animate.js" animations="true">
24289 <file name="index.html">
24290 <div ng-controller="ExampleController">
24291 <select ng-model="template" ng-options="t.name for t in templates">
24292 <option value="">(blank)</option>
24294 url of the template: <code>{{template.url}}</code>
24296 <div class="slide-animate-container">
24297 <div class="slide-animate" ng-include="template.url"></div>
24301 <file name="script.js">
24302 angular.module('includeExample', ['ngAnimate'])
24303 .controller('ExampleController', ['$scope', function($scope) {
24305 [ { name: 'template1.html', url: 'template1.html'},
24306 { name: 'template2.html', url: 'template2.html'} ];
24307 $scope.template = $scope.templates[0];
24310 <file name="template1.html">
24311 Content of template1.html
24313 <file name="template2.html">
24314 Content of template2.html
24316 <file name="animations.css">
24317 .slide-animate-container {
24320 border:1px solid black;
24329 .slide-animate.ng-enter, .slide-animate.ng-leave {
24330 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24341 .slide-animate.ng-enter {
24344 .slide-animate.ng-enter.ng-enter-active {
24348 .slide-animate.ng-leave {
24351 .slide-animate.ng-leave.ng-leave-active {
24355 <file name="protractor.js" type="protractor">
24356 var templateSelect = element(by.model('template'));
24357 var includeElem = element(by.css('[ng-include]'));
24359 it('should load template1.html', function() {
24360 expect(includeElem.getText()).toMatch(/Content of template1.html/);
24363 it('should load template2.html', function() {
24364 if (browser.params.browser == 'firefox') {
24365 // Firefox can't handle using selects
24366 // See https://github.com/angular/protractor/issues/480
24369 templateSelect.click();
24370 templateSelect.all(by.css('option')).get(2).click();
24371 expect(includeElem.getText()).toMatch(/Content of template2.html/);
24374 it('should change to blank', function() {
24375 if (browser.params.browser == 'firefox') {
24376 // Firefox can't handle using selects
24379 templateSelect.click();
24380 templateSelect.all(by.css('option')).get(0).click();
24381 expect(includeElem.isPresent()).toBe(false);
24390 * @name ngInclude#$includeContentRequested
24391 * @eventType emit on the scope ngInclude was declared in
24393 * Emitted every time the ngInclude content is requested.
24395 * @param {Object} angularEvent Synthetic event object.
24396 * @param {String} src URL of content to load.
24402 * @name ngInclude#$includeContentLoaded
24403 * @eventType emit on the current ngInclude scope
24405 * Emitted every time the ngInclude content is reloaded.
24407 * @param {Object} angularEvent Synthetic event object.
24408 * @param {String} src URL of content to load.
24414 * @name ngInclude#$includeContentError
24415 * @eventType emit on the scope ngInclude was declared in
24417 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
24419 * @param {Object} angularEvent Synthetic event object.
24420 * @param {String} src URL of content to load.
24422 var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
24423 function($templateRequest, $anchorScroll, $animate) {
24428 transclude: 'element',
24429 controller: angular.noop,
24430 compile: function(element, attr) {
24431 var srcExp = attr.ngInclude || attr.src,
24432 onloadExp = attr.onload || '',
24433 autoScrollExp = attr.autoscroll;
24435 return function(scope, $element, $attr, ctrl, $transclude) {
24436 var changeCounter = 0,
24441 var cleanupLastIncludeContent = function() {
24442 if (previousElement) {
24443 previousElement.remove();
24444 previousElement = null;
24446 if (currentScope) {
24447 currentScope.$destroy();
24448 currentScope = null;
24450 if (currentElement) {
24451 $animate.leave(currentElement).then(function() {
24452 previousElement = null;
24454 previousElement = currentElement;
24455 currentElement = null;
24459 scope.$watch(srcExp, function ngIncludeWatchAction(src) {
24460 var afterAnimation = function() {
24461 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
24465 var thisChangeId = ++changeCounter;
24468 //set the 2nd param to true to ignore the template request error so that the inner
24469 //contents and scope can be cleaned up.
24470 $templateRequest(src, true).then(function(response) {
24471 if (thisChangeId !== changeCounter) return;
24472 var newScope = scope.$new();
24473 ctrl.template = response;
24475 // Note: This will also link all children of ng-include that were contained in the original
24476 // html. If that content contains controllers, ... they could pollute/change the scope.
24477 // However, using ng-include on an element with additional content does not make sense...
24478 // Note: We can't remove them in the cloneAttchFn of $transclude as that
24479 // function is called before linking the content, which would apply child
24480 // directives to non existing elements.
24481 var clone = $transclude(newScope, function(clone) {
24482 cleanupLastIncludeContent();
24483 $animate.enter(clone, null, $element).then(afterAnimation);
24486 currentScope = newScope;
24487 currentElement = clone;
24489 currentScope.$emit('$includeContentLoaded', src);
24490 scope.$eval(onloadExp);
24492 if (thisChangeId === changeCounter) {
24493 cleanupLastIncludeContent();
24494 scope.$emit('$includeContentError', src);
24497 scope.$emit('$includeContentRequested', src);
24499 cleanupLastIncludeContent();
24500 ctrl.template = null;
24508 // This directive is called during the $transclude call of the first `ngInclude` directive.
24509 // It will replace and compile the content of the element with the loaded template.
24510 // We need this directive so that the element content is already filled when
24511 // the link function of another directive on the same element as ngInclude
24513 var ngIncludeFillContentDirective = ['$compile',
24514 function($compile) {
24518 require: 'ngInclude',
24519 link: function(scope, $element, $attr, ctrl) {
24520 if (/SVG/.test($element[0].toString())) {
24521 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
24522 // support innerHTML, so detect this here and try to generate the contents
24525 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
24526 function namespaceAdaptedClone(clone) {
24527 $element.append(clone);
24528 }, {futureParentElement: $element});
24532 $element.html(ctrl.template);
24533 $compile($element.contents())(scope);
24544 * The `ngInit` directive allows you to evaluate an expression in the
24547 * <div class="alert alert-danger">
24548 * This directive can be abused to add unnecessary amounts of logic into your templates.
24549 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
24550 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
24551 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
24552 * rather than `ngInit` to initialize values on a scope.
24555 * <div class="alert alert-warning">
24556 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
24557 * sure you have parentheses to ensure correct operator precedence:
24558 * <pre class="prettyprint">
24559 * `<div ng-init="test1 = ($index | toString)"></div>`
24566 * @param {expression} ngInit {@link guide/expression Expression} to eval.
24569 <example module="initExample">
24570 <file name="index.html">
24572 angular.module('initExample', [])
24573 .controller('ExampleController', ['$scope', function($scope) {
24574 $scope.list = [['a', 'b'], ['c', 'd']];
24577 <div ng-controller="ExampleController">
24578 <div ng-repeat="innerList in list" ng-init="outerIndex = $index">
24579 <div ng-repeat="value in innerList" ng-init="innerIndex = $index">
24580 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
24585 <file name="protractor.js" type="protractor">
24586 it('should alias index positions', function() {
24587 var elements = element.all(by.css('.example-init'));
24588 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
24589 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
24590 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
24591 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
24596 var ngInitDirective = ngDirective({
24598 compile: function() {
24600 pre: function(scope, element, attrs) {
24601 scope.$eval(attrs.ngInit);
24612 * Text input that converts between a delimited string and an array of strings. The default
24613 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
24614 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
24616 * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
24617 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
24618 * list item is respected. This implies that the user of the directive is responsible for
24619 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
24620 * tab or newline character.
24621 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
24622 * when joining the list items back together) and whitespace around each list item is stripped
24623 * before it is added to the model.
24625 * ### Example with Validation
24627 * <example name="ngList-directive" module="listExample">
24628 * <file name="app.js">
24629 * angular.module('listExample', [])
24630 * .controller('ExampleController', ['$scope', function($scope) {
24631 * $scope.names = ['morpheus', 'neo', 'trinity'];
24634 * <file name="index.html">
24635 * <form name="myForm" ng-controller="ExampleController">
24636 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label>
24637 * <span role="alert">
24638 * <span class="error" ng-show="myForm.namesInput.$error.required">
24642 * <tt>names = {{names}}</tt><br/>
24643 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
24644 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
24645 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24646 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24649 * <file name="protractor.js" type="protractor">
24650 * var listInput = element(by.model('names'));
24651 * var names = element(by.exactBinding('names'));
24652 * var valid = element(by.binding('myForm.namesInput.$valid'));
24653 * var error = element(by.css('span.error'));
24655 * it('should initialize to model', function() {
24656 * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
24657 * expect(valid.getText()).toContain('true');
24658 * expect(error.getCssValue('display')).toBe('none');
24661 * it('should be invalid if empty', function() {
24662 * listInput.clear();
24663 * listInput.sendKeys('');
24665 * expect(names.getText()).toContain('');
24666 * expect(valid.getText()).toContain('false');
24667 * expect(error.getCssValue('display')).not.toBe('none');
24672 * ### Example - splitting on newline
24673 * <example name="ngList-directive-newlines">
24674 * <file name="index.html">
24675 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea>
24676 * <pre>{{ list | json }}</pre>
24678 * <file name="protractor.js" type="protractor">
24679 * it("should split the text by newlines", function() {
24680 * var listInput = element(by.model('list'));
24681 * var output = element(by.binding('list | json'));
24682 * listInput.sendKeys('abc\ndef\nghi');
24683 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
24689 * @param {string=} ngList optional delimiter that should be used to split the value.
24691 var ngListDirective = function() {
24695 require: 'ngModel',
24696 link: function(scope, element, attr, ctrl) {
24697 // We want to control whitespace trimming so we use this convoluted approach
24698 // to access the ngList attribute, which doesn't pre-trim the attribute
24699 var ngList = element.attr(attr.$attr.ngList) || ', ';
24700 var trimValues = attr.ngTrim !== 'false';
24701 var separator = trimValues ? trim(ngList) : ngList;
24703 var parse = function(viewValue) {
24704 // If the viewValue is invalid (say required but empty) it will be `undefined`
24705 if (isUndefined(viewValue)) return;
24710 forEach(viewValue.split(separator), function(value) {
24711 if (value) list.push(trimValues ? trim(value) : value);
24718 ctrl.$parsers.push(parse);
24719 ctrl.$formatters.push(function(value) {
24720 if (isArray(value)) {
24721 return value.join(ngList);
24727 // Override the standard $isEmpty because an empty array means the input is empty.
24728 ctrl.$isEmpty = function(value) {
24729 return !value || !value.length;
24735 /* global VALID_CLASS: true,
24736 INVALID_CLASS: true,
24737 PRISTINE_CLASS: true,
24739 UNTOUCHED_CLASS: true,
24740 TOUCHED_CLASS: true,
24743 var VALID_CLASS = 'ng-valid',
24744 INVALID_CLASS = 'ng-invalid',
24745 PRISTINE_CLASS = 'ng-pristine',
24746 DIRTY_CLASS = 'ng-dirty',
24747 UNTOUCHED_CLASS = 'ng-untouched',
24748 TOUCHED_CLASS = 'ng-touched',
24749 PENDING_CLASS = 'ng-pending';
24751 var ngModelMinErr = minErr('ngModel');
24755 * @name ngModel.NgModelController
24757 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
24758 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
24760 * @property {*} $modelValue The value in the model that the control is bound to.
24761 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
24762 the control reads value from the DOM. The functions are called in array order, each passing
24763 its return value through to the next. The last return value is forwarded to the
24764 {@link ngModel.NgModelController#$validators `$validators`} collection.
24766 Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
24769 Returning `undefined` from a parser means a parse error occurred. In that case,
24770 no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
24771 will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
24772 is set to `true`. The parse error is stored in `ngModel.$error.parse`.
24775 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
24776 the model value changes. The functions are called in reverse array order, each passing the value through to the
24777 next. The last return value is used as the actual DOM value.
24778 Used to format / convert values for display in the control.
24780 * function formatter(value) {
24782 * return value.toUpperCase();
24785 * ngModel.$formatters.push(formatter);
24788 * @property {Object.<string, function>} $validators A collection of validators that are applied
24789 * whenever the model value changes. The key value within the object refers to the name of the
24790 * validator while the function refers to the validation operation. The validation operation is
24791 * provided with the model value as an argument and must return a true or false value depending
24792 * on the response of that validation.
24795 * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
24796 * var value = modelValue || viewValue;
24797 * return /[0-9]+/.test(value) &&
24798 * /[a-z]+/.test(value) &&
24799 * /[A-Z]+/.test(value) &&
24800 * /\W+/.test(value);
24804 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
24805 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
24806 * is expected to return a promise when it is run during the model validation process. Once the promise
24807 * is delivered then the validation status will be set to true when fulfilled and false when rejected.
24808 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
24809 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
24810 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
24811 * will only run once all synchronous validators have passed.
24813 * Please note that if $http is used then it is important that the server returns a success HTTP response code
24814 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
24817 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
24818 * var value = modelValue || viewValue;
24820 * // Lookup user by username
24821 * return $http.get('/api/users/' + value).
24822 * then(function resolved() {
24823 * //username exists, this means validation fails
24824 * return $q.reject('exists');
24825 * }, function rejected() {
24826 * //username does not exist, therefore this validation passes
24832 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
24833 * view value has changed. It is called with no arguments, and its return value is ignored.
24834 * This can be used in place of additional $watches against the model value.
24836 * @property {Object} $error An object hash with all failing validator ids as keys.
24837 * @property {Object} $pending An object hash with all pending validator ids as keys.
24839 * @property {boolean} $untouched True if control has not lost focus yet.
24840 * @property {boolean} $touched True if control has lost focus.
24841 * @property {boolean} $pristine True if user has not interacted with the control yet.
24842 * @property {boolean} $dirty True if user has already interacted with the control.
24843 * @property {boolean} $valid True if there is no error.
24844 * @property {boolean} $invalid True if at least one error on the control.
24845 * @property {string} $name The name attribute of the control.
24849 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
24850 * The controller contains services for data-binding, validation, CSS updates, and value formatting
24851 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
24852 * listening to DOM events.
24853 * Such DOM related logic should be provided by other directives which make use of
24854 * `NgModelController` for data-binding to control elements.
24855 * Angular provides this DOM logic for most {@link input `input`} elements.
24856 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
24857 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
24860 * ### Custom Control Example
24861 * This example shows how to use `NgModelController` with a custom control to achieve
24862 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
24863 * collaborate together to achieve the desired result.
24865 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
24866 * contents be edited in place by the user.
24868 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
24869 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
24870 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
24871 * that content using the `$sce` service.
24873 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
24874 <file name="style.css">
24875 [contenteditable] {
24876 border: 1px solid black;
24877 background-color: white;
24882 border: 1px solid red;
24886 <file name="script.js">
24887 angular.module('customControl', ['ngSanitize']).
24888 directive('contenteditable', ['$sce', function($sce) {
24890 restrict: 'A', // only activate on element attribute
24891 require: '?ngModel', // get a hold of NgModelController
24892 link: function(scope, element, attrs, ngModel) {
24893 if (!ngModel) return; // do nothing if no ng-model
24895 // Specify how UI should be updated
24896 ngModel.$render = function() {
24897 element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
24900 // Listen for change events to enable binding
24901 element.on('blur keyup change', function() {
24902 scope.$evalAsync(read);
24904 read(); // initialize
24906 // Write data to the model
24908 var html = element.html();
24909 // When we clear the content editable the browser leaves a <br> behind
24910 // If strip-br attribute is provided then we strip this out
24911 if ( attrs.stripBr && html == '<br>' ) {
24914 ngModel.$setViewValue(html);
24920 <file name="index.html">
24921 <form name="myForm">
24922 <div contenteditable
24923 name="myWidget" ng-model="userContent"
24925 required>Change me!</div>
24926 <span ng-show="myForm.myWidget.$error.required">Required!</span>
24928 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
24931 <file name="protractor.js" type="protractor">
24932 it('should data-bind and become invalid', function() {
24933 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
24934 // SafariDriver can't handle contenteditable
24935 // and Firefox driver can't clear contenteditables very well
24938 var contentEditable = element(by.css('[contenteditable]'));
24939 var content = 'Change me!';
24941 expect(contentEditable.getText()).toEqual(content);
24943 contentEditable.clear();
24944 contentEditable.sendKeys(protractor.Key.BACK_SPACE);
24945 expect(contentEditable.getText()).toEqual('');
24946 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
24953 var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
24954 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
24955 this.$viewValue = Number.NaN;
24956 this.$modelValue = Number.NaN;
24957 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
24958 this.$validators = {};
24959 this.$asyncValidators = {};
24960 this.$parsers = [];
24961 this.$formatters = [];
24962 this.$viewChangeListeners = [];
24963 this.$untouched = true;
24964 this.$touched = false;
24965 this.$pristine = true;
24966 this.$dirty = false;
24967 this.$valid = true;
24968 this.$invalid = false;
24969 this.$error = {}; // keep invalid keys here
24970 this.$$success = {}; // keep valid keys here
24971 this.$pending = undefined; // keep pending keys here
24972 this.$name = $interpolate($attr.name || '', false)($scope);
24973 this.$$parentForm = nullFormCtrl;
24975 var parsedNgModel = $parse($attr.ngModel),
24976 parsedNgModelAssign = parsedNgModel.assign,
24977 ngModelGet = parsedNgModel,
24978 ngModelSet = parsedNgModelAssign,
24979 pendingDebounce = null,
24983 this.$$setOptions = function(options) {
24984 ctrl.$options = options;
24985 if (options && options.getterSetter) {
24986 var invokeModelGetter = $parse($attr.ngModel + '()'),
24987 invokeModelSetter = $parse($attr.ngModel + '($$$p)');
24989 ngModelGet = function($scope) {
24990 var modelValue = parsedNgModel($scope);
24991 if (isFunction(modelValue)) {
24992 modelValue = invokeModelGetter($scope);
24996 ngModelSet = function($scope, newValue) {
24997 if (isFunction(parsedNgModel($scope))) {
24998 invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
25000 parsedNgModelAssign($scope, ctrl.$modelValue);
25003 } else if (!parsedNgModel.assign) {
25004 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
25005 $attr.ngModel, startingTag($element));
25011 * @name ngModel.NgModelController#$render
25014 * Called when the view needs to be updated. It is expected that the user of the ng-model
25015 * directive will implement this method.
25017 * The `$render()` method is invoked in the following situations:
25019 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
25020 * committed value then `$render()` is called to update the input control.
25021 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
25022 * the `$viewValue` are different from last time.
25024 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
25025 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
25026 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
25027 * invoked if you only change a property on the objects.
25029 this.$render = noop;
25033 * @name ngModel.NgModelController#$isEmpty
25036 * This is called when we need to determine if the value of an input is empty.
25038 * For instance, the required directive does this to work out if the input has data or not.
25040 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
25042 * You can override this for input directives whose concept of being empty is different from the
25043 * default. The `checkboxInputType` directive does this because in its case a value of `false`
25046 * @param {*} value The value of the input to check for emptiness.
25047 * @returns {boolean} True if `value` is "empty".
25049 this.$isEmpty = function(value) {
25050 return isUndefined(value) || value === '' || value === null || value !== value;
25053 var currentValidationRunId = 0;
25057 * @name ngModel.NgModelController#$setValidity
25060 * Change the validity state, and notify the form.
25062 * This method can be called within $parsers/$formatters or a custom validation implementation.
25063 * However, in most cases it should be sufficient to use the `ngModel.$validators` and
25064 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
25066 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
25067 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
25068 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
25069 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
25070 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
25071 * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
25072 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
25073 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
25074 * Skipped is used by Angular when validators do not run because of parse errors and
25075 * when `$asyncValidators` do not run because any of the `$validators` failed.
25077 addSetValidityMethod({
25079 $element: $element,
25080 set: function(object, property) {
25081 object[property] = true;
25083 unset: function(object, property) {
25084 delete object[property];
25091 * @name ngModel.NgModelController#$setPristine
25094 * Sets the control to its pristine state.
25096 * This method can be called to remove the `ng-dirty` class and set the control to its pristine
25097 * state (`ng-pristine` class). A model is considered to be pristine when the control
25098 * has not been changed from when first compiled.
25100 this.$setPristine = function() {
25101 ctrl.$dirty = false;
25102 ctrl.$pristine = true;
25103 $animate.removeClass($element, DIRTY_CLASS);
25104 $animate.addClass($element, PRISTINE_CLASS);
25109 * @name ngModel.NgModelController#$setDirty
25112 * Sets the control to its dirty state.
25114 * This method can be called to remove the `ng-pristine` class and set the control to its dirty
25115 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
25116 * from when first compiled.
25118 this.$setDirty = function() {
25119 ctrl.$dirty = true;
25120 ctrl.$pristine = false;
25121 $animate.removeClass($element, PRISTINE_CLASS);
25122 $animate.addClass($element, DIRTY_CLASS);
25123 ctrl.$$parentForm.$setDirty();
25128 * @name ngModel.NgModelController#$setUntouched
25131 * Sets the control to its untouched state.
25133 * This method can be called to remove the `ng-touched` class and set the control to its
25134 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
25135 * by default, however this function can be used to restore that state if the model has
25136 * already been touched by the user.
25138 this.$setUntouched = function() {
25139 ctrl.$touched = false;
25140 ctrl.$untouched = true;
25141 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
25146 * @name ngModel.NgModelController#$setTouched
25149 * Sets the control to its touched state.
25151 * This method can be called to remove the `ng-untouched` class and set the control to its
25152 * touched state (`ng-touched` class). A model is considered to be touched when the user has
25153 * first focused the control element and then shifted focus away from the control (blur event).
25155 this.$setTouched = function() {
25156 ctrl.$touched = true;
25157 ctrl.$untouched = false;
25158 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
25163 * @name ngModel.NgModelController#$rollbackViewValue
25166 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
25167 * which may be caused by a pending debounced event or because the input is waiting for a some
25170 * If you have an input that uses `ng-model-options` to set up debounced events or events such
25171 * as blur you can have a situation where there is a period when the `$viewValue`
25172 * is out of synch with the ngModel's `$modelValue`.
25174 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
25175 * programmatically before these debounced/future events have resolved/occurred, because Angular's
25176 * dirty checking mechanism is not able to tell whether the model has actually changed or not.
25178 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
25179 * input which may have such events pending. This is important in order to make sure that the
25180 * input field will be updated with the new model value and any pending operations are cancelled.
25182 * <example name="ng-model-cancel-update" module="cancel-update-example">
25183 * <file name="app.js">
25184 * angular.module('cancel-update-example', [])
25186 * .controller('CancelUpdateController', ['$scope', function($scope) {
25187 * $scope.resetWithCancel = function(e) {
25188 * if (e.keyCode == 27) {
25189 * $scope.myForm.myInput1.$rollbackViewValue();
25190 * $scope.myValue = '';
25193 * $scope.resetWithoutCancel = function(e) {
25194 * if (e.keyCode == 27) {
25195 * $scope.myValue = '';
25200 * <file name="index.html">
25201 * <div ng-controller="CancelUpdateController">
25202 * <p>Try typing something in each input. See that the model only updates when you
25203 * blur off the input.
25205 * <p>Now see what happens if you start typing then press the Escape key</p>
25207 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
25208 * <p id="inputDescription1">With $rollbackViewValue()</p>
25209 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue"
25210 * ng-keydown="resetWithCancel($event)"><br/>
25211 * myValue: "{{ myValue }}"
25213 * <p id="inputDescription2">Without $rollbackViewValue()</p>
25214 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue"
25215 * ng-keydown="resetWithoutCancel($event)"><br/>
25216 * myValue: "{{ myValue }}"
25222 this.$rollbackViewValue = function() {
25223 $timeout.cancel(pendingDebounce);
25224 ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
25230 * @name ngModel.NgModelController#$validate
25233 * Runs each of the registered validators (first synchronous validators and then
25234 * asynchronous validators).
25235 * If the validity changes to invalid, the model will be set to `undefined`,
25236 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
25237 * If the validity changes to valid, it will set the model to the last available valid
25238 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
25240 this.$validate = function() {
25241 // ignore $validate before model is initialized
25242 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25246 var viewValue = ctrl.$$lastCommittedViewValue;
25247 // Note: we use the $$rawModelValue as $modelValue might have been
25248 // set to undefined during a view -> model update that found validation
25249 // errors. We can't parse the view here, since that could change
25250 // the model although neither viewValue nor the model on the scope changed
25251 var modelValue = ctrl.$$rawModelValue;
25253 var prevValid = ctrl.$valid;
25254 var prevModelValue = ctrl.$modelValue;
25256 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25258 ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
25259 // If there was no change in validity, don't update the model
25260 // This prevents changing an invalid modelValue to undefined
25261 if (!allowInvalid && prevValid !== allValid) {
25262 // Note: Don't check ctrl.$valid here, as we could have
25263 // external validators (e.g. calculated on the server),
25264 // that just call $setValidity and need the model value
25265 // to calculate their validity.
25266 ctrl.$modelValue = allValid ? modelValue : undefined;
25268 if (ctrl.$modelValue !== prevModelValue) {
25269 ctrl.$$writeModelToScope();
25276 this.$$runValidators = function(modelValue, viewValue, doneCallback) {
25277 currentValidationRunId++;
25278 var localValidationRunId = currentValidationRunId;
25280 // check parser error
25281 if (!processParseErrors()) {
25282 validationDone(false);
25285 if (!processSyncValidators()) {
25286 validationDone(false);
25289 processAsyncValidators();
25291 function processParseErrors() {
25292 var errorKey = ctrl.$$parserName || 'parse';
25293 if (isUndefined(parserValid)) {
25294 setValidity(errorKey, null);
25296 if (!parserValid) {
25297 forEach(ctrl.$validators, function(v, name) {
25298 setValidity(name, null);
25300 forEach(ctrl.$asyncValidators, function(v, name) {
25301 setValidity(name, null);
25304 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
25305 setValidity(errorKey, parserValid);
25306 return parserValid;
25311 function processSyncValidators() {
25312 var syncValidatorsValid = true;
25313 forEach(ctrl.$validators, function(validator, name) {
25314 var result = validator(modelValue, viewValue);
25315 syncValidatorsValid = syncValidatorsValid && result;
25316 setValidity(name, result);
25318 if (!syncValidatorsValid) {
25319 forEach(ctrl.$asyncValidators, function(v, name) {
25320 setValidity(name, null);
25327 function processAsyncValidators() {
25328 var validatorPromises = [];
25329 var allValid = true;
25330 forEach(ctrl.$asyncValidators, function(validator, name) {
25331 var promise = validator(modelValue, viewValue);
25332 if (!isPromiseLike(promise)) {
25333 throw ngModelMinErr("$asyncValidators",
25334 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
25336 setValidity(name, undefined);
25337 validatorPromises.push(promise.then(function() {
25338 setValidity(name, true);
25339 }, function(error) {
25341 setValidity(name, false);
25344 if (!validatorPromises.length) {
25345 validationDone(true);
25347 $q.all(validatorPromises).then(function() {
25348 validationDone(allValid);
25353 function setValidity(name, isValid) {
25354 if (localValidationRunId === currentValidationRunId) {
25355 ctrl.$setValidity(name, isValid);
25359 function validationDone(allValid) {
25360 if (localValidationRunId === currentValidationRunId) {
25362 doneCallback(allValid);
25369 * @name ngModel.NgModelController#$commitViewValue
25372 * Commit a pending update to the `$modelValue`.
25374 * Updates may be pending by a debounced event or because the input is waiting for a some future
25375 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
25376 * usually handles calling this in response to input events.
25378 this.$commitViewValue = function() {
25379 var viewValue = ctrl.$viewValue;
25381 $timeout.cancel(pendingDebounce);
25383 // If the view value has not changed then we should just exit, except in the case where there is
25384 // a native validator on the element. In this case the validation state may have changed even though
25385 // the viewValue has stayed empty.
25386 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
25389 ctrl.$$lastCommittedViewValue = viewValue;
25392 if (ctrl.$pristine) {
25395 this.$$parseAndValidate();
25398 this.$$parseAndValidate = function() {
25399 var viewValue = ctrl.$$lastCommittedViewValue;
25400 var modelValue = viewValue;
25401 parserValid = isUndefined(modelValue) ? undefined : true;
25404 for (var i = 0; i < ctrl.$parsers.length; i++) {
25405 modelValue = ctrl.$parsers[i](modelValue);
25406 if (isUndefined(modelValue)) {
25407 parserValid = false;
25412 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25413 // ctrl.$modelValue has not been touched yet...
25414 ctrl.$modelValue = ngModelGet($scope);
25416 var prevModelValue = ctrl.$modelValue;
25417 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25418 ctrl.$$rawModelValue = modelValue;
25420 if (allowInvalid) {
25421 ctrl.$modelValue = modelValue;
25422 writeToModelIfNeeded();
25425 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
25426 // This can happen if e.g. $setViewValue is called from inside a parser
25427 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
25428 if (!allowInvalid) {
25429 // Note: Don't check ctrl.$valid here, as we could have
25430 // external validators (e.g. calculated on the server),
25431 // that just call $setValidity and need the model value
25432 // to calculate their validity.
25433 ctrl.$modelValue = allValid ? modelValue : undefined;
25434 writeToModelIfNeeded();
25438 function writeToModelIfNeeded() {
25439 if (ctrl.$modelValue !== prevModelValue) {
25440 ctrl.$$writeModelToScope();
25445 this.$$writeModelToScope = function() {
25446 ngModelSet($scope, ctrl.$modelValue);
25447 forEach(ctrl.$viewChangeListeners, function(listener) {
25451 $exceptionHandler(e);
25458 * @name ngModel.NgModelController#$setViewValue
25461 * Update the view value.
25463 * This method should be called when a control wants to change the view value; typically,
25464 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
25465 * directive calls it when the value of the input changes and {@link ng.directive:select select}
25466 * calls it when an option is selected.
25468 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
25469 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
25470 * value sent directly for processing, finally to be applied to `$modelValue` and then the
25471 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners,
25472 * in the `$viewChangeListeners` list, are called.
25474 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
25475 * and the `default` trigger is not listed, all those actions will remain pending until one of the
25476 * `updateOn` events is triggered on the DOM element.
25477 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
25478 * directive is used with a custom debounce for this particular event.
25479 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
25480 * is specified, once the timer runs out.
25482 * When used with standard inputs, the view value will always be a string (which is in some cases
25483 * parsed into another type, such as a `Date` object for `input[date]`.)
25484 * However, custom controls might also pass objects to this method. In this case, we should make
25485 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
25486 * perform a deep watch of objects, it only looks for a change of identity. If you only change
25487 * the property of the object then ngModel will not realise that the object has changed and
25488 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
25489 * not change properties of the copy once it has been passed to `$setViewValue`.
25490 * Otherwise you may cause the model value on the scope to change incorrectly.
25492 * <div class="alert alert-info">
25493 * In any case, the value passed to the method should always reflect the current value
25494 * of the control. For example, if you are calling `$setViewValue` for an input element,
25495 * you should pass the input DOM value. Otherwise, the control and the scope model become
25496 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
25497 * the control's DOM value in any way. If we want to change the control's DOM value
25498 * programmatically, we should update the `ngModel` scope expression. Its new value will be
25499 * picked up by the model controller, which will run it through the `$formatters`, `$render` it
25500 * to update the DOM, and finally call `$validate` on it.
25503 * @param {*} value value from the view.
25504 * @param {string} trigger Event that triggered the update.
25506 this.$setViewValue = function(value, trigger) {
25507 ctrl.$viewValue = value;
25508 if (!ctrl.$options || ctrl.$options.updateOnDefault) {
25509 ctrl.$$debounceViewValueCommit(trigger);
25513 this.$$debounceViewValueCommit = function(trigger) {
25514 var debounceDelay = 0,
25515 options = ctrl.$options,
25518 if (options && isDefined(options.debounce)) {
25519 debounce = options.debounce;
25520 if (isNumber(debounce)) {
25521 debounceDelay = debounce;
25522 } else if (isNumber(debounce[trigger])) {
25523 debounceDelay = debounce[trigger];
25524 } else if (isNumber(debounce['default'])) {
25525 debounceDelay = debounce['default'];
25529 $timeout.cancel(pendingDebounce);
25530 if (debounceDelay) {
25531 pendingDebounce = $timeout(function() {
25532 ctrl.$commitViewValue();
25534 } else if ($rootScope.$$phase) {
25535 ctrl.$commitViewValue();
25537 $scope.$apply(function() {
25538 ctrl.$commitViewValue();
25544 // Note: we cannot use a normal scope.$watch as we want to detect the following:
25545 // 1. scope value is 'a'
25546 // 2. user enters 'b'
25547 // 3. ng-change kicks in and reverts scope value to 'a'
25548 // -> scope value did not change since the last digest as
25549 // ng-change executes in apply phase
25550 // 4. view should be changed back to 'a'
25551 $scope.$watch(function ngModelWatch() {
25552 var modelValue = ngModelGet($scope);
25554 // if scope model value and ngModel value are out of sync
25555 // TODO(perf): why not move this to the action fn?
25556 if (modelValue !== ctrl.$modelValue &&
25557 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
25558 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
25560 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
25561 parserValid = undefined;
25563 var formatters = ctrl.$formatters,
25564 idx = formatters.length;
25566 var viewValue = modelValue;
25568 viewValue = formatters[idx](viewValue);
25570 if (ctrl.$viewValue !== viewValue) {
25571 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
25574 ctrl.$$runValidators(modelValue, viewValue, noop);
25591 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
25592 * property on the scope using {@link ngModel.NgModelController NgModelController},
25593 * which is created and exposed by this directive.
25595 * `ngModel` is responsible for:
25597 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
25599 * - Providing validation behavior (i.e. required, number, email, url).
25600 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
25601 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
25602 * - Registering the control with its parent {@link ng.directive:form form}.
25604 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
25605 * current scope. If the property doesn't already exist on this scope, it will be created
25606 * implicitly and added to the scope.
25608 * For best practices on using `ngModel`, see:
25610 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
25612 * For basic examples, how to use `ngModel`, see:
25614 * - {@link ng.directive:input input}
25615 * - {@link input[text] text}
25616 * - {@link input[checkbox] checkbox}
25617 * - {@link input[radio] radio}
25618 * - {@link input[number] number}
25619 * - {@link input[email] email}
25620 * - {@link input[url] url}
25621 * - {@link input[date] date}
25622 * - {@link input[datetime-local] datetime-local}
25623 * - {@link input[time] time}
25624 * - {@link input[month] month}
25625 * - {@link input[week] week}
25626 * - {@link ng.directive:select select}
25627 * - {@link ng.directive:textarea textarea}
25630 * The following CSS classes are added and removed on the associated input/select/textarea element
25631 * depending on the validity of the model.
25633 * - `ng-valid`: the model is valid
25634 * - `ng-invalid`: the model is invalid
25635 * - `ng-valid-[key]`: for each valid key added by `$setValidity`
25636 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
25637 * - `ng-pristine`: the control hasn't been interacted with yet
25638 * - `ng-dirty`: the control has been interacted with
25639 * - `ng-touched`: the control has been blurred
25640 * - `ng-untouched`: the control hasn't been blurred
25641 * - `ng-pending`: any `$asyncValidators` are unfulfilled
25643 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
25645 * ## Animation Hooks
25647 * Animations within models are triggered when any of the associated CSS classes are added and removed
25648 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
25649 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
25650 * The animations that are triggered within ngModel are similar to how they work in ngClass and
25651 * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
25653 * The following example shows a simple way to utilize CSS transitions to style an input element
25654 * that has been rendered as invalid after it has been validated:
25657 * //be sure to include ngAnimate as a module to hook into more
25658 * //advanced animations
25660 * transition:0.5s linear all;
25661 * background: white;
25663 * .my-input.ng-invalid {
25670 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
25671 <file name="index.html">
25673 angular.module('inputExample', [])
25674 .controller('ExampleController', ['$scope', function($scope) {
25680 transition:all linear 0.5s;
25681 background: transparent;
25683 .my-input.ng-invalid {
25688 <p id="inputDescription">
25689 Update input to see transitions when valid/invalid.
25690 Integer is a valid value.
25692 <form name="testForm" ng-controller="ExampleController">
25693 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input"
25694 aria-describedby="inputDescription" />
25699 * ## Binding to a getter/setter
25701 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
25702 * function that returns a representation of the model when called with zero arguments, and sets
25703 * the internal state of a model when called with an argument. It's sometimes useful to use this
25704 * for models that have an internal representation that's different from what the model exposes
25707 * <div class="alert alert-success">
25708 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
25709 * frequently than other parts of your code.
25712 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
25713 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
25714 * a `<form>`, which will enable this behavior for all `<input>`s within it. See
25715 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
25717 * The following example shows how to use `ngModel` with a getter/setter:
25720 * <example name="ngModel-getter-setter" module="getterSetterExample">
25721 <file name="index.html">
25722 <div ng-controller="ExampleController">
25723 <form name="userForm">
25725 <input type="text" name="userName"
25726 ng-model="user.name"
25727 ng-model-options="{ getterSetter: true }" />
25730 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25733 <file name="app.js">
25734 angular.module('getterSetterExample', [])
25735 .controller('ExampleController', ['$scope', function($scope) {
25736 var _name = 'Brian';
25738 name: function(newName) {
25739 // Note that newName can be undefined for two reasons:
25740 // 1. Because it is called as a getter and thus called with no arguments
25741 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25742 // input is invalid
25743 return arguments.length ? (_name = newName) : _name;
25750 var ngModelDirective = ['$rootScope', function($rootScope) {
25753 require: ['ngModel', '^?form', '^?ngModelOptions'],
25754 controller: NgModelController,
25755 // Prelink needs to run before any input directive
25756 // so that we can set the NgModelOptions in NgModelController
25757 // before anyone else uses it.
25759 compile: function ngModelCompile(element) {
25760 // Setup initial state of the control
25761 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
25764 pre: function ngModelPreLink(scope, element, attr, ctrls) {
25765 var modelCtrl = ctrls[0],
25766 formCtrl = ctrls[1] || modelCtrl.$$parentForm;
25768 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
25770 // notify others, especially parent forms
25771 formCtrl.$addControl(modelCtrl);
25773 attr.$observe('name', function(newValue) {
25774 if (modelCtrl.$name !== newValue) {
25775 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
25779 scope.$on('$destroy', function() {
25780 modelCtrl.$$parentForm.$removeControl(modelCtrl);
25783 post: function ngModelPostLink(scope, element, attr, ctrls) {
25784 var modelCtrl = ctrls[0];
25785 if (modelCtrl.$options && modelCtrl.$options.updateOn) {
25786 element.on(modelCtrl.$options.updateOn, function(ev) {
25787 modelCtrl.$$debounceViewValueCommit(ev && ev.type);
25791 element.on('blur', function(ev) {
25792 if (modelCtrl.$touched) return;
25794 if ($rootScope.$$phase) {
25795 scope.$evalAsync(modelCtrl.$setTouched);
25797 scope.$apply(modelCtrl.$setTouched);
25806 var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
25810 * @name ngModelOptions
25813 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
25814 * events that will trigger a model update and/or a debouncing delay so that the actual update only
25815 * takes place when a timer expires; this timer will be reset after another change takes place.
25817 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
25818 * be different from the value in the actual model. This means that if you update the model you
25819 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
25820 * order to make sure it is synchronized with the model and that any debounced action is canceled.
25822 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
25823 * method is by making sure the input is placed inside a form that has a `name` attribute. This is
25824 * important because `form` controllers are published to the related scope under the name in their
25825 * `name` attribute.
25827 * Any pending changes will take place immediately when an enclosing form is submitted via the
25828 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
25829 * to have access to the updated model.
25831 * `ngModelOptions` has an effect on the element it's declared on and its descendants.
25833 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
25834 * - `updateOn`: string specifying which event should the input be bound to. You can set several
25835 * events using an space delimited list. There is a special event called `default` that
25836 * matches the default events belonging of the control.
25837 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
25838 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
25839 * custom value for each event. For example:
25840 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"`
25841 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
25842 * not validate correctly instead of the default behavior of setting the model to undefined.
25843 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
25844 `ngModel` as getters/setters.
25845 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
25846 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the
25847 * continental US time zone abbreviations, but for general use, use a time zone offset, for
25848 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
25849 * If not specified, the timezone of the browser will be used.
25853 The following example shows how to override immediate updates. Changes on the inputs within the
25854 form will update the model only when the control loses focus (blur event). If `escape` key is
25855 pressed while the input field is focused, the value is reset to the value in the current model.
25857 <example name="ngModelOptions-directive-blur" module="optionsExample">
25858 <file name="index.html">
25859 <div ng-controller="ExampleController">
25860 <form name="userForm">
25862 <input type="text" name="userName"
25863 ng-model="user.name"
25864 ng-model-options="{ updateOn: 'blur' }"
25865 ng-keyup="cancel($event)" />
25868 <input type="text" ng-model="user.data" />
25871 <pre>user.name = <span ng-bind="user.name"></span></pre>
25872 <pre>user.data = <span ng-bind="user.data"></span></pre>
25875 <file name="app.js">
25876 angular.module('optionsExample', [])
25877 .controller('ExampleController', ['$scope', function($scope) {
25878 $scope.user = { name: 'John', data: '' };
25880 $scope.cancel = function(e) {
25881 if (e.keyCode == 27) {
25882 $scope.userForm.userName.$rollbackViewValue();
25887 <file name="protractor.js" type="protractor">
25888 var model = element(by.binding('user.name'));
25889 var input = element(by.model('user.name'));
25890 var other = element(by.model('user.data'));
25892 it('should allow custom events', function() {
25893 input.sendKeys(' Doe');
25895 expect(model.getText()).toEqual('John');
25897 expect(model.getText()).toEqual('John Doe');
25900 it('should $rollbackViewValue when model changes', function() {
25901 input.sendKeys(' Doe');
25902 expect(input.getAttribute('value')).toEqual('John Doe');
25903 input.sendKeys(protractor.Key.ESCAPE);
25904 expect(input.getAttribute('value')).toEqual('John');
25906 expect(model.getText()).toEqual('John');
25911 This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
25912 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
25914 <example name="ngModelOptions-directive-debounce" module="optionsExample">
25915 <file name="index.html">
25916 <div ng-controller="ExampleController">
25917 <form name="userForm">
25919 <input type="text" name="userName"
25920 ng-model="user.name"
25921 ng-model-options="{ debounce: 1000 }" />
25923 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button>
25926 <pre>user.name = <span ng-bind="user.name"></span></pre>
25929 <file name="app.js">
25930 angular.module('optionsExample', [])
25931 .controller('ExampleController', ['$scope', function($scope) {
25932 $scope.user = { name: 'Igor' };
25937 This one shows how to bind to getter/setters:
25939 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
25940 <file name="index.html">
25941 <div ng-controller="ExampleController">
25942 <form name="userForm">
25944 <input type="text" name="userName"
25945 ng-model="user.name"
25946 ng-model-options="{ getterSetter: true }" />
25949 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25952 <file name="app.js">
25953 angular.module('getterSetterExample', [])
25954 .controller('ExampleController', ['$scope', function($scope) {
25955 var _name = 'Brian';
25957 name: function(newName) {
25958 // Note that newName can be undefined for two reasons:
25959 // 1. Because it is called as a getter and thus called with no arguments
25960 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25961 // input is invalid
25962 return arguments.length ? (_name = newName) : _name;
25969 var ngModelOptionsDirective = function() {
25972 controller: ['$scope', '$attrs', function($scope, $attrs) {
25974 this.$options = copy($scope.$eval($attrs.ngModelOptions));
25975 // Allow adding/overriding bound events
25976 if (isDefined(this.$options.updateOn)) {
25977 this.$options.updateOnDefault = false;
25978 // extract "default" pseudo-event from list of events that can trigger a model update
25979 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
25980 that.$options.updateOnDefault = true;
25984 this.$options.updateOnDefault = true;
25993 function addSetValidityMethod(context) {
25994 var ctrl = context.ctrl,
25995 $element = context.$element,
25998 unset = context.unset,
25999 $animate = context.$animate;
26001 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
26003 ctrl.$setValidity = setValidity;
26005 function setValidity(validationErrorKey, state, controller) {
26006 if (isUndefined(state)) {
26007 createAndSet('$pending', validationErrorKey, controller);
26009 unsetAndCleanup('$pending', validationErrorKey, controller);
26011 if (!isBoolean(state)) {
26012 unset(ctrl.$error, validationErrorKey, controller);
26013 unset(ctrl.$$success, validationErrorKey, controller);
26016 unset(ctrl.$error, validationErrorKey, controller);
26017 set(ctrl.$$success, validationErrorKey, controller);
26019 set(ctrl.$error, validationErrorKey, controller);
26020 unset(ctrl.$$success, validationErrorKey, controller);
26023 if (ctrl.$pending) {
26024 cachedToggleClass(PENDING_CLASS, true);
26025 ctrl.$valid = ctrl.$invalid = undefined;
26026 toggleValidationCss('', null);
26028 cachedToggleClass(PENDING_CLASS, false);
26029 ctrl.$valid = isObjectEmpty(ctrl.$error);
26030 ctrl.$invalid = !ctrl.$valid;
26031 toggleValidationCss('', ctrl.$valid);
26034 // re-read the state as the set/unset methods could have
26035 // combined state in ctrl.$error[validationError] (used for forms),
26036 // where setting/unsetting only increments/decrements the value,
26037 // and does not replace it.
26039 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
26040 combinedState = undefined;
26041 } else if (ctrl.$error[validationErrorKey]) {
26042 combinedState = false;
26043 } else if (ctrl.$$success[validationErrorKey]) {
26044 combinedState = true;
26046 combinedState = null;
26049 toggleValidationCss(validationErrorKey, combinedState);
26050 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
26053 function createAndSet(name, value, controller) {
26057 set(ctrl[name], value, controller);
26060 function unsetAndCleanup(name, value, controller) {
26062 unset(ctrl[name], value, controller);
26064 if (isObjectEmpty(ctrl[name])) {
26065 ctrl[name] = undefined;
26069 function cachedToggleClass(className, switchValue) {
26070 if (switchValue && !classCache[className]) {
26071 $animate.addClass($element, className);
26072 classCache[className] = true;
26073 } else if (!switchValue && classCache[className]) {
26074 $animate.removeClass($element, className);
26075 classCache[className] = false;
26079 function toggleValidationCss(validationErrorKey, isValid) {
26080 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
26082 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
26083 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
26087 function isObjectEmpty(obj) {
26089 for (var prop in obj) {
26090 if (obj.hasOwnProperty(prop)) {
26100 * @name ngNonBindable
26105 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current
26106 * DOM element. This is useful if the element contains what appears to be Angular directives and
26107 * bindings but which should be ignored by Angular. This could be the case if you have a site that
26108 * displays snippets of code, for instance.
26113 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
26114 * but the one wrapped in `ngNonBindable` is left alone.
26118 <file name="index.html">
26119 <div>Normal: {{1 + 2}}</div>
26120 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
26122 <file name="protractor.js" type="protractor">
26123 it('should check ng-non-bindable', function() {
26124 expect(element(by.binding('1 + 2')).getText()).toContain('3');
26125 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/);
26130 var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
26132 /* global jqLiteRemove */
26134 var ngOptionsMinErr = minErr('ngOptions');
26143 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
26144 * elements for the `<select>` element using the array or object obtained by evaluating the
26145 * `ngOptions` comprehension expression.
26147 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
26148 * similar result. However, `ngOptions` provides some benefits such as reducing memory and
26149 * increasing speed by not creating a new scope for each repeated instance, as well as providing
26150 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
26151 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
26152 * to a non-string value. This is because an option element can only be bound to string values at
26155 * When an item in the `<select>` menu is selected, the array element or object property
26156 * represented by the selected option will be bound to the model identified by the `ngModel`
26159 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
26160 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
26161 * option. See example below for demonstration.
26163 * ## Complex Models (objects or collections)
26165 * By default, `ngModel` watches the model by reference, not value. This is important to know when
26166 * binding the select to a model that is an object or a collection.
26168 * One issue occurs if you want to preselect an option. For example, if you set
26169 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
26170 * because the objects are not identical. So by default, you should always reference the item in your collection
26171 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
26173 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
26174 * of the item not by reference, but by the result of the `track by` expression. For example, if your
26175 * collection items have an id property, you would `track by item.id`.
26177 * A different issue with objects or collections is that ngModel won't detect if an object property or
26178 * a collection item changes. For that reason, `ngOptions` additionally watches the model using
26179 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
26180 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
26181 * has not changed identity, but only a property on the object or an item in the collection changes.
26183 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
26184 * if the model is an array). This means that changing a property deeper than the first level inside the
26185 * object/collection will not trigger a re-rendering.
26187 * ## `select` **`as`**
26189 * Using `select` **`as`** will bind the result of the `select` expression to the model, but
26190 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
26191 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
26192 * is used, the result of that expression will be set as the value of the `option` and `select` elements.
26195 * ### `select` **`as`** and **`track by`**
26197 * <div class="alert alert-warning">
26198 * Be careful when using `select` **`as`** and **`track by`** in the same expression.
26201 * Given this array of items on the $scope:
26204 * $scope.items = [{
26207 * subItem: { name: 'aSubItem' }
26211 * subItem: { name: 'bSubItem' }
26218 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
26221 * $scope.selected = $scope.items[0];
26224 * but this will not work:
26227 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
26230 * $scope.selected = $scope.items[0].subItem;
26233 * In both examples, the **`track by`** expression is applied successfully to each `item` in the
26234 * `items` array. Because the selected option has been set programmatically in the controller, the
26235 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the
26236 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
26237 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
26238 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
26239 * is not matched against any `<option>` and the `<select>` appears as having no selected value.
26242 * @param {string} ngModel Assignable angular expression to data-bind to.
26243 * @param {string=} name Property name of the form under which the control is published.
26244 * @param {string=} required The control is considered valid only if value is entered.
26245 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
26246 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
26247 * `required` when you want to data-bind to the `required` attribute.
26248 * @param {comprehension_expression=} ngOptions in one of the following forms:
26250 * * for array data sources:
26251 * * `label` **`for`** `value` **`in`** `array`
26252 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
26253 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
26254 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
26255 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26256 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26257 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
26258 * (for including a filter with `track by`)
26259 * * for object data sources:
26260 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26261 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26262 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
26263 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
26264 * * `select` **`as`** `label` **`group by`** `group`
26265 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26266 * * `select` **`as`** `label` **`disable when`** `disable`
26267 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26271 * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
26272 * * `value`: local variable which will refer to each item in the `array` or each property value
26273 * of `object` during iteration.
26274 * * `key`: local variable which will refer to a property name in `object` during iteration.
26275 * * `label`: The result of this expression will be the label for `<option>` element. The
26276 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
26277 * * `select`: The result of this expression will be bound to the model of the parent `<select>`
26278 * element. If not specified, `select` expression will default to `value`.
26279 * * `group`: The result of this expression will be used to group options using the `<optgroup>`
26281 * * `disable`: The result of this expression will be used to disable the rendered `<option>`
26282 * element. Return `true` to disable.
26283 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
26284 * used to identify the objects in the array. The `trackexpr` will most likely refer to the
26285 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
26286 * even when the options are recreated (e.g. reloaded from the server).
26289 <example module="selectExample">
26290 <file name="index.html">
26292 angular.module('selectExample', [])
26293 .controller('ExampleController', ['$scope', function($scope) {
26295 {name:'black', shade:'dark'},
26296 {name:'white', shade:'light', notAnOption: true},
26297 {name:'red', shade:'dark'},
26298 {name:'blue', shade:'dark', notAnOption: true},
26299 {name:'yellow', shade:'light', notAnOption: false}
26301 $scope.myColor = $scope.colors[2]; // red
26304 <div ng-controller="ExampleController">
26306 <li ng-repeat="color in colors">
26307 <label>Name: <input ng-model="color.name"></label>
26308 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
26309 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
26312 <button ng-click="colors.push({})">add</button>
26316 <label>Color (null not allowed):
26317 <select ng-model="myColor" ng-options="color.name for color in colors"></select>
26319 <label>Color (null allowed):
26320 <span class="nullable">
26321 <select ng-model="myColor" ng-options="color.name for color in colors">
26322 <option value="">-- choose color --</option>
26324 </span></label><br/>
26326 <label>Color grouped by shade:
26327 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
26331 <label>Color grouped by shade, with some disabled:
26332 <select ng-model="myColor"
26333 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
26339 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
26342 Currently selected: {{ {selected_color:myColor} }}
26343 <div style="border:solid 1px black; height:20px"
26344 ng-style="{'background-color':myColor.name}">
26348 <file name="protractor.js" type="protractor">
26349 it('should check ng-options', function() {
26350 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
26351 element.all(by.model('myColor')).first().click();
26352 element.all(by.css('select[ng-model="myColor"] option')).first().click();
26353 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
26354 element(by.css('.nullable select[ng-model="myColor"]')).click();
26355 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
26356 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
26362 // jshint maxlen: false
26363 // //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
26364 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]+?))?$/;
26365 // 1: value expression (valueFn)
26366 // 2: label expression (displayFn)
26367 // 3: group by expression (groupByFn)
26368 // 4: disable when expression (disableWhenFn)
26369 // 5: array item variable name
26370 // 6: object item key variable name
26371 // 7: object item value variable name
26372 // 8: collection expression
26373 // 9: track by expression
26374 // jshint maxlen: 100
26377 var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
26379 function parseOptionsExpression(optionsExp, selectElement, scope) {
26381 var match = optionsExp.match(NG_OPTIONS_REGEXP);
26383 throw ngOptionsMinErr('iexp',
26384 "Expected expression in form of " +
26385 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
26386 " but got '{0}'. Element: {1}",
26387 optionsExp, startingTag(selectElement));
26390 // Extract the parts from the ngOptions expression
26392 // The variable name for the value of the item in the collection
26393 var valueName = match[5] || match[7];
26394 // The variable name for the key of the item in the collection
26395 var keyName = match[6];
26397 // An expression that generates the viewValue for an option if there is a label expression
26398 var selectAs = / as /.test(match[0]) && match[1];
26399 // An expression that is used to track the id of each object in the options collection
26400 var trackBy = match[9];
26401 // An expression that generates the viewValue for an option if there is no label expression
26402 var valueFn = $parse(match[2] ? match[1] : valueName);
26403 var selectAsFn = selectAs && $parse(selectAs);
26404 var viewValueFn = selectAsFn || valueFn;
26405 var trackByFn = trackBy && $parse(trackBy);
26407 // Get the value by which we are going to track the option
26408 // if we have a trackFn then use that (passing scope and locals)
26409 // otherwise just hash the given viewValue
26410 var getTrackByValueFn = trackBy ?
26411 function(value, locals) { return trackByFn(scope, locals); } :
26412 function getHashOfValue(value) { return hashKey(value); };
26413 var getTrackByValue = function(value, key) {
26414 return getTrackByValueFn(value, getLocals(value, key));
26417 var displayFn = $parse(match[2] || match[1]);
26418 var groupByFn = $parse(match[3] || '');
26419 var disableWhenFn = $parse(match[4] || '');
26420 var valuesFn = $parse(match[8]);
26423 var getLocals = keyName ? function(value, key) {
26424 locals[keyName] = key;
26425 locals[valueName] = value;
26427 } : function(value) {
26428 locals[valueName] = value;
26433 function Option(selectValue, viewValue, label, group, disabled) {
26434 this.selectValue = selectValue;
26435 this.viewValue = viewValue;
26436 this.label = label;
26437 this.group = group;
26438 this.disabled = disabled;
26441 function getOptionValuesKeys(optionValues) {
26442 var optionValuesKeys;
26444 if (!keyName && isArrayLike(optionValues)) {
26445 optionValuesKeys = optionValues;
26447 // if object, extract keys, in enumeration order, unsorted
26448 optionValuesKeys = [];
26449 for (var itemKey in optionValues) {
26450 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
26451 optionValuesKeys.push(itemKey);
26455 return optionValuesKeys;
26460 getTrackByValue: getTrackByValue,
26461 getWatchables: $parse(valuesFn, function(optionValues) {
26462 // Create a collection of things that we would like to watch (watchedArray)
26463 // so that they can all be watched using a single $watchCollection
26464 // that only runs the handler once if anything changes
26465 var watchedArray = [];
26466 optionValues = optionValues || [];
26468 var optionValuesKeys = getOptionValuesKeys(optionValues);
26469 var optionValuesLength = optionValuesKeys.length;
26470 for (var index = 0; index < optionValuesLength; index++) {
26471 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26472 var value = optionValues[key];
26474 var locals = getLocals(optionValues[key], key);
26475 var selectValue = getTrackByValueFn(optionValues[key], locals);
26476 watchedArray.push(selectValue);
26478 // Only need to watch the displayFn if there is a specific label expression
26479 if (match[2] || match[1]) {
26480 var label = displayFn(scope, locals);
26481 watchedArray.push(label);
26484 // Only need to watch the disableWhenFn if there is a specific disable expression
26486 var disableWhen = disableWhenFn(scope, locals);
26487 watchedArray.push(disableWhen);
26490 return watchedArray;
26493 getOptions: function() {
26495 var optionItems = [];
26496 var selectValueMap = {};
26498 // The option values were already computed in the `getWatchables` fn,
26499 // which must have been called to trigger `getOptions`
26500 var optionValues = valuesFn(scope) || [];
26501 var optionValuesKeys = getOptionValuesKeys(optionValues);
26502 var optionValuesLength = optionValuesKeys.length;
26504 for (var index = 0; index < optionValuesLength; index++) {
26505 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26506 var value = optionValues[key];
26507 var locals = getLocals(value, key);
26508 var viewValue = viewValueFn(scope, locals);
26509 var selectValue = getTrackByValueFn(viewValue, locals);
26510 var label = displayFn(scope, locals);
26511 var group = groupByFn(scope, locals);
26512 var disabled = disableWhenFn(scope, locals);
26513 var optionItem = new Option(selectValue, viewValue, label, group, disabled);
26515 optionItems.push(optionItem);
26516 selectValueMap[selectValue] = optionItem;
26520 items: optionItems,
26521 selectValueMap: selectValueMap,
26522 getOptionFromViewValue: function(value) {
26523 return selectValueMap[getTrackByValue(value)];
26525 getViewValueFromOption: function(option) {
26526 // If the viewValue could be an object that may be mutated by the application,
26527 // we need to make a copy and not return the reference to the value on the option.
26528 return trackBy ? angular.copy(option.viewValue) : option.viewValue;
26536 // we can't just jqLite('<option>') since jqLite is not smart enough
26537 // to create it in <select> and IE barfs otherwise.
26538 var optionTemplate = document.createElement('option'),
26539 optGroupTemplate = document.createElement('optgroup');
26542 function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
26544 // if ngModel is not defined, we don't need to do anything
26545 var ngModelCtrl = ctrls[1];
26546 if (!ngModelCtrl) return;
26548 var selectCtrl = ctrls[0];
26549 var multiple = attr.multiple;
26551 // The emptyOption allows the application developer to provide their own custom "empty"
26552 // option when the viewValue does not match any of the option values.
26554 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
26555 if (children[i].value === '') {
26556 emptyOption = children.eq(i);
26561 var providedEmptyOption = !!emptyOption;
26563 var unknownOption = jqLite(optionTemplate.cloneNode(false));
26564 unknownOption.val('?');
26567 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
26570 var renderEmptyOption = function() {
26571 if (!providedEmptyOption) {
26572 selectElement.prepend(emptyOption);
26574 selectElement.val('');
26575 emptyOption.prop('selected', true); // needed for IE
26576 emptyOption.attr('selected', true);
26579 var removeEmptyOption = function() {
26580 if (!providedEmptyOption) {
26581 emptyOption.remove();
26586 var renderUnknownOption = function() {
26587 selectElement.prepend(unknownOption);
26588 selectElement.val('?');
26589 unknownOption.prop('selected', true); // needed for IE
26590 unknownOption.attr('selected', true);
26593 var removeUnknownOption = function() {
26594 unknownOption.remove();
26597 // Update the controller methods for multiple selectable options
26600 selectCtrl.writeValue = function writeNgOptionsValue(value) {
26601 var option = options.getOptionFromViewValue(value);
26603 if (option && !option.disabled) {
26604 if (selectElement[0].value !== option.selectValue) {
26605 removeUnknownOption();
26606 removeEmptyOption();
26608 selectElement[0].value = option.selectValue;
26609 option.element.selected = true;
26610 option.element.setAttribute('selected', 'selected');
26613 if (value === null || providedEmptyOption) {
26614 removeUnknownOption();
26615 renderEmptyOption();
26617 removeEmptyOption();
26618 renderUnknownOption();
26623 selectCtrl.readValue = function readNgOptionsValue() {
26625 var selectedOption = options.selectValueMap[selectElement.val()];
26627 if (selectedOption && !selectedOption.disabled) {
26628 removeEmptyOption();
26629 removeUnknownOption();
26630 return options.getViewValueFromOption(selectedOption);
26635 // If we are using `track by` then we must watch the tracked value on the model
26636 // since ngModel only watches for object identity change
26637 if (ngOptions.trackBy) {
26639 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
26640 function() { ngModelCtrl.$render(); }
26646 ngModelCtrl.$isEmpty = function(value) {
26647 return !value || value.length === 0;
26651 selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
26652 options.items.forEach(function(option) {
26653 option.element.selected = false;
26657 value.forEach(function(item) {
26658 var option = options.getOptionFromViewValue(item);
26659 if (option && !option.disabled) option.element.selected = true;
26665 selectCtrl.readValue = function readNgOptionsMultiple() {
26666 var selectedValues = selectElement.val() || [],
26669 forEach(selectedValues, function(value) {
26670 var option = options.selectValueMap[value];
26671 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option));
26677 // If we are using `track by` then we must watch these tracked values on the model
26678 // since ngModel only watches for object identity change
26679 if (ngOptions.trackBy) {
26681 scope.$watchCollection(function() {
26682 if (isArray(ngModelCtrl.$viewValue)) {
26683 return ngModelCtrl.$viewValue.map(function(value) {
26684 return ngOptions.getTrackByValue(value);
26688 ngModelCtrl.$render();
26695 if (providedEmptyOption) {
26697 // we need to remove it before calling selectElement.empty() because otherwise IE will
26698 // remove the label from the element. wtf?
26699 emptyOption.remove();
26701 // compile the element since there might be bindings in it
26702 $compile(emptyOption)(scope);
26704 // remove the class, which is added automatically because we recompile the element and it
26705 // becomes the compilation root
26706 emptyOption.removeClass('ng-scope');
26708 emptyOption = jqLite(optionTemplate.cloneNode(false));
26711 // We need to do this here to ensure that the options object is defined
26712 // when we first hit it in writeNgOptionsValue
26715 // We will re-render the option elements if the option values or labels change
26716 scope.$watchCollection(ngOptions.getWatchables, updateOptions);
26718 // ------------------------------------------------------------------ //
26721 function updateOptionElement(option, element) {
26722 option.element = element;
26723 element.disabled = option.disabled;
26724 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
26725 // selects in certain circumstances when multiple selects are next to each other and display
26726 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
26727 // See https://github.com/angular/angular.js/issues/11314 for more info.
26728 // This is unfortunately untestable with unit / e2e tests
26729 if (option.label !== element.label) {
26730 element.label = option.label;
26731 element.textContent = option.label;
26733 if (option.value !== element.value) element.value = option.selectValue;
26736 function addOrReuseElement(parent, current, type, templateElement) {
26738 // Check whether we can reuse the next element
26739 if (current && lowercase(current.nodeName) === type) {
26740 // The next element is the right type so reuse it
26743 // The next element is not the right type so create a new one
26744 element = templateElement.cloneNode(false);
26746 // There are no more elements so just append it to the select
26747 parent.appendChild(element);
26749 // The next element is not a group so insert the new one
26750 parent.insertBefore(element, current);
26757 function removeExcessElements(current) {
26760 next = current.nextSibling;
26761 jqLiteRemove(current);
26767 function skipEmptyAndUnknownOptions(current) {
26768 var emptyOption_ = emptyOption && emptyOption[0];
26769 var unknownOption_ = unknownOption && unknownOption[0];
26771 // We cannot rely on the extracted empty option being the same as the compiled empty option,
26772 // because the compiled empty option might have been replaced by a comment because
26773 // it had an "element" transclusion directive on it (such as ngIf)
26774 if (emptyOption_ || unknownOption_) {
26776 (current === emptyOption_ ||
26777 current === unknownOption_ ||
26778 current.nodeType === NODE_TYPE_COMMENT ||
26779 current.value === '')) {
26780 current = current.nextSibling;
26787 function updateOptions() {
26789 var previousValue = options && selectCtrl.readValue();
26791 options = ngOptions.getOptions();
26794 var currentElement = selectElement[0].firstChild;
26796 // Ensure that the empty option is always there if it was explicitly provided
26797 if (providedEmptyOption) {
26798 selectElement.prepend(emptyOption);
26801 currentElement = skipEmptyAndUnknownOptions(currentElement);
26803 options.items.forEach(function updateOption(option) {
26808 if (option.group) {
26810 // This option is to live in a group
26811 // See if we have already created this group
26812 group = groupMap[option.group];
26816 // We have not already created this group
26817 groupElement = addOrReuseElement(selectElement[0],
26821 // Move to the next element
26822 currentElement = groupElement.nextSibling;
26824 // Update the label on the group element
26825 groupElement.label = option.group;
26827 // Store it for use later
26828 group = groupMap[option.group] = {
26829 groupElement: groupElement,
26830 currentOptionElement: groupElement.firstChild
26835 // So now we have a group for this option we add the option to the group
26836 optionElement = addOrReuseElement(group.groupElement,
26837 group.currentOptionElement,
26840 updateOptionElement(option, optionElement);
26841 // Move to the next element
26842 group.currentOptionElement = optionElement.nextSibling;
26846 // This option is not in a group
26847 optionElement = addOrReuseElement(selectElement[0],
26851 updateOptionElement(option, optionElement);
26852 // Move to the next element
26853 currentElement = optionElement.nextSibling;
26858 // Now remove all excess options and group
26859 Object.keys(groupMap).forEach(function(key) {
26860 removeExcessElements(groupMap[key].currentOptionElement);
26862 removeExcessElements(currentElement);
26864 ngModelCtrl.$render();
26866 // Check to see if the value has changed due to the update to the options
26867 if (!ngModelCtrl.$isEmpty(previousValue)) {
26868 var nextValue = selectCtrl.readValue();
26869 if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
26870 ngModelCtrl.$setViewValue(nextValue);
26871 ngModelCtrl.$render();
26881 require: ['select', '?ngModel'],
26883 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
26884 // Deactivate the SelectController.register method to prevent
26885 // option directives from accidentally registering themselves
26886 // (and unwanted $destroy handlers etc.)
26887 ctrls[0].registerOption = noop;
26889 post: ngOptionsPostLink
26896 * @name ngPluralize
26900 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
26901 * These rules are bundled with angular.js, but can be overridden
26902 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
26903 * by specifying the mappings between
26904 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26905 * and the strings to be displayed.
26907 * # Plural categories and explicit number rules
26909 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26910 * in Angular's default en-US locale: "one" and "other".
26912 * While a plural category may match many numbers (for example, in en-US locale, "other" can match
26913 * any number that is not 1), an explicit number rule can only match one number. For example, the
26914 * explicit number rule for "3" matches the number 3. There are examples of plural categories
26915 * and explicit number rules throughout the rest of this documentation.
26917 * # Configuring ngPluralize
26918 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
26919 * You can also provide an optional attribute, `offset`.
26921 * The value of the `count` attribute can be either a string or an {@link guide/expression
26922 * Angular expression}; these are evaluated on the current scope for its bound value.
26924 * The `when` attribute specifies the mappings between plural categories and the actual
26925 * string to be displayed. The value of the attribute should be a JSON object.
26927 * The following example shows how to configure ngPluralize:
26930 * <ng-pluralize count="personCount"
26931 when="{'0': 'Nobody is viewing.',
26932 * 'one': '1 person is viewing.',
26933 * 'other': '{} people are viewing.'}">
26937 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
26938 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
26939 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
26940 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
26941 * show "a dozen people are viewing".
26943 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
26944 * into pluralized strings. In the previous example, Angular will replace `{}` with
26945 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
26946 * for <span ng-non-bindable>{{numberExpression}}</span>.
26948 * If no rule is defined for a category, then an empty string is displayed and a warning is generated.
26949 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
26951 * # Configuring ngPluralize with offset
26952 * The `offset` attribute allows further customization of pluralized text, which can result in
26953 * a better user experience. For example, instead of the message "4 people are viewing this document",
26954 * you might display "John, Kate and 2 others are viewing this document".
26955 * The offset attribute allows you to offset a number by any desired value.
26956 * Let's take a look at an example:
26959 * <ng-pluralize count="personCount" offset=2
26960 * when="{'0': 'Nobody is viewing.',
26961 * '1': '{{person1}} is viewing.',
26962 * '2': '{{person1}} and {{person2}} are viewing.',
26963 * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
26964 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
26968 * Notice that we are still using two plural categories(one, other), but we added
26969 * three explicit number rules 0, 1 and 2.
26970 * When one person, perhaps John, views the document, "John is viewing" will be shown.
26971 * When three people view the document, no explicit number rule is found, so
26972 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
26973 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
26976 * Note that when you specify offsets, you must provide explicit number rules for
26977 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
26978 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
26979 * plural categories "one" and "other".
26981 * @param {string|expression} count The variable to be bound to.
26982 * @param {string} when The mapping between plural category to its corresponding strings.
26983 * @param {number=} offset Offset to deduct from the total number.
26986 <example module="pluralizeExample">
26987 <file name="index.html">
26989 angular.module('pluralizeExample', [])
26990 .controller('ExampleController', ['$scope', function($scope) {
26991 $scope.person1 = 'Igor';
26992 $scope.person2 = 'Misko';
26993 $scope.personCount = 1;
26996 <div ng-controller="ExampleController">
26997 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/>
26998 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/>
26999 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/>
27001 <!--- Example with simple pluralization rules for en locale --->
27003 <ng-pluralize count="personCount"
27004 when="{'0': 'Nobody is viewing.',
27005 'one': '1 person is viewing.',
27006 'other': '{} people are viewing.'}">
27007 </ng-pluralize><br>
27009 <!--- Example with offset --->
27011 <ng-pluralize count="personCount" offset=2
27012 when="{'0': 'Nobody is viewing.',
27013 '1': '{{person1}} is viewing.',
27014 '2': '{{person1}} and {{person2}} are viewing.',
27015 'one': '{{person1}}, {{person2}} and one other person are viewing.',
27016 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
27020 <file name="protractor.js" type="protractor">
27021 it('should show correct pluralized string', function() {
27022 var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
27023 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27024 var countInput = element(by.model('personCount'));
27026 expect(withoutOffset.getText()).toEqual('1 person is viewing.');
27027 expect(withOffset.getText()).toEqual('Igor is viewing.');
27029 countInput.clear();
27030 countInput.sendKeys('0');
27032 expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
27033 expect(withOffset.getText()).toEqual('Nobody is viewing.');
27035 countInput.clear();
27036 countInput.sendKeys('2');
27038 expect(withoutOffset.getText()).toEqual('2 people are viewing.');
27039 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
27041 countInput.clear();
27042 countInput.sendKeys('3');
27044 expect(withoutOffset.getText()).toEqual('3 people are viewing.');
27045 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
27047 countInput.clear();
27048 countInput.sendKeys('4');
27050 expect(withoutOffset.getText()).toEqual('4 people are viewing.');
27051 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
27053 it('should show data-bound names', function() {
27054 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27055 var personCount = element(by.model('personCount'));
27056 var person1 = element(by.model('person1'));
27057 var person2 = element(by.model('person2'));
27058 personCount.clear();
27059 personCount.sendKeys('4');
27061 person1.sendKeys('Di');
27063 person2.sendKeys('Vojta');
27064 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
27069 var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
27071 IS_WHEN = /^when(Minus)?(.+)$/;
27074 link: function(scope, element, attr) {
27075 var numberExp = attr.count,
27076 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
27077 offset = attr.offset || 0,
27078 whens = scope.$eval(whenExp) || {},
27080 startSymbol = $interpolate.startSymbol(),
27081 endSymbol = $interpolate.endSymbol(),
27082 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
27083 watchRemover = angular.noop,
27086 forEach(attr, function(expression, attributeName) {
27087 var tmpMatch = IS_WHEN.exec(attributeName);
27089 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
27090 whens[whenKey] = element.attr(attr.$attr[attributeName]);
27093 forEach(whens, function(expression, key) {
27094 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
27098 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
27099 var count = parseFloat(newVal);
27100 var countIsNaN = isNaN(count);
27102 if (!countIsNaN && !(count in whens)) {
27103 // If an explicit number rule such as 1, 2, 3... is defined, just use it.
27104 // Otherwise, check it against pluralization rules in $locale service.
27105 count = $locale.pluralCat(count - offset);
27108 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
27109 // In JS `NaN !== NaN`, so we have to exlicitly check.
27110 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
27112 var whenExpFn = whensExpFns[count];
27113 if (isUndefined(whenExpFn)) {
27114 if (newVal != null) {
27115 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
27117 watchRemover = noop;
27118 updateElementText();
27120 watchRemover = scope.$watch(whenExpFn, updateElementText);
27126 function updateElementText(newText) {
27127 element.text(newText || '');
27139 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
27140 * instance gets its own scope, where the given loop variable is set to the current collection item,
27141 * and `$index` is set to the item index or key.
27143 * Special properties are exposed on the local scope of each template instance, including:
27145 * | Variable | Type | Details |
27146 * |-----------|-----------------|-----------------------------------------------------------------------------|
27147 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) |
27148 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. |
27149 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
27150 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
27151 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
27152 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
27154 * <div class="alert alert-info">
27155 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
27156 * This may be useful when, for instance, nesting ngRepeats.
27160 * # Iterating over object properties
27162 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
27166 * <div ng-repeat="(key, value) in myObj"> ... </div>
27169 * You need to be aware that the JavaScript specification does not define the order of keys
27170 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
27171 * used to sort the keys alphabetically.)
27173 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
27174 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
27175 * keys in the order in which they were defined, although there are exceptions when keys are deleted
27176 * 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).
27178 * If this is not desired, the recommended workaround is to convert your object into an array
27179 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
27180 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
27181 * or implement a `$watch` on the object yourself.
27184 * # Tracking and Duplicates
27186 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
27187 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM:
27189 * * When an item is added, a new instance of the template is added to the DOM.
27190 * * When an item is removed, its template instance is removed from the DOM.
27191 * * When items are reordered, their respective templates are reordered in the DOM.
27193 * To minimize creation of DOM elements, `ngRepeat` uses a function
27194 * to "keep track" of all items in the collection and their corresponding DOM elements.
27195 * For example, if an item is added to the collection, ngRepeat will know that all other items
27196 * already have DOM elements, and will not re-render them.
27198 * The default tracking function (which tracks items by their identity) does not allow
27199 * duplicate items in arrays. This is because when there are duplicates, it is not possible
27200 * to maintain a one-to-one mapping between collection items and DOM elements.
27202 * If you do need to repeat duplicate items, you can substitute the default tracking behavior
27203 * with your own using the `track by` expression.
27205 * For example, you may track items by the index of each item in the collection, using the
27206 * special scope property `$index`:
27208 * <div ng-repeat="n in [42, 42, 43, 43] track by $index">
27213 * You may also use arbitrary expressions in `track by`, including references to custom functions
27216 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
27221 * <div class="alert alert-success">
27222 * If you are working with objects that have an identifier property, you should track
27223 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
27224 * will not have to rebuild the DOM elements for items it has already rendered, even if the
27225 * JavaScript objects in the collection have been substituted for new ones. For large collections,
27226 * this signifincantly improves rendering performance. If you don't have a unique identifier,
27227 * `track by $index` can also provide a performance boost.
27230 * <div ng-repeat="model in collection track by model.id">
27235 * When no `track by` expression is provided, it is equivalent to tracking by the built-in
27236 * `$id` function, which tracks items by their identity:
27238 * <div ng-repeat="obj in collection track by $id(obj)">
27243 * <div class="alert alert-warning">
27244 * **Note:** `track by` must always be the last expression:
27247 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
27252 * # Special repeat start and end points
27253 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
27254 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
27255 * 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)
27256 * up to and including the ending HTML tag where **ng-repeat-end** is placed.
27258 * The example below makes use of this feature:
27260 * <header ng-repeat-start="item in items">
27261 * Header {{ item }}
27263 * <div class="body">
27266 * <footer ng-repeat-end>
27267 * Footer {{ item }}
27271 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
27276 * <div class="body">
27285 * <div class="body">
27293 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
27294 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
27297 * **.enter** - when a new item is added to the list or when an item is revealed after a filter
27299 * **.leave** - when an item is removed from the list or when an item is filtered out
27301 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
27306 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
27307 * formats are currently supported:
27309 * * `variable in expression` – where variable is the user defined loop variable and `expression`
27310 * is a scope expression giving the collection to enumerate.
27312 * For example: `album in artist.albums`.
27314 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
27315 * and `expression` is the scope expression giving the collection to enumerate.
27317 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
27319 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
27320 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
27321 * is specified, ng-repeat associates elements by identity. It is an error to have
27322 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
27323 * mapped to the same DOM element, which is not possible.)
27325 * Note that the tracking expression must come last, after any filters, and the alias expression.
27327 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
27328 * will be associated by item identity in the array.
27330 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
27331 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
27332 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
27333 * element in the same way in the DOM.
27335 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
27336 * case the object identity does not matter. Two objects are considered equivalent as long as their `id`
27337 * property is same.
27339 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
27340 * to items in conjunction with a tracking expression.
27342 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
27343 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
27344 * when a filter is active on the repeater, but the filtered result set is empty.
27346 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
27347 * the items have been processed through the filter.
27349 * 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
27350 * (and not as operator, inside an expression).
27352 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
27355 * This example initializes the scope to a list of names and
27356 * then uses `ngRepeat` to display every person:
27357 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27358 <file name="index.html">
27359 <div ng-init="friends = [
27360 {name:'John', age:25, gender:'boy'},
27361 {name:'Jessie', age:30, gender:'girl'},
27362 {name:'Johanna', age:28, gender:'girl'},
27363 {name:'Joy', age:15, gender:'girl'},
27364 {name:'Mary', age:28, gender:'girl'},
27365 {name:'Peter', age:95, gender:'boy'},
27366 {name:'Sebastian', age:50, gender:'boy'},
27367 {name:'Erika', age:27, gender:'girl'},
27368 {name:'Patrick', age:40, gender:'boy'},
27369 {name:'Samantha', age:60, gender:'girl'}
27371 I have {{friends.length}} friends. They are:
27372 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
27373 <ul class="example-animate-container">
27374 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results">
27375 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
27377 <li class="animate-repeat" ng-if="results.length == 0">
27378 <strong>No results found...</strong>
27383 <file name="animations.css">
27384 .example-animate-container {
27386 border:1px solid black;
27395 box-sizing:border-box;
27398 .animate-repeat.ng-move,
27399 .animate-repeat.ng-enter,
27400 .animate-repeat.ng-leave {
27401 transition:all linear 0.5s;
27404 .animate-repeat.ng-leave.ng-leave-active,
27405 .animate-repeat.ng-move,
27406 .animate-repeat.ng-enter {
27411 .animate-repeat.ng-leave,
27412 .animate-repeat.ng-move.ng-move-active,
27413 .animate-repeat.ng-enter.ng-enter-active {
27418 <file name="protractor.js" type="protractor">
27419 var friends = element.all(by.repeater('friend in friends'));
27421 it('should render initial data set', function() {
27422 expect(friends.count()).toBe(10);
27423 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
27424 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
27425 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
27426 expect(element(by.binding('friends.length')).getText())
27427 .toMatch("I have 10 friends. They are:");
27430 it('should update repeater when filter predicate changes', function() {
27431 expect(friends.count()).toBe(10);
27433 element(by.model('q')).sendKeys('ma');
27435 expect(friends.count()).toBe(2);
27436 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
27437 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
27442 var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
27443 var NG_REMOVED = '$$NG_REMOVED';
27444 var ngRepeatMinErr = minErr('ngRepeat');
27446 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
27447 // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
27448 scope[valueIdentifier] = value;
27449 if (keyIdentifier) scope[keyIdentifier] = key;
27450 scope.$index = index;
27451 scope.$first = (index === 0);
27452 scope.$last = (index === (arrayLength - 1));
27453 scope.$middle = !(scope.$first || scope.$last);
27454 // jshint bitwise: false
27455 scope.$odd = !(scope.$even = (index&1) === 0);
27456 // jshint bitwise: true
27459 var getBlockStart = function(block) {
27460 return block.clone[0];
27463 var getBlockEnd = function(block) {
27464 return block.clone[block.clone.length - 1];
27470 multiElement: true,
27471 transclude: 'element',
27475 compile: function ngRepeatCompile($element, $attr) {
27476 var expression = $attr.ngRepeat;
27477 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' ');
27479 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*$/);
27482 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
27486 var lhs = match[1];
27487 var rhs = match[2];
27488 var aliasAs = match[3];
27489 var trackByExp = match[4];
27491 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
27494 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
27497 var valueIdentifier = match[3] || match[1];
27498 var keyIdentifier = match[2];
27500 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
27501 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
27502 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
27506 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
27507 var hashFnLocals = {$id: hashKey};
27510 trackByExpGetter = $parse(trackByExp);
27512 trackByIdArrayFn = function(key, value) {
27513 return hashKey(value);
27515 trackByIdObjFn = function(key) {
27520 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
27522 if (trackByExpGetter) {
27523 trackByIdExpFn = function(key, value, index) {
27524 // assign key, value, and $index to the locals so that they can be used in hash functions
27525 if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
27526 hashFnLocals[valueIdentifier] = value;
27527 hashFnLocals.$index = index;
27528 return trackByExpGetter($scope, hashFnLocals);
27532 // Store a list of elements from previous run. This is a hash where key is the item from the
27533 // iterator, and the value is objects with following properties.
27534 // - scope: bound scope
27535 // - element: previous element.
27536 // - index: position
27538 // We are using no-proto object so that we don't need to guard against inherited props via
27540 var lastBlockMap = createMap();
27543 $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
27545 previousNode = $element[0], // node that cloned nodes should be inserted after
27546 // initialized to the comment node anchor
27548 // Same as lastBlockMap but it has the current state. It will become the
27549 // lastBlockMap on the next iteration.
27550 nextBlockMap = createMap(),
27552 key, value, // key/value of iteration
27556 block, // last object information {scope, element, id}
27561 $scope[aliasAs] = collection;
27564 if (isArrayLike(collection)) {
27565 collectionKeys = collection;
27566 trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
27568 trackByIdFn = trackByIdExpFn || trackByIdObjFn;
27569 // if object, extract keys, in enumeration order, unsorted
27570 collectionKeys = [];
27571 for (var itemKey in collection) {
27572 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
27573 collectionKeys.push(itemKey);
27578 collectionLength = collectionKeys.length;
27579 nextBlockOrder = new Array(collectionLength);
27581 // locate existing items
27582 for (index = 0; index < collectionLength; index++) {
27583 key = (collection === collectionKeys) ? index : collectionKeys[index];
27584 value = collection[key];
27585 trackById = trackByIdFn(key, value, index);
27586 if (lastBlockMap[trackById]) {
27587 // found previously seen block
27588 block = lastBlockMap[trackById];
27589 delete lastBlockMap[trackById];
27590 nextBlockMap[trackById] = block;
27591 nextBlockOrder[index] = block;
27592 } else if (nextBlockMap[trackById]) {
27593 // if collision detected. restore lastBlockMap and throw an error
27594 forEach(nextBlockOrder, function(block) {
27595 if (block && block.scope) lastBlockMap[block.id] = block;
27597 throw ngRepeatMinErr('dupes',
27598 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
27599 expression, trackById, value);
27601 // new never before seen block
27602 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
27603 nextBlockMap[trackById] = true;
27607 // remove leftover items
27608 for (var blockKey in lastBlockMap) {
27609 block = lastBlockMap[blockKey];
27610 elementsToRemove = getBlockNodes(block.clone);
27611 $animate.leave(elementsToRemove);
27612 if (elementsToRemove[0].parentNode) {
27613 // if the element was not removed yet because of pending animation, mark it as deleted
27614 // so that we can ignore it later
27615 for (index = 0, length = elementsToRemove.length; index < length; index++) {
27616 elementsToRemove[index][NG_REMOVED] = true;
27619 block.scope.$destroy();
27622 // we are not using forEach for perf reasons (trying to avoid #call)
27623 for (index = 0; index < collectionLength; index++) {
27624 key = (collection === collectionKeys) ? index : collectionKeys[index];
27625 value = collection[key];
27626 block = nextBlockOrder[index];
27629 // if we have already seen this object, then we need to reuse the
27630 // associated scope/element
27632 nextNode = previousNode;
27634 // skip nodes that are already pending removal via leave animation
27636 nextNode = nextNode.nextSibling;
27637 } while (nextNode && nextNode[NG_REMOVED]);
27639 if (getBlockStart(block) != nextNode) {
27640 // existing item which got moved
27641 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode));
27643 previousNode = getBlockEnd(block);
27644 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27646 // new item which we don't know about
27647 $transclude(function ngRepeatTransclude(clone, scope) {
27648 block.scope = scope;
27649 // http://jsperf.com/clone-vs-createcomment
27650 var endNode = ngRepeatEndComment.cloneNode(false);
27651 clone[clone.length++] = endNode;
27653 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper?
27654 $animate.enter(clone, null, jqLite(previousNode));
27655 previousNode = endNode;
27656 // Note: We only need the first/last node of the cloned nodes.
27657 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
27658 // by a directive with templateUrl when its template arrives.
27659 block.clone = clone;
27660 nextBlockMap[block.id] = block;
27661 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27665 lastBlockMap = nextBlockMap;
27672 var NG_HIDE_CLASS = 'ng-hide';
27673 var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
27680 * The `ngShow` directive shows or hides the given HTML element based on the expression
27681 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding
27682 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27683 * in AngularJS and sets the display style to none (using an !important flag).
27684 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27687 * <!-- when $scope.myValue is truthy (element is visible) -->
27688 * <div ng-show="myValue"></div>
27690 * <!-- when $scope.myValue is falsy (element is hidden) -->
27691 * <div ng-show="myValue" class="ng-hide"></div>
27694 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class
27695 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed
27696 * from the element causing the element not to appear hidden.
27698 * ## Why is !important used?
27700 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27701 * can be easily overridden by heavier selectors. For example, something as simple
27702 * as changing the display style on a HTML list item would make hidden elements appear visible.
27703 * This also becomes a bigger issue when dealing with CSS frameworks.
27705 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27706 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27707 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27709 * ### Overriding `.ng-hide`
27711 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27712 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27713 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
27714 * with extra animation classes that can be added.
27717 * .ng-hide:not(.ng-hide-animate) {
27718 * /* this is just another form of hiding an element */
27719 * display: block!important;
27720 * position: absolute;
27726 * By default you don't need to override in CSS anything and the animations will work around the display style.
27728 * ## A note about animations with `ngShow`
27730 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27731 * is true and false. This system works like the animation system present with ngClass except that
27732 * you must also include the !important flag to override the display property
27733 * so that you can perform an animation when the element is hidden during the time of the animation.
27737 * //a working example can be found at the bottom of this page
27739 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27740 * /* this is required as of 1.3x to properly
27741 * apply all styling in a show/hide animation */
27742 * transition: 0s linear all;
27745 * .my-element.ng-hide-add-active,
27746 * .my-element.ng-hide-remove-active {
27747 * /* the transition is defined in the active class */
27748 * transition: 1s linear all;
27751 * .my-element.ng-hide-add { ... }
27752 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27753 * .my-element.ng-hide-remove { ... }
27754 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27757 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27758 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27761 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible
27762 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden
27765 * @param {expression} ngShow If the {@link guide/expression expression} is truthy
27766 * then the element is shown or hidden respectively.
27769 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27770 <file name="index.html">
27771 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/>
27774 <div class="check-element animate-show" ng-show="checked">
27775 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27780 <div class="check-element animate-show" ng-hide="checked">
27781 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27785 <file name="glyphicons.css">
27786 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27788 <file name="animations.css">
27793 border: 1px solid black;
27797 .animate-show.ng-hide-add, .animate-show.ng-hide-remove {
27798 transition: all linear 0.5s;
27801 .animate-show.ng-hide {
27809 border: 1px solid black;
27813 <file name="protractor.js" type="protractor">
27814 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27815 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27817 it('should check ng-show / ng-hide', function() {
27818 expect(thumbsUp.isDisplayed()).toBeFalsy();
27819 expect(thumbsDown.isDisplayed()).toBeTruthy();
27821 element(by.model('checked')).click();
27823 expect(thumbsUp.isDisplayed()).toBeTruthy();
27824 expect(thumbsDown.isDisplayed()).toBeFalsy();
27829 var ngShowDirective = ['$animate', function($animate) {
27832 multiElement: true,
27833 link: function(scope, element, attr) {
27834 scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
27835 // we're adding a temporary, animation-specific class for ng-hide since this way
27836 // we can control when the element is actually displayed on screen without having
27837 // to have a global/greedy CSS selector that breaks when other animations are run.
27838 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
27839 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
27840 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
27854 * The `ngHide` directive shows or hides the given HTML element based on the expression
27855 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding
27856 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27857 * in AngularJS and sets the display style to none (using an !important flag).
27858 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27861 * <!-- when $scope.myValue is truthy (element is hidden) -->
27862 * <div ng-hide="myValue" class="ng-hide"></div>
27864 * <!-- when $scope.myValue is falsy (element is visible) -->
27865 * <div ng-hide="myValue"></div>
27868 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class
27869 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed
27870 * from the element causing the element not to appear hidden.
27872 * ## Why is !important used?
27874 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27875 * can be easily overridden by heavier selectors. For example, something as simple
27876 * as changing the display style on a HTML list item would make hidden elements appear visible.
27877 * This also becomes a bigger issue when dealing with CSS frameworks.
27879 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27880 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27881 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27883 * ### Overriding `.ng-hide`
27885 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27886 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27891 * /* this is just another form of hiding an element */
27892 * display: block!important;
27893 * position: absolute;
27899 * By default you don't need to override in CSS anything and the animations will work around the display style.
27901 * ## A note about animations with `ngHide`
27903 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27904 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide`
27905 * CSS class is added and removed for you instead of your own CSS class.
27909 * //a working example can be found at the bottom of this page
27911 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27912 * transition: 0.5s linear all;
27915 * .my-element.ng-hide-add { ... }
27916 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27917 * .my-element.ng-hide-remove { ... }
27918 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27921 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27922 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27925 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden
27926 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible
27929 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
27930 * the element is shown or hidden respectively.
27933 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27934 <file name="index.html">
27935 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/>
27938 <div class="check-element animate-hide" ng-show="checked">
27939 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27944 <div class="check-element animate-hide" ng-hide="checked">
27945 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27949 <file name="glyphicons.css">
27950 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27952 <file name="animations.css">
27954 transition: all linear 0.5s;
27958 border: 1px solid black;
27962 .animate-hide.ng-hide {
27970 border: 1px solid black;
27974 <file name="protractor.js" type="protractor">
27975 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27976 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27978 it('should check ng-show / ng-hide', function() {
27979 expect(thumbsUp.isDisplayed()).toBeFalsy();
27980 expect(thumbsDown.isDisplayed()).toBeTruthy();
27982 element(by.model('checked')).click();
27984 expect(thumbsUp.isDisplayed()).toBeTruthy();
27985 expect(thumbsDown.isDisplayed()).toBeFalsy();
27990 var ngHideDirective = ['$animate', function($animate) {
27993 multiElement: true,
27994 link: function(scope, element, attr) {
27995 scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
27996 // The comment inside of the ngShowDirective explains why we add and
27997 // remove a temporary class for the show/hide animation
27998 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
27999 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
28012 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
28015 * @param {expression} ngStyle
28017 * {@link guide/expression Expression} which evals to an
28018 * object whose keys are CSS style names and values are corresponding values for those CSS
28021 * Since some CSS style names are not valid keys for an object, they must be quoted.
28022 * See the 'background-color' style in the example below.
28026 <file name="index.html">
28027 <input type="button" value="set color" ng-click="myStyle={color:'red'}">
28028 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}">
28029 <input type="button" value="clear" ng-click="myStyle={}">
28031 <span ng-style="myStyle">Sample Text</span>
28032 <pre>myStyle={{myStyle}}</pre>
28034 <file name="style.css">
28039 <file name="protractor.js" type="protractor">
28040 var colorSpan = element(by.css('span'));
28042 it('should check ng-style', function() {
28043 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28044 element(by.css('input[value=\'set color\']')).click();
28045 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
28046 element(by.css('input[value=clear]')).click();
28047 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28052 var ngStyleDirective = ngDirective(function(scope, element, attr) {
28053 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
28054 if (oldStyles && (newStyles !== oldStyles)) {
28055 forEach(oldStyles, function(val, style) { element.css(style, '');});
28057 if (newStyles) element.css(newStyles);
28067 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
28068 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
28069 * as specified in the template.
28071 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
28072 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
28073 * matches the value obtained from the evaluated expression. In other words, you define a container element
28074 * (where you place the directive), place an expression on the **`on="..."` attribute**
28075 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
28076 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
28077 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
28078 * attribute is displayed.
28080 * <div class="alert alert-info">
28081 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
28082 * as literal string values to match against.
28083 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
28084 * value of the expression `$scope.someVal`.
28088 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
28089 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
28094 * <ANY ng-switch="expression">
28095 * <ANY ng-switch-when="matchValue1">...</ANY>
28096 * <ANY ng-switch-when="matchValue2">...</ANY>
28097 * <ANY ng-switch-default>...</ANY>
28104 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
28105 * On child elements add:
28107 * * `ngSwitchWhen`: the case statement to match against. If match then this
28108 * case will be displayed. If the same match appears multiple times, all the
28109 * elements will be displayed.
28110 * * `ngSwitchDefault`: the default case when no other case match. If there
28111 * are multiple default cases, all of them will be displayed when no other
28116 <example module="switchExample" deps="angular-animate.js" animations="true">
28117 <file name="index.html">
28118 <div ng-controller="ExampleController">
28119 <select ng-model="selection" ng-options="item for item in items">
28121 <code>selection={{selection}}</code>
28123 <div class="animate-switch-container"
28124 ng-switch on="selection">
28125 <div class="animate-switch" ng-switch-when="settings">Settings Div</div>
28126 <div class="animate-switch" ng-switch-when="home">Home Span</div>
28127 <div class="animate-switch" ng-switch-default>default</div>
28131 <file name="script.js">
28132 angular.module('switchExample', ['ngAnimate'])
28133 .controller('ExampleController', ['$scope', function($scope) {
28134 $scope.items = ['settings', 'home', 'other'];
28135 $scope.selection = $scope.items[0];
28138 <file name="animations.css">
28139 .animate-switch-container {
28142 border:1px solid black;
28151 .animate-switch.ng-animate {
28152 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
28161 .animate-switch.ng-leave.ng-leave-active,
28162 .animate-switch.ng-enter {
28165 .animate-switch.ng-leave,
28166 .animate-switch.ng-enter.ng-enter-active {
28170 <file name="protractor.js" type="protractor">
28171 var switchElem = element(by.css('[ng-switch]'));
28172 var select = element(by.model('selection'));
28174 it('should start in settings', function() {
28175 expect(switchElem.getText()).toMatch(/Settings Div/);
28177 it('should change to home', function() {
28178 select.all(by.css('option')).get(1).click();
28179 expect(switchElem.getText()).toMatch(/Home Span/);
28181 it('should select default', function() {
28182 select.all(by.css('option')).get(2).click();
28183 expect(switchElem.getText()).toMatch(/default/);
28188 var ngSwitchDirective = ['$animate', function($animate) {
28190 require: 'ngSwitch',
28192 // asks for $scope to fool the BC controller module
28193 controller: ['$scope', function ngSwitchController() {
28196 link: function(scope, element, attr, ngSwitchController) {
28197 var watchExpr = attr.ngSwitch || attr.on,
28198 selectedTranscludes = [],
28199 selectedElements = [],
28200 previousLeaveAnimations = [],
28201 selectedScopes = [];
28203 var spliceFactory = function(array, index) {
28204 return function() { array.splice(index, 1); };
28207 scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
28209 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) {
28210 $animate.cancel(previousLeaveAnimations[i]);
28212 previousLeaveAnimations.length = 0;
28214 for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
28215 var selected = getBlockNodes(selectedElements[i].clone);
28216 selectedScopes[i].$destroy();
28217 var promise = previousLeaveAnimations[i] = $animate.leave(selected);
28218 promise.then(spliceFactory(previousLeaveAnimations, i));
28221 selectedElements.length = 0;
28222 selectedScopes.length = 0;
28224 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
28225 forEach(selectedTranscludes, function(selectedTransclude) {
28226 selectedTransclude.transclude(function(caseElement, selectedScope) {
28227 selectedScopes.push(selectedScope);
28228 var anchor = selectedTransclude.element;
28229 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: ');
28230 var block = { clone: caseElement };
28232 selectedElements.push(block);
28233 $animate.enter(caseElement, anchor.parent(), anchor);
28242 var ngSwitchWhenDirective = ngDirective({
28243 transclude: 'element',
28245 require: '^ngSwitch',
28246 multiElement: true,
28247 link: function(scope, element, attrs, ctrl, $transclude) {
28248 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
28249 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
28253 var ngSwitchDefaultDirective = ngDirective({
28254 transclude: 'element',
28256 require: '^ngSwitch',
28257 multiElement: true,
28258 link: function(scope, element, attr, ctrl, $transclude) {
28259 ctrl.cases['?'] = (ctrl.cases['?'] || []);
28260 ctrl.cases['?'].push({ transclude: $transclude, element: element });
28266 * @name ngTransclude
28270 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
28272 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
28277 <example module="transcludeExample">
28278 <file name="index.html">
28280 angular.module('transcludeExample', [])
28281 .directive('pane', function(){
28285 scope: { title:'@' },
28286 template: '<div style="border: 1px solid black;">' +
28287 '<div style="background-color: gray">{{title}}</div>' +
28288 '<ng-transclude></ng-transclude>' +
28292 .controller('ExampleController', ['$scope', function($scope) {
28293 $scope.title = 'Lorem Ipsum';
28294 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
28297 <div ng-controller="ExampleController">
28298 <input ng-model="title" aria-label="title"> <br/>
28299 <textarea ng-model="text" aria-label="text"></textarea> <br/>
28300 <pane title="{{title}}">{{text}}</pane>
28303 <file name="protractor.js" type="protractor">
28304 it('should have transcluded', function() {
28305 var titleElement = element(by.model('title'));
28306 titleElement.clear();
28307 titleElement.sendKeys('TITLE');
28308 var textElement = element(by.model('text'));
28309 textElement.clear();
28310 textElement.sendKeys('TEXT');
28311 expect(element(by.binding('title')).getText()).toEqual('TITLE');
28312 expect(element(by.binding('text')).getText()).toEqual('TEXT');
28318 var ngTranscludeDirective = ngDirective({
28320 link: function($scope, $element, $attrs, controller, $transclude) {
28321 if (!$transclude) {
28322 throw minErr('ngTransclude')('orphan',
28323 'Illegal use of ngTransclude directive in the template! ' +
28324 'No parent directive that requires a transclusion found. ' +
28326 startingTag($element));
28329 $transclude(function(clone) {
28331 $element.append(clone);
28342 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the
28343 * template can be used by {@link ng.directive:ngInclude `ngInclude`},
28344 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
28345 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
28346 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
28348 * @param {string} type Must be set to `'text/ng-template'`.
28349 * @param {string} id Cache name of the template.
28353 <file name="index.html">
28354 <script type="text/ng-template" id="/tpl.html">
28355 Content of the template.
28358 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
28359 <div id="tpl-content" ng-include src="currentTpl"></div>
28361 <file name="protractor.js" type="protractor">
28362 it('should load template defined inside script tag', function() {
28363 element(by.css('#tpl-link')).click();
28364 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
28369 var scriptDirective = ['$templateCache', function($templateCache) {
28373 compile: function(element, attr) {
28374 if (attr.type == 'text/ng-template') {
28375 var templateUrl = attr.id,
28376 text = element[0].text;
28378 $templateCache.put(templateUrl, text);
28384 var noopNgModelController = { $setViewValue: noop, $render: noop };
28386 function chromeHack(optionElement) {
28387 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
28388 // Adding an <option selected="selected"> element to a <select required="required"> should
28389 // automatically select the new element
28390 if (optionElement[0].hasAttribute('selected')) {
28391 optionElement[0].selected = true;
28397 * @name select.SelectController
28399 * The controller for the `<select>` directive. This provides support for reading
28400 * and writing the selected value(s) of the control and also coordinates dynamically
28401 * added `<option>` elements, perhaps by an `ngRepeat` directive.
28403 var SelectController =
28404 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
28407 optionsMap = new HashMap();
28409 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
28410 self.ngModelCtrl = noopNgModelController;
28412 // The "unknown" option is one that is prepended to the list if the viewValue
28413 // does not match any of the options. When it is rendered the value of the unknown
28414 // option is '? XXX ?' where XXX is the hashKey of the value that is not known.
28416 // We can't just jqLite('<option>') since jqLite is not smart enough
28417 // to create it in <select> and IE barfs otherwise.
28418 self.unknownOption = jqLite(document.createElement('option'));
28419 self.renderUnknownOption = function(val) {
28420 var unknownVal = '? ' + hashKey(val) + ' ?';
28421 self.unknownOption.val(unknownVal);
28422 $element.prepend(self.unknownOption);
28423 $element.val(unknownVal);
28426 $scope.$on('$destroy', function() {
28427 // disable unknown option so that we don't do work when the whole select is being destroyed
28428 self.renderUnknownOption = noop;
28431 self.removeUnknownOption = function() {
28432 if (self.unknownOption.parent()) self.unknownOption.remove();
28436 // Read the value of the select control, the implementation of this changes depending
28437 // upon whether the select can have multiple values and whether ngOptions is at work.
28438 self.readValue = function readSingleValue() {
28439 self.removeUnknownOption();
28440 return $element.val();
28444 // Write the value to the select control, the implementation of this changes depending
28445 // upon whether the select can have multiple values and whether ngOptions is at work.
28446 self.writeValue = function writeSingleValue(value) {
28447 if (self.hasOption(value)) {
28448 self.removeUnknownOption();
28449 $element.val(value);
28450 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
28452 if (value == null && self.emptyOption) {
28453 self.removeUnknownOption();
28456 self.renderUnknownOption(value);
28462 // Tell the select control that an option, with the given value, has been added
28463 self.addOption = function(value, element) {
28464 assertNotHasOwnProperty(value, '"option value"');
28465 if (value === '') {
28466 self.emptyOption = element;
28468 var count = optionsMap.get(value) || 0;
28469 optionsMap.put(value, count + 1);
28470 self.ngModelCtrl.$render();
28471 chromeHack(element);
28474 // Tell the select control that an option, with the given value, has been removed
28475 self.removeOption = function(value) {
28476 var count = optionsMap.get(value);
28479 optionsMap.remove(value);
28480 if (value === '') {
28481 self.emptyOption = undefined;
28484 optionsMap.put(value, count - 1);
28489 // Check whether the select control has an option matching the given value
28490 self.hasOption = function(value) {
28491 return !!optionsMap.get(value);
28495 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
28497 if (interpolateValueFn) {
28498 // The value attribute is interpolated
28500 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
28501 if (isDefined(oldVal)) {
28502 self.removeOption(oldVal);
28505 self.addOption(newVal, optionElement);
28507 } else if (interpolateTextFn) {
28508 // The text content is interpolated
28509 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
28510 optionAttrs.$set('value', newVal);
28511 if (oldVal !== newVal) {
28512 self.removeOption(oldVal);
28514 self.addOption(newVal, optionElement);
28517 // The value attribute is static
28518 self.addOption(optionAttrs.value, optionElement);
28521 optionElement.on('$destroy', function() {
28522 self.removeOption(optionAttrs.value);
28523 self.ngModelCtrl.$render();
28534 * HTML `SELECT` element with angular data-binding.
28536 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
28537 * between the scope and the `<select>` control (including setting default values).
28538 * Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
28539 * {@link ngOptions `ngOptions`} directives.
28541 * When an item in the `<select>` menu is selected, the value of the selected option will be bound
28542 * to the model identified by the `ngModel` directive. With static or repeated options, this is
28543 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
28544 * If you want dynamic value attributes, you can use interpolation inside the value attribute.
28546 * <div class="alert alert-warning">
28547 * Note that the value of a `select` directive used without `ngOptions` is always a string.
28548 * When the model needs to be bound to a non-string value, you must either explictly convert it
28549 * using a directive (see example below) or use `ngOptions` to specify the set of options.
28550 * This is because an option element can only be bound to string values at present.
28553 * If the viewValue of `ngModel` does not match any of the options, then the control
28554 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
28556 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
28557 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
28558 * option. See example below for demonstration.
28560 * <div class="alert alert-info">
28561 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
28562 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as
28563 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
28564 * comprehension expression, and additionally in reducing memory and increasing speed by not creating
28565 * a new scope for each repeated instance.
28569 * @param {string} ngModel Assignable angular expression to data-bind to.
28570 * @param {string=} name Property name of the form under which the control is published.
28571 * @param {string=} multiple Allows multiple options to be selected. The selected values will be
28572 * bound to the model as an array.
28573 * @param {string=} required Sets `required` validation error key if the value is not entered.
28574 * @param {string=} ngRequired Adds required attribute and required validation constraint to
28575 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
28576 * when you want to data-bind to the required attribute.
28577 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
28578 * interaction with the select element.
28579 * @param {string=} ngOptions sets the options that the select is populated with and defines what is
28580 * set on the model on selection. See {@link ngOptions `ngOptions`}.
28583 * ### Simple `select` elements with static options
28585 * <example name="static-select" module="staticSelect">
28586 * <file name="index.html">
28587 * <div ng-controller="ExampleController">
28588 * <form name="myForm">
28589 * <label for="singleSelect"> Single select: </label><br>
28590 * <select name="singleSelect" ng-model="data.singleSelect">
28591 * <option value="option-1">Option 1</option>
28592 * <option value="option-2">Option 2</option>
28595 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
28596 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
28597 * <option value="">---Please select---</option> <!-- not selected / blank option -->
28598 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
28599 * <option value="option-2">Option 2</option>
28601 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
28602 * <tt>singleSelect = {{data.singleSelect}}</tt>
28605 * <label for="multipleSelect"> Multiple select: </label><br>
28606 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
28607 * <option value="option-1">Option 1</option>
28608 * <option value="option-2">Option 2</option>
28609 * <option value="option-3">Option 3</option>
28611 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
28615 * <file name="app.js">
28616 * angular.module('staticSelect', [])
28617 * .controller('ExampleController', ['$scope', function($scope) {
28619 * singleSelect: null,
28620 * multipleSelect: [],
28621 * option1: 'option-1',
28624 * $scope.forceUnknownOption = function() {
28625 * $scope.data.singleSelect = 'nonsense';
28631 * ### Using `ngRepeat` to generate `select` options
28632 * <example name="ngrepeat-select" module="ngrepeatSelect">
28633 * <file name="index.html">
28634 * <div ng-controller="ExampleController">
28635 * <form name="myForm">
28636 * <label for="repeatSelect"> Repeat select: </label>
28637 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect">
28638 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
28642 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
28645 * <file name="app.js">
28646 * angular.module('ngrepeatSelect', [])
28647 * .controller('ExampleController', ['$scope', function($scope) {
28649 * repeatSelect: null,
28650 * availableOptions: [
28651 * {id: '1', name: 'Option A'},
28652 * {id: '2', name: 'Option B'},
28653 * {id: '3', name: 'Option C'}
28661 * ### Using `select` with `ngOptions` and setting a default value
28662 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
28664 * <example name="select-with-default-values" module="defaultValueSelect">
28665 * <file name="index.html">
28666 * <div ng-controller="ExampleController">
28667 * <form name="myForm">
28668 * <label for="mySelect">Make a choice:</label>
28669 * <select name="mySelect" id="mySelect"
28670 * ng-options="option.name for option in data.availableOptions track by option.id"
28671 * ng-model="data.selectedOption"></select>
28674 * <tt>option = {{data.selectedOption}}</tt><br/>
28677 * <file name="app.js">
28678 * angular.module('defaultValueSelect', [])
28679 * .controller('ExampleController', ['$scope', function($scope) {
28681 * availableOptions: [
28682 * {id: '1', name: 'Option A'},
28683 * {id: '2', name: 'Option B'},
28684 * {id: '3', name: 'Option C'}
28686 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
28693 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting
28695 * <example name="select-with-non-string-options" module="nonStringSelect">
28696 * <file name="index.html">
28697 * <select ng-model="model.id" convert-to-number>
28698 * <option value="0">Zero</option>
28699 * <option value="1">One</option>
28700 * <option value="2">Two</option>
28704 * <file name="app.js">
28705 * angular.module('nonStringSelect', [])
28706 * .run(function($rootScope) {
28707 * $rootScope.model = { id: 2 };
28709 * .directive('convertToNumber', function() {
28711 * require: 'ngModel',
28712 * link: function(scope, element, attrs, ngModel) {
28713 * ngModel.$parsers.push(function(val) {
28714 * return parseInt(val, 10);
28716 * ngModel.$formatters.push(function(val) {
28723 * <file name="protractor.js" type="protractor">
28724 * it('should initialize to model', function() {
28725 * var select = element(by.css('select'));
28726 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
28732 var selectDirective = function() {
28736 require: ['select', '?ngModel'],
28737 controller: SelectController,
28744 function selectPreLink(scope, element, attr, ctrls) {
28746 // if ngModel is not defined, we don't need to do anything
28747 var ngModelCtrl = ctrls[1];
28748 if (!ngModelCtrl) return;
28750 var selectCtrl = ctrls[0];
28752 selectCtrl.ngModelCtrl = ngModelCtrl;
28754 // We delegate rendering to the `writeValue` method, which can be changed
28755 // if the select can have multiple selected values or if the options are being
28756 // generated by `ngOptions`
28757 ngModelCtrl.$render = function() {
28758 selectCtrl.writeValue(ngModelCtrl.$viewValue);
28761 // When the selected item(s) changes we delegate getting the value of the select control
28762 // to the `readValue` method, which can be changed if the select can have multiple
28763 // selected values or if the options are being generated by `ngOptions`
28764 element.on('change', function() {
28765 scope.$apply(function() {
28766 ngModelCtrl.$setViewValue(selectCtrl.readValue());
28770 // If the select allows multiple values then we need to modify how we read and write
28771 // values from and to the control; also what it means for the value to be empty and
28772 // we have to add an extra watch since ngModel doesn't work well with arrays - it
28773 // doesn't trigger rendering if only an item in the array changes.
28774 if (attr.multiple) {
28776 // Read value now needs to check each option to see if it is selected
28777 selectCtrl.readValue = function readMultipleValue() {
28779 forEach(element.find('option'), function(option) {
28780 if (option.selected) {
28781 array.push(option.value);
28787 // Write value now needs to set the selected property of each matching option
28788 selectCtrl.writeValue = function writeMultipleValue(value) {
28789 var items = new HashMap(value);
28790 forEach(element.find('option'), function(option) {
28791 option.selected = isDefined(items.get(option.value));
28795 // we have to do it on each watch since ngModel watches reference, but
28796 // we need to work of an array, so we need to see if anything was inserted/removed
28797 var lastView, lastViewRef = NaN;
28798 scope.$watch(function selectMultipleWatch() {
28799 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
28800 lastView = shallowCopy(ngModelCtrl.$viewValue);
28801 ngModelCtrl.$render();
28803 lastViewRef = ngModelCtrl.$viewValue;
28806 // If we are a multiple select then value is now a collection
28807 // so the meaning of $isEmpty changes
28808 ngModelCtrl.$isEmpty = function(value) {
28809 return !value || value.length === 0;
28817 // The option directive is purely designed to communicate the existence (or lack of)
28818 // of dynamically created (and destroyed) option elements to their containing select
28819 // directive via its controller.
28820 var optionDirective = ['$interpolate', function($interpolate) {
28824 compile: function(element, attr) {
28826 if (isDefined(attr.value)) {
28827 // If the value attribute is defined, check if it contains an interpolation
28828 var interpolateValueFn = $interpolate(attr.value, true);
28830 // If the value attribute is not defined then we fall back to the
28831 // text content of the option element, which may be interpolated
28832 var interpolateTextFn = $interpolate(element.text(), true);
28833 if (!interpolateTextFn) {
28834 attr.$set('value', element.text());
28838 return function(scope, element, attr) {
28840 // This is an optimization over using ^^ since we don't want to have to search
28841 // all the way to the root of the DOM for every single option element
28842 var selectCtrlName = '$selectController',
28843 parent = element.parent(),
28844 selectCtrl = parent.data(selectCtrlName) ||
28845 parent.parent().data(selectCtrlName); // in case we are in optgroup
28848 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
28855 var styleDirective = valueFn({
28860 var requiredDirective = function() {
28863 require: '?ngModel',
28864 link: function(scope, elm, attr, ctrl) {
28866 attr.required = true; // force truthy in case we are on non input element
28868 ctrl.$validators.required = function(modelValue, viewValue) {
28869 return !attr.required || !ctrl.$isEmpty(viewValue);
28872 attr.$observe('required', function() {
28880 var patternDirective = function() {
28883 require: '?ngModel',
28884 link: function(scope, elm, attr, ctrl) {
28887 var regexp, patternExp = attr.ngPattern || attr.pattern;
28888 attr.$observe('pattern', function(regex) {
28889 if (isString(regex) && regex.length > 0) {
28890 regex = new RegExp('^' + regex + '$');
28893 if (regex && !regex.test) {
28894 throw minErr('ngPattern')('noregexp',
28895 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
28896 regex, startingTag(elm));
28899 regexp = regex || undefined;
28903 ctrl.$validators.pattern = function(modelValue, viewValue) {
28904 // HTML5 pattern constraint validates the input value, so we validate the viewValue
28905 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
28912 var maxlengthDirective = function() {
28915 require: '?ngModel',
28916 link: function(scope, elm, attr, ctrl) {
28919 var maxlength = -1;
28920 attr.$observe('maxlength', function(value) {
28921 var intVal = toInt(value);
28922 maxlength = isNaN(intVal) ? -1 : intVal;
28925 ctrl.$validators.maxlength = function(modelValue, viewValue) {
28926 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
28932 var minlengthDirective = function() {
28935 require: '?ngModel',
28936 link: function(scope, elm, attr, ctrl) {
28940 attr.$observe('minlength', function(value) {
28941 minlength = toInt(value) || 0;
28944 ctrl.$validators.minlength = function(modelValue, viewValue) {
28945 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
28951 if (window.angular.bootstrap) {
28952 //AngularJS is already loaded, so we can return here...
28953 console.log('WARNING: Tried to load angular more than once.');
28957 //try to bind to jquery now so that one can write jqLite(document).ready()
28958 //but we will rebind on bootstrap again.
28961 publishExternalAPI(angular);
28963 angular.module("ngLocale", [], ["$provide", function($provide) {
28964 var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
28965 function getDecimals(n) {
28967 var i = n.indexOf('.');
28968 return (i == -1) ? 0 : n.length - i - 1;
28971 function getVF(n, opt_precision) {
28972 var v = opt_precision;
28974 if (undefined === v) {
28975 v = Math.min(getDecimals(n), 3);
28978 var base = Math.pow(10, v);
28979 var f = ((n * base) | 0) % base;
28980 return {v: v, f: f};
28983 $provide.value("$locale", {
28984 "DATETIME_FORMATS": {
29006 "FIRSTDAYOFWEEK": 6,
29048 "fullDate": "EEEE, MMMM d, y",
29049 "longDate": "MMMM d, y",
29050 "medium": "MMM d, y h:mm:ss a",
29051 "mediumDate": "MMM d, y",
29052 "mediumTime": "h:mm:ss a",
29053 "short": "M/d/yy h:mm a",
29054 "shortDate": "M/d/yy",
29055 "shortTime": "h:mm a"
29057 "NUMBER_FORMATS": {
29058 "CURRENCY_SYM": "$",
29059 "DECIMAL_SEP": ".",
29079 "negPre": "-\u00a4",
29081 "posPre": "\u00a4",
29087 "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;}
29091 jqLite(document).ready(function() {
29092 angularInit(document, bootstrap);
29095 })(window, document);
29097 !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>');
29101 /***/ function(module, exports) {
29104 * State-based routing for AngularJS
29106 * @link http://angular-ui.github.com/
29107 * @license MIT License, http://www.opensource.org/licenses/MIT
29110 /* commonjs package manager support (eg componentjs) */
29111 if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){
29112 module.exports = 'ui.router';
29115 (function (window, angular, undefined) {
29116 /*jshint globalstrict:true*/
29117 /*global angular:false*/
29120 var isDefined = angular.isDefined,
29121 isFunction = angular.isFunction,
29122 isString = angular.isString,
29123 isObject = angular.isObject,
29124 isArray = angular.isArray,
29125 forEach = angular.forEach,
29126 extend = angular.extend,
29127 copy = angular.copy;
29129 function inherit(parent, extra) {
29130 return extend(new (extend(function() {}, { prototype: parent }))(), extra);
29133 function merge(dst) {
29134 forEach(arguments, function(obj) {
29136 forEach(obj, function(value, key) {
29137 if (!dst.hasOwnProperty(key)) dst[key] = value;
29145 * Finds the common ancestor path between two states.
29147 * @param {Object} first The first state.
29148 * @param {Object} second The second state.
29149 * @return {Array} Returns an array of state names in descending order, not including the root.
29151 function ancestors(first, second) {
29154 for (var n in first.path) {
29155 if (first.path[n] !== second.path[n]) break;
29156 path.push(first.path[n]);
29162 * IE8-safe wrapper for `Object.keys()`.
29164 * @param {Object} object A JavaScript object.
29165 * @return {Array} Returns the keys of the object as an array.
29167 function objectKeys(object) {
29169 return Object.keys(object);
29173 forEach(object, function(val, key) {
29180 * IE8-safe wrapper for `Array.prototype.indexOf()`.
29182 * @param {Array} array A JavaScript array.
29183 * @param {*} value A value to search the array for.
29184 * @return {Number} Returns the array index value of `value`, or `-1` if not present.
29186 function indexOf(array, value) {
29187 if (Array.prototype.indexOf) {
29188 return array.indexOf(value, Number(arguments[2]) || 0);
29190 var len = array.length >>> 0, from = Number(arguments[2]) || 0;
29191 from = (from < 0) ? Math.ceil(from) : Math.floor(from);
29193 if (from < 0) from += len;
29195 for (; from < len; from++) {
29196 if (from in array && array[from] === value) return from;
29202 * Merges a set of parameters with all parameters inherited between the common parents of the
29203 * current state and a given destination state.
29205 * @param {Object} currentParams The value of the current state parameters ($stateParams).
29206 * @param {Object} newParams The set of parameters which will be composited with inherited params.
29207 * @param {Object} $current Internal definition of object representing the current state.
29208 * @param {Object} $to Internal definition of object representing state to transition to.
29210 function inheritParams(currentParams, newParams, $current, $to) {
29211 var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];
29213 for (var i in parents) {
29214 if (!parents[i].params) continue;
29215 parentParams = objectKeys(parents[i].params);
29216 if (!parentParams.length) continue;
29218 for (var j in parentParams) {
29219 if (indexOf(inheritList, parentParams[j]) >= 0) continue;
29220 inheritList.push(parentParams[j]);
29221 inherited[parentParams[j]] = currentParams[parentParams[j]];
29224 return extend({}, inherited, newParams);
29228 * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
29230 * @param {Object} a The first object.
29231 * @param {Object} b The second object.
29232 * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified,
29233 * it defaults to the list of keys in `a`.
29234 * @return {Boolean} Returns `true` if the keys match, otherwise `false`.
29236 function equalForKeys(a, b, keys) {
29239 for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility
29242 for (var i=0; i<keys.length; i++) {
29244 if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
29250 * Returns the subset of an object, based on a list of keys.
29252 * @param {Array} keys
29253 * @param {Object} values
29254 * @return {Boolean} Returns a subset of `values`.
29256 function filterByKeys(keys, values) {
29259 forEach(keys, function (name) {
29260 filtered[name] = values[name];
29266 // when you know that your index values will be unique, or you want last-one-in to win
29267 function indexBy(array, propName) {
29269 forEach(array, function(item) {
29270 result[item[propName]] = item;
29275 // extracted from underscore.js
29276 // Return a copy of the object only containing the whitelisted properties.
29277 function pick(obj) {
29279 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29280 forEach(keys, function(key) {
29281 if (key in obj) copy[key] = obj[key];
29286 // extracted from underscore.js
29287 // Return a copy of the object omitting the blacklisted properties.
29288 function omit(obj) {
29290 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29291 for (var key in obj) {
29292 if (indexOf(keys, key) == -1) copy[key] = obj[key];
29297 function pluck(collection, key) {
29298 var result = isArray(collection) ? [] : {};
29300 forEach(collection, function(val, i) {
29301 result[i] = isFunction(key) ? key(val) : val[key];
29306 function filter(collection, callback) {
29307 var array = isArray(collection);
29308 var result = array ? [] : {};
29309 forEach(collection, function(val, i) {
29310 if (callback(val, i)) {
29311 result[array ? result.length : i] = val;
29317 function map(collection, callback) {
29318 var result = isArray(collection) ? [] : {};
29320 forEach(collection, function(val, i) {
29321 result[i] = callback(val, i);
29328 * @name ui.router.util
29331 * # ui.router.util sub-module
29333 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29334 * in your angular app (use {@link ui.router} module instead).
29337 angular.module('ui.router.util', ['ng']);
29341 * @name ui.router.router
29343 * @requires ui.router.util
29346 * # ui.router.router sub-module
29348 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29349 * in your angular app (use {@link ui.router} module instead).
29351 angular.module('ui.router.router', ['ui.router.util']);
29355 * @name ui.router.state
29357 * @requires ui.router.router
29358 * @requires ui.router.util
29361 * # ui.router.state sub-module
29363 * This module is a dependency of the main ui.router module. Do not include this module as a dependency
29364 * in your angular app (use {@link ui.router} module instead).
29367 angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']);
29373 * @requires ui.router.state
29378 * ## The main module for ui.router
29379 * There are several sub-modules included with the ui.router module, however only this module is needed
29380 * as a dependency within your angular app. The other modules are for organization purposes.
29383 * * ui.router - the main "umbrella" module
29384 * * ui.router.router -
29386 * *You'll need to include **only** this module as the dependency within your angular app.*
29390 * <html ng-app="myApp">
29392 * <script src="js/angular.js"></script>
29393 * <!-- Include the ui-router script -->
29394 * <script src="js/angular-ui-router.min.js"></script>
29396 * // ...and add 'ui.router' as a dependency
29397 * var myApp = angular.module('myApp', ['ui.router']);
29405 angular.module('ui.router', ['ui.router.state']);
29407 angular.module('ui.router.compat', ['ui.router']);
29411 * @name ui.router.util.$resolve
29414 * @requires $injector
29417 * Manages resolution of (acyclic) graphs of promises.
29419 $Resolve.$inject = ['$q', '$injector'];
29420 function $Resolve( $q, $injector) {
29422 var VISIT_IN_PROGRESS = 1,
29425 NO_DEPENDENCIES = [],
29426 NO_LOCALS = NOTHING,
29427 NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });
29432 * @name ui.router.util.$resolve#study
29433 * @methodOf ui.router.util.$resolve
29436 * Studies a set of invocables that are likely to be used multiple times.
29438 * $resolve.study(invocables)(locals, parent, self)
29442 * $resolve.resolve(invocables, locals, parent, self)
29444 * but the former is more efficient (in fact `resolve` just calls `study`
29447 * @param {object} invocables Invocable objects
29448 * @return {function} a function to pass in locals, parent and self
29450 this.study = function (invocables) {
29451 if (!isObject(invocables)) throw new Error("'invocables' must be an object");
29452 var invocableKeys = objectKeys(invocables || {});
29454 // Perform a topological sort of invocables to build an ordered plan
29455 var plan = [], cycle = [], visited = {};
29456 function visit(value, key) {
29457 if (visited[key] === VISIT_DONE) return;
29460 if (visited[key] === VISIT_IN_PROGRESS) {
29461 cycle.splice(0, indexOf(cycle, key));
29462 throw new Error("Cyclic dependency: " + cycle.join(" -> "));
29464 visited[key] = VISIT_IN_PROGRESS;
29466 if (isString(value)) {
29467 plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
29469 var params = $injector.annotate(value);
29470 forEach(params, function (param) {
29471 if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
29473 plan.push(key, value, params);
29477 visited[key] = VISIT_DONE;
29479 forEach(invocables, visit);
29480 invocables = cycle = visited = null; // plan is all that's required
29482 function isResolve(value) {
29483 return isObject(value) && value.then && value.$$promises;
29486 return function (locals, parent, self) {
29487 if (isResolve(locals) && self === undefined) {
29488 self = parent; parent = locals; locals = null;
29490 if (!locals) locals = NO_LOCALS;
29491 else if (!isObject(locals)) {
29492 throw new Error("'locals' must be an object");
29494 if (!parent) parent = NO_PARENT;
29495 else if (!isResolve(parent)) {
29496 throw new Error("'parent' must be a promise returned by $resolve.resolve()");
29499 // To complete the overall resolution, we have to wait for the parent
29500 // promise and for the promise for each invokable in our plan.
29501 var resolution = $q.defer(),
29502 result = resolution.promise,
29503 promises = result.$$promises = {},
29504 values = extend({}, locals),
29505 wait = 1 + plan.length/3,
29509 // Merge parent values we haven't got yet and publish our own $$values
29511 if (!merged) merge(values, parent.$$values);
29512 result.$$values = values;
29513 result.$$promises = result.$$promises || true; // keep for isResolve()
29514 delete result.$$inheritedValues;
29515 resolution.resolve(values);
29519 function fail(reason) {
29520 result.$$failure = reason;
29521 resolution.reject(reason);
29524 // Short-circuit if parent has already failed
29525 if (isDefined(parent.$$failure)) {
29526 fail(parent.$$failure);
29530 if (parent.$$inheritedValues) {
29531 merge(values, omit(parent.$$inheritedValues, invocableKeys));
29534 // Merge parent values if the parent has already resolved, or merge
29535 // parent promises and wait if the parent resolve is still in progress.
29536 extend(promises, parent.$$promises);
29537 if (parent.$$values) {
29538 merged = merge(values, omit(parent.$$values, invocableKeys));
29539 result.$$inheritedValues = omit(parent.$$values, invocableKeys);
29542 if (parent.$$inheritedValues) {
29543 result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
29545 parent.then(done, fail);
29548 // Process each invocable in the plan, but ignore any where a local of the same name exists.
29549 for (var i=0, ii=plan.length; i<ii; i+=3) {
29550 if (locals.hasOwnProperty(plan[i])) done();
29551 else invoke(plan[i], plan[i+1], plan[i+2]);
29554 function invoke(key, invocable, params) {
29555 // Create a deferred for this invocation. Failures will propagate to the resolution as well.
29556 var invocation = $q.defer(), waitParams = 0;
29557 function onfailure(reason) {
29558 invocation.reject(reason);
29561 // Wait for any parameter that we have a promise for (either from parent or from this
29562 // resolve; in that case study() will have made sure it's ordered before us in the plan).
29563 forEach(params, function (dep) {
29564 if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
29566 promises[dep].then(function (result) {
29567 values[dep] = result;
29568 if (!(--waitParams)) proceed();
29572 if (!waitParams) proceed();
29573 function proceed() {
29574 if (isDefined(result.$$failure)) return;
29576 invocation.resolve($injector.invoke(invocable, self, values));
29577 invocation.promise.then(function (result) {
29578 values[key] = result;
29585 // Publish promise synchronously; invocations further down in the plan may depend on it.
29586 promises[key] = invocation.promise;
29595 * @name ui.router.util.$resolve#resolve
29596 * @methodOf ui.router.util.$resolve
29599 * Resolves a set of invocables. An invocable is a function to be invoked via
29600 * `$injector.invoke()`, and can have an arbitrary number of dependencies.
29601 * An invocable can either return a value directly,
29602 * or a `$q` promise. If a promise is returned it will be resolved and the
29603 * resulting value will be used instead. Dependencies of invocables are resolved
29604 * (in this order of precedence)
29606 * - from the specified `locals`
29607 * - from another invocable that is part of this `$resolve` call
29608 * - from an invocable that is inherited from a `parent` call to `$resolve`
29610 * - from any ancestor `$resolve` of that parent).
29612 * The return value of `$resolve` is a promise for an object that contains
29613 * (in this order of precedence)
29615 * - any `locals` (if specified)
29616 * - the resolved return values of all injectables
29617 * - any values inherited from a `parent` call to `$resolve` (if specified)
29619 * The promise will resolve after the `parent` promise (if any) and all promises
29620 * returned by injectables have been resolved. If any invocable
29621 * (or `$injector.invoke`) throws an exception, or if a promise returned by an
29622 * invocable is rejected, the `$resolve` promise is immediately rejected with the
29623 * same error. A rejection of a `parent` promise (if specified) will likewise be
29624 * propagated immediately. Once the `$resolve` promise has been rejected, no
29625 * further invocables will be called.
29627 * Cyclic dependencies between invocables are not permitted and will caues `$resolve`
29628 * to throw an error. As a special case, an injectable can depend on a parameter
29629 * with the same name as the injectable, which will be fulfilled from the `parent`
29630 * injectable of the same name. This allows inherited values to be decorated.
29631 * Note that in this case any other injectable in the same `$resolve` with the same
29632 * dependency would see the decorated value, not the inherited value.
29634 * Note that missing dependencies -- unlike cyclic dependencies -- will cause an
29635 * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous)
29638 * Invocables are invoked eagerly as soon as all dependencies are available.
29639 * This is true even for dependencies inherited from a `parent` call to `$resolve`.
29641 * As a special case, an invocable can be a string, in which case it is taken to
29642 * be a service name to be passed to `$injector.get()`. This is supported primarily
29643 * for backwards-compatibility with the `resolve` property of `$routeProvider`
29646 * @param {object} invocables functions to invoke or
29647 * `$injector` services to fetch.
29648 * @param {object} locals values to make available to the injectables
29649 * @param {object} parent a promise returned by another call to `$resolve`.
29650 * @param {object} self the `this` for the invoked methods
29651 * @return {object} Promise for an object that contains the resolved return value
29652 * of all invocables, as well as any inherited and local values.
29654 this.resolve = function (invocables, locals, parent, self) {
29655 return this.study(invocables)(locals, parent, self);
29659 angular.module('ui.router.util').service('$resolve', $Resolve);
29664 * @name ui.router.util.$templateFactory
29667 * @requires $templateCache
29668 * @requires $injector
29671 * Service. Manages loading of templates.
29673 $TemplateFactory.$inject = ['$http', '$templateCache', '$injector'];
29674 function $TemplateFactory( $http, $templateCache, $injector) {
29678 * @name ui.router.util.$templateFactory#fromConfig
29679 * @methodOf ui.router.util.$templateFactory
29682 * Creates a template from a configuration object.
29684 * @param {object} config Configuration object for which to load a template.
29685 * The following properties are search in the specified order, and the first one
29686 * that is defined is used to create the template:
29688 * @param {string|object} config.template html string template or function to
29689 * load via {@link ui.router.util.$templateFactory#fromString fromString}.
29690 * @param {string|object} config.templateUrl url to load or a function returning
29691 * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}.
29692 * @param {Function} config.templateProvider function to invoke via
29693 * {@link ui.router.util.$templateFactory#fromProvider fromProvider}.
29694 * @param {object} params Parameters to pass to the template function.
29695 * @param {object} locals Locals to pass to `invoke` if the template is loaded
29696 * via a `templateProvider`. Defaults to `{ params: params }`.
29698 * @return {string|object} The template html as a string, or a promise for
29699 * that string,or `null` if no template is configured.
29701 this.fromConfig = function (config, params, locals) {
29703 isDefined(config.template) ? this.fromString(config.template, params) :
29704 isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :
29705 isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :
29712 * @name ui.router.util.$templateFactory#fromString
29713 * @methodOf ui.router.util.$templateFactory
29716 * Creates a template from a string or a function returning a string.
29718 * @param {string|object} template html template as a string or function that
29719 * returns an html template as a string.
29720 * @param {object} params Parameters to pass to the template function.
29722 * @return {string|object} The template html as a string, or a promise for that
29725 this.fromString = function (template, params) {
29726 return isFunction(template) ? template(params) : template;
29731 * @name ui.router.util.$templateFactory#fromUrl
29732 * @methodOf ui.router.util.$templateFactory
29735 * Loads a template from the a URL via `$http` and `$templateCache`.
29737 * @param {string|Function} url url of the template to load, or a function
29738 * that returns a url.
29739 * @param {Object} params Parameters to pass to the url function.
29740 * @return {string|Promise.<string>} The template html as a string, or a promise
29743 this.fromUrl = function (url, params) {
29744 if (isFunction(url)) url = url(params);
29745 if (url == null) return null;
29747 .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
29748 .then(function(response) { return response.data; });
29753 * @name ui.router.util.$templateFactory#fromProvider
29754 * @methodOf ui.router.util.$templateFactory
29757 * Creates a template by invoking an injectable provider function.
29759 * @param {Function} provider Function to invoke via `$injector.invoke`
29760 * @param {Object} params Parameters for the template.
29761 * @param {Object} locals Locals to pass to `invoke`. Defaults to
29762 * `{ params: params }`.
29763 * @return {string|Promise.<string>} The template html as a string, or a promise
29766 this.fromProvider = function (provider, params, locals) {
29767 return $injector.invoke(provider, null, locals || { params: params });
29771 angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
29773 var $$UMFP; // reference to $UrlMatcherFactoryProvider
29777 * @name ui.router.util.type:UrlMatcher
29780 * Matches URLs against patterns and extracts named parameters from the path or the search
29781 * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
29782 * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
29783 * do not influence whether or not a URL is matched, but their values are passed through into
29784 * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
29786 * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
29787 * syntax, which optionally allows a regular expression for the parameter to be specified:
29789 * * `':'` name - colon placeholder
29790 * * `'*'` name - catch-all placeholder
29791 * * `'{' name '}'` - curly placeholder
29792 * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
29793 * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
29795 * Parameter names may contain only word characters (latin letters, digits, and underscore) and
29796 * must be unique within the pattern (across both path and search parameters). For colon
29797 * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
29798 * number of characters other than '/'. For catch-all placeholders the path parameter matches
29799 * any number of characters.
29803 * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
29804 * trailing slashes, and patterns have to match the entire path, not just a prefix.
29805 * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
29806 * '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
29807 * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
29808 * * `'/user/{id:[^/]*}'` - Same as the previous example.
29809 * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
29810 * parameter consists of 1 to 8 hex digits.
29811 * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
29812 * path into the parameter 'path'.
29813 * * `'/files/*path'` - ditto.
29814 * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
29815 * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
29817 * @param {string} pattern The pattern to compile into a matcher.
29818 * @param {Object} config A configuration object hash:
29819 * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
29820 * an existing UrlMatcher
29822 * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
29823 * * `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`.
29825 * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
29826 * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
29827 * non-null) will start with this prefix.
29829 * @property {string} source The pattern that was passed into the constructor
29831 * @property {string} sourcePath The path portion of the source property
29833 * @property {string} sourceSearch The search portion of the source property
29835 * @property {string} regex The constructed regex that will be used to match against the url when
29836 * it is time to determine which url will match.
29838 * @returns {Object} New `UrlMatcher` object
29840 function UrlMatcher(pattern, config, parentMatcher) {
29841 config = extend({ params: {} }, isObject(config) ? config : {});
29843 // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
29847 // '{' name ':' regexp '}'
29848 // The regular expression is somewhat complicated due to the need to allow curly braces
29849 // inside the regular expression. The placeholder regexp breaks down as follows:
29850 // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case)
29851 // \{([\w\[\]]+)(?:\:( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
29852 // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either
29853 // [^{}\\]+ - anything other than curly braces or backslash
29854 // \\. - a backslash escape
29855 // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
29856 var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29857 searchPlaceholder = /([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29858 compiled = '^', last = 0, m,
29859 segments = this.segments = [],
29860 parentParams = parentMatcher ? parentMatcher.params : {},
29861 params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
29864 function addParameter(id, type, config, location) {
29865 paramNames.push(id);
29866 if (parentParams[id]) return parentParams[id];
29867 if (!/^\w+(-+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
29868 if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
29869 params[id] = new $$UMFP.Param(id, type, config, location);
29873 function quoteRegExp(string, pattern, squash, optional) {
29874 var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
29875 if (!pattern) return result;
29877 case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
29878 case true: surroundPattern = ['?(', ')?']; break;
29879 default: surroundPattern = ['(' + squash + "|", ')?']; break;
29881 return result + surroundPattern[0] + pattern + surroundPattern[1];
29884 this.source = pattern;
29886 // Split into static segments separated by path parameter placeholders.
29887 // The number of segments is always 1 more than the number of parameters.
29888 function matchDetails(m, isSearch) {
29889 var id, regexp, segment, type, cfg, arrayMode;
29890 id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
29891 cfg = config.params[id];
29892 segment = pattern.substring(last, m.index);
29893 regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
29894 type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
29896 id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
29900 var p, param, segment;
29901 while ((m = placeholder.exec(pattern))) {
29902 p = matchDetails(m, false);
29903 if (p.segment.indexOf('?') >= 0) break; // we're into the search part
29905 param = addParameter(p.id, p.type, p.cfg, "path");
29906 compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
29907 segments.push(p.segment);
29908 last = placeholder.lastIndex;
29910 segment = pattern.substring(last);
29912 // Find any search parameter names and remove them from the last segment
29913 var i = segment.indexOf('?');
29916 var search = this.sourceSearch = segment.substring(i);
29917 segment = segment.substring(0, i);
29918 this.sourcePath = pattern.substring(0, last + i);
29920 if (search.length > 0) {
29922 while ((m = searchPlaceholder.exec(search))) {
29923 p = matchDetails(m, true);
29924 param = addParameter(p.id, p.type, p.cfg, "search");
29925 last = placeholder.lastIndex;
29930 this.sourcePath = pattern;
29931 this.sourceSearch = '';
29934 compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
29935 segments.push(segment);
29937 this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
29938 this.prefix = segments[0];
29939 this.$$paramNames = paramNames;
29944 * @name ui.router.util.type:UrlMatcher#concat
29945 * @methodOf ui.router.util.type:UrlMatcher
29948 * Returns a new matcher for a pattern constructed by appending the path part and adding the
29949 * search parameters of the specified pattern to this pattern. The current pattern is not
29950 * modified. This can be understood as creating a pattern for URLs that are relative to (or
29951 * suffixes of) the current pattern.
29954 * The following two matchers are equivalent:
29956 * new UrlMatcher('/user/{id}?q').concat('/details?date');
29957 * new UrlMatcher('/user/{id}/details?q&date');
29960 * @param {string} pattern The pattern to append.
29961 * @param {Object} config An object hash of the configuration for the matcher.
29962 * @returns {UrlMatcher} A matcher for the concatenated pattern.
29964 UrlMatcher.prototype.concat = function (pattern, config) {
29965 // Because order of search parameters is irrelevant, we can add our own search
29966 // parameters to the end of the new pattern. Parse the new pattern by itself
29967 // and then join the bits together, but it's much easier to do this on a string level.
29968 var defaultConfig = {
29969 caseInsensitive: $$UMFP.caseInsensitive(),
29970 strict: $$UMFP.strictMode(),
29971 squash: $$UMFP.defaultSquashPolicy()
29973 return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
29976 UrlMatcher.prototype.toString = function () {
29977 return this.source;
29982 * @name ui.router.util.type:UrlMatcher#exec
29983 * @methodOf ui.router.util.type:UrlMatcher
29986 * Tests the specified path against this matcher, and returns an object containing the captured
29987 * parameter values, or null if the path does not match. The returned object contains the values
29988 * of any search parameters that are mentioned in the pattern, but their value may be null if
29989 * they are not present in `searchParams`. This means that search parameters are always treated
29994 * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
29995 * x: '1', q: 'hello'
29997 * // returns { id: 'bob', q: 'hello', r: null }
30000 * @param {string} path The URL path to match, e.g. `$location.path()`.
30001 * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.
30002 * @returns {Object} The captured parameter values.
30004 UrlMatcher.prototype.exec = function (path, searchParams) {
30005 var m = this.regexp.exec(path);
30006 if (!m) return null;
30007 searchParams = searchParams || {};
30009 var paramNames = this.parameters(), nTotal = paramNames.length,
30010 nPath = this.segments.length - 1,
30011 values = {}, i, j, cfg, paramName;
30013 if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
30015 function decodePathArray(string) {
30016 function reverseString(str) { return str.split("").reverse().join(""); }
30017 function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }
30019 var split = reverseString(string).split(/-(?!\\)/);
30020 var allReversed = map(split, reverseString);
30021 return map(allReversed, unquoteDashes).reverse();
30024 for (i = 0; i < nPath; i++) {
30025 paramName = paramNames[i];
30026 var param = this.params[paramName];
30027 var paramVal = m[i+1];
30028 // if the param value matches a pre-replace pair, replace the value before decoding.
30029 for (j = 0; j < param.replace; j++) {
30030 if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
30032 if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
30033 values[paramName] = param.value(paramVal);
30035 for (/**/; i < nTotal; i++) {
30036 paramName = paramNames[i];
30037 values[paramName] = this.params[paramName].value(searchParams[paramName]);
30045 * @name ui.router.util.type:UrlMatcher#parameters
30046 * @methodOf ui.router.util.type:UrlMatcher
30049 * Returns the names of all path and search parameters of this pattern in an unspecified order.
30051 * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the
30052 * pattern has no parameters, an empty array is returned.
30054 UrlMatcher.prototype.parameters = function (param) {
30055 if (!isDefined(param)) return this.$$paramNames;
30056 return this.params[param] || null;
30061 * @name ui.router.util.type:UrlMatcher#validate
30062 * @methodOf ui.router.util.type:UrlMatcher
30065 * Checks an object hash of parameters to validate their correctness according to the parameter
30066 * types of this `UrlMatcher`.
30068 * @param {Object} params The object hash of parameters to validate.
30069 * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
30071 UrlMatcher.prototype.validates = function (params) {
30072 return this.params.$$validates(params);
30077 * @name ui.router.util.type:UrlMatcher#format
30078 * @methodOf ui.router.util.type:UrlMatcher
30081 * Creates a URL that matches this pattern by substituting the specified values
30082 * for the path and search parameters. Null values for path parameters are
30083 * treated as empty strings.
30087 * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
30088 * // returns '/user/bob?q=yes'
30091 * @param {Object} values the values to substitute for the parameters in this pattern.
30092 * @returns {string} the formatted URL (path and optionally search part).
30094 UrlMatcher.prototype.format = function (values) {
30095 values = values || {};
30096 var segments = this.segments, params = this.parameters(), paramset = this.params;
30097 if (!this.validates(values)) return null;
30099 var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];
30101 function encodeDashes(str) { // Replace dashes with encoded "\-"
30102 return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
30105 for (i = 0; i < nTotal; i++) {
30106 var isPathParam = i < nPath;
30107 var name = params[i], param = paramset[name], value = param.value(values[name]);
30108 var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
30109 var squash = isDefaultValue ? param.squash : false;
30110 var encoded = param.type.encode(value);
30113 var nextSegment = segments[i + 1];
30114 if (squash === false) {
30115 if (encoded != null) {
30116 if (isArray(encoded)) {
30117 result += map(encoded, encodeDashes).join("-");
30119 result += encodeURIComponent(encoded);
30122 result += nextSegment;
30123 } else if (squash === true) {
30124 var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
30125 result += nextSegment.match(capture)[1];
30126 } else if (isString(squash)) {
30127 result += squash + nextSegment;
30130 if (encoded == null || (isDefaultValue && squash !== false)) continue;
30131 if (!isArray(encoded)) encoded = [ encoded ];
30132 encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
30133 result += (search ? '&' : '?') + (name + '=' + encoded);
30143 * @name ui.router.util.type:Type
30146 * Implements an interface to define custom parameter types that can be decoded from and encoded to
30147 * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
30148 * objects when matching or formatting URLs, or comparing or validating parameter values.
30150 * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
30151 * information on registering custom types.
30153 * @param {Object} config A configuration object which contains the custom type definition. The object's
30154 * properties will override the default methods and/or pattern in `Type`'s public interface.
30158 * decode: function(val) { return parseInt(val, 10); },
30159 * encode: function(val) { return val && val.toString(); },
30160 * equals: function(a, b) { return this.is(a) && a === b; },
30161 * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
30166 * @property {RegExp} pattern The regular expression pattern used to match values of this type when
30167 * coming from a substring of a URL.
30169 * @returns {Object} Returns a new `Type` object.
30171 function Type(config) {
30172 extend(this, config);
30177 * @name ui.router.util.type:Type#is
30178 * @methodOf ui.router.util.type:Type
30181 * Detects whether a value is of a particular type. Accepts a native (decoded) value
30182 * and determines whether it matches the current `Type` object.
30184 * @param {*} val The value to check.
30185 * @param {string} key Optional. If the type check is happening in the context of a specific
30186 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
30187 * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
30188 * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.
30190 Type.prototype.is = function(val, key) {
30196 * @name ui.router.util.type:Type#encode
30197 * @methodOf ui.router.util.type:Type
30200 * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
30201 * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
30202 * only needs to be a representation of `val` that has been coerced to a string.
30204 * @param {*} val The value to encode.
30205 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30206 * meta-programming of `Type` objects.
30207 * @returns {string} Returns a string representation of `val` that can be encoded in a URL.
30209 Type.prototype.encode = function(val, key) {
30215 * @name ui.router.util.type:Type#decode
30216 * @methodOf ui.router.util.type:Type
30219 * Converts a parameter value (from URL string or transition param) to a custom/native value.
30221 * @param {string} val The URL parameter value to decode.
30222 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30223 * meta-programming of `Type` objects.
30224 * @returns {*} Returns a custom representation of the URL parameter value.
30226 Type.prototype.decode = function(val, key) {
30232 * @name ui.router.util.type:Type#equals
30233 * @methodOf ui.router.util.type:Type
30236 * Determines whether two decoded values are equivalent.
30238 * @param {*} a A value to compare against.
30239 * @param {*} b A value to compare against.
30240 * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.
30242 Type.prototype.equals = function(a, b) {
30246 Type.prototype.$subPattern = function() {
30247 var sub = this.pattern.toString();
30248 return sub.substr(1, sub.length - 2);
30251 Type.prototype.pattern = /.*/;
30253 Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
30255 /** Given an encoded string, or a decoded object, returns a decoded object */
30256 Type.prototype.$normalize = function(val) {
30257 return this.is(val) ? val : this.decode(val);
30261 * Wraps an existing custom Type as an array of Type, depending on 'mode'.
30263 * - urlmatcher pattern "/path?{queryParam[]:int}"
30264 * - url: "/path?queryParam=1&queryParam=2
30265 * - $stateParams.queryParam will be [1, 2]
30266 * if `mode` is "auto", then
30267 * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
30268 * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
30270 Type.prototype.$asArray = function(mode, isSearch) {
30271 if (!mode) return this;
30272 if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
30274 function ArrayType(type, mode) {
30275 function bindTo(type, callbackName) {
30276 return function() {
30277 return type[callbackName].apply(type, arguments);
30281 // Wrap non-array value as array
30282 function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
30283 // Unwrap array value for "auto" mode. Return undefined for empty array.
30284 function arrayUnwrap(val) {
30285 switch(val.length) {
30286 case 0: return undefined;
30287 case 1: return mode === "auto" ? val[0] : val;
30288 default: return val;
30291 function falsey(val) { return !val; }
30293 // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
30294 function arrayHandler(callback, allTruthyMode) {
30295 return function handleArray(val) {
30296 val = arrayWrap(val);
30297 var result = map(val, callback);
30298 if (allTruthyMode === true)
30299 return filter(result, falsey).length === 0;
30300 return arrayUnwrap(result);
30304 // Wraps type (.equals) functions to operate on each value of an array
30305 function arrayEqualsHandler(callback) {
30306 return function handleArray(val1, val2) {
30307 var left = arrayWrap(val1), right = arrayWrap(val2);
30308 if (left.length !== right.length) return false;
30309 for (var i = 0; i < left.length; i++) {
30310 if (!callback(left[i], right[i])) return false;
30316 this.encode = arrayHandler(bindTo(type, 'encode'));
30317 this.decode = arrayHandler(bindTo(type, 'decode'));
30318 this.is = arrayHandler(bindTo(type, 'is'), true);
30319 this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
30320 this.pattern = type.pattern;
30321 this.$normalize = arrayHandler(bindTo(type, '$normalize'));
30322 this.name = type.name;
30323 this.$arrayMode = mode;
30326 return new ArrayType(this, mode);
30333 * @name ui.router.util.$urlMatcherFactory
30336 * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
30337 * is also available to providers under the name `$urlMatcherFactoryProvider`.
30339 function $UrlMatcherFactory() {
30342 var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
30344 function valToString(val) { return val != null ? val.toString().replace(/\//g, "%2F") : val; }
30345 function valFromString(val) { return val != null ? val.toString().replace(/%2F/g, "/") : val; }
30347 var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
30349 encode: valToString,
30350 decode: valFromString,
30351 // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
30352 // In 0.2.x, string params are optional by default for backwards compat
30353 is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
30357 encode: valToString,
30358 decode: function(val) { return parseInt(val, 10); },
30359 is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
30363 encode: function(val) { return val ? 1 : 0; },
30364 decode: function(val) { return parseInt(val, 10) !== 0; },
30365 is: function(val) { return val === true || val === false; },
30369 encode: function (val) {
30372 return [ val.getFullYear(),
30373 ('0' + (val.getMonth() + 1)).slice(-2),
30374 ('0' + val.getDate()).slice(-2)
30377 decode: function (val) {
30378 if (this.is(val)) return val;
30379 var match = this.capture.exec(val);
30380 return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
30382 is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
30383 equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
30384 pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
30385 capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
30388 encode: angular.toJson,
30389 decode: angular.fromJson,
30390 is: angular.isObject,
30391 equals: angular.equals,
30394 any: { // does not encode/decode
30395 encode: angular.identity,
30396 decode: angular.identity,
30397 equals: angular.equals,
30402 function getDefaultConfig() {
30404 strict: isStrictMode,
30405 caseInsensitive: isCaseInsensitive
30409 function isInjectable(value) {
30410 return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
30414 * [Internal] Get the default value of a parameter, which may be an injectable function.
30416 $UrlMatcherFactory.$$getDefaultValue = function(config) {
30417 if (!isInjectable(config.value)) return config.value;
30418 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30419 return injector.invoke(config.value);
30424 * @name ui.router.util.$urlMatcherFactory#caseInsensitive
30425 * @methodOf ui.router.util.$urlMatcherFactory
30428 * Defines whether URL matching should be case sensitive (the default behavior), or not.
30430 * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
30431 * @returns {boolean} the current value of caseInsensitive
30433 this.caseInsensitive = function(value) {
30434 if (isDefined(value))
30435 isCaseInsensitive = value;
30436 return isCaseInsensitive;
30441 * @name ui.router.util.$urlMatcherFactory#strictMode
30442 * @methodOf ui.router.util.$urlMatcherFactory
30445 * Defines whether URLs should match trailing slashes, or not (the default behavior).
30447 * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
30448 * @returns {boolean} the current value of strictMode
30450 this.strictMode = function(value) {
30451 if (isDefined(value))
30452 isStrictMode = value;
30453 return isStrictMode;
30458 * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
30459 * @methodOf ui.router.util.$urlMatcherFactory
30462 * Sets the default behavior when generating or matching URLs with default parameter values.
30464 * @param {string} value A string that defines the default parameter URL squashing behavior.
30465 * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
30466 * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
30467 * parameter is surrounded by slashes, squash (remove) one slash from the URL
30468 * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
30469 * the parameter value from the URL and replace it with this string.
30471 this.defaultSquashPolicy = function(value) {
30472 if (!isDefined(value)) return defaultSquashPolicy;
30473 if (value !== true && value !== false && !isString(value))
30474 throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
30475 defaultSquashPolicy = value;
30481 * @name ui.router.util.$urlMatcherFactory#compile
30482 * @methodOf ui.router.util.$urlMatcherFactory
30485 * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
30487 * @param {string} pattern The URL pattern.
30488 * @param {Object} config The config object hash.
30489 * @returns {UrlMatcher} The UrlMatcher.
30491 this.compile = function (pattern, config) {
30492 return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
30497 * @name ui.router.util.$urlMatcherFactory#isMatcher
30498 * @methodOf ui.router.util.$urlMatcherFactory
30501 * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
30503 * @param {Object} object The object to perform the type check against.
30504 * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
30505 * implementing all the same methods.
30507 this.isMatcher = function (o) {
30508 if (!isObject(o)) return false;
30511 forEach(UrlMatcher.prototype, function(val, name) {
30512 if (isFunction(val)) {
30513 result = result && (isDefined(o[name]) && isFunction(o[name]));
30521 * @name ui.router.util.$urlMatcherFactory#type
30522 * @methodOf ui.router.util.$urlMatcherFactory
30525 * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
30526 * generate URLs with typed parameters.
30528 * @param {string} name The type name.
30529 * @param {Object|Function} definition The type definition. See
30530 * {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30531 * @param {Object|Function} definitionFn (optional) A function that is injected before the app
30532 * runtime starts. The result of this function is merged into the existing `definition`.
30533 * See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30535 * @returns {Object} Returns `$urlMatcherFactoryProvider`.
30538 * This is a simple example of a custom type that encodes and decodes items from an
30539 * array, using the array index as the URL-encoded value:
30542 * var list = ['John', 'Paul', 'George', 'Ringo'];
30544 * $urlMatcherFactoryProvider.type('listItem', {
30545 * encode: function(item) {
30546 * // Represent the list item in the URL using its corresponding index
30547 * return list.indexOf(item);
30549 * decode: function(item) {
30550 * // Look up the list item by index
30551 * return list[parseInt(item, 10)];
30553 * is: function(item) {
30554 * // Ensure the item is valid by checking to see that it appears
30556 * return list.indexOf(item) > -1;
30560 * $stateProvider.state('list', {
30561 * url: "/list/{item:listItem}",
30562 * controller: function($scope, $stateParams) {
30563 * console.log($stateParams.item);
30569 * // Changes URL to '/list/3', logs "Ringo" to the console
30570 * $state.go('list', { item: "Ringo" });
30573 * This is a more complex example of a type that relies on dependency injection to
30574 * interact with services, and uses the parameter name from the URL to infer how to
30575 * handle encoding and decoding parameter values:
30578 * // Defines a custom type that gets a value from a service,
30579 * // where each service gets different types of values from
30580 * // a backend API:
30581 * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
30583 * // Matches up services to URL parameter names
30590 * encode: function(object) {
30591 * // Represent the object in the URL using its unique ID
30592 * return object.id;
30594 * decode: function(value, key) {
30595 * // Look up the object by ID, using the parameter
30596 * // name (key) to call the correct service
30597 * return services[key].findById(value);
30599 * is: function(object, key) {
30600 * // Check that object is a valid dbObject
30601 * return angular.isObject(object) && object.id && services[key];
30603 * equals: function(a, b) {
30604 * // Check the equality of decoded objects by comparing
30605 * // their unique IDs
30606 * return a.id === b.id;
30611 * // In a config() block, you can then attach URLs with
30612 * // type-annotated parameters:
30613 * $stateProvider.state('users', {
30616 * }).state('users.item', {
30617 * url: "/{user:dbObject}",
30618 * controller: function($scope, $stateParams) {
30619 * // $stateParams.user will now be an object returned from
30620 * // the Users service
30626 this.type = function (name, definition, definitionFn) {
30627 if (!isDefined(definition)) return $types[name];
30628 if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");
30630 $types[name] = new Type(extend({ name: name }, definition));
30631 if (definitionFn) {
30632 typeQueue.push({ name: name, def: definitionFn });
30633 if (!enqueue) flushTypeQueue();
30638 // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
30639 function flushTypeQueue() {
30640 while(typeQueue.length) {
30641 var type = typeQueue.shift();
30642 if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
30643 angular.extend($types[type.name], injector.invoke(type.def));
30647 // Register default types. Store them in the prototype of $types.
30648 forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
30649 $types = inherit($types, {});
30651 /* No need to document $get, since it returns this */
30652 this.$get = ['$injector', function ($injector) {
30653 injector = $injector;
30657 forEach(defaultTypes, function(type, name) {
30658 if (!$types[name]) $types[name] = new Type(type);
30663 this.Param = function Param(id, type, config, location) {
30665 config = unwrapShorthand(config);
30666 type = getType(config, type, location);
30667 var arrayMode = getArrayMode();
30668 type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
30669 if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
30670 config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
30671 var isOptional = config.value !== undefined;
30672 var squash = getSquashPolicy(config, isOptional);
30673 var replace = getReplace(config, arrayMode, isOptional, squash);
30675 function unwrapShorthand(config) {
30676 var keys = isObject(config) ? objectKeys(config) : [];
30677 var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
30678 indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
30679 if (isShorthand) config = { value: config };
30680 config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
30684 function getType(config, urlType, location) {
30685 if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
30686 if (urlType) return urlType;
30687 if (!config.type) return (location === "config" ? $types.any : $types.string);
30688 return config.type instanceof Type ? config.type : new Type(config.type);
30691 // array config: param name (param[]) overrides default settings. explicit config overrides param name.
30692 function getArrayMode() {
30693 var arrayDefaults = { array: (location === "search" ? "auto" : false) };
30694 var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
30695 return extend(arrayDefaults, arrayParamNomenclature, config).array;
30699 * returns false, true, or the squash value to indicate the "default parameter url squash policy".
30701 function getSquashPolicy(config, isOptional) {
30702 var squash = config.squash;
30703 if (!isOptional || squash === false) return false;
30704 if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
30705 if (squash === true || isString(squash)) return squash;
30706 throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
30709 function getReplace(config, arrayMode, isOptional, squash) {
30710 var replace, configuredKeys, defaultPolicy = [
30711 { from: "", to: (isOptional || arrayMode ? undefined : "") },
30712 { from: null, to: (isOptional || arrayMode ? undefined : "") }
30714 replace = isArray(config.replace) ? config.replace : [];
30715 if (isString(squash))
30716 replace.push({ from: squash, to: undefined });
30717 configuredKeys = map(replace, function(item) { return item.from; } );
30718 return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
30722 * [Internal] Get the default value of a parameter, which may be an injectable function.
30724 function $$getDefaultValue() {
30725 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30726 var defaultValue = injector.invoke(config.$$fn);
30727 if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
30728 throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
30729 return defaultValue;
30733 * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
30734 * default value, which may be the result of an injectable function.
30736 function $value(value) {
30737 function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
30738 function $replace(value) {
30739 var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
30740 return replacement.length ? replacement[0] : value;
30742 value = $replace(value);
30743 return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
30746 function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
30751 location: location,
30755 isOptional: isOptional,
30757 dynamic: undefined,
30763 function ParamSet(params) {
30764 extend(this, params || {});
30767 ParamSet.prototype = {
30768 $$new: function() {
30769 return inherit(this, extend(new ParamSet(), { $$parent: this}));
30771 $$keys: function () {
30772 var keys = [], chain = [], parent = this,
30773 ignore = objectKeys(ParamSet.prototype);
30774 while (parent) { chain.push(parent); parent = parent.$$parent; }
30776 forEach(chain, function(paramset) {
30777 forEach(objectKeys(paramset), function(key) {
30778 if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
30783 $$values: function(paramValues) {
30784 var values = {}, self = this;
30785 forEach(self.$$keys(), function(key) {
30786 values[key] = self[key].value(paramValues && paramValues[key]);
30790 $$equals: function(paramValues1, paramValues2) {
30791 var equal = true, self = this;
30792 forEach(self.$$keys(), function(key) {
30793 var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
30794 if (!self[key].type.equals(left, right)) equal = false;
30798 $$validates: function $$validate(paramValues) {
30799 var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
30800 for (i = 0; i < keys.length; i++) {
30801 param = this[keys[i]];
30802 rawVal = paramValues[keys[i]];
30803 if ((rawVal === undefined || rawVal === null) && param.isOptional)
30804 break; // There was no parameter value, but the param is optional
30805 normalized = param.type.$normalize(rawVal);
30806 if (!param.type.is(normalized))
30807 return false; // The value was not of the correct Type, and could not be decoded to the correct Type
30808 encoded = param.type.encode(normalized);
30809 if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
30810 return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
30814 $$parent: undefined
30817 this.ParamSet = ParamSet;
30820 // Register as a provider so it's available to other providers
30821 angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
30822 angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
30826 * @name ui.router.router.$urlRouterProvider
30828 * @requires ui.router.util.$urlMatcherFactoryProvider
30829 * @requires $locationProvider
30832 * `$urlRouterProvider` has the responsibility of watching `$location`.
30833 * When `$location` changes it runs through a list of rules one by one until a
30834 * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify
30835 * a url in a state configuration. All urls are compiled into a UrlMatcher object.
30837 * There are several methods on `$urlRouterProvider` that make it useful to use directly
30838 * in your module config.
30840 $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
30841 function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
30842 var rules = [], otherwise = null, interceptDeferred = false, listener;
30844 // Returns a string that is a prefix of all strings matching the RegExp
30845 function regExpPrefix(re) {
30846 var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
30847 return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
30850 // Interpolates matched values into a String.replace()-style pattern
30851 function interpolate(pattern, match) {
30852 return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
30853 return match[what === '$' ? 0 : Number(what)];
30859 * @name ui.router.router.$urlRouterProvider#rule
30860 * @methodOf ui.router.router.$urlRouterProvider
30863 * Defines rules that are used by `$urlRouterProvider` to find matches for
30868 * var app = angular.module('app', ['ui.router.router']);
30870 * app.config(function ($urlRouterProvider) {
30871 * // Here's an example of how you might allow case insensitive urls
30872 * $urlRouterProvider.rule(function ($injector, $location) {
30873 * var path = $location.path(),
30874 * normalized = path.toLowerCase();
30876 * if (path !== normalized) {
30877 * return normalized;
30883 * @param {object} rule Handler function that takes `$injector` and `$location`
30884 * services as arguments. You can use them to return a valid path as a string.
30886 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30888 this.rule = function (rule) {
30889 if (!isFunction(rule)) throw new Error("'rule' must be a function");
30896 * @name ui.router.router.$urlRouterProvider#otherwise
30897 * @methodOf ui.router.router.$urlRouterProvider
30900 * Defines a path that is used when an invalid route is requested.
30904 * var app = angular.module('app', ['ui.router.router']);
30906 * app.config(function ($urlRouterProvider) {
30907 * // if the path doesn't match any of the urls you configured
30908 * // otherwise will take care of routing the user to the
30910 * $urlRouterProvider.otherwise('/index');
30912 * // Example of using function rule as param
30913 * $urlRouterProvider.otherwise(function ($injector, $location) {
30914 * return '/a/valid/url';
30919 * @param {string|object} rule The url path you want to redirect to or a function
30920 * rule that returns the url path. The function version is passed two params:
30921 * `$injector` and `$location` services, and must return a url string.
30923 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30925 this.otherwise = function (rule) {
30926 if (isString(rule)) {
30927 var redirect = rule;
30928 rule = function () { return redirect; };
30930 else if (!isFunction(rule)) throw new Error("'rule' must be a function");
30936 function handleIfMatch($injector, handler, match) {
30937 if (!match) return false;
30938 var result = $injector.invoke(handler, handler, { $match: match });
30939 return isDefined(result) ? result : true;
30944 * @name ui.router.router.$urlRouterProvider#when
30945 * @methodOf ui.router.router.$urlRouterProvider
30948 * Registers a handler for a given url matching. if handle is a string, it is
30949 * treated as a redirect, and is interpolated according to the syntax of match
30950 * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
30952 * If the handler is a function, it is injectable. It gets invoked if `$location`
30953 * matches. You have the option of inject the match object as `$match`.
30955 * The handler can return
30957 * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
30958 * will continue trying to find another one that matches.
30959 * - **string** which is treated as a redirect and passed to `$location.url()`
30960 * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
30964 * var app = angular.module('app', ['ui.router.router']);
30966 * app.config(function ($urlRouterProvider) {
30967 * $urlRouterProvider.when($state.url, function ($match, $stateParams) {
30968 * if ($state.$current.navigable !== state ||
30969 * !equalForKeys($match, $stateParams) {
30970 * $state.transitionTo(state, $match, false);
30976 * @param {string|object} what The incoming path that you want to redirect.
30977 * @param {string|object} handler The path you want to redirect your user to.
30979 this.when = function (what, handler) {
30980 var redirect, handlerIsString = isString(handler);
30981 if (isString(what)) what = $urlMatcherFactory.compile(what);
30983 if (!handlerIsString && !isFunction(handler) && !isArray(handler))
30984 throw new Error("invalid 'handler' in when()");
30987 matcher: function (what, handler) {
30988 if (handlerIsString) {
30989 redirect = $urlMatcherFactory.compile(handler);
30990 handler = ['$match', function ($match) { return redirect.format($match); }];
30992 return extend(function ($injector, $location) {
30993 return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
30995 prefix: isString(what.prefix) ? what.prefix : ''
30998 regex: function (what, handler) {
30999 if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
31001 if (handlerIsString) {
31002 redirect = handler;
31003 handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
31005 return extend(function ($injector, $location) {
31006 return handleIfMatch($injector, handler, what.exec($location.path()));
31008 prefix: regExpPrefix(what)
31013 var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
31015 for (var n in check) {
31016 if (check[n]) return this.rule(strategies[n](what, handler));
31019 throw new Error("invalid 'what' in when()");
31024 * @name ui.router.router.$urlRouterProvider#deferIntercept
31025 * @methodOf ui.router.router.$urlRouterProvider
31028 * Disables (or enables) deferring location change interception.
31030 * If you wish to customize the behavior of syncing the URL (for example, if you wish to
31031 * defer a transition but maintain the current URL), call this method at configuration time.
31032 * Then, at run time, call `$urlRouter.listen()` after you have configured your own
31033 * `$locationChangeSuccess` event handler.
31037 * var app = angular.module('app', ['ui.router.router']);
31039 * app.config(function ($urlRouterProvider) {
31041 * // Prevent $urlRouter from automatically intercepting URL changes;
31042 * // this allows you to configure custom behavior in between
31043 * // location changes and route synchronization:
31044 * $urlRouterProvider.deferIntercept();
31046 * }).run(function ($rootScope, $urlRouter, UserService) {
31048 * $rootScope.$on('$locationChangeSuccess', function(e) {
31049 * // UserService is an example service for managing user state
31050 * if (UserService.isLoggedIn()) return;
31052 * // Prevent $urlRouter's default handler from firing
31053 * e.preventDefault();
31055 * UserService.handleLogin().then(function() {
31056 * // Once the user has logged in, sync the current URL
31057 * // to the router:
31058 * $urlRouter.sync();
31062 * // Configures $urlRouter's listener *after* your custom listener
31063 * $urlRouter.listen();
31067 * @param {boolean} defer Indicates whether to defer location change interception. Passing
31068 no parameter is equivalent to `true`.
31070 this.deferIntercept = function (defer) {
31071 if (defer === undefined) defer = true;
31072 interceptDeferred = defer;
31077 * @name ui.router.router.$urlRouter
31079 * @requires $location
31080 * @requires $rootScope
31081 * @requires $injector
31082 * @requires $browser
31088 $get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
31089 function $get( $location, $rootScope, $injector, $browser) {
31091 var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
31093 function appendBasePath(url, isHtml5, absolute) {
31094 if (baseHref === '/') return url;
31095 if (isHtml5) return baseHref.slice(0, -1) + url;
31096 if (absolute) return baseHref.slice(1) + url;
31100 // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
31101 function update(evt) {
31102 if (evt && evt.defaultPrevented) return;
31103 var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
31104 lastPushedUrl = undefined;
31105 // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573
31106 //if (ignoreUpdate) return true;
31108 function check(rule) {
31109 var handled = rule($injector, $location);
31111 if (!handled) return false;
31112 if (isString(handled)) $location.replace().url(handled);
31115 var n = rules.length, i;
31117 for (i = 0; i < n; i++) {
31118 if (check(rules[i])) return;
31120 // always check otherwise last to allow dynamic updates to the set of rules
31121 if (otherwise) check(otherwise);
31124 function listen() {
31125 listener = listener || $rootScope.$on('$locationChangeSuccess', update);
31129 if (!interceptDeferred) listen();
31134 * @name ui.router.router.$urlRouter#sync
31135 * @methodOf ui.router.router.$urlRouter
31138 * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
31139 * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
31140 * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
31141 * with the transition by calling `$urlRouter.sync()`.
31145 * angular.module('app', ['ui.router'])
31146 * .run(function($rootScope, $urlRouter) {
31147 * $rootScope.$on('$locationChangeSuccess', function(evt) {
31148 * // Halt state change from even starting
31149 * evt.preventDefault();
31150 * // Perform custom logic
31151 * var meetsRequirement = ...
31152 * // Continue with the update and state transition if logic allows
31153 * if (meetsRequirement) $urlRouter.sync();
31162 listen: function() {
31166 update: function(read) {
31168 location = $location.url();
31171 if ($location.url() === location) return;
31173 $location.url(location);
31174 $location.replace();
31177 push: function(urlMatcher, params, options) {
31178 var url = urlMatcher.format(params || {});
31180 // Handle the special hash param, if needed
31181 if (url !== null && params && params['#']) {
31182 url += '#' + params['#'];
31185 $location.url(url);
31186 lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
31187 if (options && options.replace) $location.replace();
31192 * @name ui.router.router.$urlRouter#href
31193 * @methodOf ui.router.router.$urlRouter
31196 * A URL generation method that returns the compiled URL for a given
31197 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
31201 * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
31204 * // $bob == "/about/bob";
31207 * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
31208 * @param {object=} params An object of parameter values to fill the matcher's required parameters.
31209 * @param {object=} options Options object. The options are:
31211 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
31213 * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
31215 href: function(urlMatcher, params, options) {
31216 if (!urlMatcher.validates(params)) return null;
31218 var isHtml5 = $locationProvider.html5Mode();
31219 if (angular.isObject(isHtml5)) {
31220 isHtml5 = isHtml5.enabled;
31223 var url = urlMatcher.format(params);
31224 options = options || {};
31226 if (!isHtml5 && url !== null) {
31227 url = "#" + $locationProvider.hashPrefix() + url;
31230 // Handle special hash param, if needed
31231 if (url !== null && params && params['#']) {
31232 url += '#' + params['#'];
31235 url = appendBasePath(url, isHtml5, options.absolute);
31237 if (!options.absolute || !url) {
31241 var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
31242 port = (port === 80 || port === 443 ? '' : ':' + port);
31244 return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
31250 angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
31254 * @name ui.router.state.$stateProvider
31256 * @requires ui.router.router.$urlRouterProvider
31257 * @requires ui.router.util.$urlMatcherFactoryProvider
31260 * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
31263 * A state corresponds to a "place" in the application in terms of the overall UI and
31264 * navigation. A state describes (via the controller / template / view properties) what
31265 * the UI looks like and does at that place.
31267 * States often have things in common, and the primary way of factoring out these
31268 * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
31271 * The `$stateProvider` provides interfaces to declare these states for your app.
31273 $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
31274 function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
31276 var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
31278 // Builds state properties from definition passed to registerState()
31279 var stateBuilder = {
31281 // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
31282 // state.children = [];
31283 // if (parent) parent.children.push(state);
31284 parent: function(state) {
31285 if (isDefined(state.parent) && state.parent) return findState(state.parent);
31286 // regex matches any valid composite state name
31287 // would match "contact.list" but not "contacts"
31288 var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
31289 return compositeName ? findState(compositeName[1]) : root;
31292 // inherit 'data' from parent and override by own values (if any)
31293 data: function(state) {
31294 if (state.parent && state.parent.data) {
31295 state.data = state.self.data = extend({}, state.parent.data, state.data);
31300 // Build a URLMatcher if necessary, either via a relative or absolute URL
31301 url: function(state) {
31302 var url = state.url, config = { params: state.params || {} };
31304 if (isString(url)) {
31305 if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
31306 return (state.parent.navigable || root).url.concat(url, config);
31309 if (!url || $urlMatcherFactory.isMatcher(url)) return url;
31310 throw new Error("Invalid url '" + url + "' in state '" + state + "'");
31313 // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
31314 navigable: function(state) {
31315 return state.url ? state : (state.parent ? state.parent.navigable : null);
31318 // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
31319 ownParams: function(state) {
31320 var params = state.url && state.url.params || new $$UMFP.ParamSet();
31321 forEach(state.params || {}, function(config, id) {
31322 if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
31327 // Derive parameters for this state and ensure they're a super-set of parent's parameters
31328 params: function(state) {
31329 return state.parent && state.parent.params ? extend(state.parent.params.$$new(), state.ownParams) : new $$UMFP.ParamSet();
31332 // If there is no explicit multi-view configuration, make one up so we don't have
31333 // to handle both cases in the view directive later. Note that having an explicit
31334 // 'views' property will mean the default unnamed view properties are ignored. This
31335 // is also a good time to resolve view names to absolute names, so everything is a
31336 // straight lookup at link time.
31337 views: function(state) {
31340 forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
31341 if (name.indexOf('@') < 0) name += '@' + state.parent.name;
31342 views[name] = view;
31347 // Keep a full path from the root down to this state as this is needed for state activation.
31348 path: function(state) {
31349 return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
31352 // Speed up $state.contains() as it's used a lot
31353 includes: function(state) {
31354 var includes = state.parent ? extend({}, state.parent.includes) : {};
31355 includes[state.name] = true;
31362 function isRelative(stateName) {
31363 return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
31366 function findState(stateOrName, base) {
31367 if (!stateOrName) return undefined;
31369 var isStr = isString(stateOrName),
31370 name = isStr ? stateOrName : stateOrName.name,
31371 path = isRelative(name);
31374 if (!base) throw new Error("No reference point given for path '" + name + "'");
31375 base = findState(base);
31377 var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
31379 for (; i < pathLength; i++) {
31380 if (rel[i] === "" && i === 0) {
31384 if (rel[i] === "^") {
31385 if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
31386 current = current.parent;
31391 rel = rel.slice(i).join(".");
31392 name = current.name + (current.name && rel ? "." : "") + rel;
31394 var state = states[name];
31396 if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
31402 function queueState(parentName, state) {
31403 if (!queue[parentName]) {
31404 queue[parentName] = [];
31406 queue[parentName].push(state);
31409 function flushQueuedChildren(parentName) {
31410 var queued = queue[parentName] || [];
31411 while(queued.length) {
31412 registerState(queued.shift());
31416 function registerState(state) {
31417 // Wrap a new object around the state so we can store our private details easily.
31418 state = inherit(state, {
31420 resolve: state.resolve || {},
31421 toString: function() { return this.name; }
31424 var name = state.name;
31425 if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
31426 if (states.hasOwnProperty(name)) throw new Error("State '" + name + "'' is already defined");
31429 var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
31430 : (isString(state.parent)) ? state.parent
31431 : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
31434 // If parent is not registered yet, add state to queue and register later
31435 if (parentName && !states[parentName]) {
31436 return queueState(parentName, state.self);
31439 for (var key in stateBuilder) {
31440 if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
31442 states[name] = state;
31444 // Register the state in the global state list and with $urlRouter if necessary.
31445 if (!state[abstractKey] && state.url) {
31446 $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
31447 if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
31448 $state.transitionTo(state, $match, { inherit: true, location: false });
31453 // Register any queued children
31454 flushQueuedChildren(name);
31459 // Checks text to see if it looks like a glob.
31460 function isGlob (text) {
31461 return text.indexOf('*') > -1;
31464 // Returns true if glob matches current $state name.
31465 function doesStateMatchGlob (glob) {
31466 var globSegments = glob.split('.'),
31467 segments = $state.$current.name.split('.');
31469 //match single stars
31470 for (var i = 0, l = globSegments.length; i < l; i++) {
31471 if (globSegments[i] === '*') {
31476 //match greedy starts
31477 if (globSegments[0] === '**') {
31478 segments = segments.slice(indexOf(segments, globSegments[1]));
31479 segments.unshift('**');
31481 //match greedy ends
31482 if (globSegments[globSegments.length - 1] === '**') {
31483 segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
31484 segments.push('**');
31487 if (globSegments.length != segments.length) {
31491 return segments.join('') === globSegments.join('');
31495 // Implicit root state that is always active
31496 root = registerState({
31502 root.navigable = null;
31507 * @name ui.router.state.$stateProvider#decorator
31508 * @methodOf ui.router.state.$stateProvider
31511 * Allows you to extend (carefully) or override (at your own peril) the
31512 * `stateBuilder` object used internally by `$stateProvider`. This can be used
31513 * to add custom functionality to ui-router, for example inferring templateUrl
31514 * based on the state name.
31516 * When passing only a name, it returns the current (original or decorated) builder
31517 * function that matches `name`.
31519 * The builder functions that can be decorated are listed below. Though not all
31520 * necessarily have a good use case for decoration, that is up to you to decide.
31522 * In addition, users can attach custom decorators, which will generate new
31523 * properties within the state's internal definition. There is currently no clear
31524 * use-case for this beyond accessing internal states (i.e. $state.$current),
31525 * however, expect this to become increasingly relevant as we introduce additional
31526 * meta-programming features.
31528 * **Warning**: Decorators should not be interdependent because the order of
31529 * execution of the builder functions in non-deterministic. Builder functions
31530 * should only be dependent on the state definition object and super function.
31533 * Existing builder functions and current return values:
31535 * - **parent** `{object}` - returns the parent state object.
31536 * - **data** `{object}` - returns state data, including any inherited data that is not
31537 * overridden by own values (if any).
31538 * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
31540 * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
31542 * - **params** `{object}` - returns an array of state params that are ensured to
31543 * be a super-set of parent's params.
31544 * - **views** `{object}` - returns a views object where each key is an absolute view
31545 * name (i.e. "viewName@stateName") and each value is the config object
31546 * (template, controller) for the view. Even when you don't use the views object
31547 * explicitly on a state config, one is still created for you internally.
31548 * So by decorating this builder function you have access to decorating template
31549 * and controller properties.
31550 * - **ownParams** `{object}` - returns an array of params that belong to the state,
31551 * not including any params defined by ancestor states.
31552 * - **path** `{string}` - returns the full path from the root down to this state.
31553 * Needed for state activation.
31554 * - **includes** `{object}` - returns an object that includes every state that
31555 * would pass a `$state.includes()` test.
31559 * // Override the internal 'views' builder with a function that takes the state
31560 * // definition, and a reference to the internal function being overridden:
31561 * $stateProvider.decorator('views', function (state, parent) {
31563 * views = parent(state);
31565 * angular.forEach(views, function (config, name) {
31566 * var autoName = (state.name + '.' + name).replace('.', '/');
31567 * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
31568 * result[name] = config;
31573 * $stateProvider.state('home', {
31575 * 'contact.list': { controller: 'ListController' },
31576 * 'contact.item': { controller: 'ItemController' }
31582 * $state.go('home');
31583 * // Auto-populates list and item views with /partials/home/contact/list.html,
31584 * // and /partials/home/contact/item.html, respectively.
31587 * @param {string} name The name of the builder function to decorate.
31588 * @param {object} func A function that is responsible for decorating the original
31589 * builder function. The function receives two parameters:
31591 * - `{object}` - state - The state config object.
31592 * - `{object}` - super - The original builder function.
31594 * @return {object} $stateProvider - $stateProvider instance
31596 this.decorator = decorator;
31597 function decorator(name, func) {
31598 /*jshint validthis: true */
31599 if (isString(name) && !isDefined(func)) {
31600 return stateBuilder[name];
31602 if (!isFunction(func) || !isString(name)) {
31605 if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
31606 stateBuilder.$delegates[name] = stateBuilder[name];
31608 stateBuilder[name] = func;
31614 * @name ui.router.state.$stateProvider#state
31615 * @methodOf ui.router.state.$stateProvider
31618 * Registers a state configuration under a given state name. The stateConfig object
31619 * has the following acceptable properties.
31621 * @param {string} name A unique state name, e.g. "home", "about", "contacts".
31622 * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
31623 * @param {object} stateConfig State configuration object.
31624 * @param {string|function=} stateConfig.template
31625 * <a id='template'></a>
31626 * html template as a string or a function that returns
31627 * an html template as a string which should be used by the uiView directives. This property
31628 * takes precedence over templateUrl.
31630 * If `template` is a function, it will be called with the following parameters:
31632 * - {array.<object>} - state parameters extracted from the current $location.path() by
31633 * applying the current state
31636 * "<h1>inline template definition</h1>" +
31637 * "<div ui-view></div>"</pre>
31638 * <pre>template: function(params) {
31639 * return "<h1>generated template</h1>"; }</pre>
31642 * @param {string|function=} stateConfig.templateUrl
31643 * <a id='templateUrl'></a>
31645 * path or function that returns a path to an html
31646 * template that should be used by uiView.
31648 * If `templateUrl` is a function, it will be called with the following parameters:
31650 * - {array.<object>} - state parameters extracted from the current $location.path() by
31651 * applying the current state
31653 * <pre>templateUrl: "home.html"</pre>
31654 * <pre>templateUrl: function(params) {
31655 * return myTemplates[params.pageId]; }</pre>
31657 * @param {function=} stateConfig.templateProvider
31658 * <a id='templateProvider'></a>
31659 * Provider function that returns HTML content string.
31660 * <pre> templateProvider:
31661 * function(MyTemplateService, params) {
31662 * return MyTemplateService.getTemplate(params.pageId);
31665 * @param {string|function=} stateConfig.controller
31666 * <a id='controller'></a>
31668 * Controller fn that should be associated with newly
31669 * related scope or the name of a registered controller if passed as a string.
31670 * Optionally, the ControllerAs may be declared here.
31671 * <pre>controller: "MyRegisteredController"</pre>
31673 * "MyRegisteredController as fooCtrl"}</pre>
31674 * <pre>controller: function($scope, MyService) {
31675 * $scope.data = MyService.getData(); }</pre>
31677 * @param {function=} stateConfig.controllerProvider
31678 * <a id='controllerProvider'></a>
31680 * Injectable provider function that returns the actual controller or string.
31681 * <pre>controllerProvider:
31682 * function(MyResolveData) {
31683 * if (MyResolveData.foo)
31685 * else if (MyResolveData.bar)
31686 * return "BarCtrl";
31687 * else return function($scope) {
31688 * $scope.baz = "Qux";
31692 * @param {string=} stateConfig.controllerAs
31693 * <a id='controllerAs'></a>
31695 * A controller alias name. If present the controller will be
31696 * published to scope under the controllerAs name.
31697 * <pre>controllerAs: "myCtrl"</pre>
31699 * @param {string|object=} stateConfig.parent
31700 * <a id='parent'></a>
31701 * Optionally specifies the parent state of this state.
31703 * <pre>parent: 'parentState'</pre>
31704 * <pre>parent: parentState // JS variable</pre>
31706 * @param {object=} stateConfig.resolve
31707 * <a id='resolve'></a>
31709 * An optional map<string, function> of dependencies which
31710 * should be injected into the controller. If any of these dependencies are promises,
31711 * the router will wait for them all to be resolved before the controller is instantiated.
31712 * If all the promises are resolved successfully, the $stateChangeSuccess event is fired
31713 * and the values of the resolved promises are injected into any controllers that reference them.
31714 * If any of the promises are rejected the $stateChangeError event is fired.
31716 * The map object is:
31718 * - key - {string}: name of dependency to be injected into controller
31719 * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
31720 * it is injected and return value it treated as dependency. If result is a promise, it is
31721 * resolved before its value is injected into controller.
31725 * function($http, $stateParams) {
31726 * return $http.get("/api/foos/"+stateParams.fooID);
31730 * @param {string=} stateConfig.url
31733 * A url fragment with optional parameters. When a state is navigated or
31734 * transitioned to, the `$stateParams` service will be populated with any
31735 * parameters that were passed.
31737 * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
31738 * more details on acceptable patterns )
31741 * <pre>url: "/home"
31742 * url: "/users/:userid"
31743 * url: "/books/{bookid:[a-zA-Z_-]}"
31744 * url: "/books/{categoryid:int}"
31745 * url: "/books/{publishername:string}/{categoryid:int}"
31746 * url: "/messages?before&after"
31747 * url: "/messages?{before:date}&{after:date}"
31748 * url: "/messages/:mailboxid?{before:date}&{after:date}"
31751 * @param {object=} stateConfig.views
31752 * <a id='views'></a>
31753 * an optional map<string, object> which defined multiple views, or targets views
31754 * manually/explicitly.
31758 * Targets three named `ui-view`s in the parent state's template
31761 * controller: "headerCtrl",
31762 * templateUrl: "header.html"
31764 * controller: "bodyCtrl",
31765 * templateUrl: "body.html"
31767 * controller: "footCtrl",
31768 * templateUrl: "footer.html"
31772 * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template.
31775 * controller: "msgHeaderCtrl",
31776 * templateUrl: "msgHeader.html"
31778 * controller: "messagesCtrl",
31779 * templateUrl: "messages.html"
31783 * @param {boolean=} [stateConfig.abstract=false]
31784 * <a id='abstract'></a>
31785 * An abstract state will never be directly activated,
31786 * but can provide inherited properties to its common children states.
31787 * <pre>abstract: true</pre>
31789 * @param {function=} stateConfig.onEnter
31790 * <a id='onEnter'></a>
31792 * Callback function for when a state is entered. Good way
31793 * to trigger an action or dispatch an event, such as opening a dialog.
31794 * If minifying your scripts, make sure to explictly annotate this function,
31795 * because it won't be automatically annotated by your build tools.
31797 * <pre>onEnter: function(MyService, $stateParams) {
31798 * MyService.foo($stateParams.myParam);
31801 * @param {function=} stateConfig.onExit
31802 * <a id='onExit'></a>
31804 * Callback function for when a state is exited. Good way to
31805 * trigger an action or dispatch an event, such as opening a dialog.
31806 * If minifying your scripts, make sure to explictly annotate this function,
31807 * because it won't be automatically annotated by your build tools.
31809 * <pre>onExit: function(MyService, $stateParams) {
31810 * MyService.cleanup($stateParams.myParam);
31813 * @param {boolean=} [stateConfig.reloadOnSearch=true]
31814 * <a id='reloadOnSearch'></a>
31816 * If `false`, will not retrigger the same state
31817 * just because a search/query parameter has changed (via $location.search() or $location.hash()).
31818 * Useful for when you'd like to modify $location.search() without triggering a reload.
31819 * <pre>reloadOnSearch: false</pre>
31821 * @param {object=} stateConfig.data
31822 * <a id='data'></a>
31824 * Arbitrary data object, useful for custom configuration. The parent state's `data` is
31825 * prototypally inherited. In other words, adding a data property to a state adds it to
31826 * the entire subtree via prototypal inheritance.
31829 * requiredRole: 'foo'
31832 * @param {object=} stateConfig.params
31833 * <a id='params'></a>
31835 * A map which optionally configures parameters declared in the `url`, or
31836 * defines additional non-url parameters. For each parameter being
31837 * configured, add a configuration object keyed to the name of the parameter.
31839 * Each parameter configuration object may contain the following properties:
31841 * - ** value ** - {object|function=}: specifies the default value for this
31842 * parameter. This implicitly sets this parameter as optional.
31844 * When UI-Router routes to a state and no value is
31845 * specified for this parameter in the URL or transition, the
31846 * default value will be used instead. If `value` is a function,
31847 * it will be injected and invoked, and the return value used.
31849 * *Note*: `undefined` is treated as "no default value" while `null`
31850 * is treated as "the default value is `null`".
31852 * *Shorthand*: If you only need to configure the default value of the
31853 * parameter, you may use a shorthand syntax. In the **`params`**
31854 * map, instead mapping the param name to a full parameter configuration
31855 * object, simply set map it to the default parameter value, e.g.:
31857 * <pre>// define a parameter's default value
31859 * param1: { value: "defaultValue" }
31861 * // shorthand default values
31863 * param1: "defaultValue",
31864 * param2: "param2Default"
31867 * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
31868 * treated as an array of values. If you specified a Type, the value will be
31869 * treated as an array of the specified Type. Note: query parameter values
31870 * default to a special `"auto"` mode.
31872 * For query parameters in `"auto"` mode, if multiple values for a single parameter
31873 * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
31874 * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if
31875 * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
31876 * value (e.g.: `{ foo: '1' }`).
31879 * param1: { array: true }
31882 * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
31883 * the current parameter value is the same as the default value. If `squash` is not set, it uses the
31884 * configured default squash policy.
31885 * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
31887 * There are three squash settings:
31889 * - false: The parameter's default value is not squashed. It is encoded and included in the URL
31890 * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed
31891 * by slashes in the state's `url` declaration, then one of those slashes are omitted.
31892 * This can allow for cleaner looking URLs.
31893 * - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of your choice.
31897 * value: "defaultId",
31900 * // squash "defaultValue" to "~"
31903 * value: "defaultValue",
31911 * // Some state name examples
31913 * // stateName can be a single top-level name (must be unique).
31914 * $stateProvider.state("home", {});
31916 * // Or it can be a nested state name. This state is a child of the
31917 * // above "home" state.
31918 * $stateProvider.state("home.newest", {});
31920 * // Nest states as deeply as needed.
31921 * $stateProvider.state("home.newest.abc.xyz.inception", {});
31923 * // state() returns $stateProvider, so you can chain state declarations.
31925 * .state("home", {})
31926 * .state("about", {})
31927 * .state("contacts", {});
31931 this.state = state;
31932 function state(name, definition) {
31933 /*jshint validthis: true */
31934 if (isObject(name)) definition = name;
31935 else definition.name = name;
31936 registerState(definition);
31942 * @name ui.router.state.$state
31944 * @requires $rootScope
31946 * @requires ui.router.state.$view
31947 * @requires $injector
31948 * @requires ui.router.util.$resolve
31949 * @requires ui.router.state.$stateParams
31950 * @requires ui.router.router.$urlRouter
31952 * @property {object} params A param object, e.g. {sectionId: section.id)}, that
31953 * you'd like to test against the current active state.
31954 * @property {object} current A reference to the state's config object. However
31955 * you passed it in. Useful for accessing custom data.
31956 * @property {object} transition Currently pending transition. A promise that'll
31957 * resolve or reject.
31960 * `$state` service is responsible for representing states as well as transitioning
31961 * between them. It also provides interfaces to ask for current state or even states
31962 * you're coming from.
31965 $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
31966 function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
31968 var TransitionSuperseded = $q.reject(new Error('transition superseded'));
31969 var TransitionPrevented = $q.reject(new Error('transition prevented'));
31970 var TransitionAborted = $q.reject(new Error('transition aborted'));
31971 var TransitionFailed = $q.reject(new Error('transition failed'));
31973 // Handles the case where a state which is the target of a transition is not found, and the user
31974 // can optionally retry or defer the transition
31975 function handleRedirect(redirect, state, params, options) {
31978 * @name ui.router.state.$state#$stateNotFound
31979 * @eventOf ui.router.state.$state
31980 * @eventType broadcast on root scope
31982 * Fired when a requested state **cannot be found** using the provided state name during transition.
31983 * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
31984 * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
31985 * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
31986 * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
31988 * @param {Object} event Event object.
31989 * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
31990 * @param {State} fromState Current state object.
31991 * @param {Object} fromParams Current state params.
31996 * // somewhere, assume lazy.state has not been defined
31997 * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
31999 * // somewhere else
32000 * $scope.$on('$stateNotFound',
32001 * function(event, unfoundState, fromState, fromParams){
32002 * console.log(unfoundState.to); // "lazy.state"
32003 * console.log(unfoundState.toParams); // {a:1, b:2}
32004 * console.log(unfoundState.options); // {inherit:false} + default options
32008 var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);
32010 if (evt.defaultPrevented) {
32011 $urlRouter.update();
32012 return TransitionAborted;
32019 // Allow the handler to return a promise to defer state lookup retry
32020 if (options.$retry) {
32021 $urlRouter.update();
32022 return TransitionFailed;
32024 var retryTransition = $state.transition = $q.when(evt.retry);
32026 retryTransition.then(function() {
32027 if (retryTransition !== $state.transition) return TransitionSuperseded;
32028 redirect.options.$retry = true;
32029 return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
32031 return TransitionAborted;
32033 $urlRouter.update();
32035 return retryTransition;
32038 root.locals = { resolve: null, globals: { $stateParams: {} } };
32042 current: root.self,
32049 * @name ui.router.state.$state#reload
32050 * @methodOf ui.router.state.$state
32053 * A method that force reloads the current state. All resolves are re-resolved,
32054 * controllers reinstantiated, and events re-fired.
32058 * var app angular.module('app', ['ui.router']);
32060 * app.controller('ctrl', function ($scope, $state) {
32061 * $scope.reload = function(){
32067 * `reload()` is just an alias for:
32069 * $state.transitionTo($state.current, $stateParams, {
32070 * reload: true, inherit: false, notify: true
32074 * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
32077 * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item'
32078 * //and current state is 'contacts.detail.item'
32079 * var app angular.module('app', ['ui.router']);
32081 * app.controller('ctrl', function ($scope, $state) {
32082 * $scope.reload = function(){
32083 * //will reload 'contact.detail' and 'contact.detail.item' states
32084 * $state.reload('contact.detail');
32089 * `reload()` is just an alias for:
32091 * $state.transitionTo($state.current, $stateParams, {
32092 * reload: true, inherit: false, notify: true
32096 * @returns {promise} A promise representing the state of the new transition. See
32097 * {@link ui.router.state.$state#methods_go $state.go}.
32099 $state.reload = function reload(state) {
32100 return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true});
32105 * @name ui.router.state.$state#go
32106 * @methodOf ui.router.state.$state
32109 * Convenience method for transitioning to a new state. `$state.go` calls
32110 * `$state.transitionTo` internally but automatically sets options to
32111 * `{ location: true, inherit: true, relative: $state.$current, notify: true }`.
32112 * This allows you to easily use an absolute or relative to path and specify
32113 * only the parameters you'd like to update (while letting unspecified parameters
32114 * inherit from the currently active ancestor states).
32118 * var app = angular.module('app', ['ui.router']);
32120 * app.controller('ctrl', function ($scope, $state) {
32121 * $scope.changeState = function () {
32122 * $state.go('contact.detail');
32126 * <img src='../ngdoc_assets/StateGoExamples.png'/>
32128 * @param {string} to Absolute state name or relative state path. Some examples:
32130 * - `$state.go('contact.detail')` - will go to the `contact.detail` state
32131 * - `$state.go('^')` - will go to a parent state
32132 * - `$state.go('^.sibling')` - will go to a sibling state
32133 * - `$state.go('.child.grandchild')` - will go to grandchild state
32135 * @param {object=} params A map of the parameters that will be sent to the state,
32136 * will populate $stateParams. Any parameters that are not specified will be inherited from currently
32137 * defined parameters. This allows, for example, going to a sibling state that shares parameters
32138 * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
32139 * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
32140 * will get you all current parameters, etc.
32141 * @param {object=} options Options object. The options are:
32143 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32144 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32145 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32146 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32147 * defines which state to be relative from.
32148 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32149 * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
32150 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
32151 * use this when you want to force a reload when *everything* is the same, including search params.
32153 * @returns {promise} A promise representing the state of the new transition.
32155 * Possible success values:
32159 * <br/>Possible rejection values:
32161 * - 'transition superseded' - when a newer transition has been started after this one
32162 * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
32163 * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
32164 * when a `$stateNotFound` `event.retry` promise errors.
32165 * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
32166 * - *resolve error* - when an error has occurred with a `resolve`
32169 $state.go = function go(to, params, options) {
32170 return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
32175 * @name ui.router.state.$state#transitionTo
32176 * @methodOf ui.router.state.$state
32179 * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
32180 * uses `transitionTo` internally. `$state.go` is recommended in most situations.
32184 * var app = angular.module('app', ['ui.router']);
32186 * app.controller('ctrl', function ($scope, $state) {
32187 * $scope.changeState = function () {
32188 * $state.transitionTo('contact.detail');
32193 * @param {string} to State name.
32194 * @param {object=} toParams A map of the parameters that will be sent to the state,
32195 * will populate $stateParams.
32196 * @param {object=} options Options object. The options are:
32198 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32199 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32200 * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
32201 * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
32202 * defines which state to be relative from.
32203 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32204 * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params
32205 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
32206 * use this when you want to force a reload when *everything* is the same, including search params.
32207 * if String, then will reload the state with the name given in reload, and any children.
32208 * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children.
32210 * @returns {promise} A promise representing the state of the new transition. See
32211 * {@link ui.router.state.$state#methods_go $state.go}.
32213 $state.transitionTo = function transitionTo(to, toParams, options) {
32214 toParams = toParams || {};
32216 location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
32219 var from = $state.$current, fromParams = $state.params, fromPath = from.path;
32220 var evt, toState = findState(to, options.relative);
32222 // Store the hash param for later (since it will be stripped out by various methods)
32223 var hash = toParams['#'];
32225 if (!isDefined(toState)) {
32226 var redirect = { to: to, toParams: toParams, options: options };
32227 var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
32229 if (redirectResult) {
32230 return redirectResult;
32233 // Always retry once if the $stateNotFound was not prevented
32234 // (handles either redirect changed or state lazy-definition)
32236 toParams = redirect.toParams;
32237 options = redirect.options;
32238 toState = findState(to, options.relative);
32240 if (!isDefined(toState)) {
32241 if (!options.relative) throw new Error("No such state '" + to + "'");
32242 throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
32245 if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
32246 if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
32247 if (!toState.params.$$validates(toParams)) return TransitionFailed;
32249 toParams = toState.params.$$values(toParams);
32252 var toPath = to.path;
32254 // Starting from the root of the path, keep all levels that haven't changed
32255 var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
32257 if (!options.reload) {
32258 while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
32259 locals = toLocals[keep] = state.locals;
32261 state = toPath[keep];
32263 } else if (isString(options.reload) || isObject(options.reload)) {
32264 if (isObject(options.reload) && !options.reload.name) {
32265 throw new Error('Invalid reload state object');
32268 var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
32269 if (options.reload && !reloadState) {
32270 throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
32273 while (state && state === fromPath[keep] && state !== reloadState) {
32274 locals = toLocals[keep] = state.locals;
32276 state = toPath[keep];
32280 // If we're going to the same state and all locals are kept, we've got nothing to do.
32281 // But clear 'transition', as we still want to cancel any other pending transitions.
32282 // TODO: We may not want to bump 'transition' if we're called from a location change
32283 // that we've initiated ourselves, because we might accidentally abort a legitimate
32284 // transition initiated from code?
32285 if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) {
32286 if (hash) toParams['#'] = hash;
32287 $state.params = toParams;
32288 copy($state.params, $stateParams);
32289 if (options.location && to.navigable && to.navigable.url) {
32290 $urlRouter.push(to.navigable.url, toParams, {
32291 $$avoidResync: true, replace: options.location === 'replace'
32293 $urlRouter.update(true);
32295 $state.transition = null;
32296 return $q.when($state.current);
32299 // Filter parameters before we pass them to event handlers etc.
32300 toParams = filterByKeys(to.params.$$keys(), toParams || {});
32302 // Broadcast start event and cancel the transition if requested
32303 if (options.notify) {
32306 * @name ui.router.state.$state#$stateChangeStart
32307 * @eventOf ui.router.state.$state
32308 * @eventType broadcast on root scope
32310 * Fired when the state transition **begins**. You can use `event.preventDefault()`
32311 * to prevent the transition from happening and then the transition promise will be
32312 * rejected with a `'transition prevented'` value.
32314 * @param {Object} event Event object.
32315 * @param {State} toState The state being transitioned to.
32316 * @param {Object} toParams The params supplied to the `toState`.
32317 * @param {State} fromState The current state, pre-transition.
32318 * @param {Object} fromParams The params supplied to the `fromState`.
32323 * $rootScope.$on('$stateChangeStart',
32324 * function(event, toState, toParams, fromState, fromParams){
32325 * event.preventDefault();
32326 * // transitionTo() promise will be rejected with
32327 * // a 'transition prevented' error
32331 if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) {
32332 $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
32333 $urlRouter.update();
32334 return TransitionPrevented;
32338 // Resolve locals for the remaining states, but don't update any global state just
32339 // yet -- if anything fails to resolve the current state needs to remain untouched.
32340 // We also set up an inheritance chain for the locals here. This allows the view directive
32341 // to quickly look up the correct definition for each view in the current state. Even
32342 // though we create the locals object itself outside resolveState(), it is initially
32343 // empty and gets filled asynchronously. We need to keep track of the promise for the
32344 // (fully resolved) current locals, and pass this down the chain.
32345 var resolved = $q.when(locals);
32347 for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
32348 locals = toLocals[l] = inherit(locals);
32349 resolved = resolveState(state, toParams, state === to, resolved, locals, options);
32352 // Once everything is resolved, we are ready to perform the actual transition
32353 // and return a promise for the new state. We also keep track of what the
32354 // current promise is, so that we can detect overlapping transitions and
32355 // keep only the outcome of the last transition.
32356 var transition = $state.transition = resolved.then(function () {
32357 var l, entering, exiting;
32359 if ($state.transition !== transition) return TransitionSuperseded;
32361 // Exit 'from' states not kept
32362 for (l = fromPath.length - 1; l >= keep; l--) {
32363 exiting = fromPath[l];
32364 if (exiting.self.onExit) {
32365 $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
32367 exiting.locals = null;
32370 // Enter 'to' states not kept
32371 for (l = keep; l < toPath.length; l++) {
32372 entering = toPath[l];
32373 entering.locals = toLocals[l];
32374 if (entering.self.onEnter) {
32375 $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
32379 // Re-add the saved hash before we start returning things
32380 if (hash) toParams['#'] = hash;
32382 // Run it again, to catch any transitions in callbacks
32383 if ($state.transition !== transition) return TransitionSuperseded;
32385 // Update globals in $state
32386 $state.$current = to;
32387 $state.current = to.self;
32388 $state.params = toParams;
32389 copy($state.params, $stateParams);
32390 $state.transition = null;
32392 if (options.location && to.navigable) {
32393 $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
32394 $$avoidResync: true, replace: options.location === 'replace'
32398 if (options.notify) {
32401 * @name ui.router.state.$state#$stateChangeSuccess
32402 * @eventOf ui.router.state.$state
32403 * @eventType broadcast on root scope
32405 * Fired once the state transition is **complete**.
32407 * @param {Object} event Event object.
32408 * @param {State} toState The state being transitioned to.
32409 * @param {Object} toParams The params supplied to the `toState`.
32410 * @param {State} fromState The current state, pre-transition.
32411 * @param {Object} fromParams The params supplied to the `fromState`.
32413 $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
32415 $urlRouter.update(true);
32417 return $state.current;
32418 }, function (error) {
32419 if ($state.transition !== transition) return TransitionSuperseded;
32421 $state.transition = null;
32424 * @name ui.router.state.$state#$stateChangeError
32425 * @eventOf ui.router.state.$state
32426 * @eventType broadcast on root scope
32428 * Fired when an **error occurs** during transition. It's important to note that if you
32429 * have any errors in your resolve functions (javascript errors, non-existent services, etc)
32430 * they will not throw traditionally. You must listen for this $stateChangeError event to
32431 * catch **ALL** errors.
32433 * @param {Object} event Event object.
32434 * @param {State} toState The state being transitioned to.
32435 * @param {Object} toParams The params supplied to the `toState`.
32436 * @param {State} fromState The current state, pre-transition.
32437 * @param {Object} fromParams The params supplied to the `fromState`.
32438 * @param {Error} error The resolve error object.
32440 evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
32442 if (!evt.defaultPrevented) {
32443 $urlRouter.update();
32446 return $q.reject(error);
32454 * @name ui.router.state.$state#is
32455 * @methodOf ui.router.state.$state
32458 * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
32459 * but only checks for the full state name. If params is supplied then it will be
32460 * tested for strict equality against the current active params object, so all params
32461 * must match with none missing and no extras.
32465 * $state.$current.name = 'contacts.details.item';
32468 * $state.is('contact.details.item'); // returns true
32469 * $state.is(contactDetailItemStateObject); // returns true
32471 * // relative name (. and ^), typically from a template
32472 * // E.g. from the 'contacts.details' template
32473 * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
32476 * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
32477 * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
32478 * to test against the current active state.
32479 * @param {object=} options An options object. The options are:
32481 * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will
32482 * test relative to `options.relative` state (or name).
32484 * @returns {boolean} Returns true if it is the state.
32486 $state.is = function is(stateOrName, params, options) {
32487 options = extend({ relative: $state.$current }, options || {});
32488 var state = findState(stateOrName, options.relative);
32490 if (!isDefined(state)) { return undefined; }
32491 if ($state.$current !== state) { return false; }
32492 return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
32497 * @name ui.router.state.$state#includes
32498 * @methodOf ui.router.state.$state
32501 * A method to determine if the current active state is equal to or is the child of the
32502 * state stateName. If any params are passed then they will be tested for a match as well.
32503 * Not all the parameters need to be passed, just the ones you'd like to test for equality.
32506 * Partial and relative names
32508 * $state.$current.name = 'contacts.details.item';
32510 * // Using partial names
32511 * $state.includes("contacts"); // returns true
32512 * $state.includes("contacts.details"); // returns true
32513 * $state.includes("contacts.details.item"); // returns true
32514 * $state.includes("contacts.list"); // returns false
32515 * $state.includes("about"); // returns false
32517 * // Using relative names (. and ^), typically from a template
32518 * // E.g. from the 'contacts.details' template
32519 * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
32522 * Basic globbing patterns
32524 * $state.$current.name = 'contacts.details.item.url';
32526 * $state.includes("*.details.*.*"); // returns true
32527 * $state.includes("*.details.**"); // returns true
32528 * $state.includes("**.item.**"); // returns true
32529 * $state.includes("*.details.item.url"); // returns true
32530 * $state.includes("*.details.*.url"); // returns true
32531 * $state.includes("*.details.*"); // returns false
32532 * $state.includes("item.**"); // returns false
32535 * @param {string} stateOrName A partial name, relative name, or glob pattern
32536 * to be searched for within the current state name.
32537 * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
32538 * that you'd like to test against the current active state.
32539 * @param {object=} options An options object. The options are:
32541 * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set,
32542 * .includes will test relative to `options.relative` state (or name).
32544 * @returns {boolean} Returns true if it does include the state
32546 $state.includes = function includes(stateOrName, params, options) {
32547 options = extend({ relative: $state.$current }, options || {});
32548 if (isString(stateOrName) && isGlob(stateOrName)) {
32549 if (!doesStateMatchGlob(stateOrName)) {
32552 stateOrName = $state.$current.name;
32555 var state = findState(stateOrName, options.relative);
32556 if (!isDefined(state)) { return undefined; }
32557 if (!isDefined($state.$current.includes[state.name])) { return false; }
32558 return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
32564 * @name ui.router.state.$state#href
32565 * @methodOf ui.router.state.$state
32568 * A url generation method that returns the compiled url for the given state populated with the given params.
32572 * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
32575 * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
32576 * @param {object=} params An object of parameter values to fill the state's required parameters.
32577 * @param {object=} options Options object. The options are:
32579 * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
32580 * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
32581 * ancestor with a valid url).
32582 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32583 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32584 * defines which state to be relative from.
32585 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
32587 * @returns {string} compiled state url
32589 $state.href = function href(stateOrName, params, options) {
32594 relative: $state.$current
32597 var state = findState(stateOrName, options.relative);
32599 if (!isDefined(state)) return null;
32600 if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
32602 var nav = (state && options.lossy) ? state.navigable : state;
32604 if (!nav || nav.url === undefined || nav.url === null) {
32607 return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), {
32608 absolute: options.absolute
32614 * @name ui.router.state.$state#get
32615 * @methodOf ui.router.state.$state
32618 * Returns the state configuration object for any specific state or all states.
32620 * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
32621 * the requested state. If not provided, returns an array of ALL state configs.
32622 * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
32623 * @returns {Object|Array} State configuration object or array of all objects.
32625 $state.get = function (stateOrName, context) {
32626 if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; });
32627 var state = findState(stateOrName, context || $state.$current);
32628 return (state && state.self) ? state.self : null;
32631 function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
32632 // Make a restricted $stateParams with only the parameters that apply to this state if
32633 // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
32634 // we also need $stateParams to be available for any $injector calls we make during the
32635 // dependency resolution process.
32636 var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
32637 var locals = { $stateParams: $stateParams };
32639 // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
32640 // We're also including $stateParams in this; that way the parameters are restricted
32641 // to the set that should be visible to the state, and are independent of when we update
32642 // the global $state and $stateParams values.
32643 dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
32644 var promises = [dst.resolve.then(function (globals) {
32645 dst.globals = globals;
32647 if (inherited) promises.push(inherited);
32649 function resolveViews() {
32650 var viewsPromises = [];
32652 // Resolve template and dependencies for all views.
32653 forEach(state.views, function (view, name) {
32654 var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
32655 injectables.$template = [ function () {
32656 return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || '';
32659 viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) {
32660 // References to the controller (only instantiated at link time)
32661 if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
32662 var injectLocals = angular.extend({}, injectables, dst.globals);
32663 result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
32665 result.$$controller = view.controller;
32667 // Provide access to the state itself for internal use
32668 result.$$state = state;
32669 result.$$controllerAs = view.controllerAs;
32670 dst[name] = result;
32674 return $q.all(viewsPromises).then(function(){
32675 return dst.globals;
32679 // Wait for all the promises and then return the activation object
32680 return $q.all(promises).then(resolveViews).then(function (values) {
32688 function shouldSkipReload(to, toParams, from, fromParams, locals, options) {
32689 // Return true if there are no differences in non-search (path/object) params, false if there are differences
32690 function nonSearchParamsEqual(fromAndToState, fromParams, toParams) {
32691 // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params.
32692 function notSearchParam(key) {
32693 return fromAndToState.params[key].location != "search";
32695 var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam);
32696 var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys));
32697 var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams);
32698 return nonQueryParamSet.$$equals(fromParams, toParams);
32701 // If reload was not explicitly requested
32702 // and we're transitioning to the same state we're already in
32703 // and the locals didn't change
32704 // or they changed in a way that doesn't merit reloading
32705 // (reloadOnParams:false, or reloadOnSearch.false and only search params changed)
32706 // Then return true.
32707 if (!options.reload && to === from &&
32708 (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) {
32714 angular.module('ui.router.state')
32715 .value('$stateParams', {})
32716 .provider('$state', $StateProvider);
32719 $ViewProvider.$inject = [];
32720 function $ViewProvider() {
32725 * @name ui.router.state.$view
32727 * @requires ui.router.util.$templateFactory
32728 * @requires $rootScope
32733 $get.$inject = ['$rootScope', '$templateFactory'];
32734 function $get( $rootScope, $templateFactory) {
32736 // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... })
32739 * @name ui.router.state.$view#load
32740 * @methodOf ui.router.state.$view
32744 * @param {string} name name
32745 * @param {object} options option object.
32747 load: function load(name, options) {
32748 var result, defaults = {
32749 template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {}
32751 options = extend(defaults, options);
32753 if (options.view) {
32754 result = $templateFactory.fromConfig(options.view, options.params, options.locals);
32756 if (result && options.notify) {
32759 * @name ui.router.state.$state#$viewContentLoading
32760 * @eventOf ui.router.state.$view
32761 * @eventType broadcast on root scope
32764 * Fired once the view **begins loading**, *before* the DOM is rendered.
32766 * @param {Object} event Event object.
32767 * @param {Object} viewConfig The view config properties (template, controller, etc).
32772 * $scope.$on('$viewContentLoading',
32773 * function(event, viewConfig){
32774 * // Access to all the view config properties.
32775 * // and one special property 'targetView'
32776 * // viewConfig.targetView
32780 $rootScope.$broadcast('$viewContentLoading', options);
32788 angular.module('ui.router.state').provider('$view', $ViewProvider);
32792 * @name ui.router.state.$uiViewScrollProvider
32795 * Provider that returns the {@link ui.router.state.$uiViewScroll} service function.
32797 function $ViewScrollProvider() {
32799 var useAnchorScroll = false;
32803 * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll
32804 * @methodOf ui.router.state.$uiViewScrollProvider
32807 * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for
32808 * scrolling based on the url anchor.
32810 this.useAnchorScroll = function () {
32811 useAnchorScroll = true;
32816 * @name ui.router.state.$uiViewScroll
32818 * @requires $anchorScroll
32819 * @requires $timeout
32822 * When called with a jqLite element, it scrolls the element into view (after a
32823 * `$timeout` so the DOM has time to refresh).
32825 * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor,
32826 * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}.
32828 this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) {
32829 if (useAnchorScroll) {
32830 return $anchorScroll;
32833 return function ($element) {
32834 return $timeout(function () {
32835 $element[0].scrollIntoView();
32841 angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);
32845 * @name ui.router.state.directive:ui-view
32847 * @requires ui.router.state.$state
32848 * @requires $compile
32849 * @requires $controller
32850 * @requires $injector
32851 * @requires ui.router.state.$uiViewScroll
32852 * @requires $document
32857 * The ui-view directive tells $state where to place your templates.
32859 * @param {string=} name A view name. The name should be unique amongst the other views in the
32860 * same state. You can have views of the same name that live in different states.
32862 * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
32863 * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
32864 * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
32865 * scroll ui-view elements into view when they are populated during a state activation.
32867 * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
32868 * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
32870 * @param {string=} onload Expression to evaluate whenever the view updates.
32873 * A view can be unnamed or named.
32876 * <div ui-view></div>
32879 * <div ui-view="viewName"></div>
32882 * You can only have one unnamed view within any template (or root html). If you are only using a
32883 * single view and it is unnamed then you can populate it like so:
32885 * <div ui-view></div>
32886 * $stateProvider.state("home", {
32887 * template: "<h1>HELLO!</h1>"
32891 * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
32892 * config property, by name, in this case an empty name:
32894 * $stateProvider.state("home", {
32897 * template: "<h1>HELLO!</h1>"
32903 * But typically you'll only use the views property if you name your view or have more than one view
32904 * in the same template. There's not really a compelling reason to name a view if its the only one,
32905 * but you could if you wanted, like so:
32907 * <div ui-view="main"></div>
32910 * $stateProvider.state("home", {
32913 * template: "<h1>HELLO!</h1>"
32919 * Really though, you'll use views to set up multiple views:
32921 * <div ui-view></div>
32922 * <div ui-view="chart"></div>
32923 * <div ui-view="data"></div>
32927 * $stateProvider.state("home", {
32930 * template: "<h1>HELLO!</h1>"
32933 * template: "<chart_thing/>"
32936 * template: "<data_thing/>"
32942 * Examples for `autoscroll`:
32945 * <!-- If autoscroll present with no expression,
32946 * then scroll ui-view into view -->
32947 * <ui-view autoscroll/>
32949 * <!-- If autoscroll present with valid expression,
32950 * then scroll ui-view into view if expression evaluates to true -->
32951 * <ui-view autoscroll='true'/>
32952 * <ui-view autoscroll='false'/>
32953 * <ui-view autoscroll='scopeVariable'/>
32956 $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate'];
32957 function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) {
32959 function getService() {
32960 return ($injector.has) ? function(service) {
32961 return $injector.has(service) ? $injector.get(service) : null;
32962 } : function(service) {
32964 return $injector.get(service);
32971 var service = getService(),
32972 $animator = service('$animator'),
32973 $animate = service('$animate');
32975 // Returns a set of DOM manipulation functions based on which Angular version
32977 function getRenderer(attrs, scope) {
32978 var statics = function() {
32980 enter: function (element, target, cb) { target.after(element); cb(); },
32981 leave: function (element, cb) { element.remove(); cb(); }
32987 enter: function(element, target, cb) {
32988 var promise = $animate.enter(element, null, target, cb);
32989 if (promise && promise.then) promise.then(cb);
32991 leave: function(element, cb) {
32992 var promise = $animate.leave(element, cb);
32993 if (promise && promise.then) promise.then(cb);
32999 var animate = $animator && $animator(scope, attrs);
33002 enter: function(element, target, cb) {animate.enter(element, null, target); cb(); },
33003 leave: function(element, cb) { animate.leave(element); cb(); }
33014 transclude: 'element',
33015 compile: function (tElement, tAttrs, $transclude) {
33016 return function (scope, $element, attrs) {
33017 var previousEl, currentEl, currentScope, latestLocals,
33018 onloadExp = attrs.onload || '',
33019 autoScrollExp = attrs.autoscroll,
33020 renderer = getRenderer(attrs, scope);
33022 scope.$on('$stateChangeSuccess', function() {
33025 scope.$on('$viewContentLoading', function() {
33031 function cleanupLastView() {
33033 previousEl.remove();
33037 if (currentScope) {
33038 currentScope.$destroy();
33039 currentScope = null;
33043 renderer.leave(currentEl, function() {
33047 previousEl = currentEl;
33052 function updateView(firstTime) {
33054 name = getUiViewName(scope, attrs, $element, $interpolate),
33055 previousLocals = name && $state.$current && $state.$current.locals[name];
33057 if (!firstTime && previousLocals === latestLocals) return; // nothing to do
33058 newScope = scope.$new();
33059 latestLocals = $state.$current.locals[name];
33061 var clone = $transclude(newScope, function(clone) {
33062 renderer.enter(clone, $element, function onUiViewEnter() {
33064 currentScope.$emit('$viewContentAnimationEnded');
33067 if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
33068 $uiViewScroll(clone);
33075 currentScope = newScope;
33078 * @name ui.router.state.directive:ui-view#$viewContentLoaded
33079 * @eventOf ui.router.state.directive:ui-view
33080 * @eventType emits on ui-view directive scope
33082 * Fired once the view is **loaded**, *after* the DOM is rendered.
33084 * @param {Object} event Event object.
33086 currentScope.$emit('$viewContentLoaded');
33087 currentScope.$eval(onloadExp);
33096 $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
33097 function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) {
33101 compile: function (tElement) {
33102 var initial = tElement.html();
33103 return function (scope, $element, attrs) {
33104 var current = $state.$current,
33105 name = getUiViewName(scope, attrs, $element, $interpolate),
33106 locals = current && current.locals[name];
33112 $element.data('$uiView', { name: name, state: locals.$$state });
33113 $element.html(locals.$template ? locals.$template : initial);
33115 var link = $compile($element.contents());
33117 if (locals.$$controller) {
33118 locals.$scope = scope;
33119 locals.$element = $element;
33120 var controller = $controller(locals.$$controller, locals);
33121 if (locals.$$controllerAs) {
33122 scope[locals.$$controllerAs] = controller;
33124 $element.data('$ngControllerController', controller);
33125 $element.children().data('$ngControllerController', controller);
33135 * Shared ui-view code for both directives:
33136 * Given scope, element, and its attributes, return the view's name
33138 function getUiViewName(scope, attrs, element, $interpolate) {
33139 var name = $interpolate(attrs.uiView || attrs.name || '')(scope);
33140 var inherited = element.inheritedData('$uiView');
33141 return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : ''));
33144 angular.module('ui.router.state').directive('uiView', $ViewDirective);
33145 angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);
33147 function parseStateRef(ref, current) {
33148 var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
33149 if (preparsed) ref = current + '(' + preparsed[1] + ')';
33150 parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
33151 if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
33152 return { state: parsed[1], paramExpr: parsed[3] || null };
33155 function stateContext(el) {
33156 var stateData = el.parent().inheritedData('$uiView');
33158 if (stateData && stateData.state && stateData.state.name) {
33159 return stateData.state;
33165 * @name ui.router.state.directive:ui-sref
33167 * @requires ui.router.state.$state
33168 * @requires $timeout
33173 * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
33174 * URL, the directive will automatically generate & update the `href` attribute via
33175 * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
33176 * the link will trigger a state transition with optional parameters.
33178 * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
33179 * handled natively by the browser.
33181 * You can also use relative state paths within ui-sref, just like the relative
33182 * paths passed to `$state.go()`. You just need to be aware that the path is relative
33183 * to the state that the link lives in, in other words the state that loaded the
33184 * template containing the link.
33186 * You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
33187 * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
33191 * Here's an example of how you'd use ui-sref and how it would compile. If you have the
33192 * following template:
33194 * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
33197 * <li ng-repeat="contact in contacts">
33198 * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
33203 * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
33205 * <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>
33208 * <li ng-repeat="contact in contacts">
33209 * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
33211 * <li ng-repeat="contact in contacts">
33212 * <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a>
33214 * <li ng-repeat="contact in contacts">
33215 * <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a>
33219 * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
33222 * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
33223 * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()}
33225 $StateRefDirective.$inject = ['$state', '$timeout'];
33226 function $StateRefDirective($state, $timeout) {
33227 var allowedOptions = ['location', 'inherit', 'reload', 'absolute'];
33231 require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
33232 link: function(scope, element, attrs, uiSrefActive) {
33233 var ref = parseStateRef(attrs.uiSref, $state.current.name);
33234 var params = null, url = null, base = stateContext(element) || $state.$current;
33235 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
33236 var hrefKind = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
33237 'xlink:href' : 'href';
33238 var newHref = null, isAnchor = element.prop("tagName").toUpperCase() === "A";
33239 var isForm = element[0].nodeName === "FORM";
33240 var attr = isForm ? "action" : hrefKind, nav = true;
33242 var options = { relative: base, inherit: true };
33243 var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {};
33245 angular.forEach(allowedOptions, function(option) {
33246 if (option in optionsOverride) {
33247 options[option] = optionsOverride[option];
33251 var update = function(newVal) {
33252 if (newVal) params = angular.copy(newVal);
33255 newHref = $state.href(ref.state, params, options);
33257 var activeDirective = uiSrefActive[1] || uiSrefActive[0];
33258 if (activeDirective) {
33259 activeDirective.$$addStateInfo(ref.state, params);
33261 if (newHref === null) {
33265 attrs.$set(attr, newHref);
33268 if (ref.paramExpr) {
33269 scope.$watch(ref.paramExpr, function(newVal, oldVal) {
33270 if (newVal !== params) update(newVal);
33272 params = angular.copy(scope.$eval(ref.paramExpr));
33276 if (isForm) return;
33278 element.bind("click", function(e) {
33279 var button = e.which || e.button;
33280 if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
33281 // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
33282 var transition = $timeout(function() {
33283 $state.go(ref.state, params, options);
33285 e.preventDefault();
33287 // if the state has no URL, ignore one preventDefault from the <a> directive.
33288 var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0;
33289 e.preventDefault = function() {
33290 if (ignorePreventDefaultCount-- <= 0)
33291 $timeout.cancel(transition);
33301 * @name ui.router.state.directive:ui-sref-active
33303 * @requires ui.router.state.$state
33304 * @requires ui.router.state.$stateParams
33305 * @requires $interpolate
33310 * A directive working alongside ui-sref to add classes to an element when the
33311 * related ui-sref directive's state is active, and removing them when it is inactive.
33312 * The primary use-case is to simplify the special appearance of navigation menus
33313 * relying on `ui-sref`, by having the "active" state's menu button appear different,
33314 * distinguishing it from the inactive menu items.
33316 * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
33317 * ui-sref-active found at the same level or above the ui-sref will be used.
33319 * Will activate when the ui-sref's target state or any child state is active. If you
33320 * need to activate only when the ui-sref target state is active and *not* any of
33321 * it's children, then you will use
33322 * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
33325 * Given the following template:
33328 * <li ui-sref-active="active" class="item">
33329 * <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
33335 * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
33336 * the resulting HTML will appear as (note the 'active' class):
33339 * <li ui-sref-active="active" class="item active">
33340 * <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a>
33345 * The class name is interpolated **once** during the directives link time (any further changes to the
33346 * interpolated value are ignored).
33348 * Multiple classes may be specified in a space-separated format:
33351 * <li ui-sref-active='class1 class2 class3'>
33352 * <a ui-sref="app.user">link</a>
33360 * @name ui.router.state.directive:ui-sref-active-eq
33362 * @requires ui.router.state.$state
33363 * @requires ui.router.state.$stateParams
33364 * @requires $interpolate
33369 * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
33370 * when the exact target state used in the `ui-sref` is active; no child states.
33373 $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
33374 function $StateRefActiveDirective($state, $stateParams, $interpolate) {
33377 controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
33378 var states = [], activeClass;
33380 // There probably isn't much point in $observing this
33381 // uiSrefActive and uiSrefActiveEq share the same directive object with some
33382 // slight difference in logic routing
33383 activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope);
33385 // Allow uiSref to communicate with uiSrefActive[Equals]
33386 this.$$addStateInfo = function (newState, newParams) {
33387 var state = $state.get(newState, stateContext($element));
33390 state: state || { name: newState },
33397 $scope.$on('$stateChangeSuccess', update);
33399 // Update route state
33400 function update() {
33402 $element.addClass(activeClass);
33404 $element.removeClass(activeClass);
33408 function anyMatch() {
33409 for (var i = 0; i < states.length; i++) {
33410 if (isMatch(states[i].state, states[i].params)) {
33417 function isMatch(state, params) {
33418 if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
33419 return $state.is(state.name, params);
33421 return $state.includes(state.name, params);
33428 angular.module('ui.router.state')
33429 .directive('uiSref', $StateRefDirective)
33430 .directive('uiSrefActive', $StateRefActiveDirective)
33431 .directive('uiSrefActiveEq', $StateRefActiveDirective);
33435 * @name ui.router.state.filter:isState
33437 * @requires ui.router.state.$state
33440 * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}.
33442 $IsStateFilter.$inject = ['$state'];
33443 function $IsStateFilter($state) {
33444 var isFilter = function (state) {
33445 return $state.is(state);
33447 isFilter.$stateful = true;
33453 * @name ui.router.state.filter:includedByState
33455 * @requires ui.router.state.$state
33458 * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}.
33460 $IncludedByStateFilter.$inject = ['$state'];
33461 function $IncludedByStateFilter($state) {
33462 var includesFilter = function (state) {
33463 return $state.includes(state);
33465 includesFilter.$stateful = true;
33466 return includesFilter;
33469 angular.module('ui.router.state')
33470 .filter('isState', $IsStateFilter)
33471 .filter('includedByState', $IncludedByStateFilter);
33472 })(window, window.angular);
33476 /***/ function(module, exports, __webpack_require__) {
33478 __webpack_require__(5);
33479 module.exports = 'ngResource';
33484 /***/ function(module, exports) {
33487 * @license AngularJS v1.4.8
33488 * (c) 2010-2015 Google, Inc. http://angularjs.org
33491 (function(window, angular, undefined) {'use strict';
33493 var $resourceMinErr = angular.$$minErr('$resource');
33495 // Helper functions and regex to lookup a dotted path on an object
33496 // stopping at undefined/null. The path must be composed of ASCII
33497 // identifiers (just like $parse)
33498 var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;
33500 function isValidDottedPath(path) {
33501 return (path != null && path !== '' && path !== 'hasOwnProperty' &&
33502 MEMBER_NAME_REGEX.test('.' + path));
33505 function lookupDottedPath(obj, path) {
33506 if (!isValidDottedPath(path)) {
33507 throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
33509 var keys = path.split('.');
33510 for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) {
33512 obj = (obj !== null) ? obj[key] : undefined;
33518 * Create a shallow copy of an object and clear other fields from the destination
33520 function shallowClearAndCopy(src, dst) {
33523 angular.forEach(dst, function(value, key) {
33527 for (var key in src) {
33528 if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
33529 dst[key] = src[key];
33543 * The `ngResource` module provides interaction support with RESTful services
33544 * via the $resource service.
33547 * <div doc-module-components="ngResource"></div>
33549 * See {@link ngResource.$resource `$resource`} for usage.
33558 * A factory which creates a resource object that lets you interact with
33559 * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
33561 * The returned resource object has action methods which provide high-level behaviors without
33562 * the need to interact with the low level {@link ng.$http $http} service.
33564 * Requires the {@link ngResource `ngResource`} module to be installed.
33566 * By default, trailing slashes will be stripped from the calculated URLs,
33567 * which can pose problems with server backends that do not expect that
33568 * behavior. This can be disabled by configuring the `$resourceProvider` like
33572 app.config(['$resourceProvider', function($resourceProvider) {
33573 // Don't strip trailing slashes from calculated URLs
33574 $resourceProvider.defaults.stripTrailingSlashes = false;
33578 * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
33579 * `/user/:username`. If you are using a URL with a port number (e.g.
33580 * `http://example.com:8080/api`), it will be respected.
33582 * If you are using a url with a suffix, just add the suffix, like this:
33583 * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
33584 * or even `$resource('http://example.com/resource/:resource_id.:format')`
33585 * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
33586 * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
33587 * can escape it with `/\.`.
33589 * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
33590 * `actions` methods. If any of the parameter value is a function, it will be executed every time
33591 * when a param value needs to be obtained for a request (unless the param was overridden).
33593 * Each key value in the parameter object is first bound to url template if present and then any
33594 * excess keys are appended to the url search query after the `?`.
33596 * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
33597 * URL `/path/greet?salutation=Hello`.
33599 * If the parameter value is prefixed with `@` then the value for that parameter will be extracted
33600 * from the corresponding property on the `data` object (provided when calling an action method). For
33601 * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam`
33602 * will be `data.someProp`.
33604 * @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend
33605 * the default set of resource actions. The declaration should be created in the format of {@link
33606 * ng.$http#usage $http.config}:
33608 * {action1: {method:?, params:?, isArray:?, headers:?, ...},
33609 * action2: {method:?, params:?, isArray:?, headers:?, ...},
33614 * - **`action`** – {string} – The name of action. This name becomes the name of the method on
33615 * your resource object.
33616 * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`,
33617 * `DELETE`, `JSONP`, etc).
33618 * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of
33619 * the parameter value is a function, it will be executed every time when a param value needs to
33620 * be obtained for a request (unless the param was overridden).
33621 * - **`url`** – {string} – action specific `url` override. The url templating is supported just
33622 * like for the resource-level urls.
33623 * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
33624 * see `returns` section.
33625 * - **`transformRequest`** –
33626 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
33627 * transform function or an array of such functions. The transform function takes the http
33628 * request body and headers and returns its transformed (typically serialized) version.
33629 * By default, transformRequest will contain one function that checks if the request data is
33630 * an object and serializes to using `angular.toJson`. To prevent this behavior, set
33631 * `transformRequest` to an empty array: `transformRequest: []`
33632 * - **`transformResponse`** –
33633 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
33634 * transform function or an array of such functions. The transform function takes the http
33635 * response body and headers and returns its transformed (typically deserialized) version.
33636 * By default, transformResponse will contain one function that checks if the response looks like
33637 * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set
33638 * `transformResponse` to an empty array: `transformResponse: []`
33639 * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
33640 * GET request, otherwise if a cache instance built with
33641 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
33643 * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
33644 * should abort the request when resolved.
33645 * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
33647 * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
33648 * for more information.
33649 * - **`responseType`** - `{string}` - see
33650 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
33651 * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
33652 * `response` and `responseError`. Both `response` and `responseError` interceptors get called
33653 * with `http response` object. See {@link ng.$http $http interceptors}.
33655 * @param {Object} options Hash with custom settings that should extend the
33656 * default `$resourceProvider` behavior. The only supported option is
33660 * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing
33661 * slashes from any calculated URL will be stripped. (Defaults to true.)
33663 * @returns {Object} A resource "class" object with methods for the default set of resource actions
33664 * optionally extended with custom `actions`. The default set contains these actions:
33666 * { 'get': {method:'GET'},
33667 * 'save': {method:'POST'},
33668 * 'query': {method:'GET', isArray:true},
33669 * 'remove': {method:'DELETE'},
33670 * 'delete': {method:'DELETE'} };
33673 * Calling these methods invoke an {@link ng.$http} with the specified http method,
33674 * destination and parameters. When the data is returned from the server then the object is an
33675 * instance of the resource class. The actions `save`, `remove` and `delete` are available on it
33676 * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
33677 * read, update, delete) on server-side data like this:
33679 * var User = $resource('/user/:userId', {userId:'@id'});
33680 * var user = User.get({userId:123}, function() {
33686 * It is important to realize that invoking a $resource object method immediately returns an
33687 * empty reference (object or array depending on `isArray`). Once the data is returned from the
33688 * server the existing reference is populated with the actual data. This is a useful trick since
33689 * usually the resource is assigned to a model which is then rendered by the view. Having an empty
33690 * object results in no rendering, once the data arrives from the server then the object is
33691 * populated with the data and the view automatically re-renders itself showing the new data. This
33692 * means that in most cases one never has to write a callback function for the action methods.
33694 * The action methods on the class object or instance object can be invoked with the following
33697 * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
33698 * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
33699 * - non-GET instance actions: `instance.$action([parameters], [success], [error])`
33702 * Success callback is called with (value, responseHeaders) arguments, where the value is
33703 * the populated resource instance or collection object. The error callback is called
33704 * with (httpResponse) argument.
33706 * Class actions return empty instance (with additional properties below).
33707 * Instance actions return promise of the action.
33709 * The Resource instances and collection have these additional properties:
33711 * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
33712 * instance or collection.
33714 * On success, the promise is resolved with the same resource instance or collection object,
33715 * updated with data from server. This makes it easy to use in
33716 * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
33717 * rendering until the resource(s) are loaded.
33719 * On failure, the promise is resolved with the {@link ng.$http http response} object, without
33720 * the `resource` property.
33722 * If an interceptor object was provided, the promise will instead be resolved with the value
33723 * returned by the interceptor.
33725 * - `$resolved`: `true` after first server interaction is completed (either with success or
33726 * rejection), `false` before that. Knowing if the Resource has been resolved is useful in
33731 * # Credit card resource
33734 // Define CreditCard class
33735 var CreditCard = $resource('/user/:userId/card/:cardId',
33736 {userId:123, cardId:'@id'}, {
33737 charge: {method:'POST', params:{charge:true}}
33740 // We can retrieve a collection from the server
33741 var cards = CreditCard.query(function() {
33742 // GET: /user/123/card
33743 // server returns: [ {id:456, number:'1234', name:'Smith'} ];
33745 var card = cards[0];
33746 // each item is an instance of CreditCard
33747 expect(card instanceof CreditCard).toEqual(true);
33748 card.name = "J. Smith";
33749 // non GET methods are mapped onto the instances
33751 // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
33752 // server returns: {id:456, number:'1234', name: 'J. Smith'};
33754 // our custom method is mapped as well.
33755 card.$charge({amount:9.99});
33756 // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
33759 // we can create an instance as well
33760 var newCard = new CreditCard({number:'0123'});
33761 newCard.name = "Mike Smith";
33763 // POST: /user/123/card {number:'0123', name:'Mike Smith'}
33764 // server returns: {id:789, number:'0123', name: 'Mike Smith'};
33765 expect(newCard.id).toEqual(789);
33768 * The object returned from this function execution is a resource "class" which has "static" method
33769 * for each action in the definition.
33771 * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
33773 * When the data is returned from the server then the object is an instance of the resource type and
33774 * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
33775 * operations (create, read, update, delete) on server-side data.
33778 var User = $resource('/user/:userId', {userId:'@id'});
33779 User.get({userId:123}, function(user) {
33785 * It's worth noting that the success callback for `get`, `query` and other methods gets passed
33786 * in the response that came from the server as well as $http header getter function, so one
33787 * could rewrite the above example and get access to http headers as:
33790 var User = $resource('/user/:userId', {userId:'@id'});
33791 User.get({userId:123}, function(u, getResponseHeaders){
33793 u.$save(function(u, putResponseHeaders) {
33794 //u => saved user object
33795 //putResponseHeaders => $http header getter
33800 * You can also access the raw `$http` promise via the `$promise` property on the object returned
33803 var User = $resource('/user/:userId', {userId:'@id'});
33804 User.get({userId:123})
33805 .$promise.then(function(user) {
33806 $scope.user = user;
33810 * # Creating a custom 'PUT' request
33811 * In this example we create a custom method on our resource to make a PUT request
33813 * var app = angular.module('app', ['ngResource', 'ngRoute']);
33815 * // Some APIs expect a PUT request in the format URL/object/ID
33816 * // Here we are creating an 'update' method
33817 * app.factory('Notes', ['$resource', function($resource) {
33818 * return $resource('/notes/:id', null,
33820 * 'update': { method:'PUT' }
33824 * // In our controller we get the ID from the URL using ngRoute and $routeParams
33825 * // We pass in $routeParams and our Notes factory along with $scope
33826 * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
33827 function($scope, $routeParams, Notes) {
33828 * // First get a note object from the factory
33829 * var note = Notes.get({ id:$routeParams.id });
33832 * // Now call update passing in the ID first then the object you are updating
33833 * Notes.update({ id:$id }, note);
33835 * // This will PUT /notes/ID with the note object in the request payload
33839 angular.module('ngResource', ['ng']).
33840 provider('$resource', function() {
33841 var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/;
33842 var provider = this;
33845 // Strip slashes by default
33846 stripTrailingSlashes: true,
33848 // Default actions configuration
33850 'get': {method: 'GET'},
33851 'save': {method: 'POST'},
33852 'query': {method: 'GET', isArray: true},
33853 'remove': {method: 'DELETE'},
33854 'delete': {method: 'DELETE'}
33858 this.$get = ['$http', '$q', function($http, $q) {
33860 var noop = angular.noop,
33861 forEach = angular.forEach,
33862 extend = angular.extend,
33863 copy = angular.copy,
33864 isFunction = angular.isFunction;
33867 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
33868 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set
33869 * (pchar) allowed in path segments:
33871 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
33872 * pct-encoded = "%" HEXDIG HEXDIG
33873 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
33874 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
33875 * / "*" / "+" / "," / ";" / "="
33877 function encodeUriSegment(val) {
33878 return encodeUriQuery(val, true).
33879 replace(/%26/gi, '&').
33880 replace(/%3D/gi, '=').
33881 replace(/%2B/gi, '+');
33886 * This method is intended for encoding *key* or *value* parts of query component. We need a
33887 * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
33888 * have to be encoded per http://tools.ietf.org/html/rfc3986:
33889 * query = *( pchar / "/" / "?" )
33890 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
33891 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
33892 * pct-encoded = "%" HEXDIG HEXDIG
33893 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
33894 * / "*" / "+" / "," / ";" / "="
33896 function encodeUriQuery(val, pctEncodeSpaces) {
33897 return encodeURIComponent(val).
33898 replace(/%40/gi, '@').
33899 replace(/%3A/gi, ':').
33900 replace(/%24/g, '$').
33901 replace(/%2C/gi, ',').
33902 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
33905 function Route(template, defaults) {
33906 this.template = template;
33907 this.defaults = extend({}, provider.defaults, defaults);
33908 this.urlParams = {};
33911 Route.prototype = {
33912 setUrlParams: function(config, params, actionUrl) {
33914 url = actionUrl || self.template,
33917 protocolAndDomain = '';
33919 var urlParams = self.urlParams = {};
33920 forEach(url.split(/\W/), function(param) {
33921 if (param === 'hasOwnProperty') {
33922 throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name.");
33924 if (!(new RegExp("^\\d+$").test(param)) && param &&
33925 (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
33926 urlParams[param] = true;
33929 url = url.replace(/\\:/g, ':');
33930 url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) {
33931 protocolAndDomain = match;
33935 params = params || {};
33936 forEach(self.urlParams, function(_, urlParam) {
33937 val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
33938 if (angular.isDefined(val) && val !== null) {
33939 encodedVal = encodeUriSegment(val);
33940 url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) {
33941 return encodedVal + p1;
33944 url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
33945 leadingSlashes, tail) {
33946 if (tail.charAt(0) == '/') {
33949 return leadingSlashes + tail;
33955 // strip trailing slashes and set the url (unless this behavior is specifically disabled)
33956 if (self.defaults.stripTrailingSlashes) {
33957 url = url.replace(/\/+$/, '') || '/';
33960 // then replace collapse `/.` if found in the last URL path segment before the query
33961 // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
33962 url = url.replace(/\/\.(?=\w+($|\?))/, '.');
33963 // replace escaped `/\.` with `/.`
33964 config.url = protocolAndDomain + url.replace(/\/\\\./, '/.');
33967 // set params - delegate param encoding to $http
33968 forEach(params, function(value, key) {
33969 if (!self.urlParams[key]) {
33970 config.params = config.params || {};
33971 config.params[key] = value;
33978 function resourceFactory(url, paramDefaults, actions, options) {
33979 var route = new Route(url, options);
33981 actions = extend({}, provider.defaults.actions, actions);
33983 function extractParams(data, actionParams) {
33985 actionParams = extend({}, paramDefaults, actionParams);
33986 forEach(actionParams, function(value, key) {
33987 if (isFunction(value)) { value = value(); }
33988 ids[key] = value && value.charAt && value.charAt(0) == '@' ?
33989 lookupDottedPath(data, value.substr(1)) : value;
33994 function defaultResponseInterceptor(response) {
33995 return response.resource;
33998 function Resource(value) {
33999 shallowClearAndCopy(value || {}, this);
34002 Resource.prototype.toJSON = function() {
34003 var data = extend({}, this);
34004 delete data.$promise;
34005 delete data.$resolved;
34009 forEach(actions, function(action, name) {
34010 var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
34012 Resource[name] = function(a1, a2, a3, a4) {
34013 var params = {}, data, success, error;
34015 /* jshint -W086 */ /* (purposefully fall through case statements) */
34016 switch (arguments.length) {
34023 if (isFunction(a2)) {
34024 if (isFunction(a1)) {
34040 if (isFunction(a1)) success = a1;
34041 else if (hasBody) data = a1;
34046 throw $resourceMinErr('badargs',
34047 "Expected up to 4 arguments [params, data, success, error], got {0} arguments",
34050 /* jshint +W086 */ /* (purposefully fall through case statements) */
34052 var isInstanceCall = this instanceof Resource;
34053 var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
34054 var httpConfig = {};
34055 var responseInterceptor = action.interceptor && action.interceptor.response ||
34056 defaultResponseInterceptor;
34057 var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
34060 forEach(action, function(value, key) {
34063 httpConfig[key] = copy(value);
34067 case 'interceptor':
34070 httpConfig[key] = value;
34075 if (hasBody) httpConfig.data = data;
34076 route.setUrlParams(httpConfig,
34077 extend({}, extractParams(data, action.params || {}), params),
34080 var promise = $http(httpConfig).then(function(response) {
34081 var data = response.data,
34082 promise = value.$promise;
34085 // Need to convert action.isArray to boolean in case it is undefined
34087 if (angular.isArray(data) !== (!!action.isArray)) {
34088 throw $resourceMinErr('badcfg',
34089 'Error in resource configuration for action `{0}`. Expected response to ' +
34090 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object',
34091 angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url);
34094 if (action.isArray) {
34096 forEach(data, function(item) {
34097 if (typeof item === "object") {
34098 value.push(new Resource(item));
34100 // Valid JSON values may be string literals, and these should not be converted
34101 // into objects. These items will not have access to the Resource prototype
34102 // methods, but unfortunately there
34107 shallowClearAndCopy(data, value);
34108 value.$promise = promise;
34112 value.$resolved = true;
34114 response.resource = value;
34117 }, function(response) {
34118 value.$resolved = true;
34120 (error || noop)(response);
34122 return $q.reject(response);
34125 promise = promise.then(
34126 function(response) {
34127 var value = responseInterceptor(response);
34128 (success || noop)(value, response.headers);
34131 responseErrorInterceptor);
34133 if (!isInstanceCall) {
34134 // we are creating instance / collection
34135 // - set the initial promise
34136 // - return the instance / collection
34137 value.$promise = promise;
34138 value.$resolved = false;
34148 Resource.prototype['$' + name] = function(params, success, error) {
34149 if (isFunction(params)) {
34150 error = success; success = params; params = {};
34152 var result = Resource[name].call(this, params, this, success, error);
34153 return result.$promise || result;
34157 Resource.bind = function(additionalParamDefaults) {
34158 return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
34164 return resourceFactory;
34169 })(window, window.angular);
34174 /***/ function(module, exports, __webpack_require__) {
34176 __webpack_require__(7);
34178 module.exports = 'ui.bootstrap';
34183 /***/ function(module, exports) {
34186 * angular-ui-bootstrap
34187 * http://angular-ui.github.io/bootstrap/
34189 * Version: 1.0.0 - 2016-01-08
34192 angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.debounce","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
34193 angular.module("ui.bootstrap.tpls", ["uib/template/accordion/accordion-group.html","uib/template/accordion/accordion.html","uib/template/alert/alert.html","uib/template/carousel/carousel.html","uib/template/carousel/slide.html","uib/template/datepicker/datepicker.html","uib/template/datepicker/day.html","uib/template/datepicker/month.html","uib/template/datepicker/popup.html","uib/template/datepicker/year.html","uib/template/modal/backdrop.html","uib/template/modal/window.html","uib/template/pager/pager.html","uib/template/pagination/pagination.html","uib/template/tooltip/tooltip-html-popup.html","uib/template/tooltip/tooltip-popup.html","uib/template/tooltip/tooltip-template-popup.html","uib/template/popover/popover-html.html","uib/template/popover/popover-template.html","uib/template/popover/popover.html","uib/template/progressbar/bar.html","uib/template/progressbar/progress.html","uib/template/progressbar/progressbar.html","uib/template/rating/rating.html","uib/template/tabs/tab.html","uib/template/tabs/tabset.html","uib/template/timepicker/timepicker.html","uib/template/typeahead/typeahead-match.html","uib/template/typeahead/typeahead-popup.html"]);
34194 angular.module('ui.bootstrap.collapse', [])
34196 .directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) {
34197 var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
34199 link: function(scope, element, attrs) {
34200 if (!scope.$eval(attrs.uibCollapse)) {
34201 element.addClass('in')
34202 .addClass('collapse')
34203 .css({height: 'auto'});
34206 function expand() {
34207 element.removeClass('collapse')
34208 .addClass('collapsing')
34209 .attr('aria-expanded', true)
34210 .attr('aria-hidden', false);
34213 $animateCss(element, {
34216 to: { height: element[0].scrollHeight + 'px' }
34217 }).start()['finally'](expandDone);
34219 $animate.addClass(element, 'in', {
34220 to: { height: element[0].scrollHeight + 'px' }
34221 }).then(expandDone);
34225 function expandDone() {
34226 element.removeClass('collapsing')
34227 .addClass('collapse')
34228 .css({height: 'auto'});
34231 function collapse() {
34232 if (!element.hasClass('collapse') && !element.hasClass('in')) {
34233 return collapseDone();
34237 // IMPORTANT: The height must be set before adding "collapsing" class.
34238 // Otherwise, the browser attempts to animate from height 0 (in
34239 // collapsing class) to the given height here.
34240 .css({height: element[0].scrollHeight + 'px'})
34241 // initially all panel collapse have the collapse class, this removal
34242 // prevents the animation from jumping to collapsed state
34243 .removeClass('collapse')
34244 .addClass('collapsing')
34245 .attr('aria-expanded', false)
34246 .attr('aria-hidden', true);
34249 $animateCss(element, {
34252 }).start()['finally'](collapseDone);
34254 $animate.removeClass(element, 'in', {
34256 }).then(collapseDone);
34260 function collapseDone() {
34261 element.css({height: '0'}); // Required so that collapse works when animation is disabled
34262 element.removeClass('collapsing')
34263 .addClass('collapse');
34266 scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
34267 if (shouldCollapse) {
34277 angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
34279 .constant('uibAccordionConfig', {
34283 .controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
34284 // This array keeps track of the accordion groups
34287 // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
34288 this.closeOthers = function(openGroup) {
34289 var closeOthers = angular.isDefined($attrs.closeOthers) ?
34290 $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
34292 angular.forEach(this.groups, function(group) {
34293 if (group !== openGroup) {
34294 group.isOpen = false;
34300 // This is called from the accordion-group directive to add itself to the accordion
34301 this.addGroup = function(groupScope) {
34303 this.groups.push(groupScope);
34305 groupScope.$on('$destroy', function(event) {
34306 that.removeGroup(groupScope);
34310 // This is called from the accordion-group directive when to remove itself
34311 this.removeGroup = function(group) {
34312 var index = this.groups.indexOf(group);
34313 if (index !== -1) {
34314 this.groups.splice(index, 1);
34319 // The accordion directive simply sets up the directive controller
34320 // and adds an accordion CSS class to itself element.
34321 .directive('uibAccordion', function() {
34323 controller: 'UibAccordionController',
34324 controllerAs: 'accordion',
34326 templateUrl: function(element, attrs) {
34327 return attrs.templateUrl || 'uib/template/accordion/accordion.html';
34332 // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
34333 .directive('uibAccordionGroup', function() {
34335 require: '^uibAccordion', // We need this directive to be inside an accordion
34336 transclude: true, // It transcludes the contents of the directive into the template
34337 replace: true, // The element containing the directive will be replaced with the template
34338 templateUrl: function(element, attrs) {
34339 return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
34342 heading: '@', // Interpolate the heading attribute onto this scope
34346 controller: function() {
34347 this.setHeading = function(element) {
34348 this.heading = element;
34351 link: function(scope, element, attrs, accordionCtrl) {
34352 accordionCtrl.addGroup(scope);
34354 scope.openClass = attrs.openClass || 'panel-open';
34355 scope.panelClass = attrs.panelClass || 'panel-default';
34356 scope.$watch('isOpen', function(value) {
34357 element.toggleClass(scope.openClass, !!value);
34359 accordionCtrl.closeOthers(scope);
34363 scope.toggleOpen = function($event) {
34364 if (!scope.isDisabled) {
34365 if (!$event || $event.which === 32) {
34366 scope.isOpen = !scope.isOpen;
34374 // Use accordion-heading below an accordion-group to provide a heading containing HTML
34375 .directive('uibAccordionHeading', function() {
34377 transclude: true, // Grab the contents to be used as the heading
34378 template: '', // In effect remove this element!
34380 require: '^uibAccordionGroup',
34381 link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
34382 // Pass the heading to the accordion-group controller
34383 // so that it can be transcluded into the right place in the template
34384 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
34385 accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
34390 // Use in the accordion-group template to indicate where you want the heading to be transcluded
34391 // You must provide the property on the accordion-group controller that will hold the transcluded element
34392 .directive('uibAccordionTransclude', function() {
34394 require: '^uibAccordionGroup',
34395 link: function(scope, element, attrs, controller) {
34396 scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
34398 element.find('span').html('');
34399 element.find('span').append(heading);
34406 angular.module('ui.bootstrap.alert', [])
34408 .controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
34409 $scope.closeable = !!$attrs.close;
34411 var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
34412 $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
34414 if (dismissOnTimeout) {
34415 $timeout(function() {
34417 }, parseInt(dismissOnTimeout, 10));
34421 .directive('uibAlert', function() {
34423 controller: 'UibAlertController',
34424 controllerAs: 'alert',
34425 templateUrl: function(element, attrs) {
34426 return attrs.templateUrl || 'uib/template/alert/alert.html';
34437 angular.module('ui.bootstrap.buttons', [])
34439 .constant('uibButtonConfig', {
34440 activeClass: 'active',
34441 toggleEvent: 'click'
34444 .controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
34445 this.activeClass = buttonConfig.activeClass || 'active';
34446 this.toggleEvent = buttonConfig.toggleEvent || 'click';
34449 .directive('uibBtnRadio', ['$parse', function($parse) {
34451 require: ['uibBtnRadio', 'ngModel'],
34452 controller: 'UibButtonsController',
34453 controllerAs: 'buttons',
34454 link: function(scope, element, attrs, ctrls) {
34455 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34456 var uncheckableExpr = $parse(attrs.uibUncheckable);
34458 element.find('input').css({display: 'none'});
34461 ngModelCtrl.$render = function() {
34462 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
34466 element.on(buttonsCtrl.toggleEvent, function() {
34467 if (attrs.disabled) {
34471 var isActive = element.hasClass(buttonsCtrl.activeClass);
34473 if (!isActive || angular.isDefined(attrs.uncheckable)) {
34474 scope.$apply(function() {
34475 ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
34476 ngModelCtrl.$render();
34481 if (attrs.uibUncheckable) {
34482 scope.$watch(uncheckableExpr, function(uncheckable) {
34483 attrs.$set('uncheckable', uncheckable ? '' : null);
34490 .directive('uibBtnCheckbox', function() {
34492 require: ['uibBtnCheckbox', 'ngModel'],
34493 controller: 'UibButtonsController',
34494 controllerAs: 'button',
34495 link: function(scope, element, attrs, ctrls) {
34496 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34498 element.find('input').css({display: 'none'});
34500 function getTrueValue() {
34501 return getCheckboxValue(attrs.btnCheckboxTrue, true);
34504 function getFalseValue() {
34505 return getCheckboxValue(attrs.btnCheckboxFalse, false);
34508 function getCheckboxValue(attribute, defaultValue) {
34509 return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
34513 ngModelCtrl.$render = function() {
34514 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
34518 element.on(buttonsCtrl.toggleEvent, function() {
34519 if (attrs.disabled) {
34523 scope.$apply(function() {
34524 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
34525 ngModelCtrl.$render();
34532 angular.module('ui.bootstrap.carousel', [])
34534 .controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
34536 slides = self.slides = $scope.slides = [],
34537 SLIDE_DIRECTION = 'uib-slideDirection',
34539 currentInterval, isPlaying, bufferedTransitions = [];
34540 self.currentSlide = null;
34542 var destroyed = false;
34544 self.addSlide = function(slide, element) {
34545 slide.$element = element;
34546 slides.push(slide);
34547 //if this is the first slide or the slide is set to active, select it
34548 if (slides.length === 1 || slide.active) {
34549 if ($scope.$currentTransition) {
34550 $scope.$currentTransition = null;
34553 self.select(slides[slides.length - 1]);
34554 if (slides.length === 1) {
34558 slide.active = false;
34562 self.getCurrentIndex = function() {
34563 if (self.currentSlide && angular.isDefined(self.currentSlide.index)) {
34564 return +self.currentSlide.index;
34566 return currentIndex;
34569 self.next = $scope.next = function() {
34570 var newIndex = (self.getCurrentIndex() + 1) % slides.length;
34572 if (newIndex === 0 && $scope.noWrap()) {
34577 return self.select(getSlideByIndex(newIndex), 'next');
34580 self.prev = $scope.prev = function() {
34581 var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
34583 if ($scope.noWrap() && newIndex === slides.length - 1) {
34588 return self.select(getSlideByIndex(newIndex), 'prev');
34591 self.removeSlide = function(slide) {
34592 if (angular.isDefined(slide.index)) {
34593 slides.sort(function(a, b) {
34594 return +a.index > +b.index;
34598 var bufferedIndex = bufferedTransitions.indexOf(slide);
34599 if (bufferedIndex !== -1) {
34600 bufferedTransitions.splice(bufferedIndex, 1);
34602 //get the index of the slide inside the carousel
34603 var index = slides.indexOf(slide);
34604 slides.splice(index, 1);
34605 $timeout(function() {
34606 if (slides.length > 0 && slide.active) {
34607 if (index >= slides.length) {
34608 self.select(slides[index - 1]);
34610 self.select(slides[index]);
34612 } else if (currentIndex > index) {
34617 //clean the currentSlide when no more slide
34618 if (slides.length === 0) {
34619 self.currentSlide = null;
34620 clearBufferedTransitions();
34624 /* direction: "prev" or "next" */
34625 self.select = $scope.select = function(nextSlide, direction) {
34626 var nextIndex = $scope.indexOfSlide(nextSlide);
34627 //Decide direction if it's not given
34628 if (direction === undefined) {
34629 direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
34631 //Prevent this user-triggered transition from occurring if there is already one in progress
34632 if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) {
34633 goNext(nextSlide, nextIndex, direction);
34634 } else if (nextSlide && nextSlide !== self.currentSlide && $scope.$currentTransition) {
34635 bufferedTransitions.push(nextSlide);
34639 /* Allow outside people to call indexOf on slides array */
34640 $scope.indexOfSlide = function(slide) {
34641 return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide);
34644 $scope.isActive = function(slide) {
34645 return self.currentSlide === slide;
34648 $scope.pause = function() {
34649 if (!$scope.noPause) {
34655 $scope.play = function() {
34662 $scope.$on('$destroy', function() {
34667 $scope.$watch('noTransition', function(noTransition) {
34668 $animate.enabled($element, !noTransition);
34671 $scope.$watch('interval', restartTimer);
34673 $scope.$watchCollection('slides', resetTransition);
34675 function clearBufferedTransitions() {
34676 while (bufferedTransitions.length) {
34677 bufferedTransitions.shift();
34681 function getSlideByIndex(index) {
34682 if (angular.isUndefined(slides[index].index)) {
34683 return slides[index];
34685 for (var i = 0, l = slides.length; i < l; ++i) {
34686 if (slides[i].index === index) {
34692 function goNext(slide, index, direction) {
34693 if (destroyed) { return; }
34695 angular.extend(slide, {direction: direction, active: true});
34696 angular.extend(self.currentSlide || {}, {direction: direction, active: false});
34697 if ($animate.enabled($element) && !$scope.$currentTransition &&
34698 slide.$element && self.slides.length > 1) {
34699 slide.$element.data(SLIDE_DIRECTION, slide.direction);
34700 if (self.currentSlide && self.currentSlide.$element) {
34701 self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction);
34704 $scope.$currentTransition = true;
34705 $animate.on('addClass', slide.$element, function(element, phase) {
34706 if (phase === 'close') {
34707 $scope.$currentTransition = null;
34708 $animate.off('addClass', element);
34709 if (bufferedTransitions.length) {
34710 var nextSlide = bufferedTransitions.pop();
34711 var nextIndex = $scope.indexOfSlide(nextSlide);
34712 var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
34713 clearBufferedTransitions();
34715 goNext(nextSlide, nextIndex, nextDirection);
34721 self.currentSlide = slide;
34722 currentIndex = index;
34724 //every time you change slides, reset the timer
34728 function resetTimer() {
34729 if (currentInterval) {
34730 $interval.cancel(currentInterval);
34731 currentInterval = null;
34735 function resetTransition(slides) {
34736 if (!slides.length) {
34737 $scope.$currentTransition = null;
34738 clearBufferedTransitions();
34742 function restartTimer() {
34744 var interval = +$scope.interval;
34745 if (!isNaN(interval) && interval > 0) {
34746 currentInterval = $interval(timerFn, interval);
34750 function timerFn() {
34751 var interval = +$scope.interval;
34752 if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
34760 .directive('uibCarousel', function() {
34764 controller: 'UibCarouselController',
34765 controllerAs: 'carousel',
34766 templateUrl: function(element, attrs) {
34767 return attrs.templateUrl || 'uib/template/carousel/carousel.html';
34778 .directive('uibSlide', function() {
34780 require: '^uibCarousel',
34783 templateUrl: function(element, attrs) {
34784 return attrs.templateUrl || 'uib/template/carousel/slide.html';
34791 link: function (scope, element, attrs, carouselCtrl) {
34792 carouselCtrl.addSlide(scope, element);
34793 //when the scope is destroyed then remove the slide from the current slides array
34794 scope.$on('$destroy', function() {
34795 carouselCtrl.removeSlide(scope);
34798 scope.$watch('active', function(active) {
34800 carouselCtrl.select(scope);
34807 .animation('.item', ['$animateCss',
34808 function($animateCss) {
34809 var SLIDE_DIRECTION = 'uib-slideDirection';
34811 function removeClass(element, className, callback) {
34812 element.removeClass(className);
34819 beforeAddClass: function(element, className, done) {
34820 if (className === 'active') {
34821 var stopped = false;
34822 var direction = element.data(SLIDE_DIRECTION);
34823 var directionClass = direction === 'next' ? 'left' : 'right';
34824 var removeClassFn = removeClass.bind(this, element,
34825 directionClass + ' ' + direction, done);
34826 element.addClass(direction);
34828 $animateCss(element, {addClass: directionClass})
34830 .done(removeClassFn);
34832 return function() {
34838 beforeRemoveClass: function (element, className, done) {
34839 if (className === 'active') {
34840 var stopped = false;
34841 var direction = element.data(SLIDE_DIRECTION);
34842 var directionClass = direction === 'next' ? 'left' : 'right';
34843 var removeClassFn = removeClass.bind(this, element, directionClass, done);
34845 $animateCss(element, {addClass: directionClass})
34847 .done(removeClassFn);
34849 return function() {
34858 angular.module('ui.bootstrap.dateparser', [])
34860 .service('uibDateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) {
34861 // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
34862 var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
34865 var formatCodeToRegex;
34867 this.init = function() {
34868 localeId = $locale.id;
34872 formatCodeToRegex = [
34876 apply: function(value) { this.year = +value; }
34881 apply: function(value) { this.year = +value + 2000; }
34886 apply: function(value) { this.year = +value; }
34890 regex: '0?[1-9]|1[0-2]',
34891 apply: function(value) { this.month = value - 1; }
34895 regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
34896 apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
34900 regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
34901 apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
34905 regex: '0[1-9]|1[0-2]',
34906 apply: function(value) { this.month = value - 1; }
34910 regex: '[1-9]|1[0-2]',
34911 apply: function(value) { this.month = value - 1; }
34915 regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
34916 apply: function(value) { this.date = +value; }
34920 regex: '[0-2][0-9]{1}|3[0-1]{1}',
34921 apply: function(value) { this.date = +value; }
34925 regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
34926 apply: function(value) { this.date = +value; }
34930 regex: $locale.DATETIME_FORMATS.DAY.join('|')
34934 regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
34938 regex: '(?:0|1)[0-9]|2[0-3]',
34939 apply: function(value) { this.hours = +value; }
34943 regex: '0[0-9]|1[0-2]',
34944 apply: function(value) { this.hours = +value; }
34948 regex: '1?[0-9]|2[0-3]',
34949 apply: function(value) { this.hours = +value; }
34953 regex: '[0-9]|1[0-2]',
34954 apply: function(value) { this.hours = +value; }
34958 regex: '[0-5][0-9]',
34959 apply: function(value) { this.minutes = +value; }
34963 regex: '[0-9]|[1-5][0-9]',
34964 apply: function(value) { this.minutes = +value; }
34968 regex: '[0-9][0-9][0-9]',
34969 apply: function(value) { this.milliseconds = +value; }
34973 regex: '[0-5][0-9]',
34974 apply: function(value) { this.seconds = +value; }
34978 regex: '[0-9]|[1-5][0-9]',
34979 apply: function(value) { this.seconds = +value; }
34983 regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
34984 apply: function(value) {
34985 if (this.hours === 12) {
34989 if (value === 'PM') {
34996 regex: '[+-]\\d{4}',
34997 apply: function(value) {
34998 var matches = value.match(/([+-])(\d{2})(\d{2})/),
35000 hours = matches[2],
35001 minutes = matches[3];
35002 this.hours += toInt(sign + hours);
35003 this.minutes += toInt(sign + minutes);
35008 regex: '[0-4][0-9]|5[0-3]'
35012 regex: '[0-9]|[1-4][0-9]|5[0-3]'
35016 regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s')
35020 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35024 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35028 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35035 function createParser(format) {
35036 var map = [], regex = format.split('');
35038 // check for literal values
35039 var quoteIndex = format.indexOf('\'');
35040 if (quoteIndex > -1) {
35041 var inLiteral = false;
35042 format = format.split('');
35043 for (var i = quoteIndex; i < format.length; i++) {
35045 if (format[i] === '\'') {
35046 if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
35049 } else { // end of literal
35056 if (format[i] === '\'') { // start of literal
35064 format = format.join('');
35067 angular.forEach(formatCodeToRegex, function(data) {
35068 var index = format.indexOf(data.key);
35071 format = format.split('');
35073 regex[index] = '(' + data.regex + ')';
35074 format[index] = '$'; // Custom symbol to define consumed part of format
35075 for (var i = index + 1, n = index + data.key.length; i < n; i++) {
35079 format = format.join('');
35084 matcher: data.regex
35090 regex: new RegExp('^' + regex.join('') + '$'),
35091 map: orderByFilter(map, 'index')
35095 this.parse = function(input, format, baseDate) {
35096 if (!angular.isString(input) || !format) {
35100 format = $locale.DATETIME_FORMATS[format] || format;
35101 format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
35103 if ($locale.id !== localeId) {
35107 if (!this.parsers[format]) {
35108 this.parsers[format] = createParser(format);
35111 var parser = this.parsers[format],
35112 regex = parser.regex,
35114 results = input.match(regex),
35116 if (results && results.length) {
35118 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
35120 year: baseDate.getFullYear(),
35121 month: baseDate.getMonth(),
35122 date: baseDate.getDate(),
35123 hours: baseDate.getHours(),
35124 minutes: baseDate.getMinutes(),
35125 seconds: baseDate.getSeconds(),
35126 milliseconds: baseDate.getMilliseconds()
35130 $log.warn('dateparser:', 'baseDate is not a valid date');
35132 fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
35135 for (var i = 1, n = results.length; i < n; i++) {
35136 var mapper = map[i - 1];
35137 if (mapper.matcher === 'Z') {
35141 if (mapper.apply) {
35142 mapper.apply.call(fields, results[i]);
35146 var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
35147 Date.prototype.setFullYear;
35148 var timesetter = tzOffset ? Date.prototype.setUTCHours :
35149 Date.prototype.setHours;
35151 if (isValid(fields.year, fields.month, fields.date)) {
35152 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
35153 dt = new Date(baseDate);
35154 datesetter.call(dt, fields.year, fields.month, fields.date);
35155 timesetter.call(dt, fields.hours, fields.minutes,
35156 fields.seconds, fields.milliseconds);
35159 datesetter.call(dt, fields.year, fields.month, fields.date);
35160 timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
35161 fields.seconds || 0, fields.milliseconds || 0);
35169 // Check if date is valid for specific month (and year for February).
35170 // Month: 0 = Jan, 1 = Feb, etc
35171 function isValid(year, month, date) {
35176 if (month === 1 && date > 28) {
35177 return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
35180 if (month === 3 || month === 5 || month === 8 || month === 10) {
35187 function toInt(str) {
35188 return parseInt(str, 10);
35191 this.toTimezone = toTimezone;
35192 this.fromTimezone = fromTimezone;
35193 this.timezoneToOffset = timezoneToOffset;
35194 this.addDateMinutes = addDateMinutes;
35195 this.convertTimezoneToLocal = convertTimezoneToLocal;
35197 function toTimezone(date, timezone) {
35198 return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
35201 function fromTimezone(date, timezone) {
35202 return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
35205 //https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207
35206 function timezoneToOffset(timezone, fallback) {
35207 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
35208 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
35211 function addDateMinutes(date, minutes) {
35212 date = new Date(date.getTime());
35213 date.setMinutes(date.getMinutes() + minutes);
35217 function convertTimezoneToLocal(date, timezone, reverse) {
35218 reverse = reverse ? -1 : 1;
35219 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
35220 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
35224 // Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
35225 // at most one element.
35226 angular.module('ui.bootstrap.isClass', [])
35227 .directive('uibIsClass', [
35229 function ($animate) {
35230 // 11111111 22222222
35231 var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
35232 // 11111111 22222222
35233 var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
35235 var dataPerTracked = {};
35239 compile: function (tElement, tAttrs) {
35240 var linkedScopes = [];
35241 var instances = [];
35242 var expToData = {};
35243 var lastActivated = null;
35244 var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
35245 var onExp = onExpMatches[2];
35246 var expsStr = onExpMatches[1];
35247 var exps = expsStr.split(',');
35251 function linkFn(scope, element, attrs) {
35252 linkedScopes.push(scope);
35258 exps.forEach(function (exp, k) {
35259 addForExp(exp, scope);
35262 scope.$on('$destroy', removeScope);
35265 function addForExp(exp, scope) {
35266 var matches = exp.match(IS_REGEXP);
35267 var clazz = scope.$eval(matches[1]);
35268 var compareWithExp = matches[2];
35269 var data = expToData[exp];
35271 var watchFn = function (compareWithVal) {
35272 var newActivated = null;
35273 instances.some(function (instance) {
35274 var thisVal = instance.scope.$eval(onExp);
35275 if (thisVal === compareWithVal) {
35276 newActivated = instance;
35280 if (data.lastActivated !== newActivated) {
35281 if (data.lastActivated) {
35282 $animate.removeClass(data.lastActivated.element, clazz);
35284 if (newActivated) {
35285 $animate.addClass(newActivated.element, clazz);
35287 data.lastActivated = newActivated;
35290 expToData[exp] = data = {
35291 lastActivated: null,
35294 compareWithExp: compareWithExp,
35295 watcher: scope.$watch(compareWithExp, watchFn)
35298 data.watchFn(scope.$eval(compareWithExp));
35301 function removeScope(e) {
35302 var removedScope = e.targetScope;
35303 var index = linkedScopes.indexOf(removedScope);
35304 linkedScopes.splice(index, 1);
35305 instances.splice(index, 1);
35306 if (linkedScopes.length) {
35307 var newWatchScope = linkedScopes[0];
35308 angular.forEach(expToData, function (data) {
35309 if (data.scope === removedScope) {
35310 data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
35311 data.scope = newWatchScope;
35322 angular.module('ui.bootstrap.position', [])
35325 * A set of utility methods for working with the DOM.
35326 * It is meant to be used where we need to absolute-position elements in
35327 * relation to another element (this is the case for tooltips, popovers,
35328 * typeahead suggestions etc.).
35330 .factory('$uibPosition', ['$document', '$window', function($document, $window) {
35332 * Used by scrollbarWidth() function to cache scrollbar's width.
35333 * Do not access this variable directly, use scrollbarWidth() instead.
35335 var SCROLLBAR_WIDTH;
35336 var OVERFLOW_REGEX = {
35337 normal: /(auto|scroll)/,
35338 hidden: /(auto|scroll|hidden)/
35340 var PLACEMENT_REGEX = {
35341 auto: /\s?auto?\s?/i,
35342 primary: /^(top|bottom|left|right)$/,
35343 secondary: /^(top|bottom|left|right|center)$/,
35344 vertical: /^(top|bottom)$/
35350 * Provides a raw DOM element from a jQuery/jQLite element.
35352 * @param {element} elem - The element to convert.
35354 * @returns {element} A HTML element.
35356 getRawNode: function(elem) {
35357 return elem[0] || elem;
35361 * Provides a parsed number for a style property. Strips
35362 * units and casts invalid numbers to 0.
35364 * @param {string} value - The style value to parse.
35366 * @returns {number} A valid number.
35368 parseStyle: function(value) {
35369 value = parseFloat(value);
35370 return isFinite(value) ? value : 0;
35374 * Provides the closest positioned ancestor.
35376 * @param {element} element - The element to get the offest parent for.
35378 * @returns {element} The closest positioned ancestor.
35380 offsetParent: function(elem) {
35381 elem = this.getRawNode(elem);
35383 var offsetParent = elem.offsetParent || $document[0].documentElement;
35385 function isStaticPositioned(el) {
35386 return ($window.getComputedStyle(el).position || 'static') === 'static';
35389 while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
35390 offsetParent = offsetParent.offsetParent;
35393 return offsetParent || $document[0].documentElement;
35397 * Provides the scrollbar width, concept from TWBS measureScrollbar()
35398 * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
35400 * @returns {number} The width of the browser scollbar.
35402 scrollbarWidth: function() {
35403 if (angular.isUndefined(SCROLLBAR_WIDTH)) {
35404 var scrollElem = angular.element('<div style="position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll;"></div>');
35405 $document.find('body').append(scrollElem);
35406 SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
35407 SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
35408 scrollElem.remove();
35411 return SCROLLBAR_WIDTH;
35415 * Provides the closest scrollable ancestor.
35416 * A port of the jQuery UI scrollParent method:
35417 * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
35419 * @param {element} elem - The element to find the scroll parent of.
35420 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
35421 * default is false.
35423 * @returns {element} A HTML element.
35425 scrollParent: function(elem, includeHidden) {
35426 elem = this.getRawNode(elem);
35428 var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
35429 var documentEl = $document[0].documentElement;
35430 var elemStyle = $window.getComputedStyle(elem);
35431 var excludeStatic = elemStyle.position === 'absolute';
35432 var scrollParent = elem.parentElement || documentEl;
35434 if (scrollParent === documentEl || elemStyle.position === 'fixed') {
35438 while (scrollParent.parentElement && scrollParent !== documentEl) {
35439 var spStyle = $window.getComputedStyle(scrollParent);
35440 if (excludeStatic && spStyle.position !== 'static') {
35441 excludeStatic = false;
35444 if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
35447 scrollParent = scrollParent.parentElement;
35450 return scrollParent;
35454 * Provides read-only equivalent of jQuery's position function:
35455 * http://api.jquery.com/position/ - distance to closest positioned
35456 * ancestor. Does not account for margins by default like jQuery position.
35458 * @param {element} elem - The element to caclulate the position on.
35459 * @param {boolean=} [includeMargins=false] - Should margins be accounted
35460 * for, default is false.
35462 * @returns {object} An object with the following properties:
35464 * <li>**width**: the width of the element</li>
35465 * <li>**height**: the height of the element</li>
35466 * <li>**top**: distance to top edge of offset parent</li>
35467 * <li>**left**: distance to left edge of offset parent</li>
35470 position: function(elem, includeMagins) {
35471 elem = this.getRawNode(elem);
35473 var elemOffset = this.offset(elem);
35474 if (includeMagins) {
35475 var elemStyle = $window.getComputedStyle(elem);
35476 elemOffset.top -= this.parseStyle(elemStyle.marginTop);
35477 elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
35479 var parent = this.offsetParent(elem);
35480 var parentOffset = {top: 0, left: 0};
35482 if (parent !== $document[0].documentElement) {
35483 parentOffset = this.offset(parent);
35484 parentOffset.top += parent.clientTop - parent.scrollTop;
35485 parentOffset.left += parent.clientLeft - parent.scrollLeft;
35489 width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
35490 height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
35491 top: Math.round(elemOffset.top - parentOffset.top),
35492 left: Math.round(elemOffset.left - parentOffset.left)
35497 * Provides read-only equivalent of jQuery's offset function:
35498 * http://api.jquery.com/offset/ - distance to viewport. Does
35499 * not account for borders, margins, or padding on the body
35502 * @param {element} elem - The element to calculate the offset on.
35504 * @returns {object} An object with the following properties:
35506 * <li>**width**: the width of the element</li>
35507 * <li>**height**: the height of the element</li>
35508 * <li>**top**: distance to top edge of viewport</li>
35509 * <li>**right**: distance to bottom edge of viewport</li>
35512 offset: function(elem) {
35513 elem = this.getRawNode(elem);
35515 var elemBCR = elem.getBoundingClientRect();
35517 width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
35518 height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
35519 top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
35520 left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
35525 * Provides offset distance to the closest scrollable ancestor
35526 * or viewport. Accounts for border and scrollbar width.
35528 * Right and bottom dimensions represent the distance to the
35529 * respective edge of the viewport element. If the element
35530 * edge extends beyond the viewport, a negative value will be
35533 * @param {element} elem - The element to get the viewport offset for.
35534 * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
35535 * of the first scrollable element, default is false.
35536 * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
35537 * be accounted for, default is true.
35539 * @returns {object} An object with the following properties:
35541 * <li>**top**: distance to the top content edge of viewport element</li>
35542 * <li>**bottom**: distance to the bottom content edge of viewport element</li>
35543 * <li>**left**: distance to the left content edge of viewport element</li>
35544 * <li>**right**: distance to the right content edge of viewport element</li>
35547 viewportOffset: function(elem, useDocument, includePadding) {
35548 elem = this.getRawNode(elem);
35549 includePadding = includePadding !== false ? true : false;
35551 var elemBCR = elem.getBoundingClientRect();
35552 var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
35554 var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
35555 var offsetParentBCR = offsetParent.getBoundingClientRect();
35557 offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
35558 offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
35559 if (offsetParent === $document[0].documentElement) {
35560 offsetBCR.top += $window.pageYOffset;
35561 offsetBCR.left += $window.pageXOffset;
35563 offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
35564 offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
35566 if (includePadding) {
35567 var offsetParentStyle = $window.getComputedStyle(offsetParent);
35568 offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
35569 offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
35570 offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
35571 offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
35575 top: Math.round(elemBCR.top - offsetBCR.top),
35576 bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
35577 left: Math.round(elemBCR.left - offsetBCR.left),
35578 right: Math.round(offsetBCR.right - elemBCR.right)
35583 * Provides an array of placement values parsed from a placement string.
35584 * Along with the 'auto' indicator, supported placement strings are:
35586 * <li>top: element on top, horizontally centered on host element.</li>
35587 * <li>top-left: element on top, left edge aligned with host element left edge.</li>
35588 * <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
35589 * <li>bottom: element on bottom, horizontally centered on host element.</li>
35590 * <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
35591 * <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
35592 * <li>left: element on left, vertically centered on host element.</li>
35593 * <li>left-top: element on left, top edge aligned with host element top edge.</li>
35594 * <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
35595 * <li>right: element on right, vertically centered on host element.</li>
35596 * <li>right-top: element on right, top edge aligned with host element top edge.</li>
35597 * <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
35599 * A placement string with an 'auto' indicator is expected to be
35600 * space separated from the placement, i.e: 'auto bottom-left' If
35601 * the primary and secondary placement values do not match 'top,
35602 * bottom, left, right' then 'top' will be the primary placement and
35603 * 'center' will be the secondary placement. If 'auto' is passed, true
35604 * will be returned as the 3rd value of the array.
35606 * @param {string} placement - The placement string to parse.
35608 * @returns {array} An array with the following values
35610 * <li>**[0]**: The primary placement.</li>
35611 * <li>**[1]**: The secondary placement.</li>
35612 * <li>**[2]**: If auto is passed: true, else undefined.</li>
35615 parsePlacement: function(placement) {
35616 var autoPlace = PLACEMENT_REGEX.auto.test(placement);
35618 placement = placement.replace(PLACEMENT_REGEX.auto, '');
35621 placement = placement.split('-');
35623 placement[0] = placement[0] || 'top';
35624 if (!PLACEMENT_REGEX.primary.test(placement[0])) {
35625 placement[0] = 'top';
35628 placement[1] = placement[1] || 'center';
35629 if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
35630 placement[1] = 'center';
35634 placement[2] = true;
35636 placement[2] = false;
35643 * Provides coordinates for an element to be positioned relative to
35644 * another element. Passing 'auto' as part of the placement parameter
35645 * will enable smart placement - where the element fits. i.e:
35646 * 'auto left-top' will check to see if there is enough space to the left
35647 * of the hostElem to fit the targetElem, if not place right (same for secondary
35648 * top placement). Available space is calculated using the viewportOffset
35651 * @param {element} hostElem - The element to position against.
35652 * @param {element} targetElem - The element to position.
35653 * @param {string=} [placement=top] - The placement for the targetElem,
35654 * default is 'top'. 'center' is assumed as secondary placement for
35655 * 'top', 'left', 'right', and 'bottom' placements. Available placements are:
35658 * <li>top-right</li>
35659 * <li>top-left</li>
35661 * <li>bottom-left</li>
35662 * <li>bottom-right</li>
35664 * <li>left-top</li>
35665 * <li>left-bottom</li>
35667 * <li>right-top</li>
35668 * <li>right-bottom</li>
35670 * @param {boolean=} [appendToBody=false] - Should the top and left values returned
35671 * be calculated from the body element, default is false.
35673 * @returns {object} An object with the following properties:
35675 * <li>**top**: Value for targetElem top.</li>
35676 * <li>**left**: Value for targetElem left.</li>
35677 * <li>**placement**: The resolved placement.</li>
35680 positionElements: function(hostElem, targetElem, placement, appendToBody) {
35681 hostElem = this.getRawNode(hostElem);
35682 targetElem = this.getRawNode(targetElem);
35684 // need to read from prop to support tests.
35685 var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
35686 var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
35688 placement = this.parsePlacement(placement);
35690 var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
35691 var targetElemPos = {top: 0, left: 0, placement: ''};
35693 if (placement[2]) {
35694 var viewportOffset = this.viewportOffset(hostElem);
35696 var targetElemStyle = $window.getComputedStyle(targetElem);
35697 var adjustedSize = {
35698 width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
35699 height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
35702 placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
35703 placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
35704 placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
35705 placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
35708 placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
35709 placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
35710 placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
35711 placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
35714 if (placement[1] === 'center') {
35715 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35716 var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
35717 if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
35718 placement[1] = 'left';
35719 } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
35720 placement[1] = 'right';
35723 var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
35724 if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
35725 placement[1] = 'top';
35726 } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
35727 placement[1] = 'bottom';
35733 switch (placement[0]) {
35735 targetElemPos.top = hostElemPos.top - targetHeight;
35738 targetElemPos.top = hostElemPos.top + hostElemPos.height;
35741 targetElemPos.left = hostElemPos.left - targetWidth;
35744 targetElemPos.left = hostElemPos.left + hostElemPos.width;
35748 switch (placement[1]) {
35750 targetElemPos.top = hostElemPos.top;
35753 targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
35756 targetElemPos.left = hostElemPos.left;
35759 targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
35762 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35763 targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
35765 targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
35770 targetElemPos.top = Math.round(targetElemPos.top);
35771 targetElemPos.left = Math.round(targetElemPos.left);
35772 targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
35774 return targetElemPos;
35778 * Provides a way for positioning tooltip & dropdown
35779 * arrows when using placement options beyond the standard
35780 * left, right, top, or bottom.
35782 * @param {element} elem - The tooltip/dropdown element.
35783 * @param {string} placement - The placement for the elem.
35785 positionArrow: function(elem, placement) {
35786 elem = this.getRawNode(elem);
35788 var isTooltip = true;
35790 var innerElem = elem.querySelector('.tooltip-inner');
35793 innerElem = elem.querySelector('.popover-inner');
35799 var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
35804 placement = this.parsePlacement(placement);
35805 if (placement[1] === 'center') {
35806 // no adjustment necessary - just reset styles
35807 angular.element(arrowElem).css({top: '', bottom: '', right: '', left: '', margin: ''});
35811 var borderProp = 'border-' + placement[0] + '-width';
35812 var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
35814 var borderRadiusProp = 'border-';
35815 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35816 borderRadiusProp += placement[0] + '-' + placement[1];
35818 borderRadiusProp += placement[1] + '-' + placement[0];
35820 borderRadiusProp += '-radius';
35821 var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
35831 switch (placement[0]) {
35833 arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
35836 arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
35839 arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
35842 arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
35846 arrowCss[placement[1]] = borderRadius;
35848 angular.element(arrowElem).css(arrowCss);
35853 angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass', 'ui.bootstrap.position'])
35855 .value('$datepickerSuppressError', false)
35857 .constant('uibDatepickerConfig', {
35859 formatMonth: 'MMMM',
35860 formatYear: 'yyyy',
35861 formatDayHeader: 'EEE',
35862 formatDayTitle: 'MMMM yyyy',
35863 formatMonthTitle: 'yyyy',
35864 datepickerMode: 'day',
35873 shortcutPropagation: false,
35877 .controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', 'uibDateParser',
35878 function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, dateParser) {
35880 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
35881 ngModelOptions = {};
35884 this.modes = ['day', 'month', 'year'];
35886 // Interpolated configuration attributes
35887 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle'], function(key) {
35888 self[key] = angular.isDefined($attrs[key]) ? $interpolate($attrs[key])($scope.$parent) : datepickerConfig[key];
35891 // Evaled configuration attributes
35892 angular.forEach(['showWeeks', 'startingDay', 'yearRows', 'yearColumns', 'shortcutPropagation'], function(key) {
35893 self[key] = angular.isDefined($attrs[key]) ? $scope.$parent.$eval($attrs[key]) : datepickerConfig[key];
35896 // Watchable date attributes
35897 angular.forEach(['minDate', 'maxDate'], function(key) {
35899 $scope.$parent.$watch($attrs[key], function(value) {
35900 self[key] = value ? angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium')) : null;
35901 self.refreshView();
35904 self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null;
35908 angular.forEach(['minMode', 'maxMode'], function(key) {
35910 $scope.$parent.$watch($attrs[key], function(value) {
35911 self[key] = $scope[key] = angular.isDefined(value) ? value : $attrs[key];
35912 if (key === 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key]) ||
35913 key === 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key])) {
35914 $scope.datepickerMode = self[key];
35918 self[key] = $scope[key] = datepickerConfig[key] || null;
35922 $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
35923 $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
35925 if (angular.isDefined($attrs.initDate)) {
35926 this.activeDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone) || new Date();
35927 $scope.$parent.$watch($attrs.initDate, function(initDate) {
35928 if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
35929 self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
35930 self.refreshView();
35934 this.activeDate = new Date();
35937 $scope.disabled = angular.isDefined($attrs.disabled) || false;
35938 if (angular.isDefined($attrs.ngDisabled)) {
35939 $scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
35940 $scope.disabled = disabled;
35941 self.refreshView();
35945 $scope.isActive = function(dateObject) {
35946 if (self.compare(dateObject.date, self.activeDate) === 0) {
35947 $scope.activeDateId = dateObject.uid;
35953 this.init = function(ngModelCtrl_) {
35954 ngModelCtrl = ngModelCtrl_;
35955 ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions;
35957 if (ngModelCtrl.$modelValue) {
35958 this.activeDate = ngModelCtrl.$modelValue;
35961 ngModelCtrl.$render = function() {
35966 this.render = function() {
35967 if (ngModelCtrl.$viewValue) {
35968 var date = new Date(ngModelCtrl.$viewValue),
35969 isValid = !isNaN(date);
35972 this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
35973 } else if (!$datepickerSuppressError) {
35974 $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
35977 this.refreshView();
35980 this.refreshView = function() {
35981 if (this.element) {
35982 $scope.selectedDt = null;
35983 this._refreshView();
35984 if ($scope.activeDt) {
35985 $scope.activeDateId = $scope.activeDt.uid;
35988 var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
35989 date = dateParser.fromTimezone(date, ngModelOptions.timezone);
35990 ngModelCtrl.$setValidity('dateDisabled', !date ||
35991 this.element && !this.isDisabled(date));
35995 this.createDateObject = function(date, format) {
35996 var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
35997 model = dateParser.fromTimezone(model, ngModelOptions.timezone);
36000 label: dateFilter(date, format),
36001 selected: model && this.compare(date, model) === 0,
36002 disabled: this.isDisabled(date),
36003 current: this.compare(date, new Date()) === 0,
36004 customClass: this.customClass(date) || null
36007 if (model && this.compare(date, model) === 0) {
36008 $scope.selectedDt = dt;
36011 if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
36012 $scope.activeDt = dt;
36018 this.isDisabled = function(date) {
36019 return $scope.disabled ||
36020 this.minDate && this.compare(date, this.minDate) < 0 ||
36021 this.maxDate && this.compare(date, this.maxDate) > 0 ||
36022 $attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
36025 this.customClass = function(date) {
36026 return $scope.customClass({date: date, mode: $scope.datepickerMode});
36029 // Split array into smaller arrays
36030 this.split = function(arr, size) {
36032 while (arr.length > 0) {
36033 arrays.push(arr.splice(0, size));
36038 $scope.select = function(date) {
36039 if ($scope.datepickerMode === self.minMode) {
36040 var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
36041 dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
36042 dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
36043 ngModelCtrl.$setViewValue(dt);
36044 ngModelCtrl.$render();
36046 self.activeDate = date;
36047 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
36051 $scope.move = function(direction) {
36052 var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
36053 month = self.activeDate.getMonth() + direction * (self.step.months || 0);
36054 self.activeDate.setFullYear(year, month, 1);
36055 self.refreshView();
36058 $scope.toggleMode = function(direction) {
36059 direction = direction || 1;
36061 if ($scope.datepickerMode === self.maxMode && direction === 1 ||
36062 $scope.datepickerMode === self.minMode && direction === -1) {
36066 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
36069 // Key event mapper
36070 $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
36072 var focusElement = function() {
36073 self.element[0].focus();
36076 // Listen for focus requests from popup directive
36077 $scope.$on('uib:datepicker.focus', focusElement);
36079 $scope.keydown = function(evt) {
36080 var key = $scope.keys[evt.which];
36082 if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
36086 evt.preventDefault();
36087 if (!self.shortcutPropagation) {
36088 evt.stopPropagation();
36091 if (key === 'enter' || key === 'space') {
36092 if (self.isDisabled(self.activeDate)) {
36093 return; // do nothing
36095 $scope.select(self.activeDate);
36096 } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
36097 $scope.toggleMode(key === 'up' ? 1 : -1);
36099 self.handleKeyDown(key, evt);
36100 self.refreshView();
36105 .controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36106 var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
36108 this.step = { months: 1 };
36109 this.element = $element;
36110 function getDaysInMonth(year, month) {
36111 return month === 1 && year % 4 === 0 &&
36112 (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
36115 this.init = function(ctrl) {
36116 angular.extend(ctrl, this);
36117 scope.showWeeks = ctrl.showWeeks;
36118 ctrl.refreshView();
36121 this.getDates = function(startDate, n) {
36122 var dates = new Array(n), current = new Date(startDate), i = 0, date;
36124 date = new Date(current);
36126 current.setDate(current.getDate() + 1);
36131 this._refreshView = function() {
36132 var year = this.activeDate.getFullYear(),
36133 month = this.activeDate.getMonth(),
36134 firstDayOfMonth = new Date(this.activeDate);
36136 firstDayOfMonth.setFullYear(year, month, 1);
36138 var difference = this.startingDay - firstDayOfMonth.getDay(),
36139 numDisplayedFromPreviousMonth = difference > 0 ?
36140 7 - difference : - difference,
36141 firstDate = new Date(firstDayOfMonth);
36143 if (numDisplayedFromPreviousMonth > 0) {
36144 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
36147 // 42 is the number of days on a six-week calendar
36148 var days = this.getDates(firstDate, 42);
36149 for (var i = 0; i < 42; i ++) {
36150 days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
36151 secondary: days[i].getMonth() !== month,
36152 uid: scope.uniqueId + '-' + i
36156 scope.labels = new Array(7);
36157 for (var j = 0; j < 7; j++) {
36158 scope.labels[j] = {
36159 abbr: dateFilter(days[j].date, this.formatDayHeader),
36160 full: dateFilter(days[j].date, 'EEEE')
36164 scope.title = dateFilter(this.activeDate, this.formatDayTitle);
36165 scope.rows = this.split(days, 7);
36167 if (scope.showWeeks) {
36168 scope.weekNumbers = [];
36169 var thursdayIndex = (4 + 7 - this.startingDay) % 7,
36170 numWeeks = scope.rows.length;
36171 for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
36172 scope.weekNumbers.push(
36173 getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
36178 this.compare = function(date1, date2) {
36179 var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
36180 var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
36181 _date1.setFullYear(date1.getFullYear());
36182 _date2.setFullYear(date2.getFullYear());
36183 return _date1 - _date2;
36186 function getISO8601WeekNumber(date) {
36187 var checkDate = new Date(date);
36188 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
36189 var time = checkDate.getTime();
36190 checkDate.setMonth(0); // Compare with Jan 1
36191 checkDate.setDate(1);
36192 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
36195 this.handleKeyDown = function(key, evt) {
36196 var date = this.activeDate.getDate();
36198 if (key === 'left') {
36200 } else if (key === 'up') {
36202 } else if (key === 'right') {
36204 } else if (key === 'down') {
36206 } else if (key === 'pageup' || key === 'pagedown') {
36207 var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
36208 this.activeDate.setMonth(month, 1);
36209 date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
36210 } else if (key === 'home') {
36212 } else if (key === 'end') {
36213 date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
36215 this.activeDate.setDate(date);
36219 .controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36220 this.step = { years: 1 };
36221 this.element = $element;
36223 this.init = function(ctrl) {
36224 angular.extend(ctrl, this);
36225 ctrl.refreshView();
36228 this._refreshView = function() {
36229 var months = new Array(12),
36230 year = this.activeDate.getFullYear(),
36233 for (var i = 0; i < 12; i++) {
36234 date = new Date(this.activeDate);
36235 date.setFullYear(year, i, 1);
36236 months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
36237 uid: scope.uniqueId + '-' + i
36241 scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
36242 scope.rows = this.split(months, 3);
36245 this.compare = function(date1, date2) {
36246 var _date1 = new Date(date1.getFullYear(), date1.getMonth());
36247 var _date2 = new Date(date2.getFullYear(), date2.getMonth());
36248 _date1.setFullYear(date1.getFullYear());
36249 _date2.setFullYear(date2.getFullYear());
36250 return _date1 - _date2;
36253 this.handleKeyDown = function(key, evt) {
36254 var date = this.activeDate.getMonth();
36256 if (key === 'left') {
36258 } else if (key === 'up') {
36260 } else if (key === 'right') {
36262 } else if (key === 'down') {
36264 } else if (key === 'pageup' || key === 'pagedown') {
36265 var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
36266 this.activeDate.setFullYear(year);
36267 } else if (key === 'home') {
36269 } else if (key === 'end') {
36272 this.activeDate.setMonth(date);
36276 .controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36277 var columns, range;
36278 this.element = $element;
36280 function getStartingYear(year) {
36281 return parseInt((year - 1) / range, 10) * range + 1;
36284 this.yearpickerInit = function() {
36285 columns = this.yearColumns;
36286 range = this.yearRows * columns;
36287 this.step = { years: range };
36290 this._refreshView = function() {
36291 var years = new Array(range), date;
36293 for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
36294 date = new Date(this.activeDate);
36295 date.setFullYear(start + i, 0, 1);
36296 years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
36297 uid: scope.uniqueId + '-' + i
36301 scope.title = [years[0].label, years[range - 1].label].join(' - ');
36302 scope.rows = this.split(years, columns);
36303 scope.columns = columns;
36306 this.compare = function(date1, date2) {
36307 return date1.getFullYear() - date2.getFullYear();
36310 this.handleKeyDown = function(key, evt) {
36311 var date = this.activeDate.getFullYear();
36313 if (key === 'left') {
36315 } else if (key === 'up') {
36316 date = date - columns;
36317 } else if (key === 'right') {
36319 } else if (key === 'down') {
36320 date = date + columns;
36321 } else if (key === 'pageup' || key === 'pagedown') {
36322 date += (key === 'pageup' ? - 1 : 1) * range;
36323 } else if (key === 'home') {
36324 date = getStartingYear(this.activeDate.getFullYear());
36325 } else if (key === 'end') {
36326 date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
36328 this.activeDate.setFullYear(date);
36332 .directive('uibDatepicker', function() {
36335 templateUrl: function(element, attrs) {
36336 return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
36339 datepickerMode: '=?',
36342 shortcutPropagation: '&?'
36344 require: ['uibDatepicker', '^ngModel'],
36345 controller: 'UibDatepickerController',
36346 controllerAs: 'datepicker',
36347 link: function(scope, element, attrs, ctrls) {
36348 var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
36350 datepickerCtrl.init(ngModelCtrl);
36355 .directive('uibDaypicker', function() {
36358 templateUrl: function(element, attrs) {
36359 return attrs.templateUrl || 'uib/template/datepicker/day.html';
36361 require: ['^uibDatepicker', 'uibDaypicker'],
36362 controller: 'UibDaypickerController',
36363 link: function(scope, element, attrs, ctrls) {
36364 var datepickerCtrl = ctrls[0],
36365 daypickerCtrl = ctrls[1];
36367 daypickerCtrl.init(datepickerCtrl);
36372 .directive('uibMonthpicker', function() {
36375 templateUrl: function(element, attrs) {
36376 return attrs.templateUrl || 'uib/template/datepicker/month.html';
36378 require: ['^uibDatepicker', 'uibMonthpicker'],
36379 controller: 'UibMonthpickerController',
36380 link: function(scope, element, attrs, ctrls) {
36381 var datepickerCtrl = ctrls[0],
36382 monthpickerCtrl = ctrls[1];
36384 monthpickerCtrl.init(datepickerCtrl);
36389 .directive('uibYearpicker', function() {
36392 templateUrl: function(element, attrs) {
36393 return attrs.templateUrl || 'uib/template/datepicker/year.html';
36395 require: ['^uibDatepicker', 'uibYearpicker'],
36396 controller: 'UibYearpickerController',
36397 link: function(scope, element, attrs, ctrls) {
36398 var ctrl = ctrls[0];
36399 angular.extend(ctrl, ctrls[1]);
36400 ctrl.yearpickerInit();
36402 ctrl.refreshView();
36407 .constant('uibDatepickerPopupConfig', {
36408 datepickerPopup: 'yyyy-MM-dd',
36409 datepickerPopupTemplateUrl: 'uib/template/datepicker/popup.html',
36410 datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
36412 date: 'yyyy-MM-dd',
36413 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
36416 currentText: 'Today',
36417 clearText: 'Clear',
36419 closeOnDateSelection: true,
36420 appendToBody: false,
36421 showButtonBar: true,
36423 altInputFormats: []
36426 .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig',
36427 function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig) {
36430 isHtml5DateInput = false;
36431 var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
36432 datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
36433 ngModel, ngModelOptions, $popup, altInputFormats;
36435 scope.watchData = {};
36437 this.init = function(_ngModel_) {
36438 ngModel = _ngModel_;
36439 ngModelOptions = _ngModel_.$options || datepickerConfig.ngModelOptions;
36440 closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
36441 appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
36442 onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
36443 datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl;
36444 datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
36445 altInputFormats = angular.isDefined(attrs.altInputFormats) ? scope.$parent.$eval(attrs.altInputFormats) : datepickerPopupConfig.altInputFormats;
36447 scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
36449 if (datepickerPopupConfig.html5Types[attrs.type]) {
36450 dateFormat = datepickerPopupConfig.html5Types[attrs.type];
36451 isHtml5DateInput = true;
36453 dateFormat = attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
36454 attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
36455 var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
36456 // Invalidate the $modelValue to ensure that formatters re-run
36457 // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
36458 if (newDateFormat !== dateFormat) {
36459 dateFormat = newDateFormat;
36460 ngModel.$modelValue = null;
36463 throw new Error('uibDatepickerPopup must have a date format specified.');
36470 throw new Error('uibDatepickerPopup must have a date format specified.');
36473 if (isHtml5DateInput && attrs.uibDatepickerPopup) {
36474 throw new Error('HTML5 date input types do not support custom formats.');
36477 // popup element used to display calendar
36478 popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
36479 scope.ngModelOptions = angular.copy(ngModelOptions);
36480 scope.ngModelOptions.timezone = null;
36482 'ng-model': 'date',
36483 'ng-model-options': 'ngModelOptions',
36484 'ng-change': 'dateSelection(date)',
36485 'template-url': datepickerPopupTemplateUrl
36488 // datepicker element
36489 datepickerEl = angular.element(popupEl.children()[0]);
36490 datepickerEl.attr('template-url', datepickerTemplateUrl);
36492 if (isHtml5DateInput) {
36493 if (attrs.type === 'month') {
36494 datepickerEl.attr('datepicker-mode', '"month"');
36495 datepickerEl.attr('min-mode', 'month');
36499 if (attrs.datepickerOptions) {
36500 var options = scope.$parent.$eval(attrs.datepickerOptions);
36501 if (options && options.initDate) {
36502 scope.initDate = dateParser.fromTimezone(options.initDate, ngModelOptions.timezone);
36503 datepickerEl.attr('init-date', 'initDate');
36504 delete options.initDate;
36506 angular.forEach(options, function(value, option) {
36507 datepickerEl.attr(cameltoDash(option), value);
36511 angular.forEach(['minMode', 'maxMode'], function(key) {
36513 scope.$parent.$watch(function() { return attrs[key]; }, function(value) {
36514 scope.watchData[key] = value;
36516 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36520 angular.forEach(['datepickerMode', 'shortcutPropagation'], function(key) {
36522 var getAttribute = $parse(attrs[key]);
36525 return getAttribute(scope.$parent);
36529 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36531 // Propagate changes from datepicker to outside
36532 if (key === 'datepickerMode') {
36533 var setAttribute = getAttribute.assign;
36534 propConfig.set = function(v) {
36535 setAttribute(scope.$parent, v);
36539 Object.defineProperty(scope.watchData, key, propConfig);
36543 angular.forEach(['minDate', 'maxDate', 'initDate'], function(key) {
36545 var getAttribute = $parse(attrs[key]);
36547 scope.$parent.$watch(getAttribute, function(value) {
36548 if (key === 'minDate' || key === 'maxDate') {
36549 cache[key] = angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium'));
36552 scope.watchData[key] = cache[key] || dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
36555 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36559 if (attrs.dateDisabled) {
36560 datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
36563 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', 'showWeeks', 'startingDay', 'yearRows', 'yearColumns'], function(key) {
36564 if (angular.isDefined(attrs[key])) {
36565 datepickerEl.attr(cameltoDash(key), attrs[key]);
36569 if (attrs.customClass) {
36570 datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
36573 if (!isHtml5DateInput) {
36574 // Internal API to maintain the correct ng-invalid-[key] class
36575 ngModel.$$parserName = 'date';
36576 ngModel.$validators.date = validator;
36577 ngModel.$parsers.unshift(parseDate);
36578 ngModel.$formatters.push(function(value) {
36579 if (ngModel.$isEmpty(value)) {
36580 scope.date = value;
36583 scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
36584 return dateFilter(scope.date, dateFormat);
36587 ngModel.$formatters.push(function(value) {
36588 scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
36593 // Detect changes in the view from the text box
36594 ngModel.$viewChangeListeners.push(function() {
36595 scope.date = parseDateString(ngModel.$viewValue);
36598 element.bind('keydown', inputKeydownBind);
36600 $popup = $compile(popupEl)(scope);
36601 // Prevent jQuery cache memory leak (template is now redundant after linking)
36604 if (appendToBody) {
36605 $document.find('body').append($popup);
36607 element.after($popup);
36610 scope.$on('$destroy', function() {
36611 if (scope.isOpen === true) {
36612 if (!$rootScope.$$phase) {
36613 scope.$apply(function() {
36614 scope.isOpen = false;
36620 element.unbind('keydown', inputKeydownBind);
36621 $document.unbind('click', documentClickBind);
36625 scope.getText = function(key) {
36626 return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
36629 scope.isDisabled = function(date) {
36630 if (date === 'today') {
36634 return scope.watchData.minDate && scope.compare(date, cache.minDate) < 0 ||
36635 scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0;
36638 scope.compare = function(date1, date2) {
36639 return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
36643 scope.dateSelection = function(dt) {
36644 if (angular.isDefined(dt)) {
36647 var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
36649 ngModel.$setViewValue(date);
36651 if (closeOnDateSelection) {
36652 scope.isOpen = false;
36653 element[0].focus();
36657 scope.keydown = function(evt) {
36658 if (evt.which === 27) {
36659 evt.stopPropagation();
36660 scope.isOpen = false;
36661 element[0].focus();
36665 scope.select = function(date) {
36666 if (date === 'today') {
36667 var today = new Date();
36668 if (angular.isDate(scope.date)) {
36669 date = new Date(scope.date);
36670 date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
36672 date = new Date(today.setHours(0, 0, 0, 0));
36675 scope.dateSelection(date);
36678 scope.close = function() {
36679 scope.isOpen = false;
36680 element[0].focus();
36683 scope.disabled = angular.isDefined(attrs.disabled) || false;
36684 if (attrs.ngDisabled) {
36685 scope.$parent.$watch($parse(attrs.ngDisabled), function(disabled) {
36686 scope.disabled = disabled;
36690 scope.$watch('isOpen', function(value) {
36692 if (!scope.disabled) {
36693 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
36694 scope.position.top = scope.position.top + element.prop('offsetHeight');
36696 $timeout(function() {
36698 scope.$broadcast('uib:datepicker.focus');
36700 $document.bind('click', documentClickBind);
36703 scope.isOpen = false;
36706 $document.unbind('click', documentClickBind);
36710 function cameltoDash(string) {
36711 return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
36714 function parseDateString(viewValue) {
36715 var date = dateParser.parse(viewValue, dateFormat, scope.date);
36717 for (var i = 0; i < altInputFormats.length; i++) {
36718 date = dateParser.parse(viewValue, altInputFormats[i], scope.date);
36719 if (!isNaN(date)) {
36727 function parseDate(viewValue) {
36728 if (angular.isNumber(viewValue)) {
36729 // presumably timestamp to date object
36730 viewValue = new Date(viewValue);
36737 if (angular.isDate(viewValue) && !isNaN(viewValue)) {
36741 if (angular.isString(viewValue)) {
36742 var date = parseDateString(viewValue);
36743 if (!isNaN(date)) {
36744 return dateParser.toTimezone(date, ngModelOptions.timezone);
36748 return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
36751 function validator(modelValue, viewValue) {
36752 var value = modelValue || viewValue;
36754 if (!attrs.ngRequired && !value) {
36758 if (angular.isNumber(value)) {
36759 value = new Date(value);
36766 if (angular.isDate(value) && !isNaN(value)) {
36770 if (angular.isString(value)) {
36771 return !isNaN(parseDateString(viewValue));
36777 function documentClickBind(event) {
36778 if (!scope.isOpen && scope.disabled) {
36782 var popup = $popup[0];
36783 var dpContainsTarget = element[0].contains(event.target);
36784 // The popup node may not be an element node
36785 // In some browsers (IE) only element nodes have the 'contains' function
36786 var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
36787 if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
36788 scope.$apply(function() {
36789 scope.isOpen = false;
36794 function inputKeydownBind(evt) {
36795 if (evt.which === 27 && scope.isOpen) {
36796 evt.preventDefault();
36797 evt.stopPropagation();
36798 scope.$apply(function() {
36799 scope.isOpen = false;
36801 element[0].focus();
36802 } else if (evt.which === 40 && !scope.isOpen) {
36803 evt.preventDefault();
36804 evt.stopPropagation();
36805 scope.$apply(function() {
36806 scope.isOpen = true;
36812 .directive('uibDatepickerPopup', function() {
36814 require: ['ngModel', 'uibDatepickerPopup'],
36815 controller: 'UibDatepickerPopupController',
36824 link: function(scope, element, attrs, ctrls) {
36825 var ngModel = ctrls[0],
36828 ctrl.init(ngModel);
36833 .directive('uibDatepickerPopupWrap', function() {
36837 templateUrl: function(element, attrs) {
36838 return attrs.templateUrl || 'uib/template/datepicker/popup.html';
36843 angular.module('ui.bootstrap.debounce', [])
36845 * A helper, internal service that debounces a function
36847 .factory('$$debounce', ['$timeout', function($timeout) {
36848 return function(callback, debounceTime) {
36849 var timeoutPromise;
36851 return function() {
36853 var args = Array.prototype.slice.call(arguments);
36854 if (timeoutPromise) {
36855 $timeout.cancel(timeoutPromise);
36858 timeoutPromise = $timeout(function() {
36859 callback.apply(self, args);
36865 angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
36867 .constant('uibDropdownConfig', {
36868 appendToOpenClass: 'uib-dropdown-open',
36872 .service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
36873 var openScope = null;
36875 this.open = function(dropdownScope) {
36877 $document.on('click', closeDropdown);
36878 $document.on('keydown', keybindFilter);
36881 if (openScope && openScope !== dropdownScope) {
36882 openScope.isOpen = false;
36885 openScope = dropdownScope;
36888 this.close = function(dropdownScope) {
36889 if (openScope === dropdownScope) {
36891 $document.off('click', closeDropdown);
36892 $document.off('keydown', keybindFilter);
36896 var closeDropdown = function(evt) {
36897 // This method may still be called during the same mouse event that
36898 // unbound this event handler. So check openScope before proceeding.
36899 if (!openScope) { return; }
36901 if (evt && openScope.getAutoClose() === 'disabled') { return; }
36903 if (evt && evt.which === 3) { return; }
36905 var toggleElement = openScope.getToggleElement();
36906 if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
36910 var dropdownElement = openScope.getDropdownElement();
36911 if (evt && openScope.getAutoClose() === 'outsideClick' &&
36912 dropdownElement && dropdownElement[0].contains(evt.target)) {
36916 openScope.isOpen = false;
36918 if (!$rootScope.$$phase) {
36919 openScope.$apply();
36923 var keybindFilter = function(evt) {
36924 if (evt.which === 27) {
36925 openScope.focusToggleElement();
36927 } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) {
36928 evt.preventDefault();
36929 evt.stopPropagation();
36930 openScope.focusDropdownEntry(evt.which);
36935 .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) {
36937 scope = $scope.$new(), // create a child scope so we are not polluting original one
36939 appendToOpenClass = dropdownConfig.appendToOpenClass,
36940 openClass = dropdownConfig.openClass,
36942 setIsOpen = angular.noop,
36943 toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
36944 appendToBody = false,
36946 keynavEnabled = false,
36947 selectedOption = null,
36948 body = $document.find('body');
36950 $element.addClass('dropdown');
36952 this.init = function() {
36953 if ($attrs.isOpen) {
36954 getIsOpen = $parse($attrs.isOpen);
36955 setIsOpen = getIsOpen.assign;
36957 $scope.$watch(getIsOpen, function(value) {
36958 scope.isOpen = !!value;
36962 if (angular.isDefined($attrs.dropdownAppendTo)) {
36963 var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
36965 appendTo = angular.element(appendToEl);
36969 appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
36970 keynavEnabled = angular.isDefined($attrs.keyboardNav);
36972 if (appendToBody && !appendTo) {
36976 if (appendTo && self.dropdownMenu) {
36977 appendTo.append(self.dropdownMenu);
36978 $element.on('$destroy', function handleDestroyEvent() {
36979 self.dropdownMenu.remove();
36984 this.toggle = function(open) {
36985 return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
36988 // Allow other directives to watch status
36989 this.isOpen = function() {
36990 return scope.isOpen;
36993 scope.getToggleElement = function() {
36994 return self.toggleElement;
36997 scope.getAutoClose = function() {
36998 return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
37001 scope.getElement = function() {
37005 scope.isKeynavEnabled = function() {
37006 return keynavEnabled;
37009 scope.focusDropdownEntry = function(keyCode) {
37010 var elems = self.dropdownMenu ? //If append to body is used.
37011 angular.element(self.dropdownMenu).find('a') :
37012 $element.find('ul').eq(0).find('a');
37016 if (!angular.isNumber(self.selectedOption)) {
37017 self.selectedOption = 0;
37019 self.selectedOption = self.selectedOption === elems.length - 1 ?
37020 self.selectedOption :
37021 self.selectedOption + 1;
37026 if (!angular.isNumber(self.selectedOption)) {
37027 self.selectedOption = elems.length - 1;
37029 self.selectedOption = self.selectedOption === 0 ?
37030 0 : self.selectedOption - 1;
37035 elems[self.selectedOption].focus();
37038 scope.getDropdownElement = function() {
37039 return self.dropdownMenu;
37042 scope.focusToggleElement = function() {
37043 if (self.toggleElement) {
37044 self.toggleElement[0].focus();
37048 scope.$watch('isOpen', function(isOpen, wasOpen) {
37049 if (appendTo && self.dropdownMenu) {
37050 var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
37055 top: pos.top + 'px',
37056 display: isOpen ? 'block' : 'none'
37059 rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
37061 css.left = pos.left + 'px';
37062 css.right = 'auto';
37065 css.right = window.innerWidth -
37066 (pos.left + $element.prop('offsetWidth')) + 'px';
37069 // Need to adjust our positioning to be relative to the appendTo container
37070 // if it's not the body element
37071 if (!appendToBody) {
37072 var appendOffset = $position.offset(appendTo);
37074 css.top = pos.top - appendOffset.top + 'px';
37077 css.left = pos.left - appendOffset.left + 'px';
37079 css.right = window.innerWidth -
37080 (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
37084 self.dropdownMenu.css(css);
37087 var openContainer = appendTo ? appendTo : $element;
37089 $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
37090 if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
37091 toggleInvoker($scope, { open: !!isOpen });
37096 if (self.dropdownMenuTemplateUrl) {
37097 $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
37098 templateScope = scope.$new();
37099 $compile(tplContent.trim())(templateScope, function(dropdownElement) {
37100 var newEl = dropdownElement;
37101 self.dropdownMenu.replaceWith(newEl);
37102 self.dropdownMenu = newEl;
37107 scope.focusToggleElement();
37108 uibDropdownService.open(scope);
37110 if (self.dropdownMenuTemplateUrl) {
37111 if (templateScope) {
37112 templateScope.$destroy();
37114 var newEl = angular.element('<ul class="dropdown-menu"></ul>');
37115 self.dropdownMenu.replaceWith(newEl);
37116 self.dropdownMenu = newEl;
37119 uibDropdownService.close(scope);
37120 self.selectedOption = null;
37123 if (angular.isFunction(setIsOpen)) {
37124 setIsOpen($scope, isOpen);
37128 $scope.$on('$locationChangeSuccess', function() {
37129 if (scope.getAutoClose() !== 'disabled') {
37130 scope.isOpen = false;
37135 .directive('uibDropdown', function() {
37137 controller: 'UibDropdownController',
37138 link: function(scope, element, attrs, dropdownCtrl) {
37139 dropdownCtrl.init();
37144 .directive('uibDropdownMenu', function() {
37147 require: '?^uibDropdown',
37148 link: function(scope, element, attrs, dropdownCtrl) {
37149 if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
37153 element.addClass('dropdown-menu');
37155 var tplUrl = attrs.templateUrl;
37157 dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
37160 if (!dropdownCtrl.dropdownMenu) {
37161 dropdownCtrl.dropdownMenu = element;
37167 .directive('uibDropdownToggle', function() {
37169 require: '?^uibDropdown',
37170 link: function(scope, element, attrs, dropdownCtrl) {
37171 if (!dropdownCtrl) {
37175 element.addClass('dropdown-toggle');
37177 dropdownCtrl.toggleElement = element;
37179 var toggleDropdown = function(event) {
37180 event.preventDefault();
37182 if (!element.hasClass('disabled') && !attrs.disabled) {
37183 scope.$apply(function() {
37184 dropdownCtrl.toggle();
37189 element.bind('click', toggleDropdown);
37192 element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
37193 scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
37194 element.attr('aria-expanded', !!isOpen);
37197 scope.$on('$destroy', function() {
37198 element.unbind('click', toggleDropdown);
37204 angular.module('ui.bootstrap.stackedMap', [])
37206 * A helper, internal data structure that acts as a map but also allows getting / removing
37207 * elements in the LIFO order
37209 .factory('$$stackedMap', function() {
37211 createNew: function() {
37215 add: function(key, value) {
37221 get: function(key) {
37222 for (var i = 0; i < stack.length; i++) {
37223 if (key === stack[i].key) {
37230 for (var i = 0; i < stack.length; i++) {
37231 keys.push(stack[i].key);
37236 return stack[stack.length - 1];
37238 remove: function(key) {
37240 for (var i = 0; i < stack.length; i++) {
37241 if (key === stack[i].key) {
37246 return stack.splice(idx, 1)[0];
37248 removeTop: function() {
37249 return stack.splice(stack.length - 1, 1)[0];
37251 length: function() {
37252 return stack.length;
37258 angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
37260 * A helper, internal data structure that stores all references attached to key
37262 .factory('$$multiMap', function() {
37264 createNew: function() {
37268 entries: function() {
37269 return Object.keys(map).map(function(key) {
37276 get: function(key) {
37279 hasKey: function(key) {
37283 return Object.keys(map);
37285 put: function(key, value) {
37290 map[key].push(value);
37292 remove: function(key, value) {
37293 var values = map[key];
37299 var idx = values.indexOf(value);
37302 values.splice(idx, 1);
37305 if (!values.length) {
37315 * Pluggable resolve mechanism for the modal resolve resolution
37316 * Supports UI Router's $resolve service
37318 .provider('$uibResolve', function() {
37319 var resolve = this;
37320 this.resolver = null;
37322 this.setResolver = function(resolver) {
37323 this.resolver = resolver;
37326 this.$get = ['$injector', '$q', function($injector, $q) {
37327 var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
37329 resolve: function(invocables, locals, parent, self) {
37331 return resolver.resolve(invocables, locals, parent, self);
37336 angular.forEach(invocables, function(value) {
37337 if (angular.isFunction(value) || angular.isArray(value)) {
37338 promises.push($q.resolve($injector.invoke(value)));
37339 } else if (angular.isString(value)) {
37340 promises.push($q.resolve($injector.get(value)));
37342 promises.push($q.resolve(value));
37346 return $q.all(promises).then(function(resolves) {
37347 var resolveObj = {};
37348 var resolveIter = 0;
37349 angular.forEach(invocables, function(value, key) {
37350 resolveObj[key] = resolves[resolveIter++];
37361 * A helper directive for the $modal service. It creates a backdrop element.
37363 .directive('uibModalBackdrop', ['$animateCss', '$injector', '$uibModalStack',
37364 function($animateCss, $injector, $modalStack) {
37367 templateUrl: 'uib/template/modal/backdrop.html',
37368 compile: function(tElement, tAttrs) {
37369 tElement.addClass(tAttrs.backdropClass);
37374 function linkFn(scope, element, attrs) {
37375 if (attrs.modalInClass) {
37376 $animateCss(element, {
37377 addClass: attrs.modalInClass
37380 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
37381 var done = setIsAsync();
37382 if (scope.modalOptions.animation) {
37383 $animateCss(element, {
37384 removeClass: attrs.modalInClass
37385 }).start().then(done);
37394 .directive('uibModalWindow', ['$uibModalStack', '$q', '$animate', '$animateCss', '$document',
37395 function($modalStack, $q, $animate, $animateCss, $document) {
37402 templateUrl: function(tElement, tAttrs) {
37403 return tAttrs.templateUrl || 'uib/template/modal/window.html';
37405 link: function(scope, element, attrs) {
37406 element.addClass(attrs.windowClass || '');
37407 element.addClass(attrs.windowTopClass || '');
37408 scope.size = attrs.size;
37410 scope.close = function(evt) {
37411 var modal = $modalStack.getTop();
37412 if (modal && modal.value.backdrop &&
37413 modal.value.backdrop !== 'static' &&
37414 evt.target === evt.currentTarget) {
37415 evt.preventDefault();
37416 evt.stopPropagation();
37417 $modalStack.dismiss(modal.key, 'backdrop click');
37421 // moved from template to fix issue #2280
37422 element.on('click', scope.close);
37424 // This property is only added to the scope for the purpose of detecting when this directive is rendered.
37425 // We can detect that by using this property in the template associated with this directive and then use
37426 // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
37427 scope.$isRendered = true;
37429 // Deferred object that will be resolved when this modal is render.
37430 var modalRenderDeferObj = $q.defer();
37431 // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
37432 // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
37433 attrs.$observe('modalRender', function(value) {
37434 if (value === 'true') {
37435 modalRenderDeferObj.resolve();
37439 modalRenderDeferObj.promise.then(function() {
37440 var animationPromise = null;
37442 if (attrs.modalInClass) {
37443 animationPromise = $animateCss(element, {
37444 addClass: attrs.modalInClass
37447 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
37448 var done = setIsAsync();
37450 $animateCss(element, {
37451 removeClass: attrs.modalInClass
37452 }).start().then(done);
37454 $animate.removeClass(element, attrs.modalInClass).then(done);
37460 $q.when(animationPromise).then(function() {
37462 * If something within the freshly-opened modal already has focus (perhaps via a
37463 * directive that causes focus). then no need to try and focus anything.
37465 if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
37466 var inputWithAutofocus = element[0].querySelector('[autofocus]');
37468 * Auto-focusing of a freshly-opened modal element causes any child elements
37469 * with the autofocus attribute to lose focus. This is an issue on touch
37470 * based devices which will show and then hide the onscreen keyboard.
37471 * Attempts to refocus the autofocus element via JavaScript will not reopen
37472 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
37473 * the modal element if the modal does not contain an autofocus element.
37475 if (inputWithAutofocus) {
37476 inputWithAutofocus.focus();
37478 element[0].focus();
37483 // Notify {@link $modalStack} that modal is rendered.
37484 var modal = $modalStack.getTop();
37486 $modalStack.modalRendered(modal.key);
37493 .directive('uibModalAnimationClass', function() {
37495 compile: function(tElement, tAttrs) {
37496 if (tAttrs.modalAnimation) {
37497 tElement.addClass(tAttrs.uibModalAnimationClass);
37503 .directive('uibModalTransclude', function() {
37505 link: function(scope, element, attrs, controller, transclude) {
37506 transclude(scope.$parent, function(clone) {
37508 element.append(clone);
37514 .factory('$uibModalStack', ['$animate', '$animateCss', '$document',
37515 '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap',
37516 function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap) {
37517 var OPENED_MODAL_CLASS = 'modal-open';
37519 var backdropDomEl, backdropScope;
37520 var openedWindows = $$stackedMap.createNew();
37521 var openedClasses = $$multiMap.createNew();
37522 var $modalStack = {
37523 NOW_CLOSING_EVENT: 'modal.stack.now-closing'
37526 //Modal focus behavior
37527 var focusableElementList;
37528 var focusIndex = 0;
37529 var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
37530 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
37531 'iframe, object, embed, *[tabindex], *[contenteditable=true]';
37533 function backdropIndex() {
37534 var topBackdropIndex = -1;
37535 var opened = openedWindows.keys();
37536 for (var i = 0; i < opened.length; i++) {
37537 if (openedWindows.get(opened[i]).value.backdrop) {
37538 topBackdropIndex = i;
37541 return topBackdropIndex;
37544 $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
37545 if (backdropScope) {
37546 backdropScope.index = newBackdropIndex;
37550 function removeModalWindow(modalInstance, elementToReceiveFocus) {
37551 var modalWindow = openedWindows.get(modalInstance).value;
37552 var appendToElement = modalWindow.appendTo;
37554 //clean up the stack
37555 openedWindows.remove(modalInstance);
37557 removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
37558 var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
37559 openedClasses.remove(modalBodyClass, modalInstance);
37560 appendToElement.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
37561 toggleTopWindowClass(true);
37563 checkRemoveBackdrop();
37565 //move focus to specified element if available, or else to body
37566 if (elementToReceiveFocus && elementToReceiveFocus.focus) {
37567 elementToReceiveFocus.focus();
37568 } else if (appendToElement.focus) {
37569 appendToElement.focus();
37573 // Add or remove "windowTopClass" from the top window in the stack
37574 function toggleTopWindowClass(toggleSwitch) {
37577 if (openedWindows.length() > 0) {
37578 modalWindow = openedWindows.top().value;
37579 modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
37583 function checkRemoveBackdrop() {
37584 //remove backdrop if no longer needed
37585 if (backdropDomEl && backdropIndex() === -1) {
37586 var backdropScopeRef = backdropScope;
37587 removeAfterAnimate(backdropDomEl, backdropScope, function() {
37588 backdropScopeRef = null;
37590 backdropDomEl = undefined;
37591 backdropScope = undefined;
37595 function removeAfterAnimate(domEl, scope, done, closedDeferred) {
37597 var asyncPromise = null;
37598 var setIsAsync = function() {
37599 if (!asyncDeferred) {
37600 asyncDeferred = $q.defer();
37601 asyncPromise = asyncDeferred.promise;
37604 return function asyncDone() {
37605 asyncDeferred.resolve();
37608 scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
37610 // Note that it's intentional that asyncPromise might be null.
37611 // That's when setIsAsync has not been called during the
37612 // NOW_CLOSING_EVENT broadcast.
37613 return $q.when(asyncPromise).then(afterAnimating);
37615 function afterAnimating() {
37616 if (afterAnimating.done) {
37619 afterAnimating.done = true;
37621 $animateCss(domEl, {
37623 }).start().then(function() {
37625 if (closedDeferred) {
37626 closedDeferred.resolve();
37637 $document.on('keydown', keydownListener);
37639 $rootScope.$on('$destroy', function() {
37640 $document.off('keydown', keydownListener);
37643 function keydownListener(evt) {
37644 if (evt.isDefaultPrevented()) {
37648 var modal = openedWindows.top();
37650 switch (evt.which) {
37652 if (modal.value.keyboard) {
37653 evt.preventDefault();
37654 $rootScope.$apply(function() {
37655 $modalStack.dismiss(modal.key, 'escape key press');
37661 $modalStack.loadFocusElementList(modal);
37662 var focusChanged = false;
37663 if (evt.shiftKey) {
37664 if ($modalStack.isFocusInFirstItem(evt)) {
37665 focusChanged = $modalStack.focusLastFocusableElement();
37668 if ($modalStack.isFocusInLastItem(evt)) {
37669 focusChanged = $modalStack.focusFirstFocusableElement();
37673 if (focusChanged) {
37674 evt.preventDefault();
37675 evt.stopPropagation();
37683 $modalStack.open = function(modalInstance, modal) {
37684 var modalOpener = $document[0].activeElement,
37685 modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
37687 toggleTopWindowClass(false);
37689 openedWindows.add(modalInstance, {
37690 deferred: modal.deferred,
37691 renderDeferred: modal.renderDeferred,
37692 closedDeferred: modal.closedDeferred,
37693 modalScope: modal.scope,
37694 backdrop: modal.backdrop,
37695 keyboard: modal.keyboard,
37696 openedClass: modal.openedClass,
37697 windowTopClass: modal.windowTopClass,
37698 animation: modal.animation,
37699 appendTo: modal.appendTo
37702 openedClasses.put(modalBodyClass, modalInstance);
37704 var appendToElement = modal.appendTo,
37705 currBackdropIndex = backdropIndex();
37707 if (!appendToElement.length) {
37708 throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
37711 if (currBackdropIndex >= 0 && !backdropDomEl) {
37712 backdropScope = $rootScope.$new(true);
37713 backdropScope.modalOptions = modal;
37714 backdropScope.index = currBackdropIndex;
37715 backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
37716 backdropDomEl.attr('backdrop-class', modal.backdropClass);
37717 if (modal.animation) {
37718 backdropDomEl.attr('modal-animation', 'true');
37720 $compile(backdropDomEl)(backdropScope);
37721 $animate.enter(backdropDomEl, appendToElement);
37724 var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
37725 angularDomEl.attr({
37726 'template-url': modal.windowTemplateUrl,
37727 'window-class': modal.windowClass,
37728 'window-top-class': modal.windowTopClass,
37729 'size': modal.size,
37730 'index': openedWindows.length() - 1,
37731 'animate': 'animate'
37732 }).html(modal.content);
37733 if (modal.animation) {
37734 angularDomEl.attr('modal-animation', 'true');
37737 $animate.enter(angularDomEl, appendToElement)
37739 $compile(angularDomEl)(modal.scope);
37740 $animate.addClass(appendToElement, modalBodyClass);
37743 openedWindows.top().value.modalDomEl = angularDomEl;
37744 openedWindows.top().value.modalOpener = modalOpener;
37746 $modalStack.clearFocusListCache();
37749 function broadcastClosing(modalWindow, resultOrReason, closing) {
37750 return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
37753 $modalStack.close = function(modalInstance, result) {
37754 var modalWindow = openedWindows.get(modalInstance);
37755 if (modalWindow && broadcastClosing(modalWindow, result, true)) {
37756 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
37757 modalWindow.value.deferred.resolve(result);
37758 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
37761 return !modalWindow;
37764 $modalStack.dismiss = function(modalInstance, reason) {
37765 var modalWindow = openedWindows.get(modalInstance);
37766 if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
37767 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
37768 modalWindow.value.deferred.reject(reason);
37769 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
37772 return !modalWindow;
37775 $modalStack.dismissAll = function(reason) {
37776 var topModal = this.getTop();
37777 while (topModal && this.dismiss(topModal.key, reason)) {
37778 topModal = this.getTop();
37782 $modalStack.getTop = function() {
37783 return openedWindows.top();
37786 $modalStack.modalRendered = function(modalInstance) {
37787 var modalWindow = openedWindows.get(modalInstance);
37789 modalWindow.value.renderDeferred.resolve();
37793 $modalStack.focusFirstFocusableElement = function() {
37794 if (focusableElementList.length > 0) {
37795 focusableElementList[0].focus();
37800 $modalStack.focusLastFocusableElement = function() {
37801 if (focusableElementList.length > 0) {
37802 focusableElementList[focusableElementList.length - 1].focus();
37808 $modalStack.isFocusInFirstItem = function(evt) {
37809 if (focusableElementList.length > 0) {
37810 return (evt.target || evt.srcElement) === focusableElementList[0];
37815 $modalStack.isFocusInLastItem = function(evt) {
37816 if (focusableElementList.length > 0) {
37817 return (evt.target || evt.srcElement) === focusableElementList[focusableElementList.length - 1];
37822 $modalStack.clearFocusListCache = function() {
37823 focusableElementList = [];
37827 $modalStack.loadFocusElementList = function(modalWindow) {
37828 if (focusableElementList === undefined || !focusableElementList.length) {
37830 var modalDomE1 = modalWindow.value.modalDomEl;
37831 if (modalDomE1 && modalDomE1.length) {
37832 focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
37838 return $modalStack;
37841 .provider('$uibModal', function() {
37842 var $modalProvider = {
37845 backdrop: true, //can also be false or 'static'
37848 $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
37849 function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
37852 function getTemplatePromise(options) {
37853 return options.template ? $q.when(options.template) :
37854 $templateRequest(angular.isFunction(options.templateUrl) ?
37855 options.templateUrl() : options.templateUrl);
37858 var promiseChain = null;
37859 $modal.getPromiseChain = function() {
37860 return promiseChain;
37863 $modal.open = function(modalOptions) {
37864 var modalResultDeferred = $q.defer();
37865 var modalOpenedDeferred = $q.defer();
37866 var modalClosedDeferred = $q.defer();
37867 var modalRenderDeferred = $q.defer();
37869 //prepare an instance of a modal to be injected into controllers and returned to a caller
37870 var modalInstance = {
37871 result: modalResultDeferred.promise,
37872 opened: modalOpenedDeferred.promise,
37873 closed: modalClosedDeferred.promise,
37874 rendered: modalRenderDeferred.promise,
37875 close: function (result) {
37876 return $modalStack.close(modalInstance, result);
37878 dismiss: function (reason) {
37879 return $modalStack.dismiss(modalInstance, reason);
37883 //merge and clean up options
37884 modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
37885 modalOptions.resolve = modalOptions.resolve || {};
37886 modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
37889 if (!modalOptions.template && !modalOptions.templateUrl) {
37890 throw new Error('One of template or templateUrl options is required.');
37893 var templateAndResolvePromise =
37894 $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
37896 function resolveWithTemplate() {
37897 return templateAndResolvePromise;
37900 // Wait for the resolution of the existing promise chain.
37901 // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
37902 // Then add to $modalStack and resolve opened.
37903 // Finally clean up the chain variable if no subsequent modal has overwritten it.
37905 samePromise = promiseChain = $q.all([promiseChain])
37906 .then(resolveWithTemplate, resolveWithTemplate)
37907 .then(function resolveSuccess(tplAndVars) {
37908 var providedScope = modalOptions.scope || $rootScope;
37910 var modalScope = providedScope.$new();
37911 modalScope.$close = modalInstance.close;
37912 modalScope.$dismiss = modalInstance.dismiss;
37914 modalScope.$on('$destroy', function() {
37915 if (!modalScope.$$uibDestructionScheduled) {
37916 modalScope.$dismiss('$uibUnscheduledDestruction');
37920 var ctrlInstance, ctrlLocals = {};
37923 if (modalOptions.controller) {
37924 ctrlLocals.$scope = modalScope;
37925 ctrlLocals.$uibModalInstance = modalInstance;
37926 angular.forEach(tplAndVars[1], function(value, key) {
37927 ctrlLocals[key] = value;
37930 ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
37931 if (modalOptions.controllerAs) {
37932 if (modalOptions.bindToController) {
37933 ctrlInstance.$close = modalScope.$close;
37934 ctrlInstance.$dismiss = modalScope.$dismiss;
37935 angular.extend(ctrlInstance, providedScope);
37938 modalScope[modalOptions.controllerAs] = ctrlInstance;
37942 $modalStack.open(modalInstance, {
37944 deferred: modalResultDeferred,
37945 renderDeferred: modalRenderDeferred,
37946 closedDeferred: modalClosedDeferred,
37947 content: tplAndVars[0],
37948 animation: modalOptions.animation,
37949 backdrop: modalOptions.backdrop,
37950 keyboard: modalOptions.keyboard,
37951 backdropClass: modalOptions.backdropClass,
37952 windowTopClass: modalOptions.windowTopClass,
37953 windowClass: modalOptions.windowClass,
37954 windowTemplateUrl: modalOptions.windowTemplateUrl,
37955 size: modalOptions.size,
37956 openedClass: modalOptions.openedClass,
37957 appendTo: modalOptions.appendTo
37959 modalOpenedDeferred.resolve(true);
37961 }, function resolveError(reason) {
37962 modalOpenedDeferred.reject(reason);
37963 modalResultDeferred.reject(reason);
37964 })['finally'](function() {
37965 if (promiseChain === samePromise) {
37966 promiseChain = null;
37970 return modalInstance;
37978 return $modalProvider;
37981 angular.module('ui.bootstrap.paging', [])
37983 * Helper internal service for generating common controller code between the
37984 * pager and pagination components
37986 .factory('uibPaging', ['$parse', function($parse) {
37988 create: function(ctrl, $scope, $attrs) {
37989 ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
37990 ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
37992 ctrl.init = function(ngModelCtrl, config) {
37993 ctrl.ngModelCtrl = ngModelCtrl;
37994 ctrl.config = config;
37996 ngModelCtrl.$render = function() {
38000 if ($attrs.itemsPerPage) {
38001 $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
38002 ctrl.itemsPerPage = parseInt(value, 10);
38003 $scope.totalPages = ctrl.calculateTotalPages();
38007 ctrl.itemsPerPage = config.itemsPerPage;
38010 $scope.$watch('totalItems', function(newTotal, oldTotal) {
38011 if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
38012 $scope.totalPages = ctrl.calculateTotalPages();
38018 ctrl.calculateTotalPages = function() {
38019 var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
38020 return Math.max(totalPages || 0, 1);
38023 ctrl.render = function() {
38024 $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
38027 $scope.selectPage = function(page, evt) {
38029 evt.preventDefault();
38032 var clickAllowed = !$scope.ngDisabled || !evt;
38033 if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
38034 if (evt && evt.target) {
38037 ctrl.ngModelCtrl.$setViewValue(page);
38038 ctrl.ngModelCtrl.$render();
38042 $scope.getText = function(key) {
38043 return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
38046 $scope.noPrevious = function() {
38047 return $scope.page === 1;
38050 $scope.noNext = function() {
38051 return $scope.page === $scope.totalPages;
38054 ctrl.updatePage = function() {
38055 ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable
38057 if ($scope.page > $scope.totalPages) {
38058 $scope.selectPage($scope.totalPages);
38060 ctrl.ngModelCtrl.$render();
38067 angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
38069 .controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
38070 $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
38072 uibPaging.create(this, $scope, $attrs);
38075 .constant('uibPagerConfig', {
38077 previousText: '« Previous',
38078 nextText: 'Next »',
38082 .directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {
38090 require: ['uibPager', '?ngModel'],
38091 controller: 'UibPagerController',
38092 controllerAs: 'pager',
38093 templateUrl: function(element, attrs) {
38094 return attrs.templateUrl || 'uib/template/pager/pager.html';
38097 link: function(scope, element, attrs, ctrls) {
38098 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38100 if (!ngModelCtrl) {
38101 return; // do nothing if no ng-model
38104 paginationCtrl.init(ngModelCtrl, uibPagerConfig);
38109 angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
38110 .controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
38112 // Setup configuration parameters
38113 var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
38114 rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
38115 forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
38116 boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers;
38117 $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
38118 $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
38120 uibPaging.create(this, $scope, $attrs);
38122 if ($attrs.maxSize) {
38123 $scope.$parent.$watch($parse($attrs.maxSize), function(value) {
38124 maxSize = parseInt(value, 10);
38129 // Create page object used in template
38130 function makePage(number, text, isActive) {
38138 function getPages(currentPage, totalPages) {
38141 // Default page limits
38142 var startPage = 1, endPage = totalPages;
38143 var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
38145 // recompute if maxSize
38148 // Current page is displayed in the middle of the visible ones
38149 startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
38150 endPage = startPage + maxSize - 1;
38152 // Adjust if limit is exceeded
38153 if (endPage > totalPages) {
38154 endPage = totalPages;
38155 startPage = endPage - maxSize + 1;
38158 // Visible pages are paginated with maxSize
38159 startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;
38161 // Adjust last page if limit is exceeded
38162 endPage = Math.min(startPage + maxSize - 1, totalPages);
38166 // Add page number links
38167 for (var number = startPage; number <= endPage; number++) {
38168 var page = makePage(number, number, number === currentPage);
38172 // Add links to move between page sets
38173 if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
38174 if (startPage > 1) {
38175 if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
38176 var previousPageSet = makePage(startPage - 1, '...', false);
38177 pages.unshift(previousPageSet);
38179 if (boundaryLinkNumbers) {
38180 if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
38181 var secondPageLink = makePage(2, '2', false);
38182 pages.unshift(secondPageLink);
38184 //add the first page
38185 var firstPageLink = makePage(1, '1', false);
38186 pages.unshift(firstPageLink);
38190 if (endPage < totalPages) {
38191 if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
38192 var nextPageSet = makePage(endPage + 1, '...', false);
38193 pages.push(nextPageSet);
38195 if (boundaryLinkNumbers) {
38196 if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
38197 var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
38198 pages.push(secondToLastPageLink);
38200 //add the last page
38201 var lastPageLink = makePage(totalPages, totalPages, false);
38202 pages.push(lastPageLink);
38209 var originalRender = this.render;
38210 this.render = function() {
38212 if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
38213 $scope.pages = getPages($scope.page, $scope.totalPages);
38218 .constant('uibPaginationConfig', {
38220 boundaryLinks: false,
38221 boundaryLinkNumbers: false,
38222 directionLinks: true,
38223 firstText: 'First',
38224 previousText: 'Previous',
38228 forceEllipses: false
38231 .directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {
38241 require: ['uibPagination', '?ngModel'],
38242 controller: 'UibPaginationController',
38243 controllerAs: 'pagination',
38244 templateUrl: function(element, attrs) {
38245 return attrs.templateUrl || 'uib/template/pagination/pagination.html';
38248 link: function(scope, element, attrs, ctrls) {
38249 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38251 if (!ngModelCtrl) {
38252 return; // do nothing if no ng-model
38255 paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
38261 * The following features are still outstanding: animation as a
38262 * function, placement as a function, inside, support for more triggers than
38263 * just mouse enter/leave, html tooltips, and selector delegation.
38265 angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
38268 * The $tooltip service creates tooltip- and popover-like directives as well as
38269 * houses global options for them.
38271 .provider('$uibTooltip', function() {
38272 // The default options tooltip and popover.
38273 var defaultOptions = {
38275 placementClassPrefix: '',
38278 popupCloseDelay: 0,
38279 useContentExp: false
38282 // Default hide triggers for each show trigger
38284 'mouseenter': 'mouseleave',
38286 'outsideClick': 'outsideClick',
38291 // The options specified to the provider globally.
38292 var globalOptions = {};
38295 * `options({})` allows global configuration of all tooltips in the
38298 * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
38299 * // place tooltips left instead of top by default
38300 * $tooltipProvider.options( { placement: 'left' } );
38303 this.options = function(value) {
38304 angular.extend(globalOptions, value);
38308 * This allows you to extend the set of trigger mappings available. E.g.:
38310 * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
38312 this.setTriggers = function setTriggers(triggers) {
38313 angular.extend(triggerMap, triggers);
38317 * This is a helper function for translating camel-case to snake_case.
38319 function snake_case(name) {
38320 var regexp = /[A-Z]/g;
38321 var separator = '-';
38322 return name.replace(regexp, function(letter, pos) {
38323 return (pos ? separator : '') + letter.toLowerCase();
38328 * Returns the actual instance of the $tooltip service.
38329 * TODO support multiple triggers
38331 this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
38332 var openedTooltips = $$stackedMap.createNew();
38333 $document.on('keypress', keypressListener);
38335 $rootScope.$on('$destroy', function() {
38336 $document.off('keypress', keypressListener);
38339 function keypressListener(e) {
38340 if (e.which === 27) {
38341 var last = openedTooltips.top();
38343 last.value.close();
38344 openedTooltips.removeTop();
38350 return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
38351 options = angular.extend({}, defaultOptions, globalOptions, options);
38354 * Returns an object of show and hide triggers.
38356 * If a trigger is supplied,
38357 * it is used to show the tooltip; otherwise, it will use the `trigger`
38358 * option passed to the `$tooltipProvider.options` method; else it will
38359 * default to the trigger supplied to this directive factory.
38361 * The hide trigger is based on the show trigger. If the `trigger` option
38362 * was passed to the `$tooltipProvider.options` method, it will use the
38363 * mapped trigger from `triggerMap` or the passed trigger if the map is
38364 * undefined; otherwise, it uses the `triggerMap` value of the show
38365 * trigger; else it will just use the show trigger.
38367 function getTriggers(trigger) {
38368 var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
38369 var hide = show.map(function(trigger) {
38370 return triggerMap[trigger] || trigger;
38378 var directiveName = snake_case(ttType);
38380 var startSym = $interpolate.startSymbol();
38381 var endSym = $interpolate.endSymbol();
38383 '<div '+ directiveName + '-popup '+
38384 'title="' + startSym + 'title' + endSym + '" '+
38385 (options.useContentExp ?
38386 'content-exp="contentExp()" ' :
38387 'content="' + startSym + 'content' + endSym + '" ') +
38388 'placement="' + startSym + 'placement' + endSym + '" '+
38389 'popup-class="' + startSym + 'popupClass' + endSym + '" '+
38390 'animation="animation" ' +
38391 'is-open="isOpen"' +
38392 'origin-scope="origScope" ' +
38393 'style="visibility: hidden; display: block; top: -9999px; left: -9999px;"' +
38398 compile: function(tElem, tAttrs) {
38399 var tooltipLinker = $compile(template);
38401 return function link(scope, element, attrs, tooltipCtrl) {
38403 var tooltipLinkedScope;
38404 var transitionTimeout;
38407 var positionTimeout;
38408 var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
38409 var triggers = getTriggers(undefined);
38410 var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
38411 var ttScope = scope.$new(true);
38412 var repositionScheduled = false;
38413 var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
38414 var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
38415 var observers = [];
38417 var positionTooltip = function() {
38418 // check if tooltip exists and is not empty
38419 if (!tooltip || !tooltip.html()) { return; }
38421 if (!positionTimeout) {
38422 positionTimeout = $timeout(function() {
38423 // Reset the positioning.
38424 tooltip.css({ top: 0, left: 0 });
38426 // Now set the calculated positioning.
38427 var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
38428 tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px', visibility: 'visible' });
38430 // If the placement class is prefixed, still need
38431 // to remove the TWBS standard class.
38432 if (options.placementClassPrefix) {
38433 tooltip.removeClass('top bottom left right');
38436 tooltip.removeClass(
38437 options.placementClassPrefix + 'top ' +
38438 options.placementClassPrefix + 'top-left ' +
38439 options.placementClassPrefix + 'top-right ' +
38440 options.placementClassPrefix + 'bottom ' +
38441 options.placementClassPrefix + 'bottom-left ' +
38442 options.placementClassPrefix + 'bottom-right ' +
38443 options.placementClassPrefix + 'left ' +
38444 options.placementClassPrefix + 'left-top ' +
38445 options.placementClassPrefix + 'left-bottom ' +
38446 options.placementClassPrefix + 'right ' +
38447 options.placementClassPrefix + 'right-top ' +
38448 options.placementClassPrefix + 'right-bottom');
38450 var placement = ttPosition.placement.split('-');
38451 tooltip.addClass(placement[0], options.placementClassPrefix + ttPosition.placement);
38452 $position.positionArrow(tooltip, ttPosition.placement);
38454 positionTimeout = null;
38459 // Set up the correct scope to allow transclusion later
38460 ttScope.origScope = scope;
38462 // By default, the tooltip is not open.
38463 // TODO add ability to start tooltip opened
38464 ttScope.isOpen = false;
38465 openedTooltips.add(ttScope, {
38469 function toggleTooltipBind() {
38470 if (!ttScope.isOpen) {
38477 // Show the tooltip with delay if specified, otherwise show it immediately
38478 function showTooltipBind() {
38479 if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
38486 if (ttScope.popupDelay) {
38487 // Do nothing if the tooltip was already scheduled to pop-up.
38488 // This happens if show is triggered multiple times before any hide is triggered.
38489 if (!showTimeout) {
38490 showTimeout = $timeout(show, ttScope.popupDelay, false);
38497 function hideTooltipBind() {
38500 if (ttScope.popupCloseDelay) {
38501 if (!hideTimeout) {
38502 hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
38509 // Show the tooltip popup element.
38514 // Don't show empty tooltips.
38515 if (!ttScope.content) {
38516 return angular.noop;
38521 // And show the tooltip.
38522 ttScope.$evalAsync(function() {
38523 ttScope.isOpen = true;
38524 assignIsOpen(true);
38529 function cancelShow() {
38531 $timeout.cancel(showTimeout);
38532 showTimeout = null;
38535 if (positionTimeout) {
38536 $timeout.cancel(positionTimeout);
38537 positionTimeout = null;
38541 // Hide the tooltip popup element.
38547 // First things first: we don't show it anymore.
38548 ttScope.$evalAsync(function() {
38549 ttScope.isOpen = false;
38550 assignIsOpen(false);
38551 // And now we remove it from the DOM. However, if we have animation, we
38552 // need to wait for it to expire beforehand.
38553 // FIXME: this is a placeholder for a port of the transitions library.
38554 // The fade transition in TWBS is 150ms.
38555 if (ttScope.animation) {
38556 if (!transitionTimeout) {
38557 transitionTimeout = $timeout(removeTooltip, 150, false);
38565 function cancelHide() {
38567 $timeout.cancel(hideTimeout);
38568 hideTimeout = null;
38570 if (transitionTimeout) {
38571 $timeout.cancel(transitionTimeout);
38572 transitionTimeout = null;
38576 function createTooltip() {
38577 // There can only be one tooltip element per directive shown at once.
38582 tooltipLinkedScope = ttScope.$new();
38583 tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
38584 if (appendToBody) {
38585 $document.find('body').append(tooltip);
38587 element.after(tooltip);
38594 function removeTooltip() {
38597 unregisterObservers();
38603 if (tooltipLinkedScope) {
38604 tooltipLinkedScope.$destroy();
38605 tooltipLinkedScope = null;
38610 * Set the initial scope values. Once
38611 * the tooltip is created, the observers
38612 * will be added to keep things in sync.
38614 function prepareTooltip() {
38615 ttScope.title = attrs[prefix + 'Title'];
38616 if (contentParse) {
38617 ttScope.content = contentParse(scope);
38619 ttScope.content = attrs[ttType];
38622 ttScope.popupClass = attrs[prefix + 'Class'];
38623 ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
38625 var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
38626 var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
38627 ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
38628 ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
38631 function assignIsOpen(isOpen) {
38632 if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
38633 isOpenParse.assign(scope, isOpen);
38637 ttScope.contentExp = function() {
38638 return ttScope.content;
38642 * Observe the relevant attributes.
38644 attrs.$observe('disabled', function(val) {
38649 if (val && ttScope.isOpen) {
38655 scope.$watch(isOpenParse, function(val) {
38656 if (ttScope && !val === ttScope.isOpen) {
38657 toggleTooltipBind();
38662 function prepObservers() {
38663 observers.length = 0;
38665 if (contentParse) {
38667 scope.$watch(contentParse, function(val) {
38668 ttScope.content = val;
38669 if (!val && ttScope.isOpen) {
38676 tooltipLinkedScope.$watch(function() {
38677 if (!repositionScheduled) {
38678 repositionScheduled = true;
38679 tooltipLinkedScope.$$postDigest(function() {
38680 repositionScheduled = false;
38681 if (ttScope && ttScope.isOpen) {
38690 attrs.$observe(ttType, function(val) {
38691 ttScope.content = val;
38692 if (!val && ttScope.isOpen) {
38702 attrs.$observe(prefix + 'Title', function(val) {
38703 ttScope.title = val;
38704 if (ttScope.isOpen) {
38711 attrs.$observe(prefix + 'Placement', function(val) {
38712 ttScope.placement = val ? val : options.placement;
38713 if (ttScope.isOpen) {
38720 function unregisterObservers() {
38721 if (observers.length) {
38722 angular.forEach(observers, function(observer) {
38725 observers.length = 0;
38729 // hide tooltips/popovers for outsideClick trigger
38730 function bodyHideTooltipBind(e) {
38731 if (!ttScope || !ttScope.isOpen || !tooltip) {
38734 // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
38735 if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
38740 var unregisterTriggers = function() {
38741 triggers.show.forEach(function(trigger) {
38742 if (trigger === 'outsideClick') {
38743 element.off('click', toggleTooltipBind);
38745 element.off(trigger, showTooltipBind);
38746 element.off(trigger, toggleTooltipBind);
38749 triggers.hide.forEach(function(trigger) {
38750 if (trigger === 'outsideClick') {
38751 $document.off('click', bodyHideTooltipBind);
38753 element.off(trigger, hideTooltipBind);
38758 function prepTriggers() {
38759 var val = attrs[prefix + 'Trigger'];
38760 unregisterTriggers();
38762 triggers = getTriggers(val);
38764 if (triggers.show !== 'none') {
38765 triggers.show.forEach(function(trigger, idx) {
38766 if (trigger === 'outsideClick') {
38767 element.on('click', toggleTooltipBind);
38768 $document.on('click', bodyHideTooltipBind);
38769 } else if (trigger === triggers.hide[idx]) {
38770 element.on(trigger, toggleTooltipBind);
38771 } else if (trigger) {
38772 element.on(trigger, showTooltipBind);
38773 element.on(triggers.hide[idx], hideTooltipBind);
38776 element.on('keypress', function(e) {
38777 if (e.which === 27) {
38787 var animation = scope.$eval(attrs[prefix + 'Animation']);
38788 ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
38790 var appendToBodyVal;
38791 var appendKey = prefix + 'AppendToBody';
38792 if (appendKey in attrs && attrs[appendKey] === undefined) {
38793 appendToBodyVal = true;
38795 appendToBodyVal = scope.$eval(attrs[appendKey]);
38798 appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
38800 // if a tooltip is attached to <body> we need to remove it on
38801 // location change as its parent scope will probably not be destroyed
38803 if (appendToBody) {
38804 scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() {
38805 if (ttScope.isOpen) {
38811 // Make sure tooltip is destroyed and removed.
38812 scope.$on('$destroy', function onDestroyTooltip() {
38813 unregisterTriggers();
38815 openedTooltips.remove(ttScope);
38825 // This is mostly ngInclude code but with a custom scope
38826 .directive('uibTooltipTemplateTransclude', [
38827 '$animate', '$sce', '$compile', '$templateRequest',
38828 function ($animate, $sce, $compile, $templateRequest) {
38830 link: function(scope, elem, attrs) {
38831 var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
38833 var changeCounter = 0,
38838 var cleanupLastIncludeContent = function() {
38839 if (previousElement) {
38840 previousElement.remove();
38841 previousElement = null;
38844 if (currentScope) {
38845 currentScope.$destroy();
38846 currentScope = null;
38849 if (currentElement) {
38850 $animate.leave(currentElement).then(function() {
38851 previousElement = null;
38853 previousElement = currentElement;
38854 currentElement = null;
38858 scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
38859 var thisChangeId = ++changeCounter;
38862 //set the 2nd param to true to ignore the template request error so that the inner
38863 //contents and scope can be cleaned up.
38864 $templateRequest(src, true).then(function(response) {
38865 if (thisChangeId !== changeCounter) { return; }
38866 var newScope = origScope.$new();
38867 var template = response;
38869 var clone = $compile(template)(newScope, function(clone) {
38870 cleanupLastIncludeContent();
38871 $animate.enter(clone, elem);
38874 currentScope = newScope;
38875 currentElement = clone;
38877 currentScope.$emit('$includeContentLoaded', src);
38879 if (thisChangeId === changeCounter) {
38880 cleanupLastIncludeContent();
38881 scope.$emit('$includeContentError', src);
38884 scope.$emit('$includeContentRequested', src);
38886 cleanupLastIncludeContent();
38890 scope.$on('$destroy', cleanupLastIncludeContent);
38896 * Note that it's intentional that these classes are *not* applied through $animate.
38897 * They must not be animated as they're expected to be present on the tooltip on
38900 .directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
38903 link: function(scope, element, attrs) {
38904 // need to set the primary position so the
38905 // arrow has space during position measure.
38906 // tooltip.positionTooltip()
38907 if (scope.placement) {
38908 // // There are no top-left etc... classes
38909 // // in TWBS, so we need the primary position.
38910 var position = $uibPosition.parsePlacement(scope.placement);
38911 element.addClass(position[0]);
38913 element.addClass('top');
38916 if (scope.popupClass) {
38917 element.addClass(scope.popupClass);
38920 if (scope.animation()) {
38921 element.addClass(attrs.tooltipAnimationClass);
38927 .directive('uibTooltipPopup', function() {
38930 scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38931 templateUrl: 'uib/template/tooltip/tooltip-popup.html'
38935 .directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
38936 return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
38939 .directive('uibTooltipTemplatePopup', function() {
38942 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
38943 originScope: '&' },
38944 templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
38948 .directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
38949 return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
38950 useContentExp: true
38954 .directive('uibTooltipHtmlPopup', function() {
38957 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38958 templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
38962 .directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
38963 return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
38964 useContentExp: true
38969 * The following features are still outstanding: popup delay, animation as a
38970 * function, placement as a function, inside, support for more triggers than
38971 * just mouse enter/leave, and selector delegatation.
38973 angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
38975 .directive('uibPopoverTemplatePopup', function() {
38978 scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
38979 originScope: '&' },
38980 templateUrl: 'uib/template/popover/popover-template.html'
38984 .directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
38985 return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
38986 useContentExp: true
38990 .directive('uibPopoverHtmlPopup', function() {
38993 scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38994 templateUrl: 'uib/template/popover/popover-html.html'
38998 .directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
38999 return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
39000 useContentExp: true
39004 .directive('uibPopoverPopup', function() {
39007 scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
39008 templateUrl: 'uib/template/popover/popover.html'
39012 .directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
39013 return $uibTooltip('uibPopover', 'popover', 'click');
39016 angular.module('ui.bootstrap.progressbar', [])
39018 .constant('uibProgressConfig', {
39023 .controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
39025 animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
39028 $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
39030 this.addBar = function(bar, element, attrs) {
39032 element.css({'transition': 'none'});
39035 this.bars.push(bar);
39037 bar.max = $scope.max;
39038 bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
39040 bar.$watch('value', function(value) {
39041 bar.recalculatePercentage();
39044 bar.recalculatePercentage = function() {
39045 var totalPercentage = self.bars.reduce(function(total, bar) {
39046 bar.percent = +(100 * bar.value / bar.max).toFixed(2);
39047 return total + bar.percent;
39050 if (totalPercentage > 100) {
39051 bar.percent -= totalPercentage - 100;
39055 bar.$on('$destroy', function() {
39057 self.removeBar(bar);
39061 this.removeBar = function(bar) {
39062 this.bars.splice(this.bars.indexOf(bar), 1);
39063 this.bars.forEach(function (bar) {
39064 bar.recalculatePercentage();
39068 $scope.$watch('max', function(max) {
39069 self.bars.forEach(function(bar) {
39070 bar.max = $scope.max;
39071 bar.recalculatePercentage();
39076 .directive('uibProgress', function() {
39080 controller: 'UibProgressController',
39081 require: 'uibProgress',
39085 templateUrl: 'uib/template/progressbar/progress.html'
39089 .directive('uibBar', function() {
39093 require: '^uibProgress',
39098 templateUrl: 'uib/template/progressbar/bar.html',
39099 link: function(scope, element, attrs, progressCtrl) {
39100 progressCtrl.addBar(scope, element, attrs);
39105 .directive('uibProgressbar', function() {
39109 controller: 'UibProgressController',
39115 templateUrl: 'uib/template/progressbar/progressbar.html',
39116 link: function(scope, element, attrs, progressCtrl) {
39117 progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
39122 angular.module('ui.bootstrap.rating', [])
39124 .constant('uibRatingConfig', {
39128 titles : ['one', 'two', 'three', 'four', 'five']
39131 .controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
39132 var ngModelCtrl = { $setViewValue: angular.noop };
39134 this.init = function(ngModelCtrl_) {
39135 ngModelCtrl = ngModelCtrl_;
39136 ngModelCtrl.$render = this.render;
39138 ngModelCtrl.$formatters.push(function(value) {
39139 if (angular.isNumber(value) && value << 0 !== value) {
39140 value = Math.round(value);
39146 this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
39147 this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
39148 var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
39149 this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
39150 tmpTitles : ratingConfig.titles;
39152 var ratingStates = angular.isDefined($attrs.ratingStates) ?
39153 $scope.$parent.$eval($attrs.ratingStates) :
39154 new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
39155 $scope.range = this.buildTemplateObjects(ratingStates);
39158 this.buildTemplateObjects = function(states) {
39159 for (var i = 0, n = states.length; i < n; i++) {
39160 states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
39165 this.getTitle = function(index) {
39166 if (index >= this.titles.length) {
39170 return this.titles[index];
39173 $scope.rate = function(value) {
39174 if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
39175 ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value);
39176 ngModelCtrl.$render();
39180 $scope.enter = function(value) {
39181 if (!$scope.readonly) {
39182 $scope.value = value;
39184 $scope.onHover({value: value});
39187 $scope.reset = function() {
39188 $scope.value = ngModelCtrl.$viewValue;
39192 $scope.onKeydown = function(evt) {
39193 if (/(37|38|39|40)/.test(evt.which)) {
39194 evt.preventDefault();
39195 evt.stopPropagation();
39196 $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
39200 this.render = function() {
39201 $scope.value = ngModelCtrl.$viewValue;
39205 .directive('uibRating', function() {
39207 require: ['uibRating', 'ngModel'],
39213 controller: 'UibRatingController',
39214 templateUrl: 'uib/template/rating/rating.html',
39216 link: function(scope, element, attrs, ctrls) {
39217 var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
39218 ratingCtrl.init(ngModelCtrl);
39223 angular.module('ui.bootstrap.tabs', [])
39225 .controller('UibTabsetController', ['$scope', function ($scope) {
39227 tabs = ctrl.tabs = $scope.tabs = [];
39229 ctrl.select = function(selectedTab) {
39230 angular.forEach(tabs, function(tab) {
39231 if (tab.active && tab !== selectedTab) {
39232 tab.active = false;
39234 selectedTab.selectCalled = false;
39237 selectedTab.active = true;
39238 // only call select if it has not already been called
39239 if (!selectedTab.selectCalled) {
39240 selectedTab.onSelect();
39241 selectedTab.selectCalled = true;
39245 ctrl.addTab = function addTab(tab) {
39247 // we can't run the select function on the first tab
39248 // since that would select it twice
39249 if (tabs.length === 1 && tab.active !== false) {
39251 } else if (tab.active) {
39254 tab.active = false;
39258 ctrl.removeTab = function removeTab(tab) {
39259 var index = tabs.indexOf(tab);
39260 //Select a new tab if the tab to be removed is selected and not destroyed
39261 if (tab.active && tabs.length > 1 && !destroyed) {
39262 //If this is the last tab, select the previous tab. else, the next tab.
39263 var newActiveIndex = index === tabs.length - 1 ? index - 1 : index + 1;
39264 ctrl.select(tabs[newActiveIndex]);
39266 tabs.splice(index, 1);
39270 $scope.$on('$destroy', function() {
39275 .directive('uibTabset', function() {
39282 controller: 'UibTabsetController',
39283 templateUrl: 'uib/template/tabs/tabset.html',
39284 link: function(scope, element, attrs) {
39285 scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
39286 scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
39291 .directive('uibTab', ['$parse', function($parse) {
39293 require: '^uibTabset',
39295 templateUrl: 'uib/template/tabs/tab.html',
39300 onSelect: '&select', //This callback is called in contentHeadingTransclude
39301 //once it inserts the tab's content into the dom
39302 onDeselect: '&deselect'
39304 controller: function() {
39305 //Empty controller so other directives can require being 'under' a tab
39307 controllerAs: 'tab',
39308 link: function(scope, elm, attrs, tabsetCtrl, transclude) {
39309 scope.$watch('active', function(active) {
39311 tabsetCtrl.select(scope);
39315 scope.disabled = false;
39316 if (attrs.disable) {
39317 scope.$parent.$watch($parse(attrs.disable), function(value) {
39318 scope.disabled = !! value;
39322 scope.select = function() {
39323 if (!scope.disabled) {
39324 scope.active = true;
39328 tabsetCtrl.addTab(scope);
39329 scope.$on('$destroy', function() {
39330 tabsetCtrl.removeTab(scope);
39333 //We need to transclude later, once the content container is ready.
39334 //when this link happens, we're inside a tab heading.
39335 scope.$transcludeFn = transclude;
39340 .directive('uibTabHeadingTransclude', function() {
39343 require: '^uibTab',
39344 link: function(scope, elm) {
39345 scope.$watch('headingElement', function updateHeadingElement(heading) {
39348 elm.append(heading);
39355 .directive('uibTabContentTransclude', function() {
39358 require: '^uibTabset',
39359 link: function(scope, elm, attrs) {
39360 var tab = scope.$eval(attrs.uibTabContentTransclude);
39362 //Now our tab is ready to be transcluded: both the tab heading area
39363 //and the tab content area are loaded. Transclude 'em both.
39364 tab.$transcludeFn(tab.$parent, function(contents) {
39365 angular.forEach(contents, function(node) {
39366 if (isTabHeading(node)) {
39367 //Let tabHeadingTransclude know.
39368 tab.headingElement = node;
39377 function isTabHeading(node) {
39378 return node.tagName && (
39379 node.hasAttribute('uib-tab-heading') ||
39380 node.hasAttribute('data-uib-tab-heading') ||
39381 node.hasAttribute('x-uib-tab-heading') ||
39382 node.tagName.toLowerCase() === 'uib-tab-heading' ||
39383 node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
39384 node.tagName.toLowerCase() === 'x-uib-tab-heading'
39389 angular.module('ui.bootstrap.timepicker', [])
39391 .constant('uibTimepickerConfig', {
39395 showMeridian: true,
39396 showSeconds: false,
39398 readonlyInput: false,
39404 .controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
39405 var selected = new Date(),
39406 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
39407 meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
39409 $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
39410 $element.removeAttr('tabindex');
39412 this.init = function(ngModelCtrl_, inputs) {
39413 ngModelCtrl = ngModelCtrl_;
39414 ngModelCtrl.$render = this.render;
39416 ngModelCtrl.$formatters.unshift(function(modelValue) {
39417 return modelValue ? new Date(modelValue) : null;
39420 var hoursInputEl = inputs.eq(0),
39421 minutesInputEl = inputs.eq(1),
39422 secondsInputEl = inputs.eq(2);
39424 var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
39427 this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39430 var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
39432 this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39435 $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
39436 this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39439 var hourStep = timepickerConfig.hourStep;
39440 if ($attrs.hourStep) {
39441 $scope.$parent.$watch($parse($attrs.hourStep), function(value) {
39442 hourStep = parseInt(value, 10);
39446 var minuteStep = timepickerConfig.minuteStep;
39447 if ($attrs.minuteStep) {
39448 $scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
39449 minuteStep = parseInt(value, 10);
39454 $scope.$parent.$watch($parse($attrs.min), function(value) {
39455 var dt = new Date(value);
39456 min = isNaN(dt) ? undefined : dt;
39460 $scope.$parent.$watch($parse($attrs.max), function(value) {
39461 var dt = new Date(value);
39462 max = isNaN(dt) ? undefined : dt;
39465 var disabled = false;
39466 if ($attrs.ngDisabled) {
39467 $scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {
39472 $scope.noIncrementHours = function() {
39473 var incrementedSelected = addMinutes(selected, hourStep * 60);
39474 return disabled || incrementedSelected > max ||
39475 incrementedSelected < selected && incrementedSelected < min;
39478 $scope.noDecrementHours = function() {
39479 var decrementedSelected = addMinutes(selected, -hourStep * 60);
39480 return disabled || decrementedSelected < min ||
39481 decrementedSelected > selected && decrementedSelected > max;
39484 $scope.noIncrementMinutes = function() {
39485 var incrementedSelected = addMinutes(selected, minuteStep);
39486 return disabled || incrementedSelected > max ||
39487 incrementedSelected < selected && incrementedSelected < min;
39490 $scope.noDecrementMinutes = function() {
39491 var decrementedSelected = addMinutes(selected, -minuteStep);
39492 return disabled || decrementedSelected < min ||
39493 decrementedSelected > selected && decrementedSelected > max;
39496 $scope.noIncrementSeconds = function() {
39497 var incrementedSelected = addSeconds(selected, secondStep);
39498 return disabled || incrementedSelected > max ||
39499 incrementedSelected < selected && incrementedSelected < min;
39502 $scope.noDecrementSeconds = function() {
39503 var decrementedSelected = addSeconds(selected, -secondStep);
39504 return disabled || decrementedSelected < min ||
39505 decrementedSelected > selected && decrementedSelected > max;
39508 $scope.noToggleMeridian = function() {
39509 if (selected.getHours() < 12) {
39510 return disabled || addMinutes(selected, 12 * 60) > max;
39513 return disabled || addMinutes(selected, -12 * 60) < min;
39516 var secondStep = timepickerConfig.secondStep;
39517 if ($attrs.secondStep) {
39518 $scope.$parent.$watch($parse($attrs.secondStep), function(value) {
39519 secondStep = parseInt(value, 10);
39523 $scope.showSeconds = timepickerConfig.showSeconds;
39524 if ($attrs.showSeconds) {
39525 $scope.$parent.$watch($parse($attrs.showSeconds), function(value) {
39526 $scope.showSeconds = !!value;
39531 $scope.showMeridian = timepickerConfig.showMeridian;
39532 if ($attrs.showMeridian) {
39533 $scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
39534 $scope.showMeridian = !!value;
39536 if (ngModelCtrl.$error.time) {
39537 // Evaluate from template
39538 var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
39539 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
39540 selected.setHours(hours);
39549 // Get $scope.hours in 24H mode if valid
39550 function getHoursFromTemplate() {
39551 var hours = parseInt($scope.hours, 10);
39552 var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
39553 hours >= 0 && hours < 24;
39558 if ($scope.showMeridian) {
39559 if (hours === 12) {
39562 if ($scope.meridian === meridians[1]) {
39563 hours = hours + 12;
39569 function getMinutesFromTemplate() {
39570 var minutes = parseInt($scope.minutes, 10);
39571 return minutes >= 0 && minutes < 60 ? minutes : undefined;
39574 function getSecondsFromTemplate() {
39575 var seconds = parseInt($scope.seconds, 10);
39576 return seconds >= 0 && seconds < 60 ? seconds : undefined;
39579 function pad(value) {
39580 if (value === null) {
39584 return angular.isDefined(value) && value.toString().length < 2 ?
39585 '0' + value : value.toString();
39588 // Respond on mousewheel spin
39589 this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39590 var isScrollingUp = function(e) {
39591 if (e.originalEvent) {
39592 e = e.originalEvent;
39594 //pick correct delta variable depending on event
39595 var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
39596 return e.detail || delta > 0;
39599 hoursInputEl.bind('mousewheel wheel', function(e) {
39601 $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
39603 e.preventDefault();
39606 minutesInputEl.bind('mousewheel wheel', function(e) {
39608 $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
39610 e.preventDefault();
39613 secondsInputEl.bind('mousewheel wheel', function(e) {
39615 $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
39617 e.preventDefault();
39621 // Respond on up/down arrowkeys
39622 this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39623 hoursInputEl.bind('keydown', function(e) {
39625 if (e.which === 38) { // up
39626 e.preventDefault();
39627 $scope.incrementHours();
39629 } else if (e.which === 40) { // down
39630 e.preventDefault();
39631 $scope.decrementHours();
39637 minutesInputEl.bind('keydown', function(e) {
39639 if (e.which === 38) { // up
39640 e.preventDefault();
39641 $scope.incrementMinutes();
39643 } else if (e.which === 40) { // down
39644 e.preventDefault();
39645 $scope.decrementMinutes();
39651 secondsInputEl.bind('keydown', function(e) {
39653 if (e.which === 38) { // up
39654 e.preventDefault();
39655 $scope.incrementSeconds();
39657 } else if (e.which === 40) { // down
39658 e.preventDefault();
39659 $scope.decrementSeconds();
39666 this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39667 if ($scope.readonlyInput) {
39668 $scope.updateHours = angular.noop;
39669 $scope.updateMinutes = angular.noop;
39670 $scope.updateSeconds = angular.noop;
39674 var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {
39675 ngModelCtrl.$setViewValue(null);
39676 ngModelCtrl.$setValidity('time', false);
39677 if (angular.isDefined(invalidHours)) {
39678 $scope.invalidHours = invalidHours;
39681 if (angular.isDefined(invalidMinutes)) {
39682 $scope.invalidMinutes = invalidMinutes;
39685 if (angular.isDefined(invalidSeconds)) {
39686 $scope.invalidSeconds = invalidSeconds;
39690 $scope.updateHours = function() {
39691 var hours = getHoursFromTemplate(),
39692 minutes = getMinutesFromTemplate();
39694 ngModelCtrl.$setDirty();
39696 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
39697 selected.setHours(hours);
39698 selected.setMinutes(minutes);
39699 if (selected < min || selected > max) {
39709 hoursInputEl.bind('blur', function(e) {
39710 ngModelCtrl.$setTouched();
39711 if ($scope.hours === null || $scope.hours === '') {
39713 } else if (!$scope.invalidHours && $scope.hours < 10) {
39714 $scope.$apply(function() {
39715 $scope.hours = pad($scope.hours);
39720 $scope.updateMinutes = function() {
39721 var minutes = getMinutesFromTemplate(),
39722 hours = getHoursFromTemplate();
39724 ngModelCtrl.$setDirty();
39726 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
39727 selected.setHours(hours);
39728 selected.setMinutes(minutes);
39729 if (selected < min || selected > max) {
39730 invalidate(undefined, true);
39735 invalidate(undefined, true);
39739 minutesInputEl.bind('blur', function(e) {
39740 ngModelCtrl.$setTouched();
39741 if ($scope.minutes === null) {
39742 invalidate(undefined, true);
39743 } else if (!$scope.invalidMinutes && $scope.minutes < 10) {
39744 $scope.$apply(function() {
39745 $scope.minutes = pad($scope.minutes);
39750 $scope.updateSeconds = function() {
39751 var seconds = getSecondsFromTemplate();
39753 ngModelCtrl.$setDirty();
39755 if (angular.isDefined(seconds)) {
39756 selected.setSeconds(seconds);
39759 invalidate(undefined, undefined, true);
39763 secondsInputEl.bind('blur', function(e) {
39764 if (!$scope.invalidSeconds && $scope.seconds < 10) {
39765 $scope.$apply( function() {
39766 $scope.seconds = pad($scope.seconds);
39773 this.render = function() {
39774 var date = ngModelCtrl.$viewValue;
39777 ngModelCtrl.$setValidity('time', false);
39778 $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.');
39784 if (selected < min || selected > max) {
39785 ngModelCtrl.$setValidity('time', false);
39786 $scope.invalidHours = true;
39787 $scope.invalidMinutes = true;
39795 // Call internally when we know that model is valid.
39796 function refresh(keyboardChange) {
39798 ngModelCtrl.$setViewValue(new Date(selected));
39799 updateTemplate(keyboardChange);
39802 function makeValid() {
39803 ngModelCtrl.$setValidity('time', true);
39804 $scope.invalidHours = false;
39805 $scope.invalidMinutes = false;
39806 $scope.invalidSeconds = false;
39809 function updateTemplate(keyboardChange) {
39810 if (!ngModelCtrl.$modelValue) {
39811 $scope.hours = null;
39812 $scope.minutes = null;
39813 $scope.seconds = null;
39814 $scope.meridian = meridians[0];
39816 var hours = selected.getHours(),
39817 minutes = selected.getMinutes(),
39818 seconds = selected.getSeconds();
39820 if ($scope.showMeridian) {
39821 hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
39824 $scope.hours = keyboardChange === 'h' ? hours : pad(hours);
39825 if (keyboardChange !== 'm') {
39826 $scope.minutes = pad(minutes);
39828 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
39830 if (keyboardChange !== 's') {
39831 $scope.seconds = pad(seconds);
39833 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
39837 function addSecondsToSelected(seconds) {
39838 selected = addSeconds(selected, seconds);
39842 function addMinutes(selected, minutes) {
39843 return addSeconds(selected, minutes*60);
39846 function addSeconds(date, seconds) {
39847 var dt = new Date(date.getTime() + seconds * 1000);
39848 var newDate = new Date(date);
39849 newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
39853 $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
39854 $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
39856 $scope.incrementHours = function() {
39857 if (!$scope.noIncrementHours()) {
39858 addSecondsToSelected(hourStep * 60 * 60);
39862 $scope.decrementHours = function() {
39863 if (!$scope.noDecrementHours()) {
39864 addSecondsToSelected(-hourStep * 60 * 60);
39868 $scope.incrementMinutes = function() {
39869 if (!$scope.noIncrementMinutes()) {
39870 addSecondsToSelected(minuteStep * 60);
39874 $scope.decrementMinutes = function() {
39875 if (!$scope.noDecrementMinutes()) {
39876 addSecondsToSelected(-minuteStep * 60);
39880 $scope.incrementSeconds = function() {
39881 if (!$scope.noIncrementSeconds()) {
39882 addSecondsToSelected(secondStep);
39886 $scope.decrementSeconds = function() {
39887 if (!$scope.noDecrementSeconds()) {
39888 addSecondsToSelected(-secondStep);
39892 $scope.toggleMeridian = function() {
39893 var minutes = getMinutesFromTemplate(),
39894 hours = getHoursFromTemplate();
39896 if (!$scope.noToggleMeridian()) {
39897 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
39898 addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
39900 $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
39905 $scope.blur = function() {
39906 ngModelCtrl.$setTouched();
39910 .directive('uibTimepicker', function() {
39912 require: ['uibTimepicker', '?^ngModel'],
39913 controller: 'UibTimepickerController',
39914 controllerAs: 'timepicker',
39917 templateUrl: function(element, attrs) {
39918 return attrs.templateUrl || 'uib/template/timepicker/timepicker.html';
39920 link: function(scope, element, attrs, ctrls) {
39921 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
39924 timepickerCtrl.init(ngModelCtrl, element.find('input'));
39930 angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
39933 * A helper service that can parse typeahead's syntax (string provided by users)
39934 * Extracted to a separate service for ease of unit testing
39936 .factory('uibTypeaheadParser', ['$parse', function($parse) {
39937 // 00000111000000000000022200000000000000003333333333333330000000000044000
39938 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
39940 parse: function(input) {
39941 var match = input.match(TYPEAHEAD_REGEXP);
39944 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
39945 ' but got "' + input + '".');
39949 itemName: match[3],
39950 source: $parse(match[4]),
39951 viewMapper: $parse(match[2] || match[1]),
39952 modelMapper: $parse(match[1])
39958 .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
39959 function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
39960 var HOT_KEYS = [9, 13, 27, 38, 40];
39961 var eventDebounceTime = 200;
39962 var modelCtrl, ngModelOptions;
39963 //SUPPORTED ATTRIBUTES (OPTIONS)
39965 //minimal no of characters that needs to be entered before typeahead kicks-in
39966 var minLength = originalScope.$eval(attrs.typeaheadMinLength);
39967 if (!minLength && minLength !== 0) {
39971 //minimal wait time after last character typed before typeahead kicks-in
39972 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
39974 //should it restrict model values to the ones selected from the popup only?
39975 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
39976 originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
39977 isEditable = newVal !== false;
39980 //binding to a variable that indicates if matches are being retrieved asynchronously
39981 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
39983 //a callback executed when a match is selected
39984 var onSelectCallback = $parse(attrs.typeaheadOnSelect);
39986 //should it select highlighted popup value when losing focus?
39987 var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
39989 //binding to a variable that indicates if there were no results after the query is completed
39990 var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
39992 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
39994 var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
39996 var appendTo = attrs.typeaheadAppendTo ?
39997 originalScope.$eval(attrs.typeaheadAppendTo) : null;
39999 var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
40001 //If input matches an item of the list exactly, select it automatically
40002 var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
40004 //binding to a variable that indicates if dropdown is open
40005 var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
40007 var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
40009 //INTERNAL VARIABLES
40011 //model setter executed upon match selection
40012 var parsedModel = $parse(attrs.ngModel);
40013 var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
40014 var $setModelValue = function(scope, newValue) {
40015 if (angular.isFunction(parsedModel(originalScope)) &&
40016 ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
40017 return invokeModelSetter(scope, {$$$p: newValue});
40020 return parsedModel.assign(scope, newValue);
40023 //expressions used by typeahead
40024 var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
40028 //Used to avoid bug in iOS webview where iOS keyboard does not fire
40029 //mousedown & mouseup events
40033 //create a child scope for the typeahead directive so we are not polluting original scope
40034 //with typeahead-specific data (matches, query etc.)
40035 var scope = originalScope.$new();
40036 var offDestroy = originalScope.$on('$destroy', function() {
40039 scope.$on('$destroy', offDestroy);
40042 var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
40044 'aria-autocomplete': 'list',
40045 'aria-expanded': false,
40046 'aria-owns': popupId
40049 var inputsContainer, hintInputElem;
40050 //add read-only input to show hint
40052 inputsContainer = angular.element('<div></div>');
40053 inputsContainer.css('position', 'relative');
40054 element.after(inputsContainer);
40055 hintInputElem = element.clone();
40056 hintInputElem.attr('placeholder', '');
40057 hintInputElem.val('');
40058 hintInputElem.css({
40059 'position': 'absolute',
40062 'border-color': 'transparent',
40063 'box-shadow': 'none',
40065 'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
40069 'position': 'relative',
40070 'vertical-align': 'top',
40071 'background-color': 'transparent'
40073 inputsContainer.append(hintInputElem);
40074 hintInputElem.after(element);
40077 //pop-up element used to display matches
40078 var popUpEl = angular.element('<div uib-typeahead-popup></div>');
40081 matches: 'matches',
40082 active: 'activeIdx',
40083 select: 'select(activeIdx, evt)',
40084 'move-in-progress': 'moveInProgress',
40086 position: 'position',
40087 'assign-is-open': 'assignIsOpen(isOpen)',
40088 debounce: 'debounceUpdate'
40090 //custom item template
40091 if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
40092 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
40095 if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
40096 popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
40099 var resetHint = function() {
40101 hintInputElem.val('');
40105 var resetMatches = function() {
40106 scope.matches = [];
40107 scope.activeIdx = -1;
40108 element.attr('aria-expanded', false);
40112 var getMatchId = function(index) {
40113 return popupId + '-option-' + index;
40116 // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
40117 // This attribute is added or removed automatically when the `activeIdx` changes.
40118 scope.$watch('activeIdx', function(index) {
40120 element.removeAttr('aria-activedescendant');
40122 element.attr('aria-activedescendant', getMatchId(index));
40126 var inputIsExactMatch = function(inputValue, index) {
40127 if (scope.matches.length > index && inputValue) {
40128 return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
40134 var getMatchesAsync = function(inputValue, evt) {
40135 var locals = {$viewValue: inputValue};
40136 isLoadingSetter(originalScope, true);
40137 isNoResultsSetter(originalScope, false);
40138 $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
40139 //it might happen that several async queries were in progress if a user were typing fast
40140 //but we are interested only in responses that correspond to the current view value
40141 var onCurrentRequest = inputValue === modelCtrl.$viewValue;
40142 if (onCurrentRequest && hasFocus) {
40143 if (matches && matches.length > 0) {
40144 scope.activeIdx = focusFirst ? 0 : -1;
40145 isNoResultsSetter(originalScope, false);
40146 scope.matches.length = 0;
40149 for (var i = 0; i < matches.length; i++) {
40150 locals[parserResult.itemName] = matches[i];
40151 scope.matches.push({
40153 label: parserResult.viewMapper(scope, locals),
40158 scope.query = inputValue;
40159 //position pop-up with matches - we need to re-calculate its position each time we are opening a window
40160 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
40161 //due to other elements being rendered
40162 recalculatePosition();
40164 element.attr('aria-expanded', true);
40166 //Select the single remaining option if user input matches
40167 if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
40168 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
40169 $$debounce(function() {
40170 scope.select(0, evt);
40171 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
40173 scope.select(0, evt);
40178 var firstLabel = scope.matches[0].label;
40179 if (inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
40180 hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
40183 hintInputElem.val('');
40188 isNoResultsSetter(originalScope, true);
40191 if (onCurrentRequest) {
40192 isLoadingSetter(originalScope, false);
40196 isLoadingSetter(originalScope, false);
40197 isNoResultsSetter(originalScope, true);
40201 // bind events only if appendToBody params exist - performance feature
40202 if (appendToBody) {
40203 angular.element($window).on('resize', fireRecalculating);
40204 $document.find('body').on('scroll', fireRecalculating);
40207 // Declare the debounced function outside recalculating for
40208 // proper debouncing
40209 var debouncedRecalculate = $$debounce(function() {
40210 // if popup is visible
40211 if (scope.matches.length) {
40212 recalculatePosition();
40215 scope.moveInProgress = false;
40216 }, eventDebounceTime);
40218 // Default progress type
40219 scope.moveInProgress = false;
40221 function fireRecalculating() {
40222 if (!scope.moveInProgress) {
40223 scope.moveInProgress = true;
40227 debouncedRecalculate();
40230 // recalculate actual position and set new values to scope
40231 // after digest loop is popup in right position
40232 function recalculatePosition() {
40233 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
40234 scope.position.top += element.prop('offsetHeight');
40237 //we need to propagate user's query so we can higlight matches
40238 scope.query = undefined;
40240 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
40241 var timeoutPromise;
40243 var scheduleSearchWithTimeout = function(inputValue) {
40244 timeoutPromise = $timeout(function() {
40245 getMatchesAsync(inputValue);
40249 var cancelPreviousTimeout = function() {
40250 if (timeoutPromise) {
40251 $timeout.cancel(timeoutPromise);
40257 scope.assignIsOpen = function (isOpen) {
40258 isOpenSetter(originalScope, isOpen);
40261 scope.select = function(activeIdx, evt) {
40262 //called from within the $digest() cycle
40267 locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
40268 model = parserResult.modelMapper(originalScope, locals);
40269 $setModelValue(originalScope, model);
40270 modelCtrl.$setValidity('editable', true);
40271 modelCtrl.$setValidity('parse', true);
40273 onSelectCallback(originalScope, {
40276 $label: parserResult.viewMapper(originalScope, locals),
40282 //return focus to the input element if a match was selected via a mouse click event
40283 // use timeout to avoid $rootScope:inprog error
40284 if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
40285 $timeout(function() { element[0].focus(); }, 0, false);
40289 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
40290 element.on('keydown', function(evt) {
40291 //typeahead is open and an "interesting" key was pressed
40292 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
40296 // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
40297 if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
40303 evt.preventDefault();
40305 switch (evt.which) {
40308 scope.$apply(function () {
40309 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
40310 $$debounce(function() {
40311 scope.select(scope.activeIdx, evt);
40312 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
40314 scope.select(scope.activeIdx, evt);
40319 evt.stopPropagation();
40325 scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
40327 popUpEl.find('li')[scope.activeIdx].scrollIntoView(false);
40330 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
40332 popUpEl.find('li')[scope.activeIdx].scrollIntoView(false);
40337 element.bind('focus', function (evt) {
40339 if (minLength === 0 && !modelCtrl.$viewValue) {
40340 $timeout(function() {
40341 getMatchesAsync(modelCtrl.$viewValue, evt);
40346 element.bind('blur', function(evt) {
40347 if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
40349 scope.$apply(function() {
40350 if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
40351 $$debounce(function() {
40352 scope.select(scope.activeIdx, evt);
40353 }, scope.debounceUpdate.blur);
40355 scope.select(scope.activeIdx, evt);
40359 if (!isEditable && modelCtrl.$error.editable) {
40360 modelCtrl.$viewValue = '';
40367 // Keep reference to click handler to unbind it.
40368 var dismissClickHandler = function(evt) {
40370 // Firefox treats right click as a click on document
40371 if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
40373 if (!$rootScope.$$phase) {
40379 $document.on('click', dismissClickHandler);
40381 originalScope.$on('$destroy', function() {
40382 $document.off('click', dismissClickHandler);
40383 if (appendToBody || appendTo) {
40387 if (appendToBody) {
40388 angular.element($window).off('resize', fireRecalculating);
40389 $document.find('body').off('scroll', fireRecalculating);
40391 // Prevent jQuery cache memory leak
40395 inputsContainer.remove();
40399 var $popup = $compile(popUpEl)(scope);
40401 if (appendToBody) {
40402 $document.find('body').append($popup);
40403 } else if (appendTo) {
40404 angular.element(appendTo).eq(0).append($popup);
40406 element.after($popup);
40409 this.init = function(_modelCtrl, _ngModelOptions) {
40410 modelCtrl = _modelCtrl;
40411 ngModelOptions = _ngModelOptions;
40413 scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
40415 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
40416 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
40417 modelCtrl.$parsers.unshift(function(inputValue) {
40420 if (minLength === 0 || inputValue && inputValue.length >= minLength) {
40421 if (waitTime > 0) {
40422 cancelPreviousTimeout();
40423 scheduleSearchWithTimeout(inputValue);
40425 getMatchesAsync(inputValue);
40428 isLoadingSetter(originalScope, false);
40429 cancelPreviousTimeout();
40438 // Reset in case user had typed something previously.
40439 modelCtrl.$setValidity('editable', true);
40443 modelCtrl.$setValidity('editable', false);
40447 modelCtrl.$formatters.push(function(modelValue) {
40448 var candidateViewValue, emptyViewValue;
40451 // The validity may be set to false via $parsers (see above) if
40452 // the model is restricted to selected values. If the model
40453 // is set manually it is considered to be valid.
40455 modelCtrl.$setValidity('editable', true);
40458 if (inputFormatter) {
40459 locals.$model = modelValue;
40460 return inputFormatter(originalScope, locals);
40463 //it might happen that we don't have enough info to properly render input value
40464 //we need to check for this situation and simply return model value if we can't apply custom formatting
40465 locals[parserResult.itemName] = modelValue;
40466 candidateViewValue = parserResult.viewMapper(originalScope, locals);
40467 locals[parserResult.itemName] = undefined;
40468 emptyViewValue = parserResult.viewMapper(originalScope, locals);
40470 return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
40475 .directive('uibTypeahead', function() {
40477 controller: 'UibTypeaheadController',
40478 require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
40479 link: function(originalScope, element, attrs, ctrls) {
40480 ctrls[2].init(ctrls[0], ctrls[1]);
40485 .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {
40492 moveInProgress: '=',
40498 templateUrl: function(element, attrs) {
40499 return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
40501 link: function(scope, element, attrs) {
40502 scope.templateUrl = attrs.templateUrl;
40504 scope.isOpen = function() {
40505 var isDropdownOpen = scope.matches.length > 0;
40506 scope.assignIsOpen({ isOpen: isDropdownOpen });
40507 return isDropdownOpen;
40510 scope.isActive = function(matchIdx) {
40511 return scope.active === matchIdx;
40514 scope.selectActive = function(matchIdx) {
40515 scope.active = matchIdx;
40518 scope.selectMatch = function(activeIdx, evt) {
40519 var debounce = scope.debounce();
40520 if (angular.isNumber(debounce) || angular.isObject(debounce)) {
40521 $$debounce(function() {
40522 scope.select({activeIdx: activeIdx, evt: evt});
40523 }, angular.isNumber(debounce) ? debounce : debounce['default']);
40525 scope.select({activeIdx: activeIdx, evt: evt});
40532 .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
40539 link: function(scope, element, attrs) {
40540 var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
40541 $templateRequest(tplUrl).then(function(tplContent) {
40542 var tplEl = angular.element(tplContent.trim());
40543 element.replaceWith(tplEl);
40544 $compile(tplEl)(scope);
40550 .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
40551 var isSanitizePresent;
40552 isSanitizePresent = $injector.has('$sanitize');
40554 function escapeRegexp(queryToEscape) {
40555 // Regex: capture the whole query string and replace it with the string that will be used to match
40556 // the results, for example if the capture is "a" the result will be \a
40557 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
40560 function containsHtml(matchItem) {
40561 return /<.*>/g.test(matchItem);
40564 return function(matchItem, query) {
40565 if (!isSanitizePresent && containsHtml(matchItem)) {
40566 $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
40568 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
40569 if (!isSanitizePresent) {
40570 matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
40576 angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
40577 $templateCache.put("uib/template/accordion/accordion-group.html",
40578 "<div class=\"panel\" ng-class=\"panelClass || 'panel-default'\">\n" +
40579 " <div class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n" +
40580 " <h4 class=\"panel-title\">\n" +
40581 " <div tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></div>\n" +
40584 " <div class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n" +
40585 " <div class=\"panel-body\" ng-transclude></div>\n" +
40591 angular.module("uib/template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
40592 $templateCache.put("uib/template/accordion/accordion.html",
40593 "<div class=\"panel-group\" ng-transclude></div>");
40596 angular.module("uib/template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
40597 $templateCache.put("uib/template/alert/alert.html",
40598 "<div class=\"alert\" ng-class=\"['alert-' + (type || 'warning'), closeable ? 'alert-dismissible' : null]\" role=\"alert\">\n" +
40599 " <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close({$event: $event})\">\n" +
40600 " <span aria-hidden=\"true\">×</span>\n" +
40601 " <span class=\"sr-only\">Close</span>\n" +
40603 " <div ng-transclude></div>\n" +
40608 angular.module("uib/template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) {
40609 $templateCache.put("uib/template/carousel/carousel.html",
40610 "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" +
40611 " <div class=\"carousel-inner\" ng-transclude></div>\n" +
40612 " <a role=\"button\" href class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides.length > 1\">\n" +
40613 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></span>\n" +
40614 " <span class=\"sr-only\">previous</span>\n" +
40616 " <a role=\"button\" href class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides.length > 1\">\n" +
40617 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></span>\n" +
40618 " <span class=\"sr-only\">next</span>\n" +
40620 " <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" +
40621 " <li ng-repeat=\"slide in slides | orderBy:indexOfSlide track by $index\" ng-class=\"{ active: isActive(slide) }\" ng-click=\"select(slide)\">\n" +
40622 " <span class=\"sr-only\">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if=\"isActive(slide)\">, currently active</span></span>\n" +
40628 angular.module("uib/template/carousel/slide.html", []).run(["$templateCache", function($templateCache) {
40629 $templateCache.put("uib/template/carousel/slide.html",
40630 "<div ng-class=\"{\n" +
40631 " 'active': active\n" +
40632 " }\" class=\"item text-center\" ng-transclude></div>\n" +
40636 angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
40637 $templateCache.put("uib/template/datepicker/datepicker.html",
40638 "<div class=\"uib-datepicker\" ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
40639 " <uib-daypicker ng-switch-when=\"day\" tabindex=\"0\"></uib-daypicker>\n" +
40640 " <uib-monthpicker ng-switch-when=\"month\" tabindex=\"0\"></uib-monthpicker>\n" +
40641 " <uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\"></uib-yearpicker>\n" +
40645 angular.module("uib/template/datepicker/day.html", []).run(["$templateCache", function($templateCache) {
40646 $templateCache.put("uib/template/datepicker/day.html",
40647 "<table class=\"uib-daypicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40650 " <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" +
40651 " <th colspan=\"{{::5 + showWeeks}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
40652 " <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" +
40655 " <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n" +
40656 " <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n" +
40660 " <tr class=\"uib-weeks\" ng-repeat=\"row in rows track by $index\">\n" +
40661 " <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
40662 " <td ng-repeat=\"dt in row\" class=\"uib-day text-center\" role=\"gridcell\"\n" +
40663 " id=\"{{::dt.uid}}\"\n" +
40664 " ng-class=\"::dt.customClass\">\n" +
40665 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default btn-sm\"\n" +
40666 " uib-is-class=\"\n" +
40667 " 'btn-info' for selectedDt,\n" +
40668 " 'active' for activeDt\n" +
40670 " ng-click=\"select(dt.date)\"\n" +
40671 " ng-disabled=\"::dt.disabled\"\n" +
40672 " tabindex=\"-1\"><span ng-class=\"::{'text-muted': dt.secondary, 'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40680 angular.module("uib/template/datepicker/month.html", []).run(["$templateCache", function($templateCache) {
40681 $templateCache.put("uib/template/datepicker/month.html",
40682 "<table class=\"uib-monthpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40685 " <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" +
40686 " <th><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
40687 " <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" +
40691 " <tr class=\"uib-months\" ng-repeat=\"row in rows track by $index\">\n" +
40692 " <td ng-repeat=\"dt in row\" class=\"uib-month text-center\" role=\"gridcell\"\n" +
40693 " id=\"{{::dt.uid}}\"\n" +
40694 " ng-class=\"::dt.customClass\">\n" +
40695 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\"\n" +
40696 " uib-is-class=\"\n" +
40697 " 'btn-info' for selectedDt,\n" +
40698 " 'active' for activeDt\n" +
40700 " ng-click=\"select(dt.date)\"\n" +
40701 " ng-disabled=\"::dt.disabled\"\n" +
40702 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40710 angular.module("uib/template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) {
40711 $templateCache.put("uib/template/datepicker/popup.html",
40712 "<ul class=\"uib-datepicker-popup dropdown-menu\" dropdown-nested ng-if=\"isOpen\" style=\"display: block\" ng-style=\"{top: position.top+'px', left: position.left+'px'}\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n" +
40713 " <li ng-transclude></li>\n" +
40714 " <li ng-if=\"showButtonBar\" style=\"padding:10px 9px 2px\" class=\"uib-button-bar\">\n" +
40715 " <span class=\"btn-group pull-left\">\n" +
40716 " <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today')\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" +
40717 " <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" +
40719 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close()\">{{ getText('close') }}</button>\n" +
40725 angular.module("uib/template/datepicker/year.html", []).run(["$templateCache", function($templateCache) {
40726 $templateCache.put("uib/template/datepicker/year.html",
40727 "<table class=\"uib-yearpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40730 " <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" +
40731 " <th colspan=\"{{::columns - 2}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
40732 " <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" +
40736 " <tr class=\"uib-years\" ng-repeat=\"row in rows track by $index\">\n" +
40737 " <td ng-repeat=\"dt in row\" class=\"uib-year text-center\" role=\"gridcell\"\n" +
40738 " id=\"{{::dt.uid}}\"\n" +
40739 " ng-class=\"::dt.customClass\">\n" +
40740 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\"\n" +
40741 " uib-is-class=\"\n" +
40742 " 'btn-info' for selectedDt,\n" +
40743 " 'active' for activeDt\n" +
40745 " ng-click=\"select(dt.date)\"\n" +
40746 " ng-disabled=\"::dt.disabled\"\n" +
40747 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40755 angular.module("uib/template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
40756 $templateCache.put("uib/template/modal/backdrop.html",
40757 "<div class=\"modal-backdrop\"\n" +
40758 " uib-modal-animation-class=\"fade\"\n" +
40759 " modal-in-class=\"in\"\n" +
40760 " ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" +
40765 angular.module("uib/template/modal/window.html", []).run(["$templateCache", function($templateCache) {
40766 $templateCache.put("uib/template/modal/window.html",
40767 "<div modal-render=\"{{$isRendered}}\" tabindex=\"-1\" role=\"dialog\" class=\"modal\"\n" +
40768 " uib-modal-animation-class=\"fade\"\n" +
40769 " modal-in-class=\"in\"\n" +
40770 " ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\">\n" +
40771 " <div class=\"modal-dialog\" ng-class=\"size ? 'modal-' + size : ''\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n" +
40776 angular.module("uib/template/pager/pager.html", []).run(["$templateCache", function($templateCache) {
40777 $templateCache.put("uib/template/pager/pager.html",
40778 "<ul class=\"pager\">\n" +
40779 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
40780 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
40785 angular.module("uib/template/pagination/pager.html", []).run(["$templateCache", function($templateCache) {
40786 $templateCache.put("uib/template/pagination/pager.html",
40787 "<ul class=\"pager\">\n" +
40788 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
40789 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
40794 angular.module("uib/template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
40795 $templateCache.put("uib/template/pagination/pagination.html",
40796 "<ul class=\"pagination\">\n" +
40797 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\">{{::getText('first')}}</a></li>\n" +
40798 " <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" +
40799 " <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" +
40800 " <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" +
40801 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\">{{::getText('last')}}</a></li>\n" +
40806 angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) {
40807 $templateCache.put("uib/template/tooltip/tooltip-html-popup.html",
40808 "<div class=\"tooltip\"\n" +
40809 " tooltip-animation-class=\"fade\"\n" +
40810 " uib-tooltip-classes\n" +
40811 " ng-class=\"{ in: isOpen() }\">\n" +
40812 " <div class=\"tooltip-arrow\"></div>\n" +
40813 " <div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" +
40818 angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
40819 $templateCache.put("uib/template/tooltip/tooltip-popup.html",
40820 "<div class=\"tooltip\"\n" +
40821 " tooltip-animation-class=\"fade\"\n" +
40822 " uib-tooltip-classes\n" +
40823 " ng-class=\"{ in: isOpen() }\">\n" +
40824 " <div class=\"tooltip-arrow\"></div>\n" +
40825 " <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
40830 angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) {
40831 $templateCache.put("uib/template/tooltip/tooltip-template-popup.html",
40832 "<div class=\"tooltip\"\n" +
40833 " tooltip-animation-class=\"fade\"\n" +
40834 " uib-tooltip-classes\n" +
40835 " ng-class=\"{ in: isOpen() }\">\n" +
40836 " <div class=\"tooltip-arrow\"></div>\n" +
40837 " <div class=\"tooltip-inner\"\n" +
40838 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
40839 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
40844 angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) {
40845 $templateCache.put("uib/template/popover/popover-html.html",
40846 "<div class=\"popover\"\n" +
40847 " tooltip-animation-class=\"fade\"\n" +
40848 " uib-tooltip-classes\n" +
40849 " ng-class=\"{ in: isOpen() }\">\n" +
40850 " <div class=\"arrow\"></div>\n" +
40852 " <div class=\"popover-inner\">\n" +
40853 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40854 " <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n" +
40860 angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) {
40861 $templateCache.put("uib/template/popover/popover-template.html",
40862 "<div class=\"popover\"\n" +
40863 " tooltip-animation-class=\"fade\"\n" +
40864 " uib-tooltip-classes\n" +
40865 " ng-class=\"{ in: isOpen() }\">\n" +
40866 " <div class=\"arrow\"></div>\n" +
40868 " <div class=\"popover-inner\">\n" +
40869 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40870 " <div class=\"popover-content\"\n" +
40871 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
40872 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
40878 angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
40879 $templateCache.put("uib/template/popover/popover.html",
40880 "<div class=\"popover\"\n" +
40881 " tooltip-animation-class=\"fade\"\n" +
40882 " uib-tooltip-classes\n" +
40883 " ng-class=\"{ in: isOpen() }\">\n" +
40884 " <div class=\"arrow\"></div>\n" +
40886 " <div class=\"popover-inner\">\n" +
40887 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40888 " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
40894 angular.module("uib/template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
40895 $templateCache.put("uib/template/progressbar/bar.html",
40896 "<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" +
40900 angular.module("uib/template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
40901 $templateCache.put("uib/template/progressbar/progress.html",
40902 "<div class=\"progress\" ng-transclude aria-labelledby=\"{{::title}}\"></div>");
40905 angular.module("uib/template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
40906 $templateCache.put("uib/template/progressbar/progressbar.html",
40907 "<div class=\"progress\">\n" +
40908 " <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" +
40913 angular.module("uib/template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
40914 $templateCache.put("uib/template/rating/rating.html",
40915 "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\">\n" +
40916 " <span ng-repeat-start=\"r in range track by $index\" class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" +
40917 " <i ng-repeat-end ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < value && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\" ng-attr-title=\"{{r.title}}\" aria-valuetext=\"{{r.title}}\"></i>\n" +
40922 angular.module("uib/template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
40923 $templateCache.put("uib/template/tabs/tab.html",
40924 "<li ng-class=\"{active: active, disabled: disabled}\" class=\"uib-tab\">\n" +
40925 " <div ng-click=\"select()\" uib-tab-heading-transclude>{{heading}}</div>\n" +
40930 angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
40931 $templateCache.put("uib/template/tabs/tabset.html",
40933 " <ul class=\"nav nav-{{type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
40934 " <div class=\"tab-content\">\n" +
40935 " <div class=\"tab-pane\" \n" +
40936 " ng-repeat=\"tab in tabs\" \n" +
40937 " ng-class=\"{active: tab.active}\"\n" +
40938 " uib-tab-content-transclude=\"tab\">\n" +
40945 angular.module("uib/template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
40946 $templateCache.put("uib/template/timepicker/timepicker.html",
40947 "<table class=\"uib-timepicker\">\n" +
40949 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
40950 " <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" +
40951 " <td> </td>\n" +
40952 " <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" +
40953 " <td ng-show=\"showSeconds\"> </td>\n" +
40954 " <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" +
40955 " <td ng-show=\"showMeridian\"></td>\n" +
40958 " <td class=\"form-group uib-time hours\" ng-class=\"{'has-error': invalidHours}\">\n" +
40959 " <input style=\"width:50px;\" type=\"text\" placeholder=\"HH\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"disabled\" ng-blur=\"blur()\">\n" +
40961 " <td class=\"uib-separator\">:</td>\n" +
40962 " <td class=\"form-group uib-time minutes\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
40963 " <input style=\"width:50px;\" type=\"text\" placeholder=\"MM\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"disabled\" ng-blur=\"blur()\">\n" +
40965 " <td ng-show=\"showSeconds\" class=\"uib-separator\">:</td>\n" +
40966 " <td class=\"form-group uib-time seconds\" ng-class=\"{'has-error': invalidSeconds}\" ng-show=\"showSeconds\">\n" +
40967 " <input style=\"width:50px;\" type=\"text\" ng-model=\"seconds\" ng-change=\"updateSeconds()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"disabled\" ng-blur=\"blur()\">\n" +
40969 " <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" +
40971 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
40972 " <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" +
40973 " <td> </td>\n" +
40974 " <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" +
40975 " <td ng-show=\"showSeconds\"> </td>\n" +
40976 " <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" +
40977 " <td ng-show=\"showMeridian\"></td>\n" +
40984 angular.module("uib/template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
40985 $templateCache.put("uib/template/typeahead/typeahead-match.html",
40986 "<a href tabindex=\"-1\" ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"></a>\n" +
40990 angular.module("uib/template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
40991 $templateCache.put("uib/template/typeahead/typeahead-popup.html",
40992 "<ul class=\"dropdown-menu\" ng-show=\"isOpen() && !moveInProgress\" ng-style=\"{top: position().top+'px', left: position().left+'px'}\" style=\"display: block;\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" +
40993 " <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" +
40994 " <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
40999 angular.module('ui.bootstrap.carousel').run(function() {!angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>'); })
41003 /***/ function(module, exports) {
41008 (function (declares) {
41009 var CommandInfo = (function () {
41010 function CommandInfo(name) {
41013 return CommandInfo;
41015 declares.CommandInfo = CommandInfo;
41016 })(declares = app.declares || (app.declares = {}));
41017 })(app || (app = {}));
41021 (function (services) {
41022 var APIEndPoint = (function () {
41023 function APIEndPoint($resource, $http) {
41024 this.$resource = $resource;
41025 this.$http = $http;
41027 APIEndPoint.prototype.resource = function (endPoint, data) {
41028 var customAction = {
41034 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
41036 return this.$resource(endPoint, {}, { execute: execute });
41038 APIEndPoint.prototype.getOptionControlFile = function (command) {
41039 var endPoint = '/api/v1/optionControlFile/' + command;
41040 return this.resource(endPoint, {}).get();
41042 APIEndPoint.prototype.getFiles = function (fileId) {
41043 var endPoint = '/api/v1/workspace';
41045 endPoint += '/' + fileId;
41047 return this.resource(endPoint, {}).get();
41049 APIEndPoint.prototype.getDirectories = function () {
41050 var endPoint = '/api/v1/all/workspace/directory';
41051 return this.resource(endPoint, {}).get();
41053 APIEndPoint.prototype.getTags = function () {
41054 var endPoint = '/api/v1/tagList';
41055 return this.resource(endPoint, {}).get();
41057 APIEndPoint.prototype.getCommands = function () {
41058 var endPoint = '/api/v1/commandList';
41059 return this.resource(endPoint, {}).get();
41061 APIEndPoint.prototype.execute = function (data) {
41062 var endPoint = '/api/v1/execution';
41063 var fd = new FormData();
41064 fd.append('data', data);
41065 return this.$http.post(endPoint, fd, {
41066 headers: { 'Content-Type': undefined },
41067 transformRequest: angular.identity
41070 APIEndPoint.prototype.debug = function () {
41071 var endPoint = '/api/v1/debug';
41072 return this.$http.get(endPoint);
41074 APIEndPoint.prototype.help = function (command) {
41075 var endPoint = '/api/v1/help/' + command;
41076 return this.$http.get(endPoint);
41078 return APIEndPoint;
41080 services.APIEndPoint = APIEndPoint;
41081 })(services = app.services || (app.services = {}));
41082 })(app || (app = {}));
41086 (function (services) {
41087 var MyModal = (function () {
41088 function MyModal($uibModal) {
41089 this.$uibModal = $uibModal;
41090 this.modalOption = {
41097 MyModal.prototype.open = function (modalName) {
41098 if (modalName === 'SelectCommand') {
41099 this.modalOption.templateUrl = 'templates/select-command.html';
41100 this.modalOption.size = 'lg';
41102 return this.$uibModal.open(this.modalOption);
41104 MyModal.prototype.selectCommand = function () {
41105 this.modalOption.templateUrl = 'templates/select-command.html';
41106 this.modalOption.controller = 'selectCommandController';
41107 this.modalOption.controllerAs = 'c';
41108 this.modalOption.size = 'lg';
41109 return this.$uibModal.open(this.modalOption);
41111 MyModal.prototype.preview = function () {
41112 this.modalOption.templateUrl = 'templates/preview.html';
41113 this.modalOption.controller = 'previewController';
41114 this.modalOption.controllerAs = 'c';
41115 this.modalOption.size = 'lg';
41116 return this.$uibModal.open(this.modalOption);
41118 MyModal.$inject = ['$uibModal'];
41121 services.MyModal = MyModal;
41122 })(services = app.services || (app.services = {}));
41123 })(app || (app = {}));
41127 (function (services) {
41128 var WebSocket = (function () {
41129 function WebSocket($rootScope) {
41130 this.$rootScope = $rootScope;
41131 this.socket = io.connect();
41133 WebSocket.prototype.on = function (eventName, callback) {
41134 var socket = this.socket;
41135 var rootScope = this.$rootScope;
41136 socket.on(eventName, function () {
41137 var args = arguments;
41138 rootScope.$apply(function () {
41139 callback.apply(socket, args);
41143 WebSocket.prototype.emit = function (eventName, data, callback) {
41144 var socket = this.socket;
41145 var rootScope = this.$rootScope;
41146 this.socket.emit(eventName, data, function () {
41147 var args = arguments;
41148 rootScope.$apply(function () {
41150 callback.apply(socket, args);
41156 services.WebSocket = WebSocket;
41157 })(services = app.services || (app.services = {}));
41158 })(app || (app = {}));
41162 (function (services) {
41163 var Console = (function () {
41164 function Console(WebSocket, $rootScope) {
41165 this.WebSocket = WebSocket;
41166 this.$rootScope = $rootScope;
41167 this.WebSocket = WebSocket;
41168 this.$rootScope = $rootScope;
41169 this.directiveIDs = [];
41170 var directiveIDs = this.directiveIDs;
41171 this.WebSocket.on('console', function (d) {
41173 var message = d.message;
41174 if (directiveIDs.indexOf(id) > -1) {
41175 $rootScope.$emit(id, message);
41179 Console.prototype.addDirective = function (id) {
41180 if (!(this.directiveIDs.indexOf(id) > -1)) {
41181 this.directiveIDs.push(id);
41184 Console.prototype.removeDirective = function (id) {
41185 var i = this.directiveIDs.indexOf(id);
41187 this.directiveIDs.splice(i, 1);
41190 Console.prototype.showIDs = function () {
41191 console.log(this.directiveIDs);
41195 services.Console = Console;
41196 })(services = app.services || (app.services = {}));
41197 })(app || (app = {}));
41201 (function (directives) {
41202 var Command = (function () {
41203 function Command() {
41204 this.restrict = 'E';
41205 this.replace = true;
41207 this.controller = 'commandController';
41208 this.controllerAs = 'ctrl';
41209 this.bindToController = {
41215 this.templateUrl = 'templates/command.html';
41217 Command.Factory = function () {
41218 var directive = function () {
41219 return new Command();
41221 directive.$inject = [];
41226 directives.Command = Command;
41227 var CommandController = (function () {
41228 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
41229 this.APIEndPoint = APIEndPoint;
41230 this.$scope = $scope;
41231 this.MyModal = MyModal;
41232 this.WebSocket = WebSocket;
41233 this.$window = $window;
41234 this.$rootScope = $rootScope;
41235 this.Console = Console;
41236 var controller = this;
41238 .getOptionControlFile(this.name)
41240 .then(function (result) {
41241 controller.options = result.info;
41246 .then(function (result) {
41247 controller.dirs = result.info;
41249 this.heading = "[" + this.index + "]: dcdFilePrint";
41250 this.isOpen = true;
41251 this.$scope.$on('close', function () {
41252 controller.isOpen = false;
41256 return Math.floor((1 + Math.random()) * 0x10000)
41260 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
41261 s4() + '-' + s4() + s4() + s4();
41263 this.uuid = guid();
41264 this.Console.addDirective(this.uuid);
41265 this.Console.showIDs();
41267 CommandController.prototype.submit = function () {
41269 angular.forEach(this.options, function (option) {
41271 name: option.option,
41274 angular.forEach(option.arg, function (arg) {
41276 if (typeof arg.input === 'object') {
41277 obj.arguments.push(arg.input.name);
41280 obj.arguments.push(arg.input);
41284 if (obj.arguments.length > 0) {
41289 command: this.name,
41290 workspace: this.workspace.fileId,
41294 .execute(JSON.stringify(execObj))
41295 .then(function (result) {
41296 console.log(result);
41299 CommandController.prototype.removeMySelf = function (index) {
41300 this.$scope.$destroy();
41301 this.Console.removeDirective(this.uuid);
41302 this.remove()(index, this.list);
41303 this.Console.showIDs();
41305 CommandController.prototype.reloadFiles = function () {
41307 var fileId = this.workspace.fileId;
41311 .then(function (result) {
41312 var status = result.status;
41313 if (status === 'success') {
41314 _this.files = result.info;
41317 console.log(result.message);
41321 CommandController.prototype.debug = function () {
41322 var div = angular.element(this.$window.document).find("div");
41325 angular.forEach(div, function (v) {
41326 if (v.className === "panel-body console") {
41329 else if (v.className === "row parameters-console") {
41333 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
41334 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
41335 consoleTag.style.height = consoleHeight;
41336 consoleTag.style.width = consoleWidth;
41338 CommandController.prototype.help = function () {
41341 .then(function (result) {
41342 console.log(result);
41345 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
41346 return CommandController;
41348 directives.CommandController = CommandController;
41349 })(directives = app.directives || (app.directives = {}));
41350 })(app || (app = {}));
41354 (function (directives) {
41355 var HeaderMenu = (function () {
41356 function HeaderMenu() {
41357 this.restrict = 'E';
41358 this.replace = true;
41359 this.templateUrl = 'templates/header-menu.html';
41360 this.controller = 'HeaderMenuController';
41361 this.controllerAs = 'hmc';
41364 HeaderMenu.Factory = function () {
41365 var directive = function () {
41366 return new HeaderMenu();
41372 directives.HeaderMenu = HeaderMenu;
41373 var HeaderMenuController = (function () {
41374 function HeaderMenuController($state) {
41375 this.$state = $state;
41376 this.isExecution = this.$state.current.name === 'execution';
41377 this.isWorkspace = this.$state.current.name === 'workspace';
41378 this.isHistory = this.$state.current.name === 'history';
41380 HeaderMenuController.prototype.transit = function (state) {
41381 this.$state.go(state);
41383 HeaderMenuController.$inject = ['$state'];
41384 return HeaderMenuController;
41386 directives.HeaderMenuController = HeaderMenuController;
41387 })(directives = app.directives || (app.directives = {}));
41388 })(app || (app = {}));
41392 (function (directives) {
41393 var Option = (function () {
41394 function Option() {
41395 this.restrict = 'E';
41396 this.replace = true;
41397 this.controller = 'optionController';
41398 this.bindToController = {
41403 this.templateUrl = 'templates/option.html';
41404 this.controllerAs = 'ctrl';
41406 Option.Factory = function () {
41407 var directive = function () {
41408 return new Option();
41410 directive.$inject = [];
41415 directives.Option = Option;
41416 var OptionController = (function () {
41417 function OptionController() {
41418 var controller = this;
41419 angular.forEach(controller.info.arg, function (arg) {
41420 if (arg.initialValue) {
41421 if (arg.formType === 'number') {
41422 arg.input = parseInt(arg.initialValue);
41425 arg.input = arg.initialValue;
41430 OptionController.$inject = [];
41431 return OptionController;
41433 directives.OptionController = OptionController;
41434 })(directives = app.directives || (app.directives = {}));
41435 })(app || (app = {}));
41439 (function (directives) {
41440 var Directory = (function () {
41441 function Directory() {
41442 this.restrict = 'E';
41443 this.replace = true;
41444 this.controller = 'directoryController';
41445 this.controllerAs = 'ctrl';
41446 this.bindToController = {
41452 this.templateUrl = 'templates/directory.html';
41454 Directory.Factory = function () {
41455 var directive = function () {
41456 return new Directory();
41462 directives.Directory = Directory;
41463 var DirectoryController = (function () {
41464 function DirectoryController(APIEndPoint, $scope) {
41465 this.APIEndPoint = APIEndPoint;
41466 this.$scope = $scope;
41467 var controller = this;
41469 .getFiles(this.info.fileId)
41471 .then(function (result) {
41472 if (result.status === 'success') {
41473 controller.files = result.info;
41474 angular.forEach(result.info, function (file) {
41475 if (file.fileType === '0') {
41477 if (controller.info.path === '/') {
41478 o.path = '/' + file.name;
41481 o.path = controller.info.path + '/' + file.name;
41483 controller.add()(o, controller.list);
41490 DirectoryController.$inject = ['APIEndPoint', '$scope'];
41491 return DirectoryController;
41493 directives.DirectoryController = DirectoryController;
41494 })(directives = app.directives || (app.directives = {}));
41495 })(app || (app = {}));
41499 (function (controllers) {
41500 var Execution = (function () {
41501 function Execution(MyModal, $scope) {
41502 this.MyModal = MyModal;
41503 this.$scope = $scope;
41504 this.commandInfoList = [];
41507 Execution.prototype.add = function () {
41508 this.$scope.$broadcast('close');
41509 var commandInfoList = this.commandInfoList;
41510 var commandInstance = this.MyModal.selectCommand();
41513 .then(function (command) {
41514 commandInfoList.push(new app.declares.CommandInfo(command));
41517 Execution.prototype.open = function () {
41518 var result = this.MyModal.open('SelectCommand');
41519 console.log(result);
41521 Execution.prototype.remove = function (index, list) {
41522 list.splice(index, 1);
41524 Execution.prototype.close = function () {
41525 console.log("close");
41527 Execution.$inject = ['MyModal', '$scope'];
41530 controllers.Execution = Execution;
41531 })(controllers = app.controllers || (app.controllers = {}));
41532 })(app || (app = {}));
41536 (function (controllers) {
41537 var Workspace = (function () {
41538 function Workspace($scope, APIEndPoint, MyModal) {
41539 this.$scope = $scope;
41540 this.APIEndPoint = APIEndPoint;
41541 this.MyModal = MyModal;
41542 this.directoryList = [];
41543 var controller = this;
41544 var directoryList = this.directoryList;
41546 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
41554 directoryList.push(o);
41556 Workspace.prototype.addDirectory = function (info, directoryList) {
41557 directoryList.push(info);
41559 Workspace.prototype.debug = function () {
41560 this.MyModal.preview();
41562 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
41565 controllers.Workspace = Workspace;
41566 })(controllers = app.controllers || (app.controllers = {}));
41567 })(app || (app = {}));
41571 (function (controllers) {
41572 var History = (function () {
41573 function History($scope) {
41574 this.page = "History";
41576 History.$inject = ['$scope'];
41579 controllers.History = History;
41580 })(controllers = app.controllers || (app.controllers = {}));
41581 })(app || (app = {}));
41585 (function (controllers) {
41586 var SelectCommand = (function () {
41587 function SelectCommand($scope, APIEndPoint, $modalInstance) {
41588 this.APIEndPoint = APIEndPoint;
41589 this.$modalInstance = $modalInstance;
41590 var controller = this;
41593 .$promise.then(function (result) {
41594 controller.tags = result.info;
41598 .$promise.then(function (result) {
41599 controller.commands = result.info;
41601 this.currentTag = 'all';
41603 SelectCommand.prototype.changeTag = function (tag) {
41604 this.currentTag = tag;
41606 SelectCommand.prototype.selectCommand = function (command) {
41607 this.$modalInstance.close(command);
41609 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
41610 return SelectCommand;
41612 controllers.SelectCommand = SelectCommand;
41613 })(controllers = app.controllers || (app.controllers = {}));
41614 })(app || (app = {}));
41618 (function (controllers) {
41619 var Preview = (function () {
41620 function Preview($scope, APIEndPoint, $modalInstance) {
41621 this.APIEndPoint = APIEndPoint;
41622 this.$modalInstance = $modalInstance;
41623 var controller = this;
41624 console.log('preview');
41626 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
41629 controllers.Preview = Preview;
41630 })(controllers = app.controllers || (app.controllers = {}));
41631 })(app || (app = {}));
41633 (function (filters) {
41635 return function (commands, tag) {
41637 angular.forEach(commands, function (command) {
41639 angular.forEach(command.tags, function (value) {
41644 result.push(command);
41650 })(filters || (filters = {}));
41654 var appName = 'zephyr';
41655 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
41656 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
41657 $urlRouterProvider.otherwise('/execution');
41658 $locationProvider.html5Mode({
41663 .state('execution', {
41665 templateUrl: 'templates/execution.html',
41666 controller: 'executionController',
41669 .state('workspace', {
41671 templateUrl: 'templates/workspace.html',
41672 controller: 'workspaceController',
41675 .state('history', {
41677 templateUrl: 'templates/history.html',
41678 controller: 'historyController',
41682 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
41683 app.zephyr.service('MyModal', app.services.MyModal);
41684 app.zephyr.service('WebSocket', app.services.WebSocket);
41685 app.zephyr.service('Console', app.services.Console);
41686 app.zephyr.filter('Tag', filters.Tag);
41687 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
41688 app.zephyr.controller('previewController', app.controllers.Preview);
41689 app.zephyr.controller('executionController', app.controllers.Execution);
41690 app.zephyr.controller('workspaceController', app.controllers.Workspace);
41691 app.zephyr.controller('historyController', app.controllers.History);
41692 app.zephyr.controller('commandController', app.directives.CommandController);
41693 app.zephyr.controller('optionController', app.directives.OptionController);
41694 app.zephyr.controller('directoryController', app.directives.DirectoryController);
41695 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
41696 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
41697 app.zephyr.directive('command', app.directives.Command.Factory());
41698 app.zephyr.directive('option', app.directives.Option.Factory());
41699 app.zephyr.directive('directory', app.directives.Directory.Factory());
41700 })(app || (app = {}));
41705 /***/ function(module, exports) {
41710 (function (declares) {
41711 var CommandInfo = (function () {
41712 function CommandInfo(name) {
41715 return CommandInfo;
41717 declares.CommandInfo = CommandInfo;
41718 })(declares = app.declares || (app.declares = {}));
41719 })(app || (app = {}));
41723 (function (services) {
41724 var APIEndPoint = (function () {
41725 function APIEndPoint($resource, $http) {
41726 this.$resource = $resource;
41727 this.$http = $http;
41729 APIEndPoint.prototype.resource = function (endPoint, data) {
41730 var customAction = {
41736 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
41738 return this.$resource(endPoint, {}, { execute: execute });
41740 APIEndPoint.prototype.getOptionControlFile = function (command) {
41741 var endPoint = '/api/v1/optionControlFile/' + command;
41742 return this.resource(endPoint, {}).get();
41744 APIEndPoint.prototype.getFiles = function (fileId) {
41745 var endPoint = '/api/v1/workspace';
41747 endPoint += '/' + fileId;
41749 return this.resource(endPoint, {}).get();
41751 APIEndPoint.prototype.getDirectories = function () {
41752 var endPoint = '/api/v1/all/workspace/directory';
41753 return this.resource(endPoint, {}).get();
41755 APIEndPoint.prototype.getTags = function () {
41756 var endPoint = '/api/v1/tagList';
41757 return this.resource(endPoint, {}).get();
41759 APIEndPoint.prototype.getCommands = function () {
41760 var endPoint = '/api/v1/commandList';
41761 return this.resource(endPoint, {}).get();
41763 APIEndPoint.prototype.execute = function (data) {
41764 var endPoint = '/api/v1/execution';
41765 var fd = new FormData();
41766 fd.append('data', data);
41767 return this.$http.post(endPoint, fd, {
41768 headers: { 'Content-Type': undefined },
41769 transformRequest: angular.identity
41772 APIEndPoint.prototype.debug = function () {
41773 var endPoint = '/api/v1/debug';
41774 return this.$http.get(endPoint);
41776 APIEndPoint.prototype.help = function (command) {
41777 var endPoint = '/api/v1/help/' + command;
41778 return this.$http.get(endPoint);
41780 return APIEndPoint;
41782 services.APIEndPoint = APIEndPoint;
41783 })(services = app.services || (app.services = {}));
41784 })(app || (app = {}));
41788 (function (services) {
41789 var MyModal = (function () {
41790 function MyModal($uibModal) {
41791 this.$uibModal = $uibModal;
41792 this.modalOption = {
41799 MyModal.prototype.open = function (modalName) {
41800 if (modalName === 'SelectCommand') {
41801 this.modalOption.templateUrl = 'templates/select-command.html';
41802 this.modalOption.size = 'lg';
41804 return this.$uibModal.open(this.modalOption);
41806 MyModal.prototype.selectCommand = function () {
41807 this.modalOption.templateUrl = 'templates/select-command.html';
41808 this.modalOption.controller = 'selectCommandController';
41809 this.modalOption.controllerAs = 'c';
41810 this.modalOption.size = 'lg';
41811 return this.$uibModal.open(this.modalOption);
41813 MyModal.prototype.preview = function () {
41814 this.modalOption.templateUrl = 'templates/preview.html';
41815 this.modalOption.controller = 'previewController';
41816 this.modalOption.controllerAs = 'c';
41817 this.modalOption.size = 'lg';
41818 return this.$uibModal.open(this.modalOption);
41820 MyModal.$inject = ['$uibModal'];
41823 services.MyModal = MyModal;
41824 })(services = app.services || (app.services = {}));
41825 })(app || (app = {}));
41829 (function (services) {
41830 var WebSocket = (function () {
41831 function WebSocket($rootScope) {
41832 this.$rootScope = $rootScope;
41833 this.socket = io.connect();
41835 WebSocket.prototype.on = function (eventName, callback) {
41836 var socket = this.socket;
41837 var rootScope = this.$rootScope;
41838 socket.on(eventName, function () {
41839 var args = arguments;
41840 rootScope.$apply(function () {
41841 callback.apply(socket, args);
41845 WebSocket.prototype.emit = function (eventName, data, callback) {
41846 var socket = this.socket;
41847 var rootScope = this.$rootScope;
41848 this.socket.emit(eventName, data, function () {
41849 var args = arguments;
41850 rootScope.$apply(function () {
41852 callback.apply(socket, args);
41858 services.WebSocket = WebSocket;
41859 })(services = app.services || (app.services = {}));
41860 })(app || (app = {}));
41864 (function (services) {
41865 var Console = (function () {
41866 function Console(WebSocket, $rootScope) {
41867 this.WebSocket = WebSocket;
41868 this.$rootScope = $rootScope;
41869 this.WebSocket = WebSocket;
41870 this.$rootScope = $rootScope;
41871 this.directiveIDs = [];
41872 var directiveIDs = this.directiveIDs;
41873 this.WebSocket.on('console', function (d) {
41875 var message = d.message;
41876 if (directiveIDs.indexOf(id) > -1) {
41877 $rootScope.$emit(id, message);
41881 Console.prototype.addDirective = function (id) {
41882 if (!(this.directiveIDs.indexOf(id) > -1)) {
41883 this.directiveIDs.push(id);
41886 Console.prototype.removeDirective = function (id) {
41887 var i = this.directiveIDs.indexOf(id);
41889 this.directiveIDs.splice(i, 1);
41892 Console.prototype.showIDs = function () {
41893 console.log(this.directiveIDs);
41897 services.Console = Console;
41898 })(services = app.services || (app.services = {}));
41899 })(app || (app = {}));
41903 (function (directives) {
41904 var Command = (function () {
41905 function Command() {
41906 this.restrict = 'E';
41907 this.replace = true;
41909 this.controller = 'commandController';
41910 this.controllerAs = 'ctrl';
41911 this.bindToController = {
41917 this.templateUrl = 'templates/command.html';
41919 Command.Factory = function () {
41920 var directive = function () {
41921 return new Command();
41923 directive.$inject = [];
41928 directives.Command = Command;
41929 var CommandController = (function () {
41930 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
41931 this.APIEndPoint = APIEndPoint;
41932 this.$scope = $scope;
41933 this.MyModal = MyModal;
41934 this.WebSocket = WebSocket;
41935 this.$window = $window;
41936 this.$rootScope = $rootScope;
41937 this.Console = Console;
41938 var controller = this;
41940 .getOptionControlFile(this.name)
41942 .then(function (result) {
41943 controller.options = result.info;
41948 .then(function (result) {
41949 controller.dirs = result.info;
41951 this.heading = "[" + this.index + "]: dcdFilePrint";
41952 this.isOpen = true;
41953 this.$scope.$on('close', function () {
41954 controller.isOpen = false;
41958 return Math.floor((1 + Math.random()) * 0x10000)
41962 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
41963 s4() + '-' + s4() + s4() + s4();
41965 this.uuid = guid();
41966 this.Console.addDirective(this.uuid);
41967 this.Console.showIDs();
41969 CommandController.prototype.submit = function () {
41971 angular.forEach(this.options, function (option) {
41973 name: option.option,
41976 angular.forEach(option.arg, function (arg) {
41978 if (typeof arg.input === 'object') {
41979 obj.arguments.push(arg.input.name);
41982 obj.arguments.push(arg.input);
41986 if (obj.arguments.length > 0) {
41991 command: this.name,
41992 workspace: this.workspace.fileId,
41996 .execute(JSON.stringify(execObj))
41997 .then(function (result) {
41998 console.log(result);
42001 CommandController.prototype.removeMySelf = function (index) {
42002 this.$scope.$destroy();
42003 this.Console.removeDirective(this.uuid);
42004 this.remove()(index, this.list);
42005 this.Console.showIDs();
42007 CommandController.prototype.reloadFiles = function () {
42009 var fileId = this.workspace.fileId;
42013 .then(function (result) {
42014 var status = result.status;
42015 if (status === 'success') {
42016 _this.files = result.info;
42019 console.log(result.message);
42023 CommandController.prototype.debug = function () {
42024 var div = angular.element(this.$window.document).find("div");
42027 angular.forEach(div, function (v) {
42028 if (v.className === "panel-body console") {
42031 else if (v.className === "row parameters-console") {
42035 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
42036 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
42037 consoleTag.style.height = consoleHeight;
42038 consoleTag.style.width = consoleWidth;
42040 CommandController.prototype.help = function () {
42043 .then(function (result) {
42044 console.log(result);
42047 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
42048 return CommandController;
42050 directives.CommandController = CommandController;
42051 })(directives = app.directives || (app.directives = {}));
42052 })(app || (app = {}));
42056 (function (directives) {
42057 var HeaderMenu = (function () {
42058 function HeaderMenu() {
42059 this.restrict = 'E';
42060 this.replace = true;
42061 this.templateUrl = 'templates/header-menu.html';
42062 this.controller = 'HeaderMenuController';
42063 this.controllerAs = 'hmc';
42066 HeaderMenu.Factory = function () {
42067 var directive = function () {
42068 return new HeaderMenu();
42074 directives.HeaderMenu = HeaderMenu;
42075 var HeaderMenuController = (function () {
42076 function HeaderMenuController($state) {
42077 this.$state = $state;
42078 this.isExecution = this.$state.current.name === 'execution';
42079 this.isWorkspace = this.$state.current.name === 'workspace';
42080 this.isHistory = this.$state.current.name === 'history';
42082 HeaderMenuController.prototype.transit = function (state) {
42083 this.$state.go(state);
42085 HeaderMenuController.$inject = ['$state'];
42086 return HeaderMenuController;
42088 directives.HeaderMenuController = HeaderMenuController;
42089 })(directives = app.directives || (app.directives = {}));
42090 })(app || (app = {}));
42094 (function (directives) {
42095 var Option = (function () {
42096 function Option() {
42097 this.restrict = 'E';
42098 this.replace = true;
42099 this.controller = 'optionController';
42100 this.bindToController = {
42105 this.templateUrl = 'templates/option.html';
42106 this.controllerAs = 'ctrl';
42108 Option.Factory = function () {
42109 var directive = function () {
42110 return new Option();
42112 directive.$inject = [];
42117 directives.Option = Option;
42118 var OptionController = (function () {
42119 function OptionController() {
42120 var controller = this;
42121 angular.forEach(controller.info.arg, function (arg) {
42122 if (arg.initialValue) {
42123 if (arg.formType === 'number') {
42124 arg.input = parseInt(arg.initialValue);
42127 arg.input = arg.initialValue;
42132 OptionController.$inject = [];
42133 return OptionController;
42135 directives.OptionController = OptionController;
42136 })(directives = app.directives || (app.directives = {}));
42137 })(app || (app = {}));
42141 (function (directives) {
42142 var Directory = (function () {
42143 function Directory() {
42144 this.restrict = 'E';
42145 this.replace = true;
42146 this.controller = 'directoryController';
42147 this.controllerAs = 'ctrl';
42148 this.bindToController = {
42154 this.templateUrl = 'templates/directory.html';
42156 Directory.Factory = function () {
42157 var directive = function () {
42158 return new Directory();
42164 directives.Directory = Directory;
42165 var DirectoryController = (function () {
42166 function DirectoryController(APIEndPoint, $scope) {
42167 this.APIEndPoint = APIEndPoint;
42168 this.$scope = $scope;
42169 var controller = this;
42171 .getFiles(this.info.fileId)
42173 .then(function (result) {
42174 if (result.status === 'success') {
42175 controller.files = result.info;
42176 angular.forEach(result.info, function (file) {
42177 if (file.fileType === '0') {
42179 if (controller.info.path === '/') {
42180 o.path = '/' + file.name;
42183 o.path = controller.info.path + '/' + file.name;
42185 controller.add()(o, controller.list);
42192 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42193 return DirectoryController;
42195 directives.DirectoryController = DirectoryController;
42196 })(directives = app.directives || (app.directives = {}));
42197 })(app || (app = {}));
42201 (function (controllers) {
42202 var Execution = (function () {
42203 function Execution(MyModal, $scope) {
42204 this.MyModal = MyModal;
42205 this.$scope = $scope;
42206 this.commandInfoList = [];
42209 Execution.prototype.add = function () {
42210 this.$scope.$broadcast('close');
42211 var commandInfoList = this.commandInfoList;
42212 var commandInstance = this.MyModal.selectCommand();
42215 .then(function (command) {
42216 commandInfoList.push(new app.declares.CommandInfo(command));
42219 Execution.prototype.open = function () {
42220 var result = this.MyModal.open('SelectCommand');
42221 console.log(result);
42223 Execution.prototype.remove = function (index, list) {
42224 list.splice(index, 1);
42226 Execution.prototype.close = function () {
42227 console.log("close");
42229 Execution.$inject = ['MyModal', '$scope'];
42232 controllers.Execution = Execution;
42233 })(controllers = app.controllers || (app.controllers = {}));
42234 })(app || (app = {}));
42238 (function (controllers) {
42239 var Workspace = (function () {
42240 function Workspace($scope, APIEndPoint, MyModal) {
42241 this.$scope = $scope;
42242 this.APIEndPoint = APIEndPoint;
42243 this.MyModal = MyModal;
42244 this.directoryList = [];
42245 var controller = this;
42246 var directoryList = this.directoryList;
42248 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42256 directoryList.push(o);
42258 Workspace.prototype.addDirectory = function (info, directoryList) {
42259 directoryList.push(info);
42261 Workspace.prototype.debug = function () {
42262 this.MyModal.preview();
42264 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
42267 controllers.Workspace = Workspace;
42268 })(controllers = app.controllers || (app.controllers = {}));
42269 })(app || (app = {}));
42273 (function (controllers) {
42274 var History = (function () {
42275 function History($scope) {
42276 this.page = "History";
42278 History.$inject = ['$scope'];
42281 controllers.History = History;
42282 })(controllers = app.controllers || (app.controllers = {}));
42283 })(app || (app = {}));
42287 (function (controllers) {
42288 var SelectCommand = (function () {
42289 function SelectCommand($scope, APIEndPoint, $modalInstance) {
42290 this.APIEndPoint = APIEndPoint;
42291 this.$modalInstance = $modalInstance;
42292 var controller = this;
42295 .$promise.then(function (result) {
42296 controller.tags = result.info;
42300 .$promise.then(function (result) {
42301 controller.commands = result.info;
42303 this.currentTag = 'all';
42305 SelectCommand.prototype.changeTag = function (tag) {
42306 this.currentTag = tag;
42308 SelectCommand.prototype.selectCommand = function (command) {
42309 this.$modalInstance.close(command);
42311 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42312 return SelectCommand;
42314 controllers.SelectCommand = SelectCommand;
42315 })(controllers = app.controllers || (app.controllers = {}));
42316 })(app || (app = {}));
42320 (function (controllers) {
42321 var Preview = (function () {
42322 function Preview($scope, APIEndPoint, $modalInstance) {
42323 this.APIEndPoint = APIEndPoint;
42324 this.$modalInstance = $modalInstance;
42325 var controller = this;
42326 console.log('preview');
42328 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42331 controllers.Preview = Preview;
42332 })(controllers = app.controllers || (app.controllers = {}));
42333 })(app || (app = {}));
42335 (function (filters) {
42337 return function (commands, tag) {
42339 angular.forEach(commands, function (command) {
42341 angular.forEach(command.tags, function (value) {
42346 result.push(command);
42352 })(filters || (filters = {}));
42356 var appName = 'zephyr';
42357 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42358 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42359 $urlRouterProvider.otherwise('/execution');
42360 $locationProvider.html5Mode({
42365 .state('execution', {
42367 templateUrl: 'templates/execution.html',
42368 controller: 'executionController',
42371 .state('workspace', {
42373 templateUrl: 'templates/workspace.html',
42374 controller: 'workspaceController',
42377 .state('history', {
42379 templateUrl: 'templates/history.html',
42380 controller: 'historyController',
42384 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42385 app.zephyr.service('MyModal', app.services.MyModal);
42386 app.zephyr.service('WebSocket', app.services.WebSocket);
42387 app.zephyr.service('Console', app.services.Console);
42388 app.zephyr.filter('Tag', filters.Tag);
42389 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
42390 app.zephyr.controller('previewController', app.controllers.Preview);
42391 app.zephyr.controller('executionController', app.controllers.Execution);
42392 app.zephyr.controller('workspaceController', app.controllers.Workspace);
42393 app.zephyr.controller('historyController', app.controllers.History);
42394 app.zephyr.controller('commandController', app.directives.CommandController);
42395 app.zephyr.controller('optionController', app.directives.OptionController);
42396 app.zephyr.controller('directoryController', app.directives.DirectoryController);
42397 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
42398 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42399 app.zephyr.directive('command', app.directives.Command.Factory());
42400 app.zephyr.directive('option', app.directives.Option.Factory());
42401 app.zephyr.directive('directory', app.directives.Directory.Factory());
42402 })(app || (app = {}));
42407 /***/ function(module, exports) {
42412 (function (declares) {
42413 var CommandInfo = (function () {
42414 function CommandInfo(name) {
42417 return CommandInfo;
42419 declares.CommandInfo = CommandInfo;
42420 })(declares = app.declares || (app.declares = {}));
42421 })(app || (app = {}));
42425 (function (services) {
42426 var APIEndPoint = (function () {
42427 function APIEndPoint($resource, $http) {
42428 this.$resource = $resource;
42429 this.$http = $http;
42431 APIEndPoint.prototype.resource = function (endPoint, data) {
42432 var customAction = {
42438 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
42440 return this.$resource(endPoint, {}, { execute: execute });
42442 APIEndPoint.prototype.getOptionControlFile = function (command) {
42443 var endPoint = '/api/v1/optionControlFile/' + command;
42444 return this.resource(endPoint, {}).get();
42446 APIEndPoint.prototype.getFiles = function (fileId) {
42447 var endPoint = '/api/v1/workspace';
42449 endPoint += '/' + fileId;
42451 return this.resource(endPoint, {}).get();
42453 APIEndPoint.prototype.getDirectories = function () {
42454 var endPoint = '/api/v1/all/workspace/directory';
42455 return this.resource(endPoint, {}).get();
42457 APIEndPoint.prototype.getTags = function () {
42458 var endPoint = '/api/v1/tagList';
42459 return this.resource(endPoint, {}).get();
42461 APIEndPoint.prototype.getCommands = function () {
42462 var endPoint = '/api/v1/commandList';
42463 return this.resource(endPoint, {}).get();
42465 APIEndPoint.prototype.execute = function (data) {
42466 var endPoint = '/api/v1/execution';
42467 var fd = new FormData();
42468 fd.append('data', data);
42469 return this.$http.post(endPoint, fd, {
42470 headers: { 'Content-Type': undefined },
42471 transformRequest: angular.identity
42474 APIEndPoint.prototype.debug = function () {
42475 var endPoint = '/api/v1/debug';
42476 return this.$http.get(endPoint);
42478 APIEndPoint.prototype.help = function (command) {
42479 var endPoint = '/api/v1/help/' + command;
42480 return this.$http.get(endPoint);
42482 return APIEndPoint;
42484 services.APIEndPoint = APIEndPoint;
42485 })(services = app.services || (app.services = {}));
42486 })(app || (app = {}));
42490 (function (services) {
42491 var MyModal = (function () {
42492 function MyModal($uibModal) {
42493 this.$uibModal = $uibModal;
42494 this.modalOption = {
42501 MyModal.prototype.open = function (modalName) {
42502 if (modalName === 'SelectCommand') {
42503 this.modalOption.templateUrl = 'templates/select-command.html';
42504 this.modalOption.size = 'lg';
42506 return this.$uibModal.open(this.modalOption);
42508 MyModal.prototype.selectCommand = function () {
42509 this.modalOption.templateUrl = 'templates/select-command.html';
42510 this.modalOption.controller = 'selectCommandController';
42511 this.modalOption.controllerAs = 'c';
42512 this.modalOption.size = 'lg';
42513 return this.$uibModal.open(this.modalOption);
42515 MyModal.prototype.preview = function () {
42516 this.modalOption.templateUrl = 'templates/preview.html';
42517 this.modalOption.controller = 'previewController';
42518 this.modalOption.controllerAs = 'c';
42519 this.modalOption.size = 'lg';
42520 return this.$uibModal.open(this.modalOption);
42522 MyModal.$inject = ['$uibModal'];
42525 services.MyModal = MyModal;
42526 })(services = app.services || (app.services = {}));
42527 })(app || (app = {}));
42531 (function (services) {
42532 var WebSocket = (function () {
42533 function WebSocket($rootScope) {
42534 this.$rootScope = $rootScope;
42535 this.socket = io.connect();
42537 WebSocket.prototype.on = function (eventName, callback) {
42538 var socket = this.socket;
42539 var rootScope = this.$rootScope;
42540 socket.on(eventName, function () {
42541 var args = arguments;
42542 rootScope.$apply(function () {
42543 callback.apply(socket, args);
42547 WebSocket.prototype.emit = function (eventName, data, callback) {
42548 var socket = this.socket;
42549 var rootScope = this.$rootScope;
42550 this.socket.emit(eventName, data, function () {
42551 var args = arguments;
42552 rootScope.$apply(function () {
42554 callback.apply(socket, args);
42560 services.WebSocket = WebSocket;
42561 })(services = app.services || (app.services = {}));
42562 })(app || (app = {}));
42566 (function (services) {
42567 var Console = (function () {
42568 function Console(WebSocket, $rootScope) {
42569 this.WebSocket = WebSocket;
42570 this.$rootScope = $rootScope;
42571 this.WebSocket = WebSocket;
42572 this.$rootScope = $rootScope;
42573 this.directiveIDs = [];
42574 var directiveIDs = this.directiveIDs;
42575 this.WebSocket.on('console', function (d) {
42577 var message = d.message;
42578 if (directiveIDs.indexOf(id) > -1) {
42579 $rootScope.$emit(id, message);
42583 Console.prototype.addDirective = function (id) {
42584 if (!(this.directiveIDs.indexOf(id) > -1)) {
42585 this.directiveIDs.push(id);
42588 Console.prototype.removeDirective = function (id) {
42589 var i = this.directiveIDs.indexOf(id);
42591 this.directiveIDs.splice(i, 1);
42594 Console.prototype.showIDs = function () {
42595 console.log(this.directiveIDs);
42599 services.Console = Console;
42600 })(services = app.services || (app.services = {}));
42601 })(app || (app = {}));
42605 (function (directives) {
42606 var Command = (function () {
42607 function Command() {
42608 this.restrict = 'E';
42609 this.replace = true;
42611 this.controller = 'commandController';
42612 this.controllerAs = 'ctrl';
42613 this.bindToController = {
42619 this.templateUrl = 'templates/command.html';
42621 Command.Factory = function () {
42622 var directive = function () {
42623 return new Command();
42625 directive.$inject = [];
42630 directives.Command = Command;
42631 var CommandController = (function () {
42632 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
42633 this.APIEndPoint = APIEndPoint;
42634 this.$scope = $scope;
42635 this.MyModal = MyModal;
42636 this.WebSocket = WebSocket;
42637 this.$window = $window;
42638 this.$rootScope = $rootScope;
42639 this.Console = Console;
42640 var controller = this;
42642 .getOptionControlFile(this.name)
42644 .then(function (result) {
42645 controller.options = result.info;
42650 .then(function (result) {
42651 controller.dirs = result.info;
42653 this.heading = "[" + this.index + "]: dcdFilePrint";
42654 this.isOpen = true;
42655 this.$scope.$on('close', function () {
42656 controller.isOpen = false;
42660 return Math.floor((1 + Math.random()) * 0x10000)
42664 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
42665 s4() + '-' + s4() + s4() + s4();
42667 this.uuid = guid();
42668 this.Console.addDirective(this.uuid);
42669 this.Console.showIDs();
42671 CommandController.prototype.submit = function () {
42673 angular.forEach(this.options, function (option) {
42675 name: option.option,
42678 angular.forEach(option.arg, function (arg) {
42680 if (typeof arg.input === 'object') {
42681 obj.arguments.push(arg.input.name);
42684 obj.arguments.push(arg.input);
42688 if (obj.arguments.length > 0) {
42693 command: this.name,
42694 workspace: this.workspace.fileId,
42698 .execute(JSON.stringify(execObj))
42699 .then(function (result) {
42700 console.log(result);
42703 CommandController.prototype.removeMySelf = function (index) {
42704 this.$scope.$destroy();
42705 this.Console.removeDirective(this.uuid);
42706 this.remove()(index, this.list);
42707 this.Console.showIDs();
42709 CommandController.prototype.reloadFiles = function () {
42711 var fileId = this.workspace.fileId;
42715 .then(function (result) {
42716 var status = result.status;
42717 if (status === 'success') {
42718 _this.files = result.info;
42721 console.log(result.message);
42725 CommandController.prototype.debug = function () {
42726 var div = angular.element(this.$window.document).find("div");
42729 angular.forEach(div, function (v) {
42730 if (v.className === "panel-body console") {
42733 else if (v.className === "row parameters-console") {
42737 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
42738 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
42739 consoleTag.style.height = consoleHeight;
42740 consoleTag.style.width = consoleWidth;
42742 CommandController.prototype.help = function () {
42745 .then(function (result) {
42746 console.log(result);
42749 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
42750 return CommandController;
42752 directives.CommandController = CommandController;
42753 })(directives = app.directives || (app.directives = {}));
42754 })(app || (app = {}));
42758 (function (directives) {
42759 var HeaderMenu = (function () {
42760 function HeaderMenu() {
42761 this.restrict = 'E';
42762 this.replace = true;
42763 this.templateUrl = 'templates/header-menu.html';
42764 this.controller = 'HeaderMenuController';
42765 this.controllerAs = 'hmc';
42768 HeaderMenu.Factory = function () {
42769 var directive = function () {
42770 return new HeaderMenu();
42776 directives.HeaderMenu = HeaderMenu;
42777 var HeaderMenuController = (function () {
42778 function HeaderMenuController($state) {
42779 this.$state = $state;
42780 this.isExecution = this.$state.current.name === 'execution';
42781 this.isWorkspace = this.$state.current.name === 'workspace';
42782 this.isHistory = this.$state.current.name === 'history';
42784 HeaderMenuController.prototype.transit = function (state) {
42785 this.$state.go(state);
42787 HeaderMenuController.$inject = ['$state'];
42788 return HeaderMenuController;
42790 directives.HeaderMenuController = HeaderMenuController;
42791 })(directives = app.directives || (app.directives = {}));
42792 })(app || (app = {}));
42796 (function (directives) {
42797 var Option = (function () {
42798 function Option() {
42799 this.restrict = 'E';
42800 this.replace = true;
42801 this.controller = 'optionController';
42802 this.bindToController = {
42807 this.templateUrl = 'templates/option.html';
42808 this.controllerAs = 'ctrl';
42810 Option.Factory = function () {
42811 var directive = function () {
42812 return new Option();
42814 directive.$inject = [];
42819 directives.Option = Option;
42820 var OptionController = (function () {
42821 function OptionController() {
42822 var controller = this;
42823 angular.forEach(controller.info.arg, function (arg) {
42824 if (arg.initialValue) {
42825 if (arg.formType === 'number') {
42826 arg.input = parseInt(arg.initialValue);
42829 arg.input = arg.initialValue;
42834 OptionController.$inject = [];
42835 return OptionController;
42837 directives.OptionController = OptionController;
42838 })(directives = app.directives || (app.directives = {}));
42839 })(app || (app = {}));
42843 (function (directives) {
42844 var Directory = (function () {
42845 function Directory() {
42846 this.restrict = 'E';
42847 this.replace = true;
42848 this.controller = 'directoryController';
42849 this.controllerAs = 'ctrl';
42850 this.bindToController = {
42856 this.templateUrl = 'templates/directory.html';
42858 Directory.Factory = function () {
42859 var directive = function () {
42860 return new Directory();
42866 directives.Directory = Directory;
42867 var DirectoryController = (function () {
42868 function DirectoryController(APIEndPoint, $scope) {
42869 this.APIEndPoint = APIEndPoint;
42870 this.$scope = $scope;
42871 var controller = this;
42873 .getFiles(this.info.fileId)
42875 .then(function (result) {
42876 if (result.status === 'success') {
42877 controller.files = result.info;
42878 angular.forEach(result.info, function (file) {
42879 if (file.fileType === '0') {
42881 if (controller.info.path === '/') {
42882 o.path = '/' + file.name;
42885 o.path = controller.info.path + '/' + file.name;
42887 controller.add()(o, controller.list);
42894 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42895 return DirectoryController;
42897 directives.DirectoryController = DirectoryController;
42898 })(directives = app.directives || (app.directives = {}));
42899 })(app || (app = {}));
42903 (function (controllers) {
42904 var Execution = (function () {
42905 function Execution(MyModal, $scope) {
42906 this.MyModal = MyModal;
42907 this.$scope = $scope;
42908 this.commandInfoList = [];
42911 Execution.prototype.add = function () {
42912 this.$scope.$broadcast('close');
42913 var commandInfoList = this.commandInfoList;
42914 var commandInstance = this.MyModal.selectCommand();
42917 .then(function (command) {
42918 commandInfoList.push(new app.declares.CommandInfo(command));
42921 Execution.prototype.open = function () {
42922 var result = this.MyModal.open('SelectCommand');
42923 console.log(result);
42925 Execution.prototype.remove = function (index, list) {
42926 list.splice(index, 1);
42928 Execution.prototype.close = function () {
42929 console.log("close");
42931 Execution.$inject = ['MyModal', '$scope'];
42934 controllers.Execution = Execution;
42935 })(controllers = app.controllers || (app.controllers = {}));
42936 })(app || (app = {}));
42940 (function (controllers) {
42941 var Workspace = (function () {
42942 function Workspace($scope, APIEndPoint, MyModal) {
42943 this.$scope = $scope;
42944 this.APIEndPoint = APIEndPoint;
42945 this.MyModal = MyModal;
42946 this.directoryList = [];
42947 var controller = this;
42948 var directoryList = this.directoryList;
42950 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42958 directoryList.push(o);
42960 Workspace.prototype.addDirectory = function (info, directoryList) {
42961 directoryList.push(info);
42963 Workspace.prototype.debug = function () {
42964 this.MyModal.preview();
42966 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
42969 controllers.Workspace = Workspace;
42970 })(controllers = app.controllers || (app.controllers = {}));
42971 })(app || (app = {}));
42975 (function (controllers) {
42976 var History = (function () {
42977 function History($scope) {
42978 this.page = "History";
42980 History.$inject = ['$scope'];
42983 controllers.History = History;
42984 })(controllers = app.controllers || (app.controllers = {}));
42985 })(app || (app = {}));
42989 (function (controllers) {
42990 var SelectCommand = (function () {
42991 function SelectCommand($scope, APIEndPoint, $modalInstance) {
42992 this.APIEndPoint = APIEndPoint;
42993 this.$modalInstance = $modalInstance;
42994 var controller = this;
42997 .$promise.then(function (result) {
42998 controller.tags = result.info;
43002 .$promise.then(function (result) {
43003 controller.commands = result.info;
43005 this.currentTag = 'all';
43007 SelectCommand.prototype.changeTag = function (tag) {
43008 this.currentTag = tag;
43010 SelectCommand.prototype.selectCommand = function (command) {
43011 this.$modalInstance.close(command);
43013 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
43014 return SelectCommand;
43016 controllers.SelectCommand = SelectCommand;
43017 })(controllers = app.controllers || (app.controllers = {}));
43018 })(app || (app = {}));
43022 (function (controllers) {
43023 var Preview = (function () {
43024 function Preview($scope, APIEndPoint, $modalInstance) {
43025 this.APIEndPoint = APIEndPoint;
43026 this.$modalInstance = $modalInstance;
43027 var controller = this;
43028 console.log('preview');
43030 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
43033 controllers.Preview = Preview;
43034 })(controllers = app.controllers || (app.controllers = {}));
43035 })(app || (app = {}));
43037 (function (filters) {
43039 return function (commands, tag) {
43041 angular.forEach(commands, function (command) {
43043 angular.forEach(command.tags, function (value) {
43048 result.push(command);
43054 })(filters || (filters = {}));
43058 var appName = 'zephyr';
43059 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43060 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43061 $urlRouterProvider.otherwise('/execution');
43062 $locationProvider.html5Mode({
43067 .state('execution', {
43069 templateUrl: 'templates/execution.html',
43070 controller: 'executionController',
43073 .state('workspace', {
43075 templateUrl: 'templates/workspace.html',
43076 controller: 'workspaceController',
43079 .state('history', {
43081 templateUrl: 'templates/history.html',
43082 controller: 'historyController',
43086 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43087 app.zephyr.service('MyModal', app.services.MyModal);
43088 app.zephyr.service('WebSocket', app.services.WebSocket);
43089 app.zephyr.service('Console', app.services.Console);
43090 app.zephyr.filter('Tag', filters.Tag);
43091 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
43092 app.zephyr.controller('previewController', app.controllers.Preview);
43093 app.zephyr.controller('executionController', app.controllers.Execution);
43094 app.zephyr.controller('workspaceController', app.controllers.Workspace);
43095 app.zephyr.controller('historyController', app.controllers.History);
43096 app.zephyr.controller('commandController', app.directives.CommandController);
43097 app.zephyr.controller('optionController', app.directives.OptionController);
43098 app.zephyr.controller('directoryController', app.directives.DirectoryController);
43099 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
43100 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43101 app.zephyr.directive('command', app.directives.Command.Factory());
43102 app.zephyr.directive('option', app.directives.Option.Factory());
43103 app.zephyr.directive('directory', app.directives.Directory.Factory());
43104 })(app || (app = {}));
43109 /***/ function(module, exports) {
43114 (function (declares) {
43115 var CommandInfo = (function () {
43116 function CommandInfo(name) {
43119 return CommandInfo;
43121 declares.CommandInfo = CommandInfo;
43122 })(declares = app.declares || (app.declares = {}));
43123 })(app || (app = {}));
43127 (function (services) {
43128 var APIEndPoint = (function () {
43129 function APIEndPoint($resource, $http) {
43130 this.$resource = $resource;
43131 this.$http = $http;
43133 APIEndPoint.prototype.resource = function (endPoint, data) {
43134 var customAction = {
43140 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
43142 return this.$resource(endPoint, {}, { execute: execute });
43144 APIEndPoint.prototype.getOptionControlFile = function (command) {
43145 var endPoint = '/api/v1/optionControlFile/' + command;
43146 return this.resource(endPoint, {}).get();
43148 APIEndPoint.prototype.getFiles = function (fileId) {
43149 var endPoint = '/api/v1/workspace';
43151 endPoint += '/' + fileId;
43153 return this.resource(endPoint, {}).get();
43155 APIEndPoint.prototype.getDirectories = function () {
43156 var endPoint = '/api/v1/all/workspace/directory';
43157 return this.resource(endPoint, {}).get();
43159 APIEndPoint.prototype.getTags = function () {
43160 var endPoint = '/api/v1/tagList';
43161 return this.resource(endPoint, {}).get();
43163 APIEndPoint.prototype.getCommands = function () {
43164 var endPoint = '/api/v1/commandList';
43165 return this.resource(endPoint, {}).get();
43167 APIEndPoint.prototype.execute = function (data) {
43168 var endPoint = '/api/v1/execution';
43169 var fd = new FormData();
43170 fd.append('data', data);
43171 return this.$http.post(endPoint, fd, {
43172 headers: { 'Content-Type': undefined },
43173 transformRequest: angular.identity
43176 APIEndPoint.prototype.debug = function () {
43177 var endPoint = '/api/v1/debug';
43178 return this.$http.get(endPoint);
43180 APIEndPoint.prototype.help = function (command) {
43181 var endPoint = '/api/v1/help/' + command;
43182 return this.$http.get(endPoint);
43184 return APIEndPoint;
43186 services.APIEndPoint = APIEndPoint;
43187 })(services = app.services || (app.services = {}));
43188 })(app || (app = {}));
43192 (function (services) {
43193 var MyModal = (function () {
43194 function MyModal($uibModal) {
43195 this.$uibModal = $uibModal;
43196 this.modalOption = {
43203 MyModal.prototype.open = function (modalName) {
43204 if (modalName === 'SelectCommand') {
43205 this.modalOption.templateUrl = 'templates/select-command.html';
43206 this.modalOption.size = 'lg';
43208 return this.$uibModal.open(this.modalOption);
43210 MyModal.prototype.selectCommand = function () {
43211 this.modalOption.templateUrl = 'templates/select-command.html';
43212 this.modalOption.controller = 'selectCommandController';
43213 this.modalOption.controllerAs = 'c';
43214 this.modalOption.size = 'lg';
43215 return this.$uibModal.open(this.modalOption);
43217 MyModal.prototype.preview = function () {
43218 this.modalOption.templateUrl = 'templates/preview.html';
43219 this.modalOption.controller = 'previewController';
43220 this.modalOption.controllerAs = 'c';
43221 this.modalOption.size = 'lg';
43222 return this.$uibModal.open(this.modalOption);
43224 MyModal.$inject = ['$uibModal'];
43227 services.MyModal = MyModal;
43228 })(services = app.services || (app.services = {}));
43229 })(app || (app = {}));
43233 (function (services) {
43234 var WebSocket = (function () {
43235 function WebSocket($rootScope) {
43236 this.$rootScope = $rootScope;
43237 this.socket = io.connect();
43239 WebSocket.prototype.on = function (eventName, callback) {
43240 var socket = this.socket;
43241 var rootScope = this.$rootScope;
43242 socket.on(eventName, function () {
43243 var args = arguments;
43244 rootScope.$apply(function () {
43245 callback.apply(socket, args);
43249 WebSocket.prototype.emit = function (eventName, data, callback) {
43250 var socket = this.socket;
43251 var rootScope = this.$rootScope;
43252 this.socket.emit(eventName, data, function () {
43253 var args = arguments;
43254 rootScope.$apply(function () {
43256 callback.apply(socket, args);
43262 services.WebSocket = WebSocket;
43263 })(services = app.services || (app.services = {}));
43264 })(app || (app = {}));
43268 (function (services) {
43269 var Console = (function () {
43270 function Console(WebSocket, $rootScope) {
43271 this.WebSocket = WebSocket;
43272 this.$rootScope = $rootScope;
43273 this.WebSocket = WebSocket;
43274 this.$rootScope = $rootScope;
43275 this.directiveIDs = [];
43276 var directiveIDs = this.directiveIDs;
43277 this.WebSocket.on('console', function (d) {
43279 var message = d.message;
43280 if (directiveIDs.indexOf(id) > -1) {
43281 $rootScope.$emit(id, message);
43285 Console.prototype.addDirective = function (id) {
43286 if (!(this.directiveIDs.indexOf(id) > -1)) {
43287 this.directiveIDs.push(id);
43290 Console.prototype.removeDirective = function (id) {
43291 var i = this.directiveIDs.indexOf(id);
43293 this.directiveIDs.splice(i, 1);
43296 Console.prototype.showIDs = function () {
43297 console.log(this.directiveIDs);
43301 services.Console = Console;
43302 })(services = app.services || (app.services = {}));
43303 })(app || (app = {}));
43307 (function (directives) {
43308 var Command = (function () {
43309 function Command() {
43310 this.restrict = 'E';
43311 this.replace = true;
43313 this.controller = 'commandController';
43314 this.controllerAs = 'ctrl';
43315 this.bindToController = {
43321 this.templateUrl = 'templates/command.html';
43323 Command.Factory = function () {
43324 var directive = function () {
43325 return new Command();
43327 directive.$inject = [];
43332 directives.Command = Command;
43333 var CommandController = (function () {
43334 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
43335 this.APIEndPoint = APIEndPoint;
43336 this.$scope = $scope;
43337 this.MyModal = MyModal;
43338 this.WebSocket = WebSocket;
43339 this.$window = $window;
43340 this.$rootScope = $rootScope;
43341 this.Console = Console;
43342 var controller = this;
43344 .getOptionControlFile(this.name)
43346 .then(function (result) {
43347 controller.options = result.info;
43352 .then(function (result) {
43353 controller.dirs = result.info;
43355 this.heading = "[" + this.index + "]: dcdFilePrint";
43356 this.isOpen = true;
43357 this.$scope.$on('close', function () {
43358 controller.isOpen = false;
43362 return Math.floor((1 + Math.random()) * 0x10000)
43366 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
43367 s4() + '-' + s4() + s4() + s4();
43369 this.uuid = guid();
43370 this.Console.addDirective(this.uuid);
43371 this.Console.showIDs();
43373 CommandController.prototype.submit = function () {
43375 angular.forEach(this.options, function (option) {
43377 name: option.option,
43380 angular.forEach(option.arg, function (arg) {
43382 if (typeof arg.input === 'object') {
43383 obj.arguments.push(arg.input.name);
43386 obj.arguments.push(arg.input);
43390 if (obj.arguments.length > 0) {
43395 command: this.name,
43396 workspace: this.workspace.fileId,
43400 .execute(JSON.stringify(execObj))
43401 .then(function (result) {
43402 console.log(result);
43405 CommandController.prototype.removeMySelf = function (index) {
43406 this.$scope.$destroy();
43407 this.Console.removeDirective(this.uuid);
43408 this.remove()(index, this.list);
43409 this.Console.showIDs();
43411 CommandController.prototype.reloadFiles = function () {
43413 var fileId = this.workspace.fileId;
43417 .then(function (result) {
43418 var status = result.status;
43419 if (status === 'success') {
43420 _this.files = result.info;
43423 console.log(result.message);
43427 CommandController.prototype.debug = function () {
43428 var div = angular.element(this.$window.document).find("div");
43431 angular.forEach(div, function (v) {
43432 if (v.className === "panel-body console") {
43435 else if (v.className === "row parameters-console") {
43439 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
43440 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
43441 consoleTag.style.height = consoleHeight;
43442 consoleTag.style.width = consoleWidth;
43444 CommandController.prototype.help = function () {
43447 .then(function (result) {
43448 console.log(result);
43451 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
43452 return CommandController;
43454 directives.CommandController = CommandController;
43455 })(directives = app.directives || (app.directives = {}));
43456 })(app || (app = {}));
43460 (function (directives) {
43461 var HeaderMenu = (function () {
43462 function HeaderMenu() {
43463 this.restrict = 'E';
43464 this.replace = true;
43465 this.templateUrl = 'templates/header-menu.html';
43466 this.controller = 'HeaderMenuController';
43467 this.controllerAs = 'hmc';
43470 HeaderMenu.Factory = function () {
43471 var directive = function () {
43472 return new HeaderMenu();
43478 directives.HeaderMenu = HeaderMenu;
43479 var HeaderMenuController = (function () {
43480 function HeaderMenuController($state) {
43481 this.$state = $state;
43482 this.isExecution = this.$state.current.name === 'execution';
43483 this.isWorkspace = this.$state.current.name === 'workspace';
43484 this.isHistory = this.$state.current.name === 'history';
43486 HeaderMenuController.prototype.transit = function (state) {
43487 this.$state.go(state);
43489 HeaderMenuController.$inject = ['$state'];
43490 return HeaderMenuController;
43492 directives.HeaderMenuController = HeaderMenuController;
43493 })(directives = app.directives || (app.directives = {}));
43494 })(app || (app = {}));
43498 (function (directives) {
43499 var Option = (function () {
43500 function Option() {
43501 this.restrict = 'E';
43502 this.replace = true;
43503 this.controller = 'optionController';
43504 this.bindToController = {
43509 this.templateUrl = 'templates/option.html';
43510 this.controllerAs = 'ctrl';
43512 Option.Factory = function () {
43513 var directive = function () {
43514 return new Option();
43516 directive.$inject = [];
43521 directives.Option = Option;
43522 var OptionController = (function () {
43523 function OptionController() {
43524 var controller = this;
43525 angular.forEach(controller.info.arg, function (arg) {
43526 if (arg.initialValue) {
43527 if (arg.formType === 'number') {
43528 arg.input = parseInt(arg.initialValue);
43531 arg.input = arg.initialValue;
43536 OptionController.$inject = [];
43537 return OptionController;
43539 directives.OptionController = OptionController;
43540 })(directives = app.directives || (app.directives = {}));
43541 })(app || (app = {}));
43545 (function (directives) {
43546 var Directory = (function () {
43547 function Directory() {
43548 this.restrict = 'E';
43549 this.replace = true;
43550 this.controller = 'directoryController';
43551 this.controllerAs = 'ctrl';
43552 this.bindToController = {
43558 this.templateUrl = 'templates/directory.html';
43560 Directory.Factory = function () {
43561 var directive = function () {
43562 return new Directory();
43568 directives.Directory = Directory;
43569 var DirectoryController = (function () {
43570 function DirectoryController(APIEndPoint, $scope) {
43571 this.APIEndPoint = APIEndPoint;
43572 this.$scope = $scope;
43573 var controller = this;
43575 .getFiles(this.info.fileId)
43577 .then(function (result) {
43578 if (result.status === 'success') {
43579 controller.files = result.info;
43580 angular.forEach(result.info, function (file) {
43581 if (file.fileType === '0') {
43583 if (controller.info.path === '/') {
43584 o.path = '/' + file.name;
43587 o.path = controller.info.path + '/' + file.name;
43589 controller.add()(o, controller.list);
43596 DirectoryController.$inject = ['APIEndPoint', '$scope'];
43597 return DirectoryController;
43599 directives.DirectoryController = DirectoryController;
43600 })(directives = app.directives || (app.directives = {}));
43601 })(app || (app = {}));
43605 (function (controllers) {
43606 var Execution = (function () {
43607 function Execution(MyModal, $scope) {
43608 this.MyModal = MyModal;
43609 this.$scope = $scope;
43610 this.commandInfoList = [];
43613 Execution.prototype.add = function () {
43614 this.$scope.$broadcast('close');
43615 var commandInfoList = this.commandInfoList;
43616 var commandInstance = this.MyModal.selectCommand();
43619 .then(function (command) {
43620 commandInfoList.push(new app.declares.CommandInfo(command));
43623 Execution.prototype.open = function () {
43624 var result = this.MyModal.open('SelectCommand');
43625 console.log(result);
43627 Execution.prototype.remove = function (index, list) {
43628 list.splice(index, 1);
43630 Execution.prototype.close = function () {
43631 console.log("close");
43633 Execution.$inject = ['MyModal', '$scope'];
43636 controllers.Execution = Execution;
43637 })(controllers = app.controllers || (app.controllers = {}));
43638 })(app || (app = {}));
43642 (function (controllers) {
43643 var Workspace = (function () {
43644 function Workspace($scope, APIEndPoint, MyModal) {
43645 this.$scope = $scope;
43646 this.APIEndPoint = APIEndPoint;
43647 this.MyModal = MyModal;
43648 this.directoryList = [];
43649 var controller = this;
43650 var directoryList = this.directoryList;
43652 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
43660 directoryList.push(o);
43662 Workspace.prototype.addDirectory = function (info, directoryList) {
43663 directoryList.push(info);
43665 Workspace.prototype.debug = function () {
43666 this.MyModal.preview();
43668 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
43671 controllers.Workspace = Workspace;
43672 })(controllers = app.controllers || (app.controllers = {}));
43673 })(app || (app = {}));
43677 (function (controllers) {
43678 var History = (function () {
43679 function History($scope) {
43680 this.page = "History";
43682 History.$inject = ['$scope'];
43685 controllers.History = History;
43686 })(controllers = app.controllers || (app.controllers = {}));
43687 })(app || (app = {}));
43691 (function (controllers) {
43692 var SelectCommand = (function () {
43693 function SelectCommand($scope, APIEndPoint, $modalInstance) {
43694 this.APIEndPoint = APIEndPoint;
43695 this.$modalInstance = $modalInstance;
43696 var controller = this;
43699 .$promise.then(function (result) {
43700 controller.tags = result.info;
43704 .$promise.then(function (result) {
43705 controller.commands = result.info;
43707 this.currentTag = 'all';
43709 SelectCommand.prototype.changeTag = function (tag) {
43710 this.currentTag = tag;
43712 SelectCommand.prototype.selectCommand = function (command) {
43713 this.$modalInstance.close(command);
43715 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
43716 return SelectCommand;
43718 controllers.SelectCommand = SelectCommand;
43719 })(controllers = app.controllers || (app.controllers = {}));
43720 })(app || (app = {}));
43724 (function (controllers) {
43725 var Preview = (function () {
43726 function Preview($scope, APIEndPoint, $modalInstance) {
43727 this.APIEndPoint = APIEndPoint;
43728 this.$modalInstance = $modalInstance;
43729 var controller = this;
43730 console.log('preview');
43732 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
43735 controllers.Preview = Preview;
43736 })(controllers = app.controllers || (app.controllers = {}));
43737 })(app || (app = {}));
43739 (function (filters) {
43741 return function (commands, tag) {
43743 angular.forEach(commands, function (command) {
43745 angular.forEach(command.tags, function (value) {
43750 result.push(command);
43756 })(filters || (filters = {}));
43760 var appName = 'zephyr';
43761 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43762 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43763 $urlRouterProvider.otherwise('/execution');
43764 $locationProvider.html5Mode({
43769 .state('execution', {
43771 templateUrl: 'templates/execution.html',
43772 controller: 'executionController',
43775 .state('workspace', {
43777 templateUrl: 'templates/workspace.html',
43778 controller: 'workspaceController',
43781 .state('history', {
43783 templateUrl: 'templates/history.html',
43784 controller: 'historyController',
43788 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43789 app.zephyr.service('MyModal', app.services.MyModal);
43790 app.zephyr.service('WebSocket', app.services.WebSocket);
43791 app.zephyr.service('Console', app.services.Console);
43792 app.zephyr.filter('Tag', filters.Tag);
43793 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
43794 app.zephyr.controller('previewController', app.controllers.Preview);
43795 app.zephyr.controller('executionController', app.controllers.Execution);
43796 app.zephyr.controller('workspaceController', app.controllers.Workspace);
43797 app.zephyr.controller('historyController', app.controllers.History);
43798 app.zephyr.controller('commandController', app.directives.CommandController);
43799 app.zephyr.controller('optionController', app.directives.OptionController);
43800 app.zephyr.controller('directoryController', app.directives.DirectoryController);
43801 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
43802 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43803 app.zephyr.directive('command', app.directives.Command.Factory());
43804 app.zephyr.directive('option', app.directives.Option.Factory());
43805 app.zephyr.directive('directory', app.directives.Directory.Factory());
43806 })(app || (app = {}));
43811 /***/ function(module, exports) {
43816 (function (declares) {
43817 var CommandInfo = (function () {
43818 function CommandInfo(name) {
43821 return CommandInfo;
43823 declares.CommandInfo = CommandInfo;
43824 })(declares = app.declares || (app.declares = {}));
43825 })(app || (app = {}));
43829 (function (services) {
43830 var APIEndPoint = (function () {
43831 function APIEndPoint($resource, $http) {
43832 this.$resource = $resource;
43833 this.$http = $http;
43835 APIEndPoint.prototype.resource = function (endPoint, data) {
43836 var customAction = {
43842 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
43844 return this.$resource(endPoint, {}, { execute: execute });
43846 APIEndPoint.prototype.getOptionControlFile = function (command) {
43847 var endPoint = '/api/v1/optionControlFile/' + command;
43848 return this.resource(endPoint, {}).get();
43850 APIEndPoint.prototype.getFiles = function (fileId) {
43851 var endPoint = '/api/v1/workspace';
43853 endPoint += '/' + fileId;
43855 return this.resource(endPoint, {}).get();
43857 APIEndPoint.prototype.getDirectories = function () {
43858 var endPoint = '/api/v1/all/workspace/directory';
43859 return this.resource(endPoint, {}).get();
43861 APIEndPoint.prototype.getTags = function () {
43862 var endPoint = '/api/v1/tagList';
43863 return this.resource(endPoint, {}).get();
43865 APIEndPoint.prototype.getCommands = function () {
43866 var endPoint = '/api/v1/commandList';
43867 return this.resource(endPoint, {}).get();
43869 APIEndPoint.prototype.execute = function (data) {
43870 var endPoint = '/api/v1/execution';
43871 var fd = new FormData();
43872 fd.append('data', data);
43873 return this.$http.post(endPoint, fd, {
43874 headers: { 'Content-Type': undefined },
43875 transformRequest: angular.identity
43878 APIEndPoint.prototype.debug = function () {
43879 var endPoint = '/api/v1/debug';
43880 return this.$http.get(endPoint);
43882 APIEndPoint.prototype.help = function (command) {
43883 var endPoint = '/api/v1/help/' + command;
43884 return this.$http.get(endPoint);
43886 return APIEndPoint;
43888 services.APIEndPoint = APIEndPoint;
43889 })(services = app.services || (app.services = {}));
43890 })(app || (app = {}));
43894 (function (services) {
43895 var MyModal = (function () {
43896 function MyModal($uibModal) {
43897 this.$uibModal = $uibModal;
43898 this.modalOption = {
43905 MyModal.prototype.open = function (modalName) {
43906 if (modalName === 'SelectCommand') {
43907 this.modalOption.templateUrl = 'templates/select-command.html';
43908 this.modalOption.size = 'lg';
43910 return this.$uibModal.open(this.modalOption);
43912 MyModal.prototype.selectCommand = function () {
43913 this.modalOption.templateUrl = 'templates/select-command.html';
43914 this.modalOption.controller = 'selectCommandController';
43915 this.modalOption.controllerAs = 'c';
43916 this.modalOption.size = 'lg';
43917 return this.$uibModal.open(this.modalOption);
43919 MyModal.prototype.preview = function () {
43920 this.modalOption.templateUrl = 'templates/preview.html';
43921 this.modalOption.controller = 'previewController';
43922 this.modalOption.controllerAs = 'c';
43923 this.modalOption.size = 'lg';
43924 return this.$uibModal.open(this.modalOption);
43926 MyModal.$inject = ['$uibModal'];
43929 services.MyModal = MyModal;
43930 })(services = app.services || (app.services = {}));
43931 })(app || (app = {}));
43935 (function (services) {
43936 var WebSocket = (function () {
43937 function WebSocket($rootScope) {
43938 this.$rootScope = $rootScope;
43939 this.socket = io.connect();
43941 WebSocket.prototype.on = function (eventName, callback) {
43942 var socket = this.socket;
43943 var rootScope = this.$rootScope;
43944 socket.on(eventName, function () {
43945 var args = arguments;
43946 rootScope.$apply(function () {
43947 callback.apply(socket, args);
43951 WebSocket.prototype.emit = function (eventName, data, callback) {
43952 var socket = this.socket;
43953 var rootScope = this.$rootScope;
43954 this.socket.emit(eventName, data, function () {
43955 var args = arguments;
43956 rootScope.$apply(function () {
43958 callback.apply(socket, args);
43964 services.WebSocket = WebSocket;
43965 })(services = app.services || (app.services = {}));
43966 })(app || (app = {}));
43970 (function (services) {
43971 var Console = (function () {
43972 function Console(WebSocket, $rootScope) {
43973 this.WebSocket = WebSocket;
43974 this.$rootScope = $rootScope;
43975 this.WebSocket = WebSocket;
43976 this.$rootScope = $rootScope;
43977 this.directiveIDs = [];
43978 var directiveIDs = this.directiveIDs;
43979 this.WebSocket.on('console', function (d) {
43981 var message = d.message;
43982 if (directiveIDs.indexOf(id) > -1) {
43983 $rootScope.$emit(id, message);
43987 Console.prototype.addDirective = function (id) {
43988 if (!(this.directiveIDs.indexOf(id) > -1)) {
43989 this.directiveIDs.push(id);
43992 Console.prototype.removeDirective = function (id) {
43993 var i = this.directiveIDs.indexOf(id);
43995 this.directiveIDs.splice(i, 1);
43998 Console.prototype.showIDs = function () {
43999 console.log(this.directiveIDs);
44003 services.Console = Console;
44004 })(services = app.services || (app.services = {}));
44005 })(app || (app = {}));
44009 (function (directives) {
44010 var Command = (function () {
44011 function Command() {
44012 this.restrict = 'E';
44013 this.replace = true;
44015 this.controller = 'commandController';
44016 this.controllerAs = 'ctrl';
44017 this.bindToController = {
44023 this.templateUrl = 'templates/command.html';
44025 Command.Factory = function () {
44026 var directive = function () {
44027 return new Command();
44029 directive.$inject = [];
44034 directives.Command = Command;
44035 var CommandController = (function () {
44036 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
44037 this.APIEndPoint = APIEndPoint;
44038 this.$scope = $scope;
44039 this.MyModal = MyModal;
44040 this.WebSocket = WebSocket;
44041 this.$window = $window;
44042 this.$rootScope = $rootScope;
44043 this.Console = Console;
44044 var controller = this;
44046 .getOptionControlFile(this.name)
44048 .then(function (result) {
44049 controller.options = result.info;
44054 .then(function (result) {
44055 controller.dirs = result.info;
44057 this.heading = "[" + this.index + "]: dcdFilePrint";
44058 this.isOpen = true;
44059 this.$scope.$on('close', function () {
44060 controller.isOpen = false;
44064 return Math.floor((1 + Math.random()) * 0x10000)
44068 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
44069 s4() + '-' + s4() + s4() + s4();
44071 this.uuid = guid();
44072 this.Console.addDirective(this.uuid);
44073 this.Console.showIDs();
44075 CommandController.prototype.submit = function () {
44077 angular.forEach(this.options, function (option) {
44079 name: option.option,
44082 angular.forEach(option.arg, function (arg) {
44084 if (typeof arg.input === 'object') {
44085 obj.arguments.push(arg.input.name);
44088 obj.arguments.push(arg.input);
44092 if (obj.arguments.length > 0) {
44097 command: this.name,
44098 workspace: this.workspace.fileId,
44102 .execute(JSON.stringify(execObj))
44103 .then(function (result) {
44104 console.log(result);
44107 CommandController.prototype.removeMySelf = function (index) {
44108 this.$scope.$destroy();
44109 this.Console.removeDirective(this.uuid);
44110 this.remove()(index, this.list);
44111 this.Console.showIDs();
44113 CommandController.prototype.reloadFiles = function () {
44115 var fileId = this.workspace.fileId;
44119 .then(function (result) {
44120 var status = result.status;
44121 if (status === 'success') {
44122 _this.files = result.info;
44125 console.log(result.message);
44129 CommandController.prototype.debug = function () {
44130 var div = angular.element(this.$window.document).find("div");
44133 angular.forEach(div, function (v) {
44134 if (v.className === "panel-body console") {
44137 else if (v.className === "row parameters-console") {
44141 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
44142 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
44143 consoleTag.style.height = consoleHeight;
44144 consoleTag.style.width = consoleWidth;
44146 CommandController.prototype.help = function () {
44149 .then(function (result) {
44150 console.log(result);
44153 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
44154 return CommandController;
44156 directives.CommandController = CommandController;
44157 })(directives = app.directives || (app.directives = {}));
44158 })(app || (app = {}));
44162 (function (directives) {
44163 var HeaderMenu = (function () {
44164 function HeaderMenu() {
44165 this.restrict = 'E';
44166 this.replace = true;
44167 this.templateUrl = 'templates/header-menu.html';
44168 this.controller = 'HeaderMenuController';
44169 this.controllerAs = 'hmc';
44172 HeaderMenu.Factory = function () {
44173 var directive = function () {
44174 return new HeaderMenu();
44180 directives.HeaderMenu = HeaderMenu;
44181 var HeaderMenuController = (function () {
44182 function HeaderMenuController($state) {
44183 this.$state = $state;
44184 this.isExecution = this.$state.current.name === 'execution';
44185 this.isWorkspace = this.$state.current.name === 'workspace';
44186 this.isHistory = this.$state.current.name === 'history';
44188 HeaderMenuController.prototype.transit = function (state) {
44189 this.$state.go(state);
44191 HeaderMenuController.$inject = ['$state'];
44192 return HeaderMenuController;
44194 directives.HeaderMenuController = HeaderMenuController;
44195 })(directives = app.directives || (app.directives = {}));
44196 })(app || (app = {}));
44200 (function (directives) {
44201 var Option = (function () {
44202 function Option() {
44203 this.restrict = 'E';
44204 this.replace = true;
44205 this.controller = 'optionController';
44206 this.bindToController = {
44211 this.templateUrl = 'templates/option.html';
44212 this.controllerAs = 'ctrl';
44214 Option.Factory = function () {
44215 var directive = function () {
44216 return new Option();
44218 directive.$inject = [];
44223 directives.Option = Option;
44224 var OptionController = (function () {
44225 function OptionController() {
44226 var controller = this;
44227 angular.forEach(controller.info.arg, function (arg) {
44228 if (arg.initialValue) {
44229 if (arg.formType === 'number') {
44230 arg.input = parseInt(arg.initialValue);
44233 arg.input = arg.initialValue;
44238 OptionController.$inject = [];
44239 return OptionController;
44241 directives.OptionController = OptionController;
44242 })(directives = app.directives || (app.directives = {}));
44243 })(app || (app = {}));
44247 (function (directives) {
44248 var Directory = (function () {
44249 function Directory() {
44250 this.restrict = 'E';
44251 this.replace = true;
44252 this.controller = 'directoryController';
44253 this.controllerAs = 'ctrl';
44254 this.bindToController = {
44260 this.templateUrl = 'templates/directory.html';
44262 Directory.Factory = function () {
44263 var directive = function () {
44264 return new Directory();
44270 directives.Directory = Directory;
44271 var DirectoryController = (function () {
44272 function DirectoryController(APIEndPoint, $scope) {
44273 this.APIEndPoint = APIEndPoint;
44274 this.$scope = $scope;
44275 var controller = this;
44277 .getFiles(this.info.fileId)
44279 .then(function (result) {
44280 if (result.status === 'success') {
44281 controller.files = result.info;
44282 angular.forEach(result.info, function (file) {
44283 if (file.fileType === '0') {
44285 if (controller.info.path === '/') {
44286 o.path = '/' + file.name;
44289 o.path = controller.info.path + '/' + file.name;
44291 controller.add()(o, controller.list);
44298 DirectoryController.$inject = ['APIEndPoint', '$scope'];
44299 return DirectoryController;
44301 directives.DirectoryController = DirectoryController;
44302 })(directives = app.directives || (app.directives = {}));
44303 })(app || (app = {}));
44307 (function (controllers) {
44308 var Execution = (function () {
44309 function Execution(MyModal, $scope) {
44310 this.MyModal = MyModal;
44311 this.$scope = $scope;
44312 this.commandInfoList = [];
44315 Execution.prototype.add = function () {
44316 this.$scope.$broadcast('close');
44317 var commandInfoList = this.commandInfoList;
44318 var commandInstance = this.MyModal.selectCommand();
44321 .then(function (command) {
44322 commandInfoList.push(new app.declares.CommandInfo(command));
44325 Execution.prototype.open = function () {
44326 var result = this.MyModal.open('SelectCommand');
44327 console.log(result);
44329 Execution.prototype.remove = function (index, list) {
44330 list.splice(index, 1);
44332 Execution.prototype.close = function () {
44333 console.log("close");
44335 Execution.$inject = ['MyModal', '$scope'];
44338 controllers.Execution = Execution;
44339 })(controllers = app.controllers || (app.controllers = {}));
44340 })(app || (app = {}));
44344 (function (controllers) {
44345 var Workspace = (function () {
44346 function Workspace($scope, APIEndPoint, MyModal) {
44347 this.$scope = $scope;
44348 this.APIEndPoint = APIEndPoint;
44349 this.MyModal = MyModal;
44350 this.directoryList = [];
44351 var controller = this;
44352 var directoryList = this.directoryList;
44354 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
44362 directoryList.push(o);
44364 Workspace.prototype.addDirectory = function (info, directoryList) {
44365 directoryList.push(info);
44367 Workspace.prototype.debug = function () {
44368 this.MyModal.preview();
44370 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
44373 controllers.Workspace = Workspace;
44374 })(controllers = app.controllers || (app.controllers = {}));
44375 })(app || (app = {}));
44379 (function (controllers) {
44380 var History = (function () {
44381 function History($scope) {
44382 this.page = "History";
44384 History.$inject = ['$scope'];
44387 controllers.History = History;
44388 })(controllers = app.controllers || (app.controllers = {}));
44389 })(app || (app = {}));
44393 (function (controllers) {
44394 var SelectCommand = (function () {
44395 function SelectCommand($scope, APIEndPoint, $modalInstance) {
44396 this.APIEndPoint = APIEndPoint;
44397 this.$modalInstance = $modalInstance;
44398 var controller = this;
44401 .$promise.then(function (result) {
44402 controller.tags = result.info;
44406 .$promise.then(function (result) {
44407 controller.commands = result.info;
44409 this.currentTag = 'all';
44411 SelectCommand.prototype.changeTag = function (tag) {
44412 this.currentTag = tag;
44414 SelectCommand.prototype.selectCommand = function (command) {
44415 this.$modalInstance.close(command);
44417 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44418 return SelectCommand;
44420 controllers.SelectCommand = SelectCommand;
44421 })(controllers = app.controllers || (app.controllers = {}));
44422 })(app || (app = {}));
44426 (function (controllers) {
44427 var Preview = (function () {
44428 function Preview($scope, APIEndPoint, $modalInstance) {
44429 this.APIEndPoint = APIEndPoint;
44430 this.$modalInstance = $modalInstance;
44431 var controller = this;
44432 console.log('preview');
44434 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44437 controllers.Preview = Preview;
44438 })(controllers = app.controllers || (app.controllers = {}));
44439 })(app || (app = {}));
44441 (function (filters) {
44443 return function (commands, tag) {
44445 angular.forEach(commands, function (command) {
44447 angular.forEach(command.tags, function (value) {
44452 result.push(command);
44458 })(filters || (filters = {}));
44462 var appName = 'zephyr';
44463 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44464 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44465 $urlRouterProvider.otherwise('/execution');
44466 $locationProvider.html5Mode({
44471 .state('execution', {
44473 templateUrl: 'templates/execution.html',
44474 controller: 'executionController',
44477 .state('workspace', {
44479 templateUrl: 'templates/workspace.html',
44480 controller: 'workspaceController',
44483 .state('history', {
44485 templateUrl: 'templates/history.html',
44486 controller: 'historyController',
44490 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44491 app.zephyr.service('MyModal', app.services.MyModal);
44492 app.zephyr.service('WebSocket', app.services.WebSocket);
44493 app.zephyr.service('Console', app.services.Console);
44494 app.zephyr.filter('Tag', filters.Tag);
44495 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
44496 app.zephyr.controller('previewController', app.controllers.Preview);
44497 app.zephyr.controller('executionController', app.controllers.Execution);
44498 app.zephyr.controller('workspaceController', app.controllers.Workspace);
44499 app.zephyr.controller('historyController', app.controllers.History);
44500 app.zephyr.controller('commandController', app.directives.CommandController);
44501 app.zephyr.controller('optionController', app.directives.OptionController);
44502 app.zephyr.controller('directoryController', app.directives.DirectoryController);
44503 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
44504 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
44505 app.zephyr.directive('command', app.directives.Command.Factory());
44506 app.zephyr.directive('option', app.directives.Option.Factory());
44507 app.zephyr.directive('directory', app.directives.Directory.Factory());
44508 })(app || (app = {}));
44513 /***/ function(module, exports) {
44518 (function (declares) {
44519 var CommandInfo = (function () {
44520 function CommandInfo(name) {
44523 return CommandInfo;
44525 declares.CommandInfo = CommandInfo;
44526 })(declares = app.declares || (app.declares = {}));
44527 })(app || (app = {}));
44531 (function (services) {
44532 var APIEndPoint = (function () {
44533 function APIEndPoint($resource, $http) {
44534 this.$resource = $resource;
44535 this.$http = $http;
44537 APIEndPoint.prototype.resource = function (endPoint, data) {
44538 var customAction = {
44544 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
44546 return this.$resource(endPoint, {}, { execute: execute });
44548 APIEndPoint.prototype.getOptionControlFile = function (command) {
44549 var endPoint = '/api/v1/optionControlFile/' + command;
44550 return this.resource(endPoint, {}).get();
44552 APIEndPoint.prototype.getFiles = function (fileId) {
44553 var endPoint = '/api/v1/workspace';
44555 endPoint += '/' + fileId;
44557 return this.resource(endPoint, {}).get();
44559 APIEndPoint.prototype.getDirectories = function () {
44560 var endPoint = '/api/v1/all/workspace/directory';
44561 return this.resource(endPoint, {}).get();
44563 APIEndPoint.prototype.getTags = function () {
44564 var endPoint = '/api/v1/tagList';
44565 return this.resource(endPoint, {}).get();
44567 APIEndPoint.prototype.getCommands = function () {
44568 var endPoint = '/api/v1/commandList';
44569 return this.resource(endPoint, {}).get();
44571 APIEndPoint.prototype.execute = function (data) {
44572 var endPoint = '/api/v1/execution';
44573 var fd = new FormData();
44574 fd.append('data', data);
44575 return this.$http.post(endPoint, fd, {
44576 headers: { 'Content-Type': undefined },
44577 transformRequest: angular.identity
44580 APIEndPoint.prototype.debug = function () {
44581 var endPoint = '/api/v1/debug';
44582 return this.$http.get(endPoint);
44584 APIEndPoint.prototype.help = function (command) {
44585 var endPoint = '/api/v1/help/' + command;
44586 return this.$http.get(endPoint);
44588 return APIEndPoint;
44590 services.APIEndPoint = APIEndPoint;
44591 })(services = app.services || (app.services = {}));
44592 })(app || (app = {}));
44596 (function (services) {
44597 var MyModal = (function () {
44598 function MyModal($uibModal) {
44599 this.$uibModal = $uibModal;
44600 this.modalOption = {
44607 MyModal.prototype.open = function (modalName) {
44608 if (modalName === 'SelectCommand') {
44609 this.modalOption.templateUrl = 'templates/select-command.html';
44610 this.modalOption.size = 'lg';
44612 return this.$uibModal.open(this.modalOption);
44614 MyModal.prototype.selectCommand = function () {
44615 this.modalOption.templateUrl = 'templates/select-command.html';
44616 this.modalOption.controller = 'selectCommandController';
44617 this.modalOption.controllerAs = 'c';
44618 this.modalOption.size = 'lg';
44619 return this.$uibModal.open(this.modalOption);
44621 MyModal.prototype.preview = function () {
44622 this.modalOption.templateUrl = 'templates/preview.html';
44623 this.modalOption.controller = 'previewController';
44624 this.modalOption.controllerAs = 'c';
44625 this.modalOption.size = 'lg';
44626 return this.$uibModal.open(this.modalOption);
44628 MyModal.$inject = ['$uibModal'];
44631 services.MyModal = MyModal;
44632 })(services = app.services || (app.services = {}));
44633 })(app || (app = {}));
44637 (function (services) {
44638 var WebSocket = (function () {
44639 function WebSocket($rootScope) {
44640 this.$rootScope = $rootScope;
44641 this.socket = io.connect();
44643 WebSocket.prototype.on = function (eventName, callback) {
44644 var socket = this.socket;
44645 var rootScope = this.$rootScope;
44646 socket.on(eventName, function () {
44647 var args = arguments;
44648 rootScope.$apply(function () {
44649 callback.apply(socket, args);
44653 WebSocket.prototype.emit = function (eventName, data, callback) {
44654 var socket = this.socket;
44655 var rootScope = this.$rootScope;
44656 this.socket.emit(eventName, data, function () {
44657 var args = arguments;
44658 rootScope.$apply(function () {
44660 callback.apply(socket, args);
44666 services.WebSocket = WebSocket;
44667 })(services = app.services || (app.services = {}));
44668 })(app || (app = {}));
44672 (function (services) {
44673 var Console = (function () {
44674 function Console(WebSocket, $rootScope) {
44675 this.WebSocket = WebSocket;
44676 this.$rootScope = $rootScope;
44677 this.WebSocket = WebSocket;
44678 this.$rootScope = $rootScope;
44679 this.directiveIDs = [];
44680 var directiveIDs = this.directiveIDs;
44681 this.WebSocket.on('console', function (d) {
44683 var message = d.message;
44684 if (directiveIDs.indexOf(id) > -1) {
44685 $rootScope.$emit(id, message);
44689 Console.prototype.addDirective = function (id) {
44690 if (!(this.directiveIDs.indexOf(id) > -1)) {
44691 this.directiveIDs.push(id);
44694 Console.prototype.removeDirective = function (id) {
44695 var i = this.directiveIDs.indexOf(id);
44697 this.directiveIDs.splice(i, 1);
44700 Console.prototype.showIDs = function () {
44701 console.log(this.directiveIDs);
44705 services.Console = Console;
44706 })(services = app.services || (app.services = {}));
44707 })(app || (app = {}));
44711 (function (directives) {
44712 var Command = (function () {
44713 function Command() {
44714 this.restrict = 'E';
44715 this.replace = true;
44717 this.controller = 'commandController';
44718 this.controllerAs = 'ctrl';
44719 this.bindToController = {
44725 this.templateUrl = 'templates/command.html';
44727 Command.Factory = function () {
44728 var directive = function () {
44729 return new Command();
44731 directive.$inject = [];
44736 directives.Command = Command;
44737 var CommandController = (function () {
44738 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
44739 this.APIEndPoint = APIEndPoint;
44740 this.$scope = $scope;
44741 this.MyModal = MyModal;
44742 this.WebSocket = WebSocket;
44743 this.$window = $window;
44744 this.$rootScope = $rootScope;
44745 this.Console = Console;
44746 var controller = this;
44748 .getOptionControlFile(this.name)
44750 .then(function (result) {
44751 controller.options = result.info;
44756 .then(function (result) {
44757 controller.dirs = result.info;
44759 this.heading = "[" + this.index + "]: dcdFilePrint";
44760 this.isOpen = true;
44761 this.$scope.$on('close', function () {
44762 controller.isOpen = false;
44766 return Math.floor((1 + Math.random()) * 0x10000)
44770 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
44771 s4() + '-' + s4() + s4() + s4();
44773 this.uuid = guid();
44774 this.Console.addDirective(this.uuid);
44775 this.Console.showIDs();
44777 CommandController.prototype.submit = function () {
44779 angular.forEach(this.options, function (option) {
44781 name: option.option,
44784 angular.forEach(option.arg, function (arg) {
44786 if (typeof arg.input === 'object') {
44787 obj.arguments.push(arg.input.name);
44790 obj.arguments.push(arg.input);
44794 if (obj.arguments.length > 0) {
44799 command: this.name,
44800 workspace: this.workspace.fileId,
44804 .execute(JSON.stringify(execObj))
44805 .then(function (result) {
44806 console.log(result);
44809 CommandController.prototype.removeMySelf = function (index) {
44810 this.$scope.$destroy();
44811 this.Console.removeDirective(this.uuid);
44812 this.remove()(index, this.list);
44813 this.Console.showIDs();
44815 CommandController.prototype.reloadFiles = function () {
44817 var fileId = this.workspace.fileId;
44821 .then(function (result) {
44822 var status = result.status;
44823 if (status === 'success') {
44824 _this.files = result.info;
44827 console.log(result.message);
44831 CommandController.prototype.debug = function () {
44832 var div = angular.element(this.$window.document).find("div");
44835 angular.forEach(div, function (v) {
44836 if (v.className === "panel-body console") {
44839 else if (v.className === "row parameters-console") {
44843 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
44844 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
44845 consoleTag.style.height = consoleHeight;
44846 consoleTag.style.width = consoleWidth;
44848 CommandController.prototype.help = function () {
44851 .then(function (result) {
44852 console.log(result);
44855 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
44856 return CommandController;
44858 directives.CommandController = CommandController;
44859 })(directives = app.directives || (app.directives = {}));
44860 })(app || (app = {}));
44864 (function (directives) {
44865 var HeaderMenu = (function () {
44866 function HeaderMenu() {
44867 this.restrict = 'E';
44868 this.replace = true;
44869 this.templateUrl = 'templates/header-menu.html';
44870 this.controller = 'HeaderMenuController';
44871 this.controllerAs = 'hmc';
44874 HeaderMenu.Factory = function () {
44875 var directive = function () {
44876 return new HeaderMenu();
44882 directives.HeaderMenu = HeaderMenu;
44883 var HeaderMenuController = (function () {
44884 function HeaderMenuController($state) {
44885 this.$state = $state;
44886 this.isExecution = this.$state.current.name === 'execution';
44887 this.isWorkspace = this.$state.current.name === 'workspace';
44888 this.isHistory = this.$state.current.name === 'history';
44890 HeaderMenuController.prototype.transit = function (state) {
44891 this.$state.go(state);
44893 HeaderMenuController.$inject = ['$state'];
44894 return HeaderMenuController;
44896 directives.HeaderMenuController = HeaderMenuController;
44897 })(directives = app.directives || (app.directives = {}));
44898 })(app || (app = {}));
44902 (function (directives) {
44903 var Option = (function () {
44904 function Option() {
44905 this.restrict = 'E';
44906 this.replace = true;
44907 this.controller = 'optionController';
44908 this.bindToController = {
44913 this.templateUrl = 'templates/option.html';
44914 this.controllerAs = 'ctrl';
44916 Option.Factory = function () {
44917 var directive = function () {
44918 return new Option();
44920 directive.$inject = [];
44925 directives.Option = Option;
44926 var OptionController = (function () {
44927 function OptionController() {
44928 var controller = this;
44929 angular.forEach(controller.info.arg, function (arg) {
44930 if (arg.initialValue) {
44931 if (arg.formType === 'number') {
44932 arg.input = parseInt(arg.initialValue);
44935 arg.input = arg.initialValue;
44940 OptionController.$inject = [];
44941 return OptionController;
44943 directives.OptionController = OptionController;
44944 })(directives = app.directives || (app.directives = {}));
44945 })(app || (app = {}));
44949 (function (directives) {
44950 var Directory = (function () {
44951 function Directory() {
44952 this.restrict = 'E';
44953 this.replace = true;
44954 this.controller = 'directoryController';
44955 this.controllerAs = 'ctrl';
44956 this.bindToController = {
44962 this.templateUrl = 'templates/directory.html';
44964 Directory.Factory = function () {
44965 var directive = function () {
44966 return new Directory();
44972 directives.Directory = Directory;
44973 var DirectoryController = (function () {
44974 function DirectoryController(APIEndPoint, $scope) {
44975 this.APIEndPoint = APIEndPoint;
44976 this.$scope = $scope;
44977 var controller = this;
44979 .getFiles(this.info.fileId)
44981 .then(function (result) {
44982 if (result.status === 'success') {
44983 controller.files = result.info;
44984 angular.forEach(result.info, function (file) {
44985 if (file.fileType === '0') {
44987 if (controller.info.path === '/') {
44988 o.path = '/' + file.name;
44991 o.path = controller.info.path + '/' + file.name;
44993 controller.add()(o, controller.list);
45000 DirectoryController.$inject = ['APIEndPoint', '$scope'];
45001 return DirectoryController;
45003 directives.DirectoryController = DirectoryController;
45004 })(directives = app.directives || (app.directives = {}));
45005 })(app || (app = {}));
45009 (function (controllers) {
45010 var Execution = (function () {
45011 function Execution(MyModal, $scope) {
45012 this.MyModal = MyModal;
45013 this.$scope = $scope;
45014 this.commandInfoList = [];
45017 Execution.prototype.add = function () {
45018 this.$scope.$broadcast('close');
45019 var commandInfoList = this.commandInfoList;
45020 var commandInstance = this.MyModal.selectCommand();
45023 .then(function (command) {
45024 commandInfoList.push(new app.declares.CommandInfo(command));
45027 Execution.prototype.open = function () {
45028 var result = this.MyModal.open('SelectCommand');
45029 console.log(result);
45031 Execution.prototype.remove = function (index, list) {
45032 list.splice(index, 1);
45034 Execution.prototype.close = function () {
45035 console.log("close");
45037 Execution.$inject = ['MyModal', '$scope'];
45040 controllers.Execution = Execution;
45041 })(controllers = app.controllers || (app.controllers = {}));
45042 })(app || (app = {}));
45046 (function (controllers) {
45047 var Workspace = (function () {
45048 function Workspace($scope, APIEndPoint, MyModal) {
45049 this.$scope = $scope;
45050 this.APIEndPoint = APIEndPoint;
45051 this.MyModal = MyModal;
45052 this.directoryList = [];
45053 var controller = this;
45054 var directoryList = this.directoryList;
45056 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
45064 directoryList.push(o);
45066 Workspace.prototype.addDirectory = function (info, directoryList) {
45067 directoryList.push(info);
45069 Workspace.prototype.debug = function () {
45070 this.MyModal.preview();
45072 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
45075 controllers.Workspace = Workspace;
45076 })(controllers = app.controllers || (app.controllers = {}));
45077 })(app || (app = {}));
45081 (function (controllers) {
45082 var History = (function () {
45083 function History($scope) {
45084 this.page = "History";
45086 History.$inject = ['$scope'];
45089 controllers.History = History;
45090 })(controllers = app.controllers || (app.controllers = {}));
45091 })(app || (app = {}));
45095 (function (controllers) {
45096 var SelectCommand = (function () {
45097 function SelectCommand($scope, APIEndPoint, $modalInstance) {
45098 this.APIEndPoint = APIEndPoint;
45099 this.$modalInstance = $modalInstance;
45100 var controller = this;
45103 .$promise.then(function (result) {
45104 controller.tags = result.info;
45108 .$promise.then(function (result) {
45109 controller.commands = result.info;
45111 this.currentTag = 'all';
45113 SelectCommand.prototype.changeTag = function (tag) {
45114 this.currentTag = tag;
45116 SelectCommand.prototype.selectCommand = function (command) {
45117 this.$modalInstance.close(command);
45119 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45120 return SelectCommand;
45122 controllers.SelectCommand = SelectCommand;
45123 })(controllers = app.controllers || (app.controllers = {}));
45124 })(app || (app = {}));
45128 (function (controllers) {
45129 var Preview = (function () {
45130 function Preview($scope, APIEndPoint, $modalInstance) {
45131 this.APIEndPoint = APIEndPoint;
45132 this.$modalInstance = $modalInstance;
45133 var controller = this;
45134 console.log('preview');
45136 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45139 controllers.Preview = Preview;
45140 })(controllers = app.controllers || (app.controllers = {}));
45141 })(app || (app = {}));
45143 (function (filters) {
45145 return function (commands, tag) {
45147 angular.forEach(commands, function (command) {
45149 angular.forEach(command.tags, function (value) {
45154 result.push(command);
45160 })(filters || (filters = {}));
45164 var appName = 'zephyr';
45165 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
45166 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
45167 $urlRouterProvider.otherwise('/execution');
45168 $locationProvider.html5Mode({
45173 .state('execution', {
45175 templateUrl: 'templates/execution.html',
45176 controller: 'executionController',
45179 .state('workspace', {
45181 templateUrl: 'templates/workspace.html',
45182 controller: 'workspaceController',
45185 .state('history', {
45187 templateUrl: 'templates/history.html',
45188 controller: 'historyController',
45192 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
45193 app.zephyr.service('MyModal', app.services.MyModal);
45194 app.zephyr.service('WebSocket', app.services.WebSocket);
45195 app.zephyr.service('Console', app.services.Console);
45196 app.zephyr.filter('Tag', filters.Tag);
45197 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
45198 app.zephyr.controller('previewController', app.controllers.Preview);
45199 app.zephyr.controller('executionController', app.controllers.Execution);
45200 app.zephyr.controller('workspaceController', app.controllers.Workspace);
45201 app.zephyr.controller('historyController', app.controllers.History);
45202 app.zephyr.controller('commandController', app.directives.CommandController);
45203 app.zephyr.controller('optionController', app.directives.OptionController);
45204 app.zephyr.controller('directoryController', app.directives.DirectoryController);
45205 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
45206 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
45207 app.zephyr.directive('command', app.directives.Command.Factory());
45208 app.zephyr.directive('option', app.directives.Option.Factory());
45209 app.zephyr.directive('directory', app.directives.Directory.Factory());
45210 })(app || (app = {}));
45215 /***/ function(module, exports) {
45220 (function (declares) {
45221 var CommandInfo = (function () {
45222 function CommandInfo(name) {
45225 return CommandInfo;
45227 declares.CommandInfo = CommandInfo;
45228 })(declares = app.declares || (app.declares = {}));
45229 })(app || (app = {}));
45233 (function (services) {
45234 var APIEndPoint = (function () {
45235 function APIEndPoint($resource, $http) {
45236 this.$resource = $resource;
45237 this.$http = $http;
45239 APIEndPoint.prototype.resource = function (endPoint, data) {
45240 var customAction = {
45246 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
45248 return this.$resource(endPoint, {}, { execute: execute });
45250 APIEndPoint.prototype.getOptionControlFile = function (command) {
45251 var endPoint = '/api/v1/optionControlFile/' + command;
45252 return this.resource(endPoint, {}).get();
45254 APIEndPoint.prototype.getFiles = function (fileId) {
45255 var endPoint = '/api/v1/workspace';
45257 endPoint += '/' + fileId;
45259 return this.resource(endPoint, {}).get();
45261 APIEndPoint.prototype.getDirectories = function () {
45262 var endPoint = '/api/v1/all/workspace/directory';
45263 return this.resource(endPoint, {}).get();
45265 APIEndPoint.prototype.getTags = function () {
45266 var endPoint = '/api/v1/tagList';
45267 return this.resource(endPoint, {}).get();
45269 APIEndPoint.prototype.getCommands = function () {
45270 var endPoint = '/api/v1/commandList';
45271 return this.resource(endPoint, {}).get();
45273 APIEndPoint.prototype.execute = function (data) {
45274 var endPoint = '/api/v1/execution';
45275 var fd = new FormData();
45276 fd.append('data', data);
45277 return this.$http.post(endPoint, fd, {
45278 headers: { 'Content-Type': undefined },
45279 transformRequest: angular.identity
45282 APIEndPoint.prototype.debug = function () {
45283 var endPoint = '/api/v1/debug';
45284 return this.$http.get(endPoint);
45286 APIEndPoint.prototype.help = function (command) {
45287 var endPoint = '/api/v1/help/' + command;
45288 return this.$http.get(endPoint);
45290 return APIEndPoint;
45292 services.APIEndPoint = APIEndPoint;
45293 })(services = app.services || (app.services = {}));
45294 })(app || (app = {}));
45298 (function (services) {
45299 var MyModal = (function () {
45300 function MyModal($uibModal) {
45301 this.$uibModal = $uibModal;
45302 this.modalOption = {
45309 MyModal.prototype.open = function (modalName) {
45310 if (modalName === 'SelectCommand') {
45311 this.modalOption.templateUrl = 'templates/select-command.html';
45312 this.modalOption.size = 'lg';
45314 return this.$uibModal.open(this.modalOption);
45316 MyModal.prototype.selectCommand = function () {
45317 this.modalOption.templateUrl = 'templates/select-command.html';
45318 this.modalOption.controller = 'selectCommandController';
45319 this.modalOption.controllerAs = 'c';
45320 this.modalOption.size = 'lg';
45321 return this.$uibModal.open(this.modalOption);
45323 MyModal.prototype.preview = function () {
45324 this.modalOption.templateUrl = 'templates/preview.html';
45325 this.modalOption.controller = 'previewController';
45326 this.modalOption.controllerAs = 'c';
45327 this.modalOption.size = 'lg';
45328 return this.$uibModal.open(this.modalOption);
45330 MyModal.$inject = ['$uibModal'];
45333 services.MyModal = MyModal;
45334 })(services = app.services || (app.services = {}));
45335 })(app || (app = {}));
45339 (function (services) {
45340 var WebSocket = (function () {
45341 function WebSocket($rootScope) {
45342 this.$rootScope = $rootScope;
45343 this.socket = io.connect();
45345 WebSocket.prototype.on = function (eventName, callback) {
45346 var socket = this.socket;
45347 var rootScope = this.$rootScope;
45348 socket.on(eventName, function () {
45349 var args = arguments;
45350 rootScope.$apply(function () {
45351 callback.apply(socket, args);
45355 WebSocket.prototype.emit = function (eventName, data, callback) {
45356 var socket = this.socket;
45357 var rootScope = this.$rootScope;
45358 this.socket.emit(eventName, data, function () {
45359 var args = arguments;
45360 rootScope.$apply(function () {
45362 callback.apply(socket, args);
45368 services.WebSocket = WebSocket;
45369 })(services = app.services || (app.services = {}));
45370 })(app || (app = {}));
45374 (function (services) {
45375 var Console = (function () {
45376 function Console(WebSocket, $rootScope) {
45377 this.WebSocket = WebSocket;
45378 this.$rootScope = $rootScope;
45379 this.WebSocket = WebSocket;
45380 this.$rootScope = $rootScope;
45381 this.directiveIDs = [];
45382 var directiveIDs = this.directiveIDs;
45383 this.WebSocket.on('console', function (d) {
45385 var message = d.message;
45386 if (directiveIDs.indexOf(id) > -1) {
45387 $rootScope.$emit(id, message);
45391 Console.prototype.addDirective = function (id) {
45392 if (!(this.directiveIDs.indexOf(id) > -1)) {
45393 this.directiveIDs.push(id);
45396 Console.prototype.removeDirective = function (id) {
45397 var i = this.directiveIDs.indexOf(id);
45399 this.directiveIDs.splice(i, 1);
45402 Console.prototype.showIDs = function () {
45403 console.log(this.directiveIDs);
45407 services.Console = Console;
45408 })(services = app.services || (app.services = {}));
45409 })(app || (app = {}));
45413 (function (directives) {
45414 var Command = (function () {
45415 function Command() {
45416 this.restrict = 'E';
45417 this.replace = true;
45419 this.controller = 'commandController';
45420 this.controllerAs = 'ctrl';
45421 this.bindToController = {
45427 this.templateUrl = 'templates/command.html';
45429 Command.Factory = function () {
45430 var directive = function () {
45431 return new Command();
45433 directive.$inject = [];
45438 directives.Command = Command;
45439 var CommandController = (function () {
45440 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
45441 this.APIEndPoint = APIEndPoint;
45442 this.$scope = $scope;
45443 this.MyModal = MyModal;
45444 this.WebSocket = WebSocket;
45445 this.$window = $window;
45446 this.$rootScope = $rootScope;
45447 this.Console = Console;
45448 var controller = this;
45450 .getOptionControlFile(this.name)
45452 .then(function (result) {
45453 controller.options = result.info;
45458 .then(function (result) {
45459 controller.dirs = result.info;
45461 this.heading = "[" + this.index + "]: dcdFilePrint";
45462 this.isOpen = true;
45463 this.$scope.$on('close', function () {
45464 controller.isOpen = false;
45468 return Math.floor((1 + Math.random()) * 0x10000)
45472 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
45473 s4() + '-' + s4() + s4() + s4();
45475 this.uuid = guid();
45476 this.Console.addDirective(this.uuid);
45477 this.Console.showIDs();
45479 CommandController.prototype.submit = function () {
45481 angular.forEach(this.options, function (option) {
45483 name: option.option,
45486 angular.forEach(option.arg, function (arg) {
45488 if (typeof arg.input === 'object') {
45489 obj.arguments.push(arg.input.name);
45492 obj.arguments.push(arg.input);
45496 if (obj.arguments.length > 0) {
45501 command: this.name,
45502 workspace: this.workspace.fileId,
45506 .execute(JSON.stringify(execObj))
45507 .then(function (result) {
45508 console.log(result);
45511 CommandController.prototype.removeMySelf = function (index) {
45512 this.$scope.$destroy();
45513 this.Console.removeDirective(this.uuid);
45514 this.remove()(index, this.list);
45515 this.Console.showIDs();
45517 CommandController.prototype.reloadFiles = function () {
45519 var fileId = this.workspace.fileId;
45523 .then(function (result) {
45524 var status = result.status;
45525 if (status === 'success') {
45526 _this.files = result.info;
45529 console.log(result.message);
45533 CommandController.prototype.debug = function () {
45534 var div = angular.element(this.$window.document).find("div");
45537 angular.forEach(div, function (v) {
45538 if (v.className === "panel-body console") {
45541 else if (v.className === "row parameters-console") {
45545 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
45546 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
45547 consoleTag.style.height = consoleHeight;
45548 consoleTag.style.width = consoleWidth;
45550 CommandController.prototype.help = function () {
45553 .then(function (result) {
45554 console.log(result);
45557 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
45558 return CommandController;
45560 directives.CommandController = CommandController;
45561 })(directives = app.directives || (app.directives = {}));
45562 })(app || (app = {}));
45566 (function (directives) {
45567 var HeaderMenu = (function () {
45568 function HeaderMenu() {
45569 this.restrict = 'E';
45570 this.replace = true;
45571 this.templateUrl = 'templates/header-menu.html';
45572 this.controller = 'HeaderMenuController';
45573 this.controllerAs = 'hmc';
45576 HeaderMenu.Factory = function () {
45577 var directive = function () {
45578 return new HeaderMenu();
45584 directives.HeaderMenu = HeaderMenu;
45585 var HeaderMenuController = (function () {
45586 function HeaderMenuController($state) {
45587 this.$state = $state;
45588 this.isExecution = this.$state.current.name === 'execution';
45589 this.isWorkspace = this.$state.current.name === 'workspace';
45590 this.isHistory = this.$state.current.name === 'history';
45592 HeaderMenuController.prototype.transit = function (state) {
45593 this.$state.go(state);
45595 HeaderMenuController.$inject = ['$state'];
45596 return HeaderMenuController;
45598 directives.HeaderMenuController = HeaderMenuController;
45599 })(directives = app.directives || (app.directives = {}));
45600 })(app || (app = {}));
45604 (function (directives) {
45605 var Option = (function () {
45606 function Option() {
45607 this.restrict = 'E';
45608 this.replace = true;
45609 this.controller = 'optionController';
45610 this.bindToController = {
45615 this.templateUrl = 'templates/option.html';
45616 this.controllerAs = 'ctrl';
45618 Option.Factory = function () {
45619 var directive = function () {
45620 return new Option();
45622 directive.$inject = [];
45627 directives.Option = Option;
45628 var OptionController = (function () {
45629 function OptionController() {
45630 var controller = this;
45631 angular.forEach(controller.info.arg, function (arg) {
45632 if (arg.initialValue) {
45633 if (arg.formType === 'number') {
45634 arg.input = parseInt(arg.initialValue);
45637 arg.input = arg.initialValue;
45642 OptionController.$inject = [];
45643 return OptionController;
45645 directives.OptionController = OptionController;
45646 })(directives = app.directives || (app.directives = {}));
45647 })(app || (app = {}));
45651 (function (directives) {
45652 var Directory = (function () {
45653 function Directory() {
45654 this.restrict = 'E';
45655 this.replace = true;
45656 this.controller = 'directoryController';
45657 this.controllerAs = 'ctrl';
45658 this.bindToController = {
45664 this.templateUrl = 'templates/directory.html';
45666 Directory.Factory = function () {
45667 var directive = function () {
45668 return new Directory();
45674 directives.Directory = Directory;
45675 var DirectoryController = (function () {
45676 function DirectoryController(APIEndPoint, $scope) {
45677 this.APIEndPoint = APIEndPoint;
45678 this.$scope = $scope;
45679 var controller = this;
45681 .getFiles(this.info.fileId)
45683 .then(function (result) {
45684 if (result.status === 'success') {
45685 controller.files = result.info;
45686 angular.forEach(result.info, function (file) {
45687 if (file.fileType === '0') {
45689 if (controller.info.path === '/') {
45690 o.path = '/' + file.name;
45693 o.path = controller.info.path + '/' + file.name;
45695 controller.add()(o, controller.list);
45702 DirectoryController.$inject = ['APIEndPoint', '$scope'];
45703 return DirectoryController;
45705 directives.DirectoryController = DirectoryController;
45706 })(directives = app.directives || (app.directives = {}));
45707 })(app || (app = {}));
45711 (function (controllers) {
45712 var Execution = (function () {
45713 function Execution(MyModal, $scope) {
45714 this.MyModal = MyModal;
45715 this.$scope = $scope;
45716 this.commandInfoList = [];
45719 Execution.prototype.add = function () {
45720 this.$scope.$broadcast('close');
45721 var commandInfoList = this.commandInfoList;
45722 var commandInstance = this.MyModal.selectCommand();
45725 .then(function (command) {
45726 commandInfoList.push(new app.declares.CommandInfo(command));
45729 Execution.prototype.open = function () {
45730 var result = this.MyModal.open('SelectCommand');
45731 console.log(result);
45733 Execution.prototype.remove = function (index, list) {
45734 list.splice(index, 1);
45736 Execution.prototype.close = function () {
45737 console.log("close");
45739 Execution.$inject = ['MyModal', '$scope'];
45742 controllers.Execution = Execution;
45743 })(controllers = app.controllers || (app.controllers = {}));
45744 })(app || (app = {}));
45748 (function (controllers) {
45749 var Workspace = (function () {
45750 function Workspace($scope, APIEndPoint, MyModal) {
45751 this.$scope = $scope;
45752 this.APIEndPoint = APIEndPoint;
45753 this.MyModal = MyModal;
45754 this.directoryList = [];
45755 var controller = this;
45756 var directoryList = this.directoryList;
45758 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
45766 directoryList.push(o);
45768 Workspace.prototype.addDirectory = function (info, directoryList) {
45769 directoryList.push(info);
45771 Workspace.prototype.debug = function () {
45772 this.MyModal.preview();
45774 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
45777 controllers.Workspace = Workspace;
45778 })(controllers = app.controllers || (app.controllers = {}));
45779 })(app || (app = {}));
45783 (function (controllers) {
45784 var History = (function () {
45785 function History($scope) {
45786 this.page = "History";
45788 History.$inject = ['$scope'];
45791 controllers.History = History;
45792 })(controllers = app.controllers || (app.controllers = {}));
45793 })(app || (app = {}));
45797 (function (controllers) {
45798 var SelectCommand = (function () {
45799 function SelectCommand($scope, APIEndPoint, $modalInstance) {
45800 this.APIEndPoint = APIEndPoint;
45801 this.$modalInstance = $modalInstance;
45802 var controller = this;
45805 .$promise.then(function (result) {
45806 controller.tags = result.info;
45810 .$promise.then(function (result) {
45811 controller.commands = result.info;
45813 this.currentTag = 'all';
45815 SelectCommand.prototype.changeTag = function (tag) {
45816 this.currentTag = tag;
45818 SelectCommand.prototype.selectCommand = function (command) {
45819 this.$modalInstance.close(command);
45821 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45822 return SelectCommand;
45824 controllers.SelectCommand = SelectCommand;
45825 })(controllers = app.controllers || (app.controllers = {}));
45826 })(app || (app = {}));
45830 (function (controllers) {
45831 var Preview = (function () {
45832 function Preview($scope, APIEndPoint, $modalInstance) {
45833 this.APIEndPoint = APIEndPoint;
45834 this.$modalInstance = $modalInstance;
45835 var controller = this;
45836 console.log('preview');
45838 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45841 controllers.Preview = Preview;
45842 })(controllers = app.controllers || (app.controllers = {}));
45843 })(app || (app = {}));
45845 (function (filters) {
45847 return function (commands, tag) {
45849 angular.forEach(commands, function (command) {
45851 angular.forEach(command.tags, function (value) {
45856 result.push(command);
45862 })(filters || (filters = {}));
45866 var appName = 'zephyr';
45867 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
45868 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
45869 $urlRouterProvider.otherwise('/execution');
45870 $locationProvider.html5Mode({
45875 .state('execution', {
45877 templateUrl: 'templates/execution.html',
45878 controller: 'executionController',
45881 .state('workspace', {
45883 templateUrl: 'templates/workspace.html',
45884 controller: 'workspaceController',
45887 .state('history', {
45889 templateUrl: 'templates/history.html',
45890 controller: 'historyController',
45894 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
45895 app.zephyr.service('MyModal', app.services.MyModal);
45896 app.zephyr.service('WebSocket', app.services.WebSocket);
45897 app.zephyr.service('Console', app.services.Console);
45898 app.zephyr.filter('Tag', filters.Tag);
45899 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
45900 app.zephyr.controller('previewController', app.controllers.Preview);
45901 app.zephyr.controller('executionController', app.controllers.Execution);
45902 app.zephyr.controller('workspaceController', app.controllers.Workspace);
45903 app.zephyr.controller('historyController', app.controllers.History);
45904 app.zephyr.controller('commandController', app.directives.CommandController);
45905 app.zephyr.controller('optionController', app.directives.OptionController);
45906 app.zephyr.controller('directoryController', app.directives.DirectoryController);
45907 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
45908 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
45909 app.zephyr.directive('command', app.directives.Command.Factory());
45910 app.zephyr.directive('option', app.directives.Option.Factory());
45911 app.zephyr.directive('directory', app.directives.Directory.Factory());
45912 })(app || (app = {}));
45917 /***/ function(module, exports) {
45922 (function (declares) {
45923 var CommandInfo = (function () {
45924 function CommandInfo(name) {
45927 return CommandInfo;
45929 declares.CommandInfo = CommandInfo;
45930 })(declares = app.declares || (app.declares = {}));
45931 })(app || (app = {}));
45935 (function (services) {
45936 var APIEndPoint = (function () {
45937 function APIEndPoint($resource, $http) {
45938 this.$resource = $resource;
45939 this.$http = $http;
45941 APIEndPoint.prototype.resource = function (endPoint, data) {
45942 var customAction = {
45948 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
45950 return this.$resource(endPoint, {}, { execute: execute });
45952 APIEndPoint.prototype.getOptionControlFile = function (command) {
45953 var endPoint = '/api/v1/optionControlFile/' + command;
45954 return this.resource(endPoint, {}).get();
45956 APIEndPoint.prototype.getFiles = function (fileId) {
45957 var endPoint = '/api/v1/workspace';
45959 endPoint += '/' + fileId;
45961 return this.resource(endPoint, {}).get();
45963 APIEndPoint.prototype.getDirectories = function () {
45964 var endPoint = '/api/v1/all/workspace/directory';
45965 return this.resource(endPoint, {}).get();
45967 APIEndPoint.prototype.getTags = function () {
45968 var endPoint = '/api/v1/tagList';
45969 return this.resource(endPoint, {}).get();
45971 APIEndPoint.prototype.getCommands = function () {
45972 var endPoint = '/api/v1/commandList';
45973 return this.resource(endPoint, {}).get();
45975 APIEndPoint.prototype.execute = function (data) {
45976 var endPoint = '/api/v1/execution';
45977 var fd = new FormData();
45978 fd.append('data', data);
45979 return this.$http.post(endPoint, fd, {
45980 headers: { 'Content-Type': undefined },
45981 transformRequest: angular.identity
45984 APIEndPoint.prototype.debug = function () {
45985 var endPoint = '/api/v1/debug';
45986 return this.$http.get(endPoint);
45988 APIEndPoint.prototype.help = function (command) {
45989 var endPoint = '/api/v1/help/' + command;
45990 return this.$http.get(endPoint);
45992 return APIEndPoint;
45994 services.APIEndPoint = APIEndPoint;
45995 })(services = app.services || (app.services = {}));
45996 })(app || (app = {}));
46000 (function (services) {
46001 var MyModal = (function () {
46002 function MyModal($uibModal) {
46003 this.$uibModal = $uibModal;
46004 this.modalOption = {
46011 MyModal.prototype.open = function (modalName) {
46012 if (modalName === 'SelectCommand') {
46013 this.modalOption.templateUrl = 'templates/select-command.html';
46014 this.modalOption.size = 'lg';
46016 return this.$uibModal.open(this.modalOption);
46018 MyModal.prototype.selectCommand = function () {
46019 this.modalOption.templateUrl = 'templates/select-command.html';
46020 this.modalOption.controller = 'selectCommandController';
46021 this.modalOption.controllerAs = 'c';
46022 this.modalOption.size = 'lg';
46023 return this.$uibModal.open(this.modalOption);
46025 MyModal.prototype.preview = function () {
46026 this.modalOption.templateUrl = 'templates/preview.html';
46027 this.modalOption.controller = 'previewController';
46028 this.modalOption.controllerAs = 'c';
46029 this.modalOption.size = 'lg';
46030 return this.$uibModal.open(this.modalOption);
46032 MyModal.$inject = ['$uibModal'];
46035 services.MyModal = MyModal;
46036 })(services = app.services || (app.services = {}));
46037 })(app || (app = {}));
46041 (function (services) {
46042 var WebSocket = (function () {
46043 function WebSocket($rootScope) {
46044 this.$rootScope = $rootScope;
46045 this.socket = io.connect();
46047 WebSocket.prototype.on = function (eventName, callback) {
46048 var socket = this.socket;
46049 var rootScope = this.$rootScope;
46050 socket.on(eventName, function () {
46051 var args = arguments;
46052 rootScope.$apply(function () {
46053 callback.apply(socket, args);
46057 WebSocket.prototype.emit = function (eventName, data, callback) {
46058 var socket = this.socket;
46059 var rootScope = this.$rootScope;
46060 this.socket.emit(eventName, data, function () {
46061 var args = arguments;
46062 rootScope.$apply(function () {
46064 callback.apply(socket, args);
46070 services.WebSocket = WebSocket;
46071 })(services = app.services || (app.services = {}));
46072 })(app || (app = {}));
46076 (function (services) {
46077 var Console = (function () {
46078 function Console(WebSocket, $rootScope) {
46079 this.WebSocket = WebSocket;
46080 this.$rootScope = $rootScope;
46081 this.WebSocket = WebSocket;
46082 this.$rootScope = $rootScope;
46083 this.directiveIDs = [];
46084 var directiveIDs = this.directiveIDs;
46085 this.WebSocket.on('console', function (d) {
46087 var message = d.message;
46088 if (directiveIDs.indexOf(id) > -1) {
46089 $rootScope.$emit(id, message);
46093 Console.prototype.addDirective = function (id) {
46094 if (!(this.directiveIDs.indexOf(id) > -1)) {
46095 this.directiveIDs.push(id);
46098 Console.prototype.removeDirective = function (id) {
46099 var i = this.directiveIDs.indexOf(id);
46101 this.directiveIDs.splice(i, 1);
46104 Console.prototype.showIDs = function () {
46105 console.log(this.directiveIDs);
46109 services.Console = Console;
46110 })(services = app.services || (app.services = {}));
46111 })(app || (app = {}));
46115 (function (directives) {
46116 var Command = (function () {
46117 function Command() {
46118 this.restrict = 'E';
46119 this.replace = true;
46121 this.controller = 'commandController';
46122 this.controllerAs = 'ctrl';
46123 this.bindToController = {
46129 this.templateUrl = 'templates/command.html';
46131 Command.Factory = function () {
46132 var directive = function () {
46133 return new Command();
46135 directive.$inject = [];
46140 directives.Command = Command;
46141 var CommandController = (function () {
46142 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
46143 this.APIEndPoint = APIEndPoint;
46144 this.$scope = $scope;
46145 this.MyModal = MyModal;
46146 this.WebSocket = WebSocket;
46147 this.$window = $window;
46148 this.$rootScope = $rootScope;
46149 this.Console = Console;
46150 var controller = this;
46152 .getOptionControlFile(this.name)
46154 .then(function (result) {
46155 controller.options = result.info;
46160 .then(function (result) {
46161 controller.dirs = result.info;
46163 this.heading = "[" + this.index + "]: dcdFilePrint";
46164 this.isOpen = true;
46165 this.$scope.$on('close', function () {
46166 controller.isOpen = false;
46170 return Math.floor((1 + Math.random()) * 0x10000)
46174 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
46175 s4() + '-' + s4() + s4() + s4();
46177 this.uuid = guid();
46178 this.Console.addDirective(this.uuid);
46179 this.Console.showIDs();
46181 CommandController.prototype.submit = function () {
46183 angular.forEach(this.options, function (option) {
46185 name: option.option,
46188 angular.forEach(option.arg, function (arg) {
46190 if (typeof arg.input === 'object') {
46191 obj.arguments.push(arg.input.name);
46194 obj.arguments.push(arg.input);
46198 if (obj.arguments.length > 0) {
46203 command: this.name,
46204 workspace: this.workspace.fileId,
46208 .execute(JSON.stringify(execObj))
46209 .then(function (result) {
46210 console.log(result);
46213 CommandController.prototype.removeMySelf = function (index) {
46214 this.$scope.$destroy();
46215 this.Console.removeDirective(this.uuid);
46216 this.remove()(index, this.list);
46217 this.Console.showIDs();
46219 CommandController.prototype.reloadFiles = function () {
46221 var fileId = this.workspace.fileId;
46225 .then(function (result) {
46226 var status = result.status;
46227 if (status === 'success') {
46228 _this.files = result.info;
46231 console.log(result.message);
46235 CommandController.prototype.debug = function () {
46236 var div = angular.element(this.$window.document).find("div");
46239 angular.forEach(div, function (v) {
46240 if (v.className === "panel-body console") {
46243 else if (v.className === "row parameters-console") {
46247 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
46248 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
46249 consoleTag.style.height = consoleHeight;
46250 consoleTag.style.width = consoleWidth;
46252 CommandController.prototype.help = function () {
46255 .then(function (result) {
46256 console.log(result);
46259 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
46260 return CommandController;
46262 directives.CommandController = CommandController;
46263 })(directives = app.directives || (app.directives = {}));
46264 })(app || (app = {}));
46268 (function (directives) {
46269 var HeaderMenu = (function () {
46270 function HeaderMenu() {
46271 this.restrict = 'E';
46272 this.replace = true;
46273 this.templateUrl = 'templates/header-menu.html';
46274 this.controller = 'HeaderMenuController';
46275 this.controllerAs = 'hmc';
46278 HeaderMenu.Factory = function () {
46279 var directive = function () {
46280 return new HeaderMenu();
46286 directives.HeaderMenu = HeaderMenu;
46287 var HeaderMenuController = (function () {
46288 function HeaderMenuController($state) {
46289 this.$state = $state;
46290 this.isExecution = this.$state.current.name === 'execution';
46291 this.isWorkspace = this.$state.current.name === 'workspace';
46292 this.isHistory = this.$state.current.name === 'history';
46294 HeaderMenuController.prototype.transit = function (state) {
46295 this.$state.go(state);
46297 HeaderMenuController.$inject = ['$state'];
46298 return HeaderMenuController;
46300 directives.HeaderMenuController = HeaderMenuController;
46301 })(directives = app.directives || (app.directives = {}));
46302 })(app || (app = {}));
46306 (function (directives) {
46307 var Option = (function () {
46308 function Option() {
46309 this.restrict = 'E';
46310 this.replace = true;
46311 this.controller = 'optionController';
46312 this.bindToController = {
46317 this.templateUrl = 'templates/option.html';
46318 this.controllerAs = 'ctrl';
46320 Option.Factory = function () {
46321 var directive = function () {
46322 return new Option();
46324 directive.$inject = [];
46329 directives.Option = Option;
46330 var OptionController = (function () {
46331 function OptionController() {
46332 var controller = this;
46333 angular.forEach(controller.info.arg, function (arg) {
46334 if (arg.initialValue) {
46335 if (arg.formType === 'number') {
46336 arg.input = parseInt(arg.initialValue);
46339 arg.input = arg.initialValue;
46344 OptionController.$inject = [];
46345 return OptionController;
46347 directives.OptionController = OptionController;
46348 })(directives = app.directives || (app.directives = {}));
46349 })(app || (app = {}));
46353 (function (directives) {
46354 var Directory = (function () {
46355 function Directory() {
46356 this.restrict = 'E';
46357 this.replace = true;
46358 this.controller = 'directoryController';
46359 this.controllerAs = 'ctrl';
46360 this.bindToController = {
46366 this.templateUrl = 'templates/directory.html';
46368 Directory.Factory = function () {
46369 var directive = function () {
46370 return new Directory();
46376 directives.Directory = Directory;
46377 var DirectoryController = (function () {
46378 function DirectoryController(APIEndPoint, $scope) {
46379 this.APIEndPoint = APIEndPoint;
46380 this.$scope = $scope;
46381 var controller = this;
46383 .getFiles(this.info.fileId)
46385 .then(function (result) {
46386 if (result.status === 'success') {
46387 controller.files = result.info;
46388 angular.forEach(result.info, function (file) {
46389 if (file.fileType === '0') {
46391 if (controller.info.path === '/') {
46392 o.path = '/' + file.name;
46395 o.path = controller.info.path + '/' + file.name;
46397 controller.add()(o, controller.list);
46404 DirectoryController.$inject = ['APIEndPoint', '$scope'];
46405 return DirectoryController;
46407 directives.DirectoryController = DirectoryController;
46408 })(directives = app.directives || (app.directives = {}));
46409 })(app || (app = {}));
46413 (function (controllers) {
46414 var Execution = (function () {
46415 function Execution(MyModal, $scope) {
46416 this.MyModal = MyModal;
46417 this.$scope = $scope;
46418 this.commandInfoList = [];
46421 Execution.prototype.add = function () {
46422 this.$scope.$broadcast('close');
46423 var commandInfoList = this.commandInfoList;
46424 var commandInstance = this.MyModal.selectCommand();
46427 .then(function (command) {
46428 commandInfoList.push(new app.declares.CommandInfo(command));
46431 Execution.prototype.open = function () {
46432 var result = this.MyModal.open('SelectCommand');
46433 console.log(result);
46435 Execution.prototype.remove = function (index, list) {
46436 list.splice(index, 1);
46438 Execution.prototype.close = function () {
46439 console.log("close");
46441 Execution.$inject = ['MyModal', '$scope'];
46444 controllers.Execution = Execution;
46445 })(controllers = app.controllers || (app.controllers = {}));
46446 })(app || (app = {}));
46450 (function (controllers) {
46451 var Workspace = (function () {
46452 function Workspace($scope, APIEndPoint, MyModal) {
46453 this.$scope = $scope;
46454 this.APIEndPoint = APIEndPoint;
46455 this.MyModal = MyModal;
46456 this.directoryList = [];
46457 var controller = this;
46458 var directoryList = this.directoryList;
46460 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
46468 directoryList.push(o);
46470 Workspace.prototype.addDirectory = function (info, directoryList) {
46471 directoryList.push(info);
46473 Workspace.prototype.debug = function () {
46474 this.MyModal.preview();
46476 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
46479 controllers.Workspace = Workspace;
46480 })(controllers = app.controllers || (app.controllers = {}));
46481 })(app || (app = {}));
46485 (function (controllers) {
46486 var History = (function () {
46487 function History($scope) {
46488 this.page = "History";
46490 History.$inject = ['$scope'];
46493 controllers.History = History;
46494 })(controllers = app.controllers || (app.controllers = {}));
46495 })(app || (app = {}));
46499 (function (controllers) {
46500 var SelectCommand = (function () {
46501 function SelectCommand($scope, APIEndPoint, $modalInstance) {
46502 this.APIEndPoint = APIEndPoint;
46503 this.$modalInstance = $modalInstance;
46504 var controller = this;
46507 .$promise.then(function (result) {
46508 controller.tags = result.info;
46512 .$promise.then(function (result) {
46513 controller.commands = result.info;
46515 this.currentTag = 'all';
46517 SelectCommand.prototype.changeTag = function (tag) {
46518 this.currentTag = tag;
46520 SelectCommand.prototype.selectCommand = function (command) {
46521 this.$modalInstance.close(command);
46523 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
46524 return SelectCommand;
46526 controllers.SelectCommand = SelectCommand;
46527 })(controllers = app.controllers || (app.controllers = {}));
46528 })(app || (app = {}));
46532 (function (controllers) {
46533 var Preview = (function () {
46534 function Preview($scope, APIEndPoint, $modalInstance) {
46535 this.APIEndPoint = APIEndPoint;
46536 this.$modalInstance = $modalInstance;
46537 var controller = this;
46538 console.log('preview');
46540 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
46543 controllers.Preview = Preview;
46544 })(controllers = app.controllers || (app.controllers = {}));
46545 })(app || (app = {}));
46547 (function (filters) {
46549 return function (commands, tag) {
46551 angular.forEach(commands, function (command) {
46553 angular.forEach(command.tags, function (value) {
46558 result.push(command);
46564 })(filters || (filters = {}));
46568 var appName = 'zephyr';
46569 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
46570 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
46571 $urlRouterProvider.otherwise('/execution');
46572 $locationProvider.html5Mode({
46577 .state('execution', {
46579 templateUrl: 'templates/execution.html',
46580 controller: 'executionController',
46583 .state('workspace', {
46585 templateUrl: 'templates/workspace.html',
46586 controller: 'workspaceController',
46589 .state('history', {
46591 templateUrl: 'templates/history.html',
46592 controller: 'historyController',
46596 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
46597 app.zephyr.service('MyModal', app.services.MyModal);
46598 app.zephyr.service('WebSocket', app.services.WebSocket);
46599 app.zephyr.service('Console', app.services.Console);
46600 app.zephyr.filter('Tag', filters.Tag);
46601 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
46602 app.zephyr.controller('previewController', app.controllers.Preview);
46603 app.zephyr.controller('executionController', app.controllers.Execution);
46604 app.zephyr.controller('workspaceController', app.controllers.Workspace);
46605 app.zephyr.controller('historyController', app.controllers.History);
46606 app.zephyr.controller('commandController', app.directives.CommandController);
46607 app.zephyr.controller('optionController', app.directives.OptionController);
46608 app.zephyr.controller('directoryController', app.directives.DirectoryController);
46609 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
46610 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
46611 app.zephyr.directive('command', app.directives.Command.Factory());
46612 app.zephyr.directive('option', app.directives.Option.Factory());
46613 app.zephyr.directive('directory', app.directives.Directory.Factory());
46614 })(app || (app = {}));
46619 /***/ function(module, exports) {
46624 (function (declares) {
46625 var CommandInfo = (function () {
46626 function CommandInfo(name) {
46629 return CommandInfo;
46631 declares.CommandInfo = CommandInfo;
46632 })(declares = app.declares || (app.declares = {}));
46633 })(app || (app = {}));
46637 (function (services) {
46638 var APIEndPoint = (function () {
46639 function APIEndPoint($resource, $http) {
46640 this.$resource = $resource;
46641 this.$http = $http;
46643 APIEndPoint.prototype.resource = function (endPoint, data) {
46644 var customAction = {
46650 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
46652 return this.$resource(endPoint, {}, { execute: execute });
46654 APIEndPoint.prototype.getOptionControlFile = function (command) {
46655 var endPoint = '/api/v1/optionControlFile/' + command;
46656 return this.resource(endPoint, {}).get();
46658 APIEndPoint.prototype.getFiles = function (fileId) {
46659 var endPoint = '/api/v1/workspace';
46661 endPoint += '/' + fileId;
46663 return this.resource(endPoint, {}).get();
46665 APIEndPoint.prototype.getDirectories = function () {
46666 var endPoint = '/api/v1/all/workspace/directory';
46667 return this.resource(endPoint, {}).get();
46669 APIEndPoint.prototype.getTags = function () {
46670 var endPoint = '/api/v1/tagList';
46671 return this.resource(endPoint, {}).get();
46673 APIEndPoint.prototype.getCommands = function () {
46674 var endPoint = '/api/v1/commandList';
46675 return this.resource(endPoint, {}).get();
46677 APIEndPoint.prototype.execute = function (data) {
46678 var endPoint = '/api/v1/execution';
46679 var fd = new FormData();
46680 fd.append('data', data);
46681 return this.$http.post(endPoint, fd, {
46682 headers: { 'Content-Type': undefined },
46683 transformRequest: angular.identity
46686 APIEndPoint.prototype.debug = function () {
46687 var endPoint = '/api/v1/debug';
46688 return this.$http.get(endPoint);
46690 APIEndPoint.prototype.help = function (command) {
46691 var endPoint = '/api/v1/help/' + command;
46692 return this.$http.get(endPoint);
46694 return APIEndPoint;
46696 services.APIEndPoint = APIEndPoint;
46697 })(services = app.services || (app.services = {}));
46698 })(app || (app = {}));
46702 (function (services) {
46703 var MyModal = (function () {
46704 function MyModal($uibModal) {
46705 this.$uibModal = $uibModal;
46706 this.modalOption = {
46713 MyModal.prototype.open = function (modalName) {
46714 if (modalName === 'SelectCommand') {
46715 this.modalOption.templateUrl = 'templates/select-command.html';
46716 this.modalOption.size = 'lg';
46718 return this.$uibModal.open(this.modalOption);
46720 MyModal.prototype.selectCommand = function () {
46721 this.modalOption.templateUrl = 'templates/select-command.html';
46722 this.modalOption.controller = 'selectCommandController';
46723 this.modalOption.controllerAs = 'c';
46724 this.modalOption.size = 'lg';
46725 return this.$uibModal.open(this.modalOption);
46727 MyModal.prototype.preview = function () {
46728 this.modalOption.templateUrl = 'templates/preview.html';
46729 this.modalOption.controller = 'previewController';
46730 this.modalOption.controllerAs = 'c';
46731 this.modalOption.size = 'lg';
46732 return this.$uibModal.open(this.modalOption);
46734 MyModal.$inject = ['$uibModal'];
46737 services.MyModal = MyModal;
46738 })(services = app.services || (app.services = {}));
46739 })(app || (app = {}));
46743 (function (services) {
46744 var WebSocket = (function () {
46745 function WebSocket($rootScope) {
46746 this.$rootScope = $rootScope;
46747 this.socket = io.connect();
46749 WebSocket.prototype.on = function (eventName, callback) {
46750 var socket = this.socket;
46751 var rootScope = this.$rootScope;
46752 socket.on(eventName, function () {
46753 var args = arguments;
46754 rootScope.$apply(function () {
46755 callback.apply(socket, args);
46759 WebSocket.prototype.emit = function (eventName, data, callback) {
46760 var socket = this.socket;
46761 var rootScope = this.$rootScope;
46762 this.socket.emit(eventName, data, function () {
46763 var args = arguments;
46764 rootScope.$apply(function () {
46766 callback.apply(socket, args);
46772 services.WebSocket = WebSocket;
46773 })(services = app.services || (app.services = {}));
46774 })(app || (app = {}));
46778 (function (services) {
46779 var Console = (function () {
46780 function Console(WebSocket, $rootScope) {
46781 this.WebSocket = WebSocket;
46782 this.$rootScope = $rootScope;
46783 this.WebSocket = WebSocket;
46784 this.$rootScope = $rootScope;
46785 this.directiveIDs = [];
46786 var directiveIDs = this.directiveIDs;
46787 this.WebSocket.on('console', function (d) {
46789 var message = d.message;
46790 if (directiveIDs.indexOf(id) > -1) {
46791 $rootScope.$emit(id, message);
46795 Console.prototype.addDirective = function (id) {
46796 if (!(this.directiveIDs.indexOf(id) > -1)) {
46797 this.directiveIDs.push(id);
46800 Console.prototype.removeDirective = function (id) {
46801 var i = this.directiveIDs.indexOf(id);
46803 this.directiveIDs.splice(i, 1);
46806 Console.prototype.showIDs = function () {
46807 console.log(this.directiveIDs);
46811 services.Console = Console;
46812 })(services = app.services || (app.services = {}));
46813 })(app || (app = {}));
46817 (function (directives) {
46818 var Command = (function () {
46819 function Command() {
46820 this.restrict = 'E';
46821 this.replace = true;
46823 this.controller = 'commandController';
46824 this.controllerAs = 'ctrl';
46825 this.bindToController = {
46831 this.templateUrl = 'templates/command.html';
46833 Command.Factory = function () {
46834 var directive = function () {
46835 return new Command();
46837 directive.$inject = [];
46842 directives.Command = Command;
46843 var CommandController = (function () {
46844 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
46845 this.APIEndPoint = APIEndPoint;
46846 this.$scope = $scope;
46847 this.MyModal = MyModal;
46848 this.WebSocket = WebSocket;
46849 this.$window = $window;
46850 this.$rootScope = $rootScope;
46851 this.Console = Console;
46852 var controller = this;
46854 .getOptionControlFile(this.name)
46856 .then(function (result) {
46857 controller.options = result.info;
46862 .then(function (result) {
46863 controller.dirs = result.info;
46865 this.heading = "[" + this.index + "]: dcdFilePrint";
46866 this.isOpen = true;
46867 this.$scope.$on('close', function () {
46868 controller.isOpen = false;
46872 return Math.floor((1 + Math.random()) * 0x10000)
46876 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
46877 s4() + '-' + s4() + s4() + s4();
46879 this.uuid = guid();
46880 this.Console.addDirective(this.uuid);
46881 this.Console.showIDs();
46883 CommandController.prototype.submit = function () {
46885 angular.forEach(this.options, function (option) {
46887 name: option.option,
46890 angular.forEach(option.arg, function (arg) {
46892 if (typeof arg.input === 'object') {
46893 obj.arguments.push(arg.input.name);
46896 obj.arguments.push(arg.input);
46900 if (obj.arguments.length > 0) {
46905 command: this.name,
46906 workspace: this.workspace.fileId,
46910 .execute(JSON.stringify(execObj))
46911 .then(function (result) {
46912 console.log(result);
46915 CommandController.prototype.removeMySelf = function (index) {
46916 this.$scope.$destroy();
46917 this.Console.removeDirective(this.uuid);
46918 this.remove()(index, this.list);
46919 this.Console.showIDs();
46921 CommandController.prototype.reloadFiles = function () {
46923 var fileId = this.workspace.fileId;
46927 .then(function (result) {
46928 var status = result.status;
46929 if (status === 'success') {
46930 _this.files = result.info;
46933 console.log(result.message);
46937 CommandController.prototype.debug = function () {
46938 var div = angular.element(this.$window.document).find("div");
46941 angular.forEach(div, function (v) {
46942 if (v.className === "panel-body console") {
46945 else if (v.className === "row parameters-console") {
46949 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
46950 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
46951 consoleTag.style.height = consoleHeight;
46952 consoleTag.style.width = consoleWidth;
46954 CommandController.prototype.help = function () {
46957 .then(function (result) {
46958 console.log(result);
46961 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
46962 return CommandController;
46964 directives.CommandController = CommandController;
46965 })(directives = app.directives || (app.directives = {}));
46966 })(app || (app = {}));
46970 (function (directives) {
46971 var HeaderMenu = (function () {
46972 function HeaderMenu() {
46973 this.restrict = 'E';
46974 this.replace = true;
46975 this.templateUrl = 'templates/header-menu.html';
46976 this.controller = 'HeaderMenuController';
46977 this.controllerAs = 'hmc';
46980 HeaderMenu.Factory = function () {
46981 var directive = function () {
46982 return new HeaderMenu();
46988 directives.HeaderMenu = HeaderMenu;
46989 var HeaderMenuController = (function () {
46990 function HeaderMenuController($state) {
46991 this.$state = $state;
46992 this.isExecution = this.$state.current.name === 'execution';
46993 this.isWorkspace = this.$state.current.name === 'workspace';
46994 this.isHistory = this.$state.current.name === 'history';
46996 HeaderMenuController.prototype.transit = function (state) {
46997 this.$state.go(state);
46999 HeaderMenuController.$inject = ['$state'];
47000 return HeaderMenuController;
47002 directives.HeaderMenuController = HeaderMenuController;
47003 })(directives = app.directives || (app.directives = {}));
47004 })(app || (app = {}));
47008 (function (directives) {
47009 var Option = (function () {
47010 function Option() {
47011 this.restrict = 'E';
47012 this.replace = true;
47013 this.controller = 'optionController';
47014 this.bindToController = {
47019 this.templateUrl = 'templates/option.html';
47020 this.controllerAs = 'ctrl';
47022 Option.Factory = function () {
47023 var directive = function () {
47024 return new Option();
47026 directive.$inject = [];
47031 directives.Option = Option;
47032 var OptionController = (function () {
47033 function OptionController() {
47034 var controller = this;
47035 angular.forEach(controller.info.arg, function (arg) {
47036 if (arg.initialValue) {
47037 if (arg.formType === 'number') {
47038 arg.input = parseInt(arg.initialValue);
47041 arg.input = arg.initialValue;
47046 OptionController.$inject = [];
47047 return OptionController;
47049 directives.OptionController = OptionController;
47050 })(directives = app.directives || (app.directives = {}));
47051 })(app || (app = {}));
47055 (function (directives) {
47056 var Directory = (function () {
47057 function Directory() {
47058 this.restrict = 'E';
47059 this.replace = true;
47060 this.controller = 'directoryController';
47061 this.controllerAs = 'ctrl';
47062 this.bindToController = {
47068 this.templateUrl = 'templates/directory.html';
47070 Directory.Factory = function () {
47071 var directive = function () {
47072 return new Directory();
47078 directives.Directory = Directory;
47079 var DirectoryController = (function () {
47080 function DirectoryController(APIEndPoint, $scope) {
47081 this.APIEndPoint = APIEndPoint;
47082 this.$scope = $scope;
47083 var controller = this;
47085 .getFiles(this.info.fileId)
47087 .then(function (result) {
47088 if (result.status === 'success') {
47089 controller.files = result.info;
47090 angular.forEach(result.info, function (file) {
47091 if (file.fileType === '0') {
47093 if (controller.info.path === '/') {
47094 o.path = '/' + file.name;
47097 o.path = controller.info.path + '/' + file.name;
47099 controller.add()(o, controller.list);
47106 DirectoryController.$inject = ['APIEndPoint', '$scope'];
47107 return DirectoryController;
47109 directives.DirectoryController = DirectoryController;
47110 })(directives = app.directives || (app.directives = {}));
47111 })(app || (app = {}));
47115 (function (controllers) {
47116 var Execution = (function () {
47117 function Execution(MyModal, $scope) {
47118 this.MyModal = MyModal;
47119 this.$scope = $scope;
47120 this.commandInfoList = [];
47123 Execution.prototype.add = function () {
47124 this.$scope.$broadcast('close');
47125 var commandInfoList = this.commandInfoList;
47126 var commandInstance = this.MyModal.selectCommand();
47129 .then(function (command) {
47130 commandInfoList.push(new app.declares.CommandInfo(command));
47133 Execution.prototype.open = function () {
47134 var result = this.MyModal.open('SelectCommand');
47135 console.log(result);
47137 Execution.prototype.remove = function (index, list) {
47138 list.splice(index, 1);
47140 Execution.prototype.close = function () {
47141 console.log("close");
47143 Execution.$inject = ['MyModal', '$scope'];
47146 controllers.Execution = Execution;
47147 })(controllers = app.controllers || (app.controllers = {}));
47148 })(app || (app = {}));
47152 (function (controllers) {
47153 var Workspace = (function () {
47154 function Workspace($scope, APIEndPoint, MyModal) {
47155 this.$scope = $scope;
47156 this.APIEndPoint = APIEndPoint;
47157 this.MyModal = MyModal;
47158 this.directoryList = [];
47159 var controller = this;
47160 var directoryList = this.directoryList;
47162 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
47170 directoryList.push(o);
47172 Workspace.prototype.addDirectory = function (info, directoryList) {
47173 directoryList.push(info);
47175 Workspace.prototype.debug = function () {
47176 this.MyModal.preview();
47178 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
47181 controllers.Workspace = Workspace;
47182 })(controllers = app.controllers || (app.controllers = {}));
47183 })(app || (app = {}));
47187 (function (controllers) {
47188 var History = (function () {
47189 function History($scope) {
47190 this.page = "History";
47192 History.$inject = ['$scope'];
47195 controllers.History = History;
47196 })(controllers = app.controllers || (app.controllers = {}));
47197 })(app || (app = {}));
47201 (function (controllers) {
47202 var SelectCommand = (function () {
47203 function SelectCommand($scope, APIEndPoint, $modalInstance) {
47204 this.APIEndPoint = APIEndPoint;
47205 this.$modalInstance = $modalInstance;
47206 var controller = this;
47209 .$promise.then(function (result) {
47210 controller.tags = result.info;
47214 .$promise.then(function (result) {
47215 controller.commands = result.info;
47217 this.currentTag = 'all';
47219 SelectCommand.prototype.changeTag = function (tag) {
47220 this.currentTag = tag;
47222 SelectCommand.prototype.selectCommand = function (command) {
47223 this.$modalInstance.close(command);
47225 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47226 return SelectCommand;
47228 controllers.SelectCommand = SelectCommand;
47229 })(controllers = app.controllers || (app.controllers = {}));
47230 })(app || (app = {}));
47234 (function (controllers) {
47235 var Preview = (function () {
47236 function Preview($scope, APIEndPoint, $modalInstance) {
47237 this.APIEndPoint = APIEndPoint;
47238 this.$modalInstance = $modalInstance;
47239 var controller = this;
47240 console.log('preview');
47242 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47245 controllers.Preview = Preview;
47246 })(controllers = app.controllers || (app.controllers = {}));
47247 })(app || (app = {}));
47249 (function (filters) {
47251 return function (commands, tag) {
47253 angular.forEach(commands, function (command) {
47255 angular.forEach(command.tags, function (value) {
47260 result.push(command);
47266 })(filters || (filters = {}));
47270 var appName = 'zephyr';
47271 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
47272 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
47273 $urlRouterProvider.otherwise('/execution');
47274 $locationProvider.html5Mode({
47279 .state('execution', {
47281 templateUrl: 'templates/execution.html',
47282 controller: 'executionController',
47285 .state('workspace', {
47287 templateUrl: 'templates/workspace.html',
47288 controller: 'workspaceController',
47291 .state('history', {
47293 templateUrl: 'templates/history.html',
47294 controller: 'historyController',
47298 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
47299 app.zephyr.service('MyModal', app.services.MyModal);
47300 app.zephyr.service('WebSocket', app.services.WebSocket);
47301 app.zephyr.service('Console', app.services.Console);
47302 app.zephyr.filter('Tag', filters.Tag);
47303 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
47304 app.zephyr.controller('previewController', app.controllers.Preview);
47305 app.zephyr.controller('executionController', app.controllers.Execution);
47306 app.zephyr.controller('workspaceController', app.controllers.Workspace);
47307 app.zephyr.controller('historyController', app.controllers.History);
47308 app.zephyr.controller('commandController', app.directives.CommandController);
47309 app.zephyr.controller('optionController', app.directives.OptionController);
47310 app.zephyr.controller('directoryController', app.directives.DirectoryController);
47311 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
47312 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
47313 app.zephyr.directive('command', app.directives.Command.Factory());
47314 app.zephyr.directive('option', app.directives.Option.Factory());
47315 app.zephyr.directive('directory', app.directives.Directory.Factory());
47316 })(app || (app = {}));
47321 /***/ function(module, exports) {
47326 (function (declares) {
47327 var CommandInfo = (function () {
47328 function CommandInfo(name) {
47331 return CommandInfo;
47333 declares.CommandInfo = CommandInfo;
47334 })(declares = app.declares || (app.declares = {}));
47335 })(app || (app = {}));
47339 (function (services) {
47340 var APIEndPoint = (function () {
47341 function APIEndPoint($resource, $http) {
47342 this.$resource = $resource;
47343 this.$http = $http;
47345 APIEndPoint.prototype.resource = function (endPoint, data) {
47346 var customAction = {
47352 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
47354 return this.$resource(endPoint, {}, { execute: execute });
47356 APIEndPoint.prototype.getOptionControlFile = function (command) {
47357 var endPoint = '/api/v1/optionControlFile/' + command;
47358 return this.resource(endPoint, {}).get();
47360 APIEndPoint.prototype.getFiles = function (fileId) {
47361 var endPoint = '/api/v1/workspace';
47363 endPoint += '/' + fileId;
47365 return this.resource(endPoint, {}).get();
47367 APIEndPoint.prototype.getDirectories = function () {
47368 var endPoint = '/api/v1/all/workspace/directory';
47369 return this.resource(endPoint, {}).get();
47371 APIEndPoint.prototype.getTags = function () {
47372 var endPoint = '/api/v1/tagList';
47373 return this.resource(endPoint, {}).get();
47375 APIEndPoint.prototype.getCommands = function () {
47376 var endPoint = '/api/v1/commandList';
47377 return this.resource(endPoint, {}).get();
47379 APIEndPoint.prototype.execute = function (data) {
47380 var endPoint = '/api/v1/execution';
47381 var fd = new FormData();
47382 fd.append('data', data);
47383 return this.$http.post(endPoint, fd, {
47384 headers: { 'Content-Type': undefined },
47385 transformRequest: angular.identity
47388 APIEndPoint.prototype.debug = function () {
47389 var endPoint = '/api/v1/debug';
47390 return this.$http.get(endPoint);
47392 APIEndPoint.prototype.help = function (command) {
47393 var endPoint = '/api/v1/help/' + command;
47394 return this.$http.get(endPoint);
47396 return APIEndPoint;
47398 services.APIEndPoint = APIEndPoint;
47399 })(services = app.services || (app.services = {}));
47400 })(app || (app = {}));
47404 (function (services) {
47405 var MyModal = (function () {
47406 function MyModal($uibModal) {
47407 this.$uibModal = $uibModal;
47408 this.modalOption = {
47415 MyModal.prototype.open = function (modalName) {
47416 if (modalName === 'SelectCommand') {
47417 this.modalOption.templateUrl = 'templates/select-command.html';
47418 this.modalOption.size = 'lg';
47420 return this.$uibModal.open(this.modalOption);
47422 MyModal.prototype.selectCommand = function () {
47423 this.modalOption.templateUrl = 'templates/select-command.html';
47424 this.modalOption.controller = 'selectCommandController';
47425 this.modalOption.controllerAs = 'c';
47426 this.modalOption.size = 'lg';
47427 return this.$uibModal.open(this.modalOption);
47429 MyModal.prototype.preview = function () {
47430 this.modalOption.templateUrl = 'templates/preview.html';
47431 this.modalOption.controller = 'previewController';
47432 this.modalOption.controllerAs = 'c';
47433 this.modalOption.size = 'lg';
47434 return this.$uibModal.open(this.modalOption);
47436 MyModal.$inject = ['$uibModal'];
47439 services.MyModal = MyModal;
47440 })(services = app.services || (app.services = {}));
47441 })(app || (app = {}));
47445 (function (services) {
47446 var WebSocket = (function () {
47447 function WebSocket($rootScope) {
47448 this.$rootScope = $rootScope;
47449 this.socket = io.connect();
47451 WebSocket.prototype.on = function (eventName, callback) {
47452 var socket = this.socket;
47453 var rootScope = this.$rootScope;
47454 socket.on(eventName, function () {
47455 var args = arguments;
47456 rootScope.$apply(function () {
47457 callback.apply(socket, args);
47461 WebSocket.prototype.emit = function (eventName, data, callback) {
47462 var socket = this.socket;
47463 var rootScope = this.$rootScope;
47464 this.socket.emit(eventName, data, function () {
47465 var args = arguments;
47466 rootScope.$apply(function () {
47468 callback.apply(socket, args);
47474 services.WebSocket = WebSocket;
47475 })(services = app.services || (app.services = {}));
47476 })(app || (app = {}));
47480 (function (services) {
47481 var Console = (function () {
47482 function Console(WebSocket, $rootScope) {
47483 this.WebSocket = WebSocket;
47484 this.$rootScope = $rootScope;
47485 this.WebSocket = WebSocket;
47486 this.$rootScope = $rootScope;
47487 this.directiveIDs = [];
47488 var directiveIDs = this.directiveIDs;
47489 this.WebSocket.on('console', function (d) {
47491 var message = d.message;
47492 if (directiveIDs.indexOf(id) > -1) {
47493 $rootScope.$emit(id, message);
47497 Console.prototype.addDirective = function (id) {
47498 if (!(this.directiveIDs.indexOf(id) > -1)) {
47499 this.directiveIDs.push(id);
47502 Console.prototype.removeDirective = function (id) {
47503 var i = this.directiveIDs.indexOf(id);
47505 this.directiveIDs.splice(i, 1);
47508 Console.prototype.showIDs = function () {
47509 console.log(this.directiveIDs);
47513 services.Console = Console;
47514 })(services = app.services || (app.services = {}));
47515 })(app || (app = {}));
47519 (function (directives) {
47520 var Command = (function () {
47521 function Command() {
47522 this.restrict = 'E';
47523 this.replace = true;
47525 this.controller = 'commandController';
47526 this.controllerAs = 'ctrl';
47527 this.bindToController = {
47533 this.templateUrl = 'templates/command.html';
47535 Command.Factory = function () {
47536 var directive = function () {
47537 return new Command();
47539 directive.$inject = [];
47544 directives.Command = Command;
47545 var CommandController = (function () {
47546 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
47547 this.APIEndPoint = APIEndPoint;
47548 this.$scope = $scope;
47549 this.MyModal = MyModal;
47550 this.WebSocket = WebSocket;
47551 this.$window = $window;
47552 this.$rootScope = $rootScope;
47553 this.Console = Console;
47554 var controller = this;
47556 .getOptionControlFile(this.name)
47558 .then(function (result) {
47559 controller.options = result.info;
47564 .then(function (result) {
47565 controller.dirs = result.info;
47567 this.heading = "[" + this.index + "]: dcdFilePrint";
47568 this.isOpen = true;
47569 this.$scope.$on('close', function () {
47570 controller.isOpen = false;
47574 return Math.floor((1 + Math.random()) * 0x10000)
47578 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
47579 s4() + '-' + s4() + s4() + s4();
47581 this.uuid = guid();
47582 this.Console.addDirective(this.uuid);
47583 this.Console.showIDs();
47585 CommandController.prototype.submit = function () {
47587 angular.forEach(this.options, function (option) {
47589 name: option.option,
47592 angular.forEach(option.arg, function (arg) {
47594 if (typeof arg.input === 'object') {
47595 obj.arguments.push(arg.input.name);
47598 obj.arguments.push(arg.input);
47602 if (obj.arguments.length > 0) {
47607 command: this.name,
47608 workspace: this.workspace.fileId,
47612 .execute(JSON.stringify(execObj))
47613 .then(function (result) {
47614 console.log(result);
47617 CommandController.prototype.removeMySelf = function (index) {
47618 this.$scope.$destroy();
47619 this.Console.removeDirective(this.uuid);
47620 this.remove()(index, this.list);
47621 this.Console.showIDs();
47623 CommandController.prototype.reloadFiles = function () {
47625 var fileId = this.workspace.fileId;
47629 .then(function (result) {
47630 var status = result.status;
47631 if (status === 'success') {
47632 _this.files = result.info;
47635 console.log(result.message);
47639 CommandController.prototype.debug = function () {
47640 var div = angular.element(this.$window.document).find("div");
47643 angular.forEach(div, function (v) {
47644 if (v.className === "panel-body console") {
47647 else if (v.className === "row parameters-console") {
47651 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
47652 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
47653 consoleTag.style.height = consoleHeight;
47654 consoleTag.style.width = consoleWidth;
47656 CommandController.prototype.help = function () {
47659 .then(function (result) {
47660 console.log(result);
47663 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
47664 return CommandController;
47666 directives.CommandController = CommandController;
47667 })(directives = app.directives || (app.directives = {}));
47668 })(app || (app = {}));
47672 (function (directives) {
47673 var HeaderMenu = (function () {
47674 function HeaderMenu() {
47675 this.restrict = 'E';
47676 this.replace = true;
47677 this.templateUrl = 'templates/header-menu.html';
47678 this.controller = 'HeaderMenuController';
47679 this.controllerAs = 'hmc';
47682 HeaderMenu.Factory = function () {
47683 var directive = function () {
47684 return new HeaderMenu();
47690 directives.HeaderMenu = HeaderMenu;
47691 var HeaderMenuController = (function () {
47692 function HeaderMenuController($state) {
47693 this.$state = $state;
47694 this.isExecution = this.$state.current.name === 'execution';
47695 this.isWorkspace = this.$state.current.name === 'workspace';
47696 this.isHistory = this.$state.current.name === 'history';
47698 HeaderMenuController.prototype.transit = function (state) {
47699 this.$state.go(state);
47701 HeaderMenuController.$inject = ['$state'];
47702 return HeaderMenuController;
47704 directives.HeaderMenuController = HeaderMenuController;
47705 })(directives = app.directives || (app.directives = {}));
47706 })(app || (app = {}));
47710 (function (directives) {
47711 var Option = (function () {
47712 function Option() {
47713 this.restrict = 'E';
47714 this.replace = true;
47715 this.controller = 'optionController';
47716 this.bindToController = {
47721 this.templateUrl = 'templates/option.html';
47722 this.controllerAs = 'ctrl';
47724 Option.Factory = function () {
47725 var directive = function () {
47726 return new Option();
47728 directive.$inject = [];
47733 directives.Option = Option;
47734 var OptionController = (function () {
47735 function OptionController() {
47736 var controller = this;
47737 angular.forEach(controller.info.arg, function (arg) {
47738 if (arg.initialValue) {
47739 if (arg.formType === 'number') {
47740 arg.input = parseInt(arg.initialValue);
47743 arg.input = arg.initialValue;
47748 OptionController.$inject = [];
47749 return OptionController;
47751 directives.OptionController = OptionController;
47752 })(directives = app.directives || (app.directives = {}));
47753 })(app || (app = {}));
47757 (function (directives) {
47758 var Directory = (function () {
47759 function Directory() {
47760 this.restrict = 'E';
47761 this.replace = true;
47762 this.controller = 'directoryController';
47763 this.controllerAs = 'ctrl';
47764 this.bindToController = {
47770 this.templateUrl = 'templates/directory.html';
47772 Directory.Factory = function () {
47773 var directive = function () {
47774 return new Directory();
47780 directives.Directory = Directory;
47781 var DirectoryController = (function () {
47782 function DirectoryController(APIEndPoint, $scope) {
47783 this.APIEndPoint = APIEndPoint;
47784 this.$scope = $scope;
47785 var controller = this;
47787 .getFiles(this.info.fileId)
47789 .then(function (result) {
47790 if (result.status === 'success') {
47791 controller.files = result.info;
47792 angular.forEach(result.info, function (file) {
47793 if (file.fileType === '0') {
47795 if (controller.info.path === '/') {
47796 o.path = '/' + file.name;
47799 o.path = controller.info.path + '/' + file.name;
47801 controller.add()(o, controller.list);
47808 DirectoryController.$inject = ['APIEndPoint', '$scope'];
47809 return DirectoryController;
47811 directives.DirectoryController = DirectoryController;
47812 })(directives = app.directives || (app.directives = {}));
47813 })(app || (app = {}));
47817 (function (controllers) {
47818 var Execution = (function () {
47819 function Execution(MyModal, $scope) {
47820 this.MyModal = MyModal;
47821 this.$scope = $scope;
47822 this.commandInfoList = [];
47825 Execution.prototype.add = function () {
47826 this.$scope.$broadcast('close');
47827 var commandInfoList = this.commandInfoList;
47828 var commandInstance = this.MyModal.selectCommand();
47831 .then(function (command) {
47832 commandInfoList.push(new app.declares.CommandInfo(command));
47835 Execution.prototype.open = function () {
47836 var result = this.MyModal.open('SelectCommand');
47837 console.log(result);
47839 Execution.prototype.remove = function (index, list) {
47840 list.splice(index, 1);
47842 Execution.prototype.close = function () {
47843 console.log("close");
47845 Execution.$inject = ['MyModal', '$scope'];
47848 controllers.Execution = Execution;
47849 })(controllers = app.controllers || (app.controllers = {}));
47850 })(app || (app = {}));
47854 (function (controllers) {
47855 var Workspace = (function () {
47856 function Workspace($scope, APIEndPoint, MyModal) {
47857 this.$scope = $scope;
47858 this.APIEndPoint = APIEndPoint;
47859 this.MyModal = MyModal;
47860 this.directoryList = [];
47861 var controller = this;
47862 var directoryList = this.directoryList;
47864 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
47872 directoryList.push(o);
47874 Workspace.prototype.addDirectory = function (info, directoryList) {
47875 directoryList.push(info);
47877 Workspace.prototype.debug = function () {
47878 this.MyModal.preview();
47880 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
47883 controllers.Workspace = Workspace;
47884 })(controllers = app.controllers || (app.controllers = {}));
47885 })(app || (app = {}));
47889 (function (controllers) {
47890 var History = (function () {
47891 function History($scope) {
47892 this.page = "History";
47894 History.$inject = ['$scope'];
47897 controllers.History = History;
47898 })(controllers = app.controllers || (app.controllers = {}));
47899 })(app || (app = {}));
47903 (function (controllers) {
47904 var SelectCommand = (function () {
47905 function SelectCommand($scope, APIEndPoint, $modalInstance) {
47906 this.APIEndPoint = APIEndPoint;
47907 this.$modalInstance = $modalInstance;
47908 var controller = this;
47911 .$promise.then(function (result) {
47912 controller.tags = result.info;
47916 .$promise.then(function (result) {
47917 controller.commands = result.info;
47919 this.currentTag = 'all';
47921 SelectCommand.prototype.changeTag = function (tag) {
47922 this.currentTag = tag;
47924 SelectCommand.prototype.selectCommand = function (command) {
47925 this.$modalInstance.close(command);
47927 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47928 return SelectCommand;
47930 controllers.SelectCommand = SelectCommand;
47931 })(controllers = app.controllers || (app.controllers = {}));
47932 })(app || (app = {}));
47936 (function (controllers) {
47937 var Preview = (function () {
47938 function Preview($scope, APIEndPoint, $modalInstance) {
47939 this.APIEndPoint = APIEndPoint;
47940 this.$modalInstance = $modalInstance;
47941 var controller = this;
47942 console.log('preview');
47944 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47947 controllers.Preview = Preview;
47948 })(controllers = app.controllers || (app.controllers = {}));
47949 })(app || (app = {}));
47951 (function (filters) {
47953 return function (commands, tag) {
47955 angular.forEach(commands, function (command) {
47957 angular.forEach(command.tags, function (value) {
47962 result.push(command);
47968 })(filters || (filters = {}));
47972 var appName = 'zephyr';
47973 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
47974 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
47975 $urlRouterProvider.otherwise('/execution');
47976 $locationProvider.html5Mode({
47981 .state('execution', {
47983 templateUrl: 'templates/execution.html',
47984 controller: 'executionController',
47987 .state('workspace', {
47989 templateUrl: 'templates/workspace.html',
47990 controller: 'workspaceController',
47993 .state('history', {
47995 templateUrl: 'templates/history.html',
47996 controller: 'historyController',
48000 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
48001 app.zephyr.service('MyModal', app.services.MyModal);
48002 app.zephyr.service('WebSocket', app.services.WebSocket);
48003 app.zephyr.service('Console', app.services.Console);
48004 app.zephyr.filter('Tag', filters.Tag);
48005 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
48006 app.zephyr.controller('previewController', app.controllers.Preview);
48007 app.zephyr.controller('executionController', app.controllers.Execution);
48008 app.zephyr.controller('workspaceController', app.controllers.Workspace);
48009 app.zephyr.controller('historyController', app.controllers.History);
48010 app.zephyr.controller('commandController', app.directives.CommandController);
48011 app.zephyr.controller('optionController', app.directives.OptionController);
48012 app.zephyr.controller('directoryController', app.directives.DirectoryController);
48013 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
48014 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
48015 app.zephyr.directive('command', app.directives.Command.Factory());
48016 app.zephyr.directive('option', app.directives.Option.Factory());
48017 app.zephyr.directive('directory', app.directives.Directory.Factory());
48018 })(app || (app = {}));
48023 /***/ function(module, exports) {
48028 (function (declares) {
48029 var CommandInfo = (function () {
48030 function CommandInfo(name) {
48033 return CommandInfo;
48035 declares.CommandInfo = CommandInfo;
48036 })(declares = app.declares || (app.declares = {}));
48037 })(app || (app = {}));
48041 (function (services) {
48042 var APIEndPoint = (function () {
48043 function APIEndPoint($resource, $http) {
48044 this.$resource = $resource;
48045 this.$http = $http;
48047 APIEndPoint.prototype.resource = function (endPoint, data) {
48048 var customAction = {
48054 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
48056 return this.$resource(endPoint, {}, { execute: execute });
48058 APIEndPoint.prototype.getOptionControlFile = function (command) {
48059 var endPoint = '/api/v1/optionControlFile/' + command;
48060 return this.resource(endPoint, {}).get();
48062 APIEndPoint.prototype.getFiles = function (fileId) {
48063 var endPoint = '/api/v1/workspace';
48065 endPoint += '/' + fileId;
48067 return this.resource(endPoint, {}).get();
48069 APIEndPoint.prototype.getDirectories = function () {
48070 var endPoint = '/api/v1/all/workspace/directory';
48071 return this.resource(endPoint, {}).get();
48073 APIEndPoint.prototype.getTags = function () {
48074 var endPoint = '/api/v1/tagList';
48075 return this.resource(endPoint, {}).get();
48077 APIEndPoint.prototype.getCommands = function () {
48078 var endPoint = '/api/v1/commandList';
48079 return this.resource(endPoint, {}).get();
48081 APIEndPoint.prototype.execute = function (data) {
48082 var endPoint = '/api/v1/execution';
48083 var fd = new FormData();
48084 fd.append('data', data);
48085 return this.$http.post(endPoint, fd, {
48086 headers: { 'Content-Type': undefined },
48087 transformRequest: angular.identity
48090 APIEndPoint.prototype.debug = function () {
48091 var endPoint = '/api/v1/debug';
48092 return this.$http.get(endPoint);
48094 APIEndPoint.prototype.help = function (command) {
48095 var endPoint = '/api/v1/help/' + command;
48096 return this.$http.get(endPoint);
48098 return APIEndPoint;
48100 services.APIEndPoint = APIEndPoint;
48101 })(services = app.services || (app.services = {}));
48102 })(app || (app = {}));
48106 (function (services) {
48107 var MyModal = (function () {
48108 function MyModal($uibModal) {
48109 this.$uibModal = $uibModal;
48110 this.modalOption = {
48117 MyModal.prototype.open = function (modalName) {
48118 if (modalName === 'SelectCommand') {
48119 this.modalOption.templateUrl = 'templates/select-command.html';
48120 this.modalOption.size = 'lg';
48122 return this.$uibModal.open(this.modalOption);
48124 MyModal.prototype.selectCommand = function () {
48125 this.modalOption.templateUrl = 'templates/select-command.html';
48126 this.modalOption.controller = 'selectCommandController';
48127 this.modalOption.controllerAs = 'c';
48128 this.modalOption.size = 'lg';
48129 return this.$uibModal.open(this.modalOption);
48131 MyModal.prototype.preview = function () {
48132 this.modalOption.templateUrl = 'templates/preview.html';
48133 this.modalOption.controller = 'previewController';
48134 this.modalOption.controllerAs = 'c';
48135 this.modalOption.size = 'lg';
48136 return this.$uibModal.open(this.modalOption);
48138 MyModal.$inject = ['$uibModal'];
48141 services.MyModal = MyModal;
48142 })(services = app.services || (app.services = {}));
48143 })(app || (app = {}));
48147 (function (services) {
48148 var WebSocket = (function () {
48149 function WebSocket($rootScope) {
48150 this.$rootScope = $rootScope;
48151 this.socket = io.connect();
48153 WebSocket.prototype.on = function (eventName, callback) {
48154 var socket = this.socket;
48155 var rootScope = this.$rootScope;
48156 socket.on(eventName, function () {
48157 var args = arguments;
48158 rootScope.$apply(function () {
48159 callback.apply(socket, args);
48163 WebSocket.prototype.emit = function (eventName, data, callback) {
48164 var socket = this.socket;
48165 var rootScope = this.$rootScope;
48166 this.socket.emit(eventName, data, function () {
48167 var args = arguments;
48168 rootScope.$apply(function () {
48170 callback.apply(socket, args);
48176 services.WebSocket = WebSocket;
48177 })(services = app.services || (app.services = {}));
48178 })(app || (app = {}));
48182 (function (services) {
48183 var Console = (function () {
48184 function Console(WebSocket, $rootScope) {
48185 this.WebSocket = WebSocket;
48186 this.$rootScope = $rootScope;
48187 this.WebSocket = WebSocket;
48188 this.$rootScope = $rootScope;
48189 this.directiveIDs = [];
48190 var directiveIDs = this.directiveIDs;
48191 this.WebSocket.on('console', function (d) {
48193 var message = d.message;
48194 if (directiveIDs.indexOf(id) > -1) {
48195 $rootScope.$emit(id, message);
48199 Console.prototype.addDirective = function (id) {
48200 if (!(this.directiveIDs.indexOf(id) > -1)) {
48201 this.directiveIDs.push(id);
48204 Console.prototype.removeDirective = function (id) {
48205 var i = this.directiveIDs.indexOf(id);
48207 this.directiveIDs.splice(i, 1);
48210 Console.prototype.showIDs = function () {
48211 console.log(this.directiveIDs);
48215 services.Console = Console;
48216 })(services = app.services || (app.services = {}));
48217 })(app || (app = {}));
48221 (function (directives) {
48222 var Command = (function () {
48223 function Command() {
48224 this.restrict = 'E';
48225 this.replace = true;
48227 this.controller = 'commandController';
48228 this.controllerAs = 'ctrl';
48229 this.bindToController = {
48235 this.templateUrl = 'templates/command.html';
48237 Command.Factory = function () {
48238 var directive = function () {
48239 return new Command();
48241 directive.$inject = [];
48246 directives.Command = Command;
48247 var CommandController = (function () {
48248 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
48249 this.APIEndPoint = APIEndPoint;
48250 this.$scope = $scope;
48251 this.MyModal = MyModal;
48252 this.WebSocket = WebSocket;
48253 this.$window = $window;
48254 this.$rootScope = $rootScope;
48255 this.Console = Console;
48256 var controller = this;
48258 .getOptionControlFile(this.name)
48260 .then(function (result) {
48261 controller.options = result.info;
48266 .then(function (result) {
48267 controller.dirs = result.info;
48269 this.heading = "[" + this.index + "]: dcdFilePrint";
48270 this.isOpen = true;
48271 this.$scope.$on('close', function () {
48272 controller.isOpen = false;
48276 return Math.floor((1 + Math.random()) * 0x10000)
48280 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
48281 s4() + '-' + s4() + s4() + s4();
48283 this.uuid = guid();
48284 this.Console.addDirective(this.uuid);
48285 this.Console.showIDs();
48287 CommandController.prototype.submit = function () {
48289 angular.forEach(this.options, function (option) {
48291 name: option.option,
48294 angular.forEach(option.arg, function (arg) {
48296 if (typeof arg.input === 'object') {
48297 obj.arguments.push(arg.input.name);
48300 obj.arguments.push(arg.input);
48304 if (obj.arguments.length > 0) {
48309 command: this.name,
48310 workspace: this.workspace.fileId,
48314 .execute(JSON.stringify(execObj))
48315 .then(function (result) {
48316 console.log(result);
48319 CommandController.prototype.removeMySelf = function (index) {
48320 this.$scope.$destroy();
48321 this.Console.removeDirective(this.uuid);
48322 this.remove()(index, this.list);
48323 this.Console.showIDs();
48325 CommandController.prototype.reloadFiles = function () {
48327 var fileId = this.workspace.fileId;
48331 .then(function (result) {
48332 var status = result.status;
48333 if (status === 'success') {
48334 _this.files = result.info;
48337 console.log(result.message);
48341 CommandController.prototype.debug = function () {
48342 var div = angular.element(this.$window.document).find("div");
48345 angular.forEach(div, function (v) {
48346 if (v.className === "panel-body console") {
48349 else if (v.className === "row parameters-console") {
48353 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
48354 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
48355 consoleTag.style.height = consoleHeight;
48356 consoleTag.style.width = consoleWidth;
48358 CommandController.prototype.help = function () {
48361 .then(function (result) {
48362 console.log(result);
48365 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
48366 return CommandController;
48368 directives.CommandController = CommandController;
48369 })(directives = app.directives || (app.directives = {}));
48370 })(app || (app = {}));
48374 (function (directives) {
48375 var HeaderMenu = (function () {
48376 function HeaderMenu() {
48377 this.restrict = 'E';
48378 this.replace = true;
48379 this.templateUrl = 'templates/header-menu.html';
48380 this.controller = 'HeaderMenuController';
48381 this.controllerAs = 'hmc';
48384 HeaderMenu.Factory = function () {
48385 var directive = function () {
48386 return new HeaderMenu();
48392 directives.HeaderMenu = HeaderMenu;
48393 var HeaderMenuController = (function () {
48394 function HeaderMenuController($state) {
48395 this.$state = $state;
48396 this.isExecution = this.$state.current.name === 'execution';
48397 this.isWorkspace = this.$state.current.name === 'workspace';
48398 this.isHistory = this.$state.current.name === 'history';
48400 HeaderMenuController.prototype.transit = function (state) {
48401 this.$state.go(state);
48403 HeaderMenuController.$inject = ['$state'];
48404 return HeaderMenuController;
48406 directives.HeaderMenuController = HeaderMenuController;
48407 })(directives = app.directives || (app.directives = {}));
48408 })(app || (app = {}));
48412 (function (directives) {
48413 var Option = (function () {
48414 function Option() {
48415 this.restrict = 'E';
48416 this.replace = true;
48417 this.controller = 'optionController';
48418 this.bindToController = {
48423 this.templateUrl = 'templates/option.html';
48424 this.controllerAs = 'ctrl';
48426 Option.Factory = function () {
48427 var directive = function () {
48428 return new Option();
48430 directive.$inject = [];
48435 directives.Option = Option;
48436 var OptionController = (function () {
48437 function OptionController() {
48438 var controller = this;
48439 angular.forEach(controller.info.arg, function (arg) {
48440 if (arg.initialValue) {
48441 if (arg.formType === 'number') {
48442 arg.input = parseInt(arg.initialValue);
48445 arg.input = arg.initialValue;
48450 OptionController.$inject = [];
48451 return OptionController;
48453 directives.OptionController = OptionController;
48454 })(directives = app.directives || (app.directives = {}));
48455 })(app || (app = {}));
48459 (function (directives) {
48460 var Directory = (function () {
48461 function Directory() {
48462 this.restrict = 'E';
48463 this.replace = true;
48464 this.controller = 'directoryController';
48465 this.controllerAs = 'ctrl';
48466 this.bindToController = {
48472 this.templateUrl = 'templates/directory.html';
48474 Directory.Factory = function () {
48475 var directive = function () {
48476 return new Directory();
48482 directives.Directory = Directory;
48483 var DirectoryController = (function () {
48484 function DirectoryController(APIEndPoint, $scope) {
48485 this.APIEndPoint = APIEndPoint;
48486 this.$scope = $scope;
48487 var controller = this;
48489 .getFiles(this.info.fileId)
48491 .then(function (result) {
48492 if (result.status === 'success') {
48493 controller.files = result.info;
48494 angular.forEach(result.info, function (file) {
48495 if (file.fileType === '0') {
48497 if (controller.info.path === '/') {
48498 o.path = '/' + file.name;
48501 o.path = controller.info.path + '/' + file.name;
48503 controller.add()(o, controller.list);
48510 DirectoryController.$inject = ['APIEndPoint', '$scope'];
48511 return DirectoryController;
48513 directives.DirectoryController = DirectoryController;
48514 })(directives = app.directives || (app.directives = {}));
48515 })(app || (app = {}));
48519 (function (controllers) {
48520 var Execution = (function () {
48521 function Execution(MyModal, $scope) {
48522 this.MyModal = MyModal;
48523 this.$scope = $scope;
48524 this.commandInfoList = [];
48527 Execution.prototype.add = function () {
48528 this.$scope.$broadcast('close');
48529 var commandInfoList = this.commandInfoList;
48530 var commandInstance = this.MyModal.selectCommand();
48533 .then(function (command) {
48534 commandInfoList.push(new app.declares.CommandInfo(command));
48537 Execution.prototype.open = function () {
48538 var result = this.MyModal.open('SelectCommand');
48539 console.log(result);
48541 Execution.prototype.remove = function (index, list) {
48542 list.splice(index, 1);
48544 Execution.prototype.close = function () {
48545 console.log("close");
48547 Execution.$inject = ['MyModal', '$scope'];
48550 controllers.Execution = Execution;
48551 })(controllers = app.controllers || (app.controllers = {}));
48552 })(app || (app = {}));
48556 (function (controllers) {
48557 var Workspace = (function () {
48558 function Workspace($scope, APIEndPoint, MyModal) {
48559 this.$scope = $scope;
48560 this.APIEndPoint = APIEndPoint;
48561 this.MyModal = MyModal;
48562 this.directoryList = [];
48563 var controller = this;
48564 var directoryList = this.directoryList;
48566 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
48574 directoryList.push(o);
48576 Workspace.prototype.addDirectory = function (info, directoryList) {
48577 directoryList.push(info);
48579 Workspace.prototype.debug = function () {
48580 this.MyModal.preview();
48582 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
48585 controllers.Workspace = Workspace;
48586 })(controllers = app.controllers || (app.controllers = {}));
48587 })(app || (app = {}));
48591 (function (controllers) {
48592 var History = (function () {
48593 function History($scope) {
48594 this.page = "History";
48596 History.$inject = ['$scope'];
48599 controllers.History = History;
48600 })(controllers = app.controllers || (app.controllers = {}));
48601 })(app || (app = {}));
48605 (function (controllers) {
48606 var SelectCommand = (function () {
48607 function SelectCommand($scope, APIEndPoint, $modalInstance) {
48608 this.APIEndPoint = APIEndPoint;
48609 this.$modalInstance = $modalInstance;
48610 var controller = this;
48613 .$promise.then(function (result) {
48614 controller.tags = result.info;
48618 .$promise.then(function (result) {
48619 controller.commands = result.info;
48621 this.currentTag = 'all';
48623 SelectCommand.prototype.changeTag = function (tag) {
48624 this.currentTag = tag;
48626 SelectCommand.prototype.selectCommand = function (command) {
48627 this.$modalInstance.close(command);
48629 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48630 return SelectCommand;
48632 controllers.SelectCommand = SelectCommand;
48633 })(controllers = app.controllers || (app.controllers = {}));
48634 })(app || (app = {}));
48638 (function (controllers) {
48639 var Preview = (function () {
48640 function Preview($scope, APIEndPoint, $modalInstance) {
48641 this.APIEndPoint = APIEndPoint;
48642 this.$modalInstance = $modalInstance;
48643 var controller = this;
48644 console.log('preview');
48646 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48649 controllers.Preview = Preview;
48650 })(controllers = app.controllers || (app.controllers = {}));
48651 })(app || (app = {}));
48653 (function (filters) {
48655 return function (commands, tag) {
48657 angular.forEach(commands, function (command) {
48659 angular.forEach(command.tags, function (value) {
48664 result.push(command);
48670 })(filters || (filters = {}));
48674 var appName = 'zephyr';
48675 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
48676 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
48677 $urlRouterProvider.otherwise('/execution');
48678 $locationProvider.html5Mode({
48683 .state('execution', {
48685 templateUrl: 'templates/execution.html',
48686 controller: 'executionController',
48689 .state('workspace', {
48691 templateUrl: 'templates/workspace.html',
48692 controller: 'workspaceController',
48695 .state('history', {
48697 templateUrl: 'templates/history.html',
48698 controller: 'historyController',
48702 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
48703 app.zephyr.service('MyModal', app.services.MyModal);
48704 app.zephyr.service('WebSocket', app.services.WebSocket);
48705 app.zephyr.service('Console', app.services.Console);
48706 app.zephyr.filter('Tag', filters.Tag);
48707 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
48708 app.zephyr.controller('previewController', app.controllers.Preview);
48709 app.zephyr.controller('executionController', app.controllers.Execution);
48710 app.zephyr.controller('workspaceController', app.controllers.Workspace);
48711 app.zephyr.controller('historyController', app.controllers.History);
48712 app.zephyr.controller('commandController', app.directives.CommandController);
48713 app.zephyr.controller('optionController', app.directives.OptionController);
48714 app.zephyr.controller('directoryController', app.directives.DirectoryController);
48715 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
48716 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
48717 app.zephyr.directive('command', app.directives.Command.Factory());
48718 app.zephyr.directive('option', app.directives.Option.Factory());
48719 app.zephyr.directive('directory', app.directives.Directory.Factory());
48720 })(app || (app = {}));
48725 /***/ function(module, exports) {
48730 (function (declares) {
48731 var CommandInfo = (function () {
48732 function CommandInfo(name) {
48735 return CommandInfo;
48737 declares.CommandInfo = CommandInfo;
48738 })(declares = app.declares || (app.declares = {}));
48739 })(app || (app = {}));
48743 (function (services) {
48744 var APIEndPoint = (function () {
48745 function APIEndPoint($resource, $http) {
48746 this.$resource = $resource;
48747 this.$http = $http;
48749 APIEndPoint.prototype.resource = function (endPoint, data) {
48750 var customAction = {
48756 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
48758 return this.$resource(endPoint, {}, { execute: execute });
48760 APIEndPoint.prototype.getOptionControlFile = function (command) {
48761 var endPoint = '/api/v1/optionControlFile/' + command;
48762 return this.resource(endPoint, {}).get();
48764 APIEndPoint.prototype.getFiles = function (fileId) {
48765 var endPoint = '/api/v1/workspace';
48767 endPoint += '/' + fileId;
48769 return this.resource(endPoint, {}).get();
48771 APIEndPoint.prototype.getDirectories = function () {
48772 var endPoint = '/api/v1/all/workspace/directory';
48773 return this.resource(endPoint, {}).get();
48775 APIEndPoint.prototype.getTags = function () {
48776 var endPoint = '/api/v1/tagList';
48777 return this.resource(endPoint, {}).get();
48779 APIEndPoint.prototype.getCommands = function () {
48780 var endPoint = '/api/v1/commandList';
48781 return this.resource(endPoint, {}).get();
48783 APIEndPoint.prototype.execute = function (data) {
48784 var endPoint = '/api/v1/execution';
48785 var fd = new FormData();
48786 fd.append('data', data);
48787 return this.$http.post(endPoint, fd, {
48788 headers: { 'Content-Type': undefined },
48789 transformRequest: angular.identity
48792 APIEndPoint.prototype.debug = function () {
48793 var endPoint = '/api/v1/debug';
48794 return this.$http.get(endPoint);
48796 APIEndPoint.prototype.help = function (command) {
48797 var endPoint = '/api/v1/help/' + command;
48798 return this.$http.get(endPoint);
48800 return APIEndPoint;
48802 services.APIEndPoint = APIEndPoint;
48803 })(services = app.services || (app.services = {}));
48804 })(app || (app = {}));
48808 (function (services) {
48809 var MyModal = (function () {
48810 function MyModal($uibModal) {
48811 this.$uibModal = $uibModal;
48812 this.modalOption = {
48819 MyModal.prototype.open = function (modalName) {
48820 if (modalName === 'SelectCommand') {
48821 this.modalOption.templateUrl = 'templates/select-command.html';
48822 this.modalOption.size = 'lg';
48824 return this.$uibModal.open(this.modalOption);
48826 MyModal.prototype.selectCommand = function () {
48827 this.modalOption.templateUrl = 'templates/select-command.html';
48828 this.modalOption.controller = 'selectCommandController';
48829 this.modalOption.controllerAs = 'c';
48830 this.modalOption.size = 'lg';
48831 return this.$uibModal.open(this.modalOption);
48833 MyModal.prototype.preview = function () {
48834 this.modalOption.templateUrl = 'templates/preview.html';
48835 this.modalOption.controller = 'previewController';
48836 this.modalOption.controllerAs = 'c';
48837 this.modalOption.size = 'lg';
48838 return this.$uibModal.open(this.modalOption);
48840 MyModal.$inject = ['$uibModal'];
48843 services.MyModal = MyModal;
48844 })(services = app.services || (app.services = {}));
48845 })(app || (app = {}));
48849 (function (services) {
48850 var WebSocket = (function () {
48851 function WebSocket($rootScope) {
48852 this.$rootScope = $rootScope;
48853 this.socket = io.connect();
48855 WebSocket.prototype.on = function (eventName, callback) {
48856 var socket = this.socket;
48857 var rootScope = this.$rootScope;
48858 socket.on(eventName, function () {
48859 var args = arguments;
48860 rootScope.$apply(function () {
48861 callback.apply(socket, args);
48865 WebSocket.prototype.emit = function (eventName, data, callback) {
48866 var socket = this.socket;
48867 var rootScope = this.$rootScope;
48868 this.socket.emit(eventName, data, function () {
48869 var args = arguments;
48870 rootScope.$apply(function () {
48872 callback.apply(socket, args);
48878 services.WebSocket = WebSocket;
48879 })(services = app.services || (app.services = {}));
48880 })(app || (app = {}));
48884 (function (services) {
48885 var Console = (function () {
48886 function Console(WebSocket, $rootScope) {
48887 this.WebSocket = WebSocket;
48888 this.$rootScope = $rootScope;
48889 this.WebSocket = WebSocket;
48890 this.$rootScope = $rootScope;
48891 this.directiveIDs = [];
48892 var directiveIDs = this.directiveIDs;
48893 this.WebSocket.on('console', function (d) {
48895 var message = d.message;
48896 if (directiveIDs.indexOf(id) > -1) {
48897 $rootScope.$emit(id, message);
48901 Console.prototype.addDirective = function (id) {
48902 if (!(this.directiveIDs.indexOf(id) > -1)) {
48903 this.directiveIDs.push(id);
48906 Console.prototype.removeDirective = function (id) {
48907 var i = this.directiveIDs.indexOf(id);
48909 this.directiveIDs.splice(i, 1);
48912 Console.prototype.showIDs = function () {
48913 console.log(this.directiveIDs);
48917 services.Console = Console;
48918 })(services = app.services || (app.services = {}));
48919 })(app || (app = {}));
48923 (function (directives) {
48924 var Command = (function () {
48925 function Command() {
48926 this.restrict = 'E';
48927 this.replace = true;
48929 this.controller = 'commandController';
48930 this.controllerAs = 'ctrl';
48931 this.bindToController = {
48937 this.templateUrl = 'templates/command.html';
48939 Command.Factory = function () {
48940 var directive = function () {
48941 return new Command();
48943 directive.$inject = [];
48948 directives.Command = Command;
48949 var CommandController = (function () {
48950 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
48951 this.APIEndPoint = APIEndPoint;
48952 this.$scope = $scope;
48953 this.MyModal = MyModal;
48954 this.WebSocket = WebSocket;
48955 this.$window = $window;
48956 this.$rootScope = $rootScope;
48957 this.Console = Console;
48958 var controller = this;
48960 .getOptionControlFile(this.name)
48962 .then(function (result) {
48963 controller.options = result.info;
48968 .then(function (result) {
48969 controller.dirs = result.info;
48971 this.heading = "[" + this.index + "]: dcdFilePrint";
48972 this.isOpen = true;
48973 this.$scope.$on('close', function () {
48974 controller.isOpen = false;
48978 return Math.floor((1 + Math.random()) * 0x10000)
48982 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
48983 s4() + '-' + s4() + s4() + s4();
48985 this.uuid = guid();
48986 this.Console.addDirective(this.uuid);
48987 this.Console.showIDs();
48989 CommandController.prototype.submit = function () {
48991 angular.forEach(this.options, function (option) {
48993 name: option.option,
48996 angular.forEach(option.arg, function (arg) {
48998 if (typeof arg.input === 'object') {
48999 obj.arguments.push(arg.input.name);
49002 obj.arguments.push(arg.input);
49006 if (obj.arguments.length > 0) {
49011 command: this.name,
49012 workspace: this.workspace.fileId,
49016 .execute(JSON.stringify(execObj))
49017 .then(function (result) {
49018 console.log(result);
49021 CommandController.prototype.removeMySelf = function (index) {
49022 this.$scope.$destroy();
49023 this.Console.removeDirective(this.uuid);
49024 this.remove()(index, this.list);
49025 this.Console.showIDs();
49027 CommandController.prototype.reloadFiles = function () {
49029 var fileId = this.workspace.fileId;
49033 .then(function (result) {
49034 var status = result.status;
49035 if (status === 'success') {
49036 _this.files = result.info;
49039 console.log(result.message);
49043 CommandController.prototype.debug = function () {
49044 var div = angular.element(this.$window.document).find("div");
49047 angular.forEach(div, function (v) {
49048 if (v.className === "panel-body console") {
49051 else if (v.className === "row parameters-console") {
49055 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
49056 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
49057 consoleTag.style.height = consoleHeight;
49058 consoleTag.style.width = consoleWidth;
49060 CommandController.prototype.help = function () {
49063 .then(function (result) {
49064 console.log(result);
49067 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
49068 return CommandController;
49070 directives.CommandController = CommandController;
49071 })(directives = app.directives || (app.directives = {}));
49072 })(app || (app = {}));
49076 (function (directives) {
49077 var HeaderMenu = (function () {
49078 function HeaderMenu() {
49079 this.restrict = 'E';
49080 this.replace = true;
49081 this.templateUrl = 'templates/header-menu.html';
49082 this.controller = 'HeaderMenuController';
49083 this.controllerAs = 'hmc';
49086 HeaderMenu.Factory = function () {
49087 var directive = function () {
49088 return new HeaderMenu();
49094 directives.HeaderMenu = HeaderMenu;
49095 var HeaderMenuController = (function () {
49096 function HeaderMenuController($state) {
49097 this.$state = $state;
49098 this.isExecution = this.$state.current.name === 'execution';
49099 this.isWorkspace = this.$state.current.name === 'workspace';
49100 this.isHistory = this.$state.current.name === 'history';
49102 HeaderMenuController.prototype.transit = function (state) {
49103 this.$state.go(state);
49105 HeaderMenuController.$inject = ['$state'];
49106 return HeaderMenuController;
49108 directives.HeaderMenuController = HeaderMenuController;
49109 })(directives = app.directives || (app.directives = {}));
49110 })(app || (app = {}));
49114 (function (directives) {
49115 var Option = (function () {
49116 function Option() {
49117 this.restrict = 'E';
49118 this.replace = true;
49119 this.controller = 'optionController';
49120 this.bindToController = {
49125 this.templateUrl = 'templates/option.html';
49126 this.controllerAs = 'ctrl';
49128 Option.Factory = function () {
49129 var directive = function () {
49130 return new Option();
49132 directive.$inject = [];
49137 directives.Option = Option;
49138 var OptionController = (function () {
49139 function OptionController() {
49140 var controller = this;
49141 angular.forEach(controller.info.arg, function (arg) {
49142 if (arg.initialValue) {
49143 if (arg.formType === 'number') {
49144 arg.input = parseInt(arg.initialValue);
49147 arg.input = arg.initialValue;
49152 OptionController.$inject = [];
49153 return OptionController;
49155 directives.OptionController = OptionController;
49156 })(directives = app.directives || (app.directives = {}));
49157 })(app || (app = {}));
49161 (function (directives) {
49162 var Directory = (function () {
49163 function Directory() {
49164 this.restrict = 'E';
49165 this.replace = true;
49166 this.controller = 'directoryController';
49167 this.controllerAs = 'ctrl';
49168 this.bindToController = {
49174 this.templateUrl = 'templates/directory.html';
49176 Directory.Factory = function () {
49177 var directive = function () {
49178 return new Directory();
49184 directives.Directory = Directory;
49185 var DirectoryController = (function () {
49186 function DirectoryController(APIEndPoint, $scope) {
49187 this.APIEndPoint = APIEndPoint;
49188 this.$scope = $scope;
49189 var controller = this;
49191 .getFiles(this.info.fileId)
49193 .then(function (result) {
49194 if (result.status === 'success') {
49195 controller.files = result.info;
49196 angular.forEach(result.info, function (file) {
49197 if (file.fileType === '0') {
49199 if (controller.info.path === '/') {
49200 o.path = '/' + file.name;
49203 o.path = controller.info.path + '/' + file.name;
49205 controller.add()(o, controller.list);
49212 DirectoryController.$inject = ['APIEndPoint', '$scope'];
49213 return DirectoryController;
49215 directives.DirectoryController = DirectoryController;
49216 })(directives = app.directives || (app.directives = {}));
49217 })(app || (app = {}));
49221 (function (controllers) {
49222 var Execution = (function () {
49223 function Execution(MyModal, $scope) {
49224 this.MyModal = MyModal;
49225 this.$scope = $scope;
49226 this.commandInfoList = [];
49229 Execution.prototype.add = function () {
49230 this.$scope.$broadcast('close');
49231 var commandInfoList = this.commandInfoList;
49232 var commandInstance = this.MyModal.selectCommand();
49235 .then(function (command) {
49236 commandInfoList.push(new app.declares.CommandInfo(command));
49239 Execution.prototype.open = function () {
49240 var result = this.MyModal.open('SelectCommand');
49241 console.log(result);
49243 Execution.prototype.remove = function (index, list) {
49244 list.splice(index, 1);
49246 Execution.prototype.close = function () {
49247 console.log("close");
49249 Execution.$inject = ['MyModal', '$scope'];
49252 controllers.Execution = Execution;
49253 })(controllers = app.controllers || (app.controllers = {}));
49254 })(app || (app = {}));
49258 (function (controllers) {
49259 var Workspace = (function () {
49260 function Workspace($scope, APIEndPoint, MyModal) {
49261 this.$scope = $scope;
49262 this.APIEndPoint = APIEndPoint;
49263 this.MyModal = MyModal;
49264 this.directoryList = [];
49265 var controller = this;
49266 var directoryList = this.directoryList;
49268 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
49276 directoryList.push(o);
49278 Workspace.prototype.addDirectory = function (info, directoryList) {
49279 directoryList.push(info);
49281 Workspace.prototype.debug = function () {
49282 this.MyModal.preview();
49284 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
49287 controllers.Workspace = Workspace;
49288 })(controllers = app.controllers || (app.controllers = {}));
49289 })(app || (app = {}));
49293 (function (controllers) {
49294 var History = (function () {
49295 function History($scope) {
49296 this.page = "History";
49298 History.$inject = ['$scope'];
49301 controllers.History = History;
49302 })(controllers = app.controllers || (app.controllers = {}));
49303 })(app || (app = {}));
49307 (function (controllers) {
49308 var SelectCommand = (function () {
49309 function SelectCommand($scope, APIEndPoint, $modalInstance) {
49310 this.APIEndPoint = APIEndPoint;
49311 this.$modalInstance = $modalInstance;
49312 var controller = this;
49315 .$promise.then(function (result) {
49316 controller.tags = result.info;
49320 .$promise.then(function (result) {
49321 controller.commands = result.info;
49323 this.currentTag = 'all';
49325 SelectCommand.prototype.changeTag = function (tag) {
49326 this.currentTag = tag;
49328 SelectCommand.prototype.selectCommand = function (command) {
49329 this.$modalInstance.close(command);
49331 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
49332 return SelectCommand;
49334 controllers.SelectCommand = SelectCommand;
49335 })(controllers = app.controllers || (app.controllers = {}));
49336 })(app || (app = {}));
49340 (function (controllers) {
49341 var Preview = (function () {
49342 function Preview($scope, APIEndPoint, $modalInstance) {
49343 this.APIEndPoint = APIEndPoint;
49344 this.$modalInstance = $modalInstance;
49345 var controller = this;
49346 console.log('preview');
49348 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
49351 controllers.Preview = Preview;
49352 })(controllers = app.controllers || (app.controllers = {}));
49353 })(app || (app = {}));
49355 (function (filters) {
49357 return function (commands, tag) {
49359 angular.forEach(commands, function (command) {
49361 angular.forEach(command.tags, function (value) {
49366 result.push(command);
49372 })(filters || (filters = {}));
49376 var appName = 'zephyr';
49377 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
49378 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
49379 $urlRouterProvider.otherwise('/execution');
49380 $locationProvider.html5Mode({
49385 .state('execution', {
49387 templateUrl: 'templates/execution.html',
49388 controller: 'executionController',
49391 .state('workspace', {
49393 templateUrl: 'templates/workspace.html',
49394 controller: 'workspaceController',
49397 .state('history', {
49399 templateUrl: 'templates/history.html',
49400 controller: 'historyController',
49404 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
49405 app.zephyr.service('MyModal', app.services.MyModal);
49406 app.zephyr.service('WebSocket', app.services.WebSocket);
49407 app.zephyr.service('Console', app.services.Console);
49408 app.zephyr.filter('Tag', filters.Tag);
49409 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
49410 app.zephyr.controller('previewController', app.controllers.Preview);
49411 app.zephyr.controller('executionController', app.controllers.Execution);
49412 app.zephyr.controller('workspaceController', app.controllers.Workspace);
49413 app.zephyr.controller('historyController', app.controllers.History);
49414 app.zephyr.controller('commandController', app.directives.CommandController);
49415 app.zephyr.controller('optionController', app.directives.OptionController);
49416 app.zephyr.controller('directoryController', app.directives.DirectoryController);
49417 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
49418 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
49419 app.zephyr.directive('command', app.directives.Command.Factory());
49420 app.zephyr.directive('option', app.directives.Option.Factory());
49421 app.zephyr.directive('directory', app.directives.Directory.Factory());
49422 })(app || (app = {}));
49427 /***/ function(module, exports) {
49432 (function (declares) {
49433 var CommandInfo = (function () {
49434 function CommandInfo(name) {
49437 return CommandInfo;
49439 declares.CommandInfo = CommandInfo;
49440 })(declares = app.declares || (app.declares = {}));
49441 })(app || (app = {}));
49445 (function (services) {
49446 var APIEndPoint = (function () {
49447 function APIEndPoint($resource, $http) {
49448 this.$resource = $resource;
49449 this.$http = $http;
49451 APIEndPoint.prototype.resource = function (endPoint, data) {
49452 var customAction = {
49458 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
49460 return this.$resource(endPoint, {}, { execute: execute });
49462 APIEndPoint.prototype.getOptionControlFile = function (command) {
49463 var endPoint = '/api/v1/optionControlFile/' + command;
49464 return this.resource(endPoint, {}).get();
49466 APIEndPoint.prototype.getFiles = function (fileId) {
49467 var endPoint = '/api/v1/workspace';
49469 endPoint += '/' + fileId;
49471 return this.resource(endPoint, {}).get();
49473 APIEndPoint.prototype.getDirectories = function () {
49474 var endPoint = '/api/v1/all/workspace/directory';
49475 return this.resource(endPoint, {}).get();
49477 APIEndPoint.prototype.getTags = function () {
49478 var endPoint = '/api/v1/tagList';
49479 return this.resource(endPoint, {}).get();
49481 APIEndPoint.prototype.getCommands = function () {
49482 var endPoint = '/api/v1/commandList';
49483 return this.resource(endPoint, {}).get();
49485 APIEndPoint.prototype.execute = function (data) {
49486 var endPoint = '/api/v1/execution';
49487 var fd = new FormData();
49488 fd.append('data', data);
49489 return this.$http.post(endPoint, fd, {
49490 headers: { 'Content-Type': undefined },
49491 transformRequest: angular.identity
49494 APIEndPoint.prototype.debug = function () {
49495 var endPoint = '/api/v1/debug';
49496 return this.$http.get(endPoint);
49498 APIEndPoint.prototype.help = function (command) {
49499 var endPoint = '/api/v1/help/' + command;
49500 return this.$http.get(endPoint);
49502 return APIEndPoint;
49504 services.APIEndPoint = APIEndPoint;
49505 })(services = app.services || (app.services = {}));
49506 })(app || (app = {}));
49510 (function (services) {
49511 var MyModal = (function () {
49512 function MyModal($uibModal) {
49513 this.$uibModal = $uibModal;
49514 this.modalOption = {
49521 MyModal.prototype.open = function (modalName) {
49522 if (modalName === 'SelectCommand') {
49523 this.modalOption.templateUrl = 'templates/select-command.html';
49524 this.modalOption.size = 'lg';
49526 return this.$uibModal.open(this.modalOption);
49528 MyModal.prototype.selectCommand = function () {
49529 this.modalOption.templateUrl = 'templates/select-command.html';
49530 this.modalOption.controller = 'selectCommandController';
49531 this.modalOption.controllerAs = 'c';
49532 this.modalOption.size = 'lg';
49533 return this.$uibModal.open(this.modalOption);
49535 MyModal.prototype.preview = function () {
49536 this.modalOption.templateUrl = 'templates/preview.html';
49537 this.modalOption.controller = 'previewController';
49538 this.modalOption.controllerAs = 'c';
49539 this.modalOption.size = 'lg';
49540 return this.$uibModal.open(this.modalOption);
49542 MyModal.$inject = ['$uibModal'];
49545 services.MyModal = MyModal;
49546 })(services = app.services || (app.services = {}));
49547 })(app || (app = {}));
49551 (function (services) {
49552 var WebSocket = (function () {
49553 function WebSocket($rootScope) {
49554 this.$rootScope = $rootScope;
49555 this.socket = io.connect();
49557 WebSocket.prototype.on = function (eventName, callback) {
49558 var socket = this.socket;
49559 var rootScope = this.$rootScope;
49560 socket.on(eventName, function () {
49561 var args = arguments;
49562 rootScope.$apply(function () {
49563 callback.apply(socket, args);
49567 WebSocket.prototype.emit = function (eventName, data, callback) {
49568 var socket = this.socket;
49569 var rootScope = this.$rootScope;
49570 this.socket.emit(eventName, data, function () {
49571 var args = arguments;
49572 rootScope.$apply(function () {
49574 callback.apply(socket, args);
49580 services.WebSocket = WebSocket;
49581 })(services = app.services || (app.services = {}));
49582 })(app || (app = {}));
49586 (function (services) {
49587 var Console = (function () {
49588 function Console(WebSocket, $rootScope) {
49589 this.WebSocket = WebSocket;
49590 this.$rootScope = $rootScope;
49591 this.WebSocket = WebSocket;
49592 this.$rootScope = $rootScope;
49593 this.directiveIDs = [];
49594 var directiveIDs = this.directiveIDs;
49595 this.WebSocket.on('console', function (d) {
49597 var message = d.message;
49598 if (directiveIDs.indexOf(id) > -1) {
49599 $rootScope.$emit(id, message);
49603 Console.prototype.addDirective = function (id) {
49604 if (!(this.directiveIDs.indexOf(id) > -1)) {
49605 this.directiveIDs.push(id);
49608 Console.prototype.removeDirective = function (id) {
49609 var i = this.directiveIDs.indexOf(id);
49611 this.directiveIDs.splice(i, 1);
49614 Console.prototype.showIDs = function () {
49615 console.log(this.directiveIDs);
49619 services.Console = Console;
49620 })(services = app.services || (app.services = {}));
49621 })(app || (app = {}));
49625 (function (directives) {
49626 var Command = (function () {
49627 function Command() {
49628 this.restrict = 'E';
49629 this.replace = true;
49631 this.controller = 'commandController';
49632 this.controllerAs = 'ctrl';
49633 this.bindToController = {
49639 this.templateUrl = 'templates/command.html';
49641 Command.Factory = function () {
49642 var directive = function () {
49643 return new Command();
49645 directive.$inject = [];
49650 directives.Command = Command;
49651 var CommandController = (function () {
49652 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
49653 this.APIEndPoint = APIEndPoint;
49654 this.$scope = $scope;
49655 this.MyModal = MyModal;
49656 this.WebSocket = WebSocket;
49657 this.$window = $window;
49658 this.$rootScope = $rootScope;
49659 this.Console = Console;
49660 var controller = this;
49662 .getOptionControlFile(this.name)
49664 .then(function (result) {
49665 controller.options = result.info;
49670 .then(function (result) {
49671 controller.dirs = result.info;
49673 this.heading = "[" + this.index + "]: dcdFilePrint";
49674 this.isOpen = true;
49675 this.$scope.$on('close', function () {
49676 controller.isOpen = false;
49680 return Math.floor((1 + Math.random()) * 0x10000)
49684 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
49685 s4() + '-' + s4() + s4() + s4();
49687 this.uuid = guid();
49688 this.Console.addDirective(this.uuid);
49689 this.Console.showIDs();
49691 CommandController.prototype.submit = function () {
49693 angular.forEach(this.options, function (option) {
49695 name: option.option,
49698 angular.forEach(option.arg, function (arg) {
49700 if (typeof arg.input === 'object') {
49701 obj.arguments.push(arg.input.name);
49704 obj.arguments.push(arg.input);
49708 if (obj.arguments.length > 0) {
49713 command: this.name,
49714 workspace: this.workspace.fileId,
49718 .execute(JSON.stringify(execObj))
49719 .then(function (result) {
49720 console.log(result);
49723 CommandController.prototype.removeMySelf = function (index) {
49724 this.$scope.$destroy();
49725 this.Console.removeDirective(this.uuid);
49726 this.remove()(index, this.list);
49727 this.Console.showIDs();
49729 CommandController.prototype.reloadFiles = function () {
49731 var fileId = this.workspace.fileId;
49735 .then(function (result) {
49736 var status = result.status;
49737 if (status === 'success') {
49738 _this.files = result.info;
49741 console.log(result.message);
49745 CommandController.prototype.debug = function () {
49746 var div = angular.element(this.$window.document).find("div");
49749 angular.forEach(div, function (v) {
49750 if (v.className === "panel-body console") {
49753 else if (v.className === "row parameters-console") {
49757 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
49758 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
49759 consoleTag.style.height = consoleHeight;
49760 consoleTag.style.width = consoleWidth;
49762 CommandController.prototype.help = function () {
49765 .then(function (result) {
49766 console.log(result);
49769 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
49770 return CommandController;
49772 directives.CommandController = CommandController;
49773 })(directives = app.directives || (app.directives = {}));
49774 })(app || (app = {}));
49778 (function (directives) {
49779 var HeaderMenu = (function () {
49780 function HeaderMenu() {
49781 this.restrict = 'E';
49782 this.replace = true;
49783 this.templateUrl = 'templates/header-menu.html';
49784 this.controller = 'HeaderMenuController';
49785 this.controllerAs = 'hmc';
49788 HeaderMenu.Factory = function () {
49789 var directive = function () {
49790 return new HeaderMenu();
49796 directives.HeaderMenu = HeaderMenu;
49797 var HeaderMenuController = (function () {
49798 function HeaderMenuController($state) {
49799 this.$state = $state;
49800 this.isExecution = this.$state.current.name === 'execution';
49801 this.isWorkspace = this.$state.current.name === 'workspace';
49802 this.isHistory = this.$state.current.name === 'history';
49804 HeaderMenuController.prototype.transit = function (state) {
49805 this.$state.go(state);
49807 HeaderMenuController.$inject = ['$state'];
49808 return HeaderMenuController;
49810 directives.HeaderMenuController = HeaderMenuController;
49811 })(directives = app.directives || (app.directives = {}));
49812 })(app || (app = {}));
49816 (function (directives) {
49817 var Option = (function () {
49818 function Option() {
49819 this.restrict = 'E';
49820 this.replace = true;
49821 this.controller = 'optionController';
49822 this.bindToController = {
49827 this.templateUrl = 'templates/option.html';
49828 this.controllerAs = 'ctrl';
49830 Option.Factory = function () {
49831 var directive = function () {
49832 return new Option();
49834 directive.$inject = [];
49839 directives.Option = Option;
49840 var OptionController = (function () {
49841 function OptionController() {
49842 var controller = this;
49843 angular.forEach(controller.info.arg, function (arg) {
49844 if (arg.initialValue) {
49845 if (arg.formType === 'number') {
49846 arg.input = parseInt(arg.initialValue);
49849 arg.input = arg.initialValue;
49854 OptionController.$inject = [];
49855 return OptionController;
49857 directives.OptionController = OptionController;
49858 })(directives = app.directives || (app.directives = {}));
49859 })(app || (app = {}));
49863 (function (directives) {
49864 var Directory = (function () {
49865 function Directory() {
49866 this.restrict = 'E';
49867 this.replace = true;
49868 this.controller = 'directoryController';
49869 this.controllerAs = 'ctrl';
49870 this.bindToController = {
49876 this.templateUrl = 'templates/directory.html';
49878 Directory.Factory = function () {
49879 var directive = function () {
49880 return new Directory();
49886 directives.Directory = Directory;
49887 var DirectoryController = (function () {
49888 function DirectoryController(APIEndPoint, $scope) {
49889 this.APIEndPoint = APIEndPoint;
49890 this.$scope = $scope;
49891 var controller = this;
49893 .getFiles(this.info.fileId)
49895 .then(function (result) {
49896 if (result.status === 'success') {
49897 controller.files = result.info;
49898 angular.forEach(result.info, function (file) {
49899 if (file.fileType === '0') {
49901 if (controller.info.path === '/') {
49902 o.path = '/' + file.name;
49905 o.path = controller.info.path + '/' + file.name;
49907 controller.add()(o, controller.list);
49914 DirectoryController.$inject = ['APIEndPoint', '$scope'];
49915 return DirectoryController;
49917 directives.DirectoryController = DirectoryController;
49918 })(directives = app.directives || (app.directives = {}));
49919 })(app || (app = {}));
49923 (function (controllers) {
49924 var Execution = (function () {
49925 function Execution(MyModal, $scope) {
49926 this.MyModal = MyModal;
49927 this.$scope = $scope;
49928 this.commandInfoList = [];
49931 Execution.prototype.add = function () {
49932 this.$scope.$broadcast('close');
49933 var commandInfoList = this.commandInfoList;
49934 var commandInstance = this.MyModal.selectCommand();
49937 .then(function (command) {
49938 commandInfoList.push(new app.declares.CommandInfo(command));
49941 Execution.prototype.open = function () {
49942 var result = this.MyModal.open('SelectCommand');
49943 console.log(result);
49945 Execution.prototype.remove = function (index, list) {
49946 list.splice(index, 1);
49948 Execution.prototype.close = function () {
49949 console.log("close");
49951 Execution.$inject = ['MyModal', '$scope'];
49954 controllers.Execution = Execution;
49955 })(controllers = app.controllers || (app.controllers = {}));
49956 })(app || (app = {}));
49960 (function (controllers) {
49961 var Workspace = (function () {
49962 function Workspace($scope, APIEndPoint, MyModal) {
49963 this.$scope = $scope;
49964 this.APIEndPoint = APIEndPoint;
49965 this.MyModal = MyModal;
49966 this.directoryList = [];
49967 var controller = this;
49968 var directoryList = this.directoryList;
49970 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
49978 directoryList.push(o);
49980 Workspace.prototype.addDirectory = function (info, directoryList) {
49981 directoryList.push(info);
49983 Workspace.prototype.debug = function () {
49984 this.MyModal.preview();
49986 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
49989 controllers.Workspace = Workspace;
49990 })(controllers = app.controllers || (app.controllers = {}));
49991 })(app || (app = {}));
49995 (function (controllers) {
49996 var History = (function () {
49997 function History($scope) {
49998 this.page = "History";
50000 History.$inject = ['$scope'];
50003 controllers.History = History;
50004 })(controllers = app.controllers || (app.controllers = {}));
50005 })(app || (app = {}));
50009 (function (controllers) {
50010 var SelectCommand = (function () {
50011 function SelectCommand($scope, APIEndPoint, $modalInstance) {
50012 this.APIEndPoint = APIEndPoint;
50013 this.$modalInstance = $modalInstance;
50014 var controller = this;
50017 .$promise.then(function (result) {
50018 controller.tags = result.info;
50022 .$promise.then(function (result) {
50023 controller.commands = result.info;
50025 this.currentTag = 'all';
50027 SelectCommand.prototype.changeTag = function (tag) {
50028 this.currentTag = tag;
50030 SelectCommand.prototype.selectCommand = function (command) {
50031 this.$modalInstance.close(command);
50033 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
50034 return SelectCommand;
50036 controllers.SelectCommand = SelectCommand;
50037 })(controllers = app.controllers || (app.controllers = {}));
50038 })(app || (app = {}));
50042 (function (controllers) {
50043 var Preview = (function () {
50044 function Preview($scope, APIEndPoint, $modalInstance) {
50045 this.APIEndPoint = APIEndPoint;
50046 this.$modalInstance = $modalInstance;
50047 var controller = this;
50048 console.log('preview');
50050 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
50053 controllers.Preview = Preview;
50054 })(controllers = app.controllers || (app.controllers = {}));
50055 })(app || (app = {}));
50057 (function (filters) {
50059 return function (commands, tag) {
50061 angular.forEach(commands, function (command) {
50063 angular.forEach(command.tags, function (value) {
50068 result.push(command);
50074 })(filters || (filters = {}));
50078 var appName = 'zephyr';
50079 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
50080 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
50081 $urlRouterProvider.otherwise('/execution');
50082 $locationProvider.html5Mode({
50087 .state('execution', {
50089 templateUrl: 'templates/execution.html',
50090 controller: 'executionController',
50093 .state('workspace', {
50095 templateUrl: 'templates/workspace.html',
50096 controller: 'workspaceController',
50099 .state('history', {
50101 templateUrl: 'templates/history.html',
50102 controller: 'historyController',
50106 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
50107 app.zephyr.service('MyModal', app.services.MyModal);
50108 app.zephyr.service('WebSocket', app.services.WebSocket);
50109 app.zephyr.service('Console', app.services.Console);
50110 app.zephyr.filter('Tag', filters.Tag);
50111 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
50112 app.zephyr.controller('previewController', app.controllers.Preview);
50113 app.zephyr.controller('executionController', app.controllers.Execution);
50114 app.zephyr.controller('workspaceController', app.controllers.Workspace);
50115 app.zephyr.controller('historyController', app.controllers.History);
50116 app.zephyr.controller('commandController', app.directives.CommandController);
50117 app.zephyr.controller('optionController', app.directives.OptionController);
50118 app.zephyr.controller('directoryController', app.directives.DirectoryController);
50119 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
50120 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
50121 app.zephyr.directive('command', app.directives.Command.Factory());
50122 app.zephyr.directive('option', app.directives.Option.Factory());
50123 app.zephyr.directive('directory', app.directives.Directory.Factory());
50124 })(app || (app = {}));
50129 /***/ function(module, exports) {
50134 (function (declares) {
50135 var CommandInfo = (function () {
50136 function CommandInfo(name) {
50139 return CommandInfo;
50141 declares.CommandInfo = CommandInfo;
50142 })(declares = app.declares || (app.declares = {}));
50143 })(app || (app = {}));
50147 (function (services) {
50148 var APIEndPoint = (function () {
50149 function APIEndPoint($resource, $http) {
50150 this.$resource = $resource;
50151 this.$http = $http;
50153 APIEndPoint.prototype.resource = function (endPoint, data) {
50154 var customAction = {
50160 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
50162 return this.$resource(endPoint, {}, { execute: execute });
50164 APIEndPoint.prototype.getOptionControlFile = function (command) {
50165 var endPoint = '/api/v1/optionControlFile/' + command;
50166 return this.resource(endPoint, {}).get();
50168 APIEndPoint.prototype.getFiles = function (fileId) {
50169 var endPoint = '/api/v1/workspace';
50171 endPoint += '/' + fileId;
50173 return this.resource(endPoint, {}).get();
50175 APIEndPoint.prototype.getDirectories = function () {
50176 var endPoint = '/api/v1/all/workspace/directory';
50177 return this.resource(endPoint, {}).get();
50179 APIEndPoint.prototype.getTags = function () {
50180 var endPoint = '/api/v1/tagList';
50181 return this.resource(endPoint, {}).get();
50183 APIEndPoint.prototype.getCommands = function () {
50184 var endPoint = '/api/v1/commandList';
50185 return this.resource(endPoint, {}).get();
50187 APIEndPoint.prototype.execute = function (data) {
50188 var endPoint = '/api/v1/execution';
50189 var fd = new FormData();
50190 fd.append('data', data);
50191 return this.$http.post(endPoint, fd, {
50192 headers: { 'Content-Type': undefined },
50193 transformRequest: angular.identity
50196 APIEndPoint.prototype.debug = function () {
50197 var endPoint = '/api/v1/debug';
50198 return this.$http.get(endPoint);
50200 APIEndPoint.prototype.help = function (command) {
50201 var endPoint = '/api/v1/help/' + command;
50202 return this.$http.get(endPoint);
50204 return APIEndPoint;
50206 services.APIEndPoint = APIEndPoint;
50207 })(services = app.services || (app.services = {}));
50208 })(app || (app = {}));
50212 (function (services) {
50213 var MyModal = (function () {
50214 function MyModal($uibModal) {
50215 this.$uibModal = $uibModal;
50216 this.modalOption = {
50223 MyModal.prototype.open = function (modalName) {
50224 if (modalName === 'SelectCommand') {
50225 this.modalOption.templateUrl = 'templates/select-command.html';
50226 this.modalOption.size = 'lg';
50228 return this.$uibModal.open(this.modalOption);
50230 MyModal.prototype.selectCommand = function () {
50231 this.modalOption.templateUrl = 'templates/select-command.html';
50232 this.modalOption.controller = 'selectCommandController';
50233 this.modalOption.controllerAs = 'c';
50234 this.modalOption.size = 'lg';
50235 return this.$uibModal.open(this.modalOption);
50237 MyModal.prototype.preview = function () {
50238 this.modalOption.templateUrl = 'templates/preview.html';
50239 this.modalOption.controller = 'previewController';
50240 this.modalOption.controllerAs = 'c';
50241 this.modalOption.size = 'lg';
50242 return this.$uibModal.open(this.modalOption);
50244 MyModal.$inject = ['$uibModal'];
50247 services.MyModal = MyModal;
50248 })(services = app.services || (app.services = {}));
50249 })(app || (app = {}));
50253 (function (services) {
50254 var WebSocket = (function () {
50255 function WebSocket($rootScope) {
50256 this.$rootScope = $rootScope;
50257 this.socket = io.connect();
50259 WebSocket.prototype.on = function (eventName, callback) {
50260 var socket = this.socket;
50261 var rootScope = this.$rootScope;
50262 socket.on(eventName, function () {
50263 var args = arguments;
50264 rootScope.$apply(function () {
50265 callback.apply(socket, args);
50269 WebSocket.prototype.emit = function (eventName, data, callback) {
50270 var socket = this.socket;
50271 var rootScope = this.$rootScope;
50272 this.socket.emit(eventName, data, function () {
50273 var args = arguments;
50274 rootScope.$apply(function () {
50276 callback.apply(socket, args);
50282 services.WebSocket = WebSocket;
50283 })(services = app.services || (app.services = {}));
50284 })(app || (app = {}));
50288 (function (services) {
50289 var Console = (function () {
50290 function Console(WebSocket, $rootScope) {
50291 this.WebSocket = WebSocket;
50292 this.$rootScope = $rootScope;
50293 this.WebSocket = WebSocket;
50294 this.$rootScope = $rootScope;
50295 this.directiveIDs = [];
50296 var directiveIDs = this.directiveIDs;
50297 this.WebSocket.on('console', function (d) {
50299 var message = d.message;
50300 if (directiveIDs.indexOf(id) > -1) {
50301 $rootScope.$emit(id, message);
50305 Console.prototype.addDirective = function (id) {
50306 if (!(this.directiveIDs.indexOf(id) > -1)) {
50307 this.directiveIDs.push(id);
50310 Console.prototype.removeDirective = function (id) {
50311 var i = this.directiveIDs.indexOf(id);
50313 this.directiveIDs.splice(i, 1);
50316 Console.prototype.showIDs = function () {
50317 console.log(this.directiveIDs);
50321 services.Console = Console;
50322 })(services = app.services || (app.services = {}));
50323 })(app || (app = {}));
50327 (function (directives) {
50328 var Command = (function () {
50329 function Command() {
50330 this.restrict = 'E';
50331 this.replace = true;
50333 this.controller = 'commandController';
50334 this.controllerAs = 'ctrl';
50335 this.bindToController = {
50341 this.templateUrl = 'templates/command.html';
50343 Command.Factory = function () {
50344 var directive = function () {
50345 return new Command();
50347 directive.$inject = [];
50352 directives.Command = Command;
50353 var CommandController = (function () {
50354 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
50355 this.APIEndPoint = APIEndPoint;
50356 this.$scope = $scope;
50357 this.MyModal = MyModal;
50358 this.WebSocket = WebSocket;
50359 this.$window = $window;
50360 this.$rootScope = $rootScope;
50361 this.Console = Console;
50362 var controller = this;
50364 .getOptionControlFile(this.name)
50366 .then(function (result) {
50367 controller.options = result.info;
50372 .then(function (result) {
50373 controller.dirs = result.info;
50375 this.heading = "[" + this.index + "]: dcdFilePrint";
50376 this.isOpen = true;
50377 this.$scope.$on('close', function () {
50378 controller.isOpen = false;
50382 return Math.floor((1 + Math.random()) * 0x10000)
50386 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
50387 s4() + '-' + s4() + s4() + s4();
50389 this.uuid = guid();
50390 this.Console.addDirective(this.uuid);
50391 this.Console.showIDs();
50393 CommandController.prototype.submit = function () {
50395 angular.forEach(this.options, function (option) {
50397 name: option.option,
50400 angular.forEach(option.arg, function (arg) {
50402 if (typeof arg.input === 'object') {
50403 obj.arguments.push(arg.input.name);
50406 obj.arguments.push(arg.input);
50410 if (obj.arguments.length > 0) {
50415 command: this.name,
50416 workspace: this.workspace.fileId,
50420 .execute(JSON.stringify(execObj))
50421 .then(function (result) {
50422 console.log(result);
50425 CommandController.prototype.removeMySelf = function (index) {
50426 this.$scope.$destroy();
50427 this.Console.removeDirective(this.uuid);
50428 this.remove()(index, this.list);
50429 this.Console.showIDs();
50431 CommandController.prototype.reloadFiles = function () {
50433 var fileId = this.workspace.fileId;
50437 .then(function (result) {
50438 var status = result.status;
50439 if (status === 'success') {
50440 _this.files = result.info;
50443 console.log(result.message);
50447 CommandController.prototype.debug = function () {
50448 var div = angular.element(this.$window.document).find("div");
50451 angular.forEach(div, function (v) {
50452 if (v.className === "panel-body console") {
50455 else if (v.className === "row parameters-console") {
50459 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
50460 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
50461 consoleTag.style.height = consoleHeight;
50462 consoleTag.style.width = consoleWidth;
50464 CommandController.prototype.help = function () {
50467 .then(function (result) {
50468 console.log(result);
50471 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
50472 return CommandController;
50474 directives.CommandController = CommandController;
50475 })(directives = app.directives || (app.directives = {}));
50476 })(app || (app = {}));
50480 (function (directives) {
50481 var HeaderMenu = (function () {
50482 function HeaderMenu() {
50483 this.restrict = 'E';
50484 this.replace = true;
50485 this.templateUrl = 'templates/header-menu.html';
50486 this.controller = 'HeaderMenuController';
50487 this.controllerAs = 'hmc';
50490 HeaderMenu.Factory = function () {
50491 var directive = function () {
50492 return new HeaderMenu();
50498 directives.HeaderMenu = HeaderMenu;
50499 var HeaderMenuController = (function () {
50500 function HeaderMenuController($state) {
50501 this.$state = $state;
50502 this.isExecution = this.$state.current.name === 'execution';
50503 this.isWorkspace = this.$state.current.name === 'workspace';
50504 this.isHistory = this.$state.current.name === 'history';
50506 HeaderMenuController.prototype.transit = function (state) {
50507 this.$state.go(state);
50509 HeaderMenuController.$inject = ['$state'];
50510 return HeaderMenuController;
50512 directives.HeaderMenuController = HeaderMenuController;
50513 })(directives = app.directives || (app.directives = {}));
50514 })(app || (app = {}));
50518 (function (directives) {
50519 var Option = (function () {
50520 function Option() {
50521 this.restrict = 'E';
50522 this.replace = true;
50523 this.controller = 'optionController';
50524 this.bindToController = {
50529 this.templateUrl = 'templates/option.html';
50530 this.controllerAs = 'ctrl';
50532 Option.Factory = function () {
50533 var directive = function () {
50534 return new Option();
50536 directive.$inject = [];
50541 directives.Option = Option;
50542 var OptionController = (function () {
50543 function OptionController() {
50544 var controller = this;
50545 angular.forEach(controller.info.arg, function (arg) {
50546 if (arg.initialValue) {
50547 if (arg.formType === 'number') {
50548 arg.input = parseInt(arg.initialValue);
50551 arg.input = arg.initialValue;
50556 OptionController.$inject = [];
50557 return OptionController;
50559 directives.OptionController = OptionController;
50560 })(directives = app.directives || (app.directives = {}));
50561 })(app || (app = {}));
50565 (function (directives) {
50566 var Directory = (function () {
50567 function Directory() {
50568 this.restrict = 'E';
50569 this.replace = true;
50570 this.controller = 'directoryController';
50571 this.controllerAs = 'ctrl';
50572 this.bindToController = {
50578 this.templateUrl = 'templates/directory.html';
50580 Directory.Factory = function () {
50581 var directive = function () {
50582 return new Directory();
50588 directives.Directory = Directory;
50589 var DirectoryController = (function () {
50590 function DirectoryController(APIEndPoint, $scope) {
50591 this.APIEndPoint = APIEndPoint;
50592 this.$scope = $scope;
50593 var controller = this;
50595 .getFiles(this.info.fileId)
50597 .then(function (result) {
50598 if (result.status === 'success') {
50599 controller.files = result.info;
50600 angular.forEach(result.info, function (file) {
50601 if (file.fileType === '0') {
50603 if (controller.info.path === '/') {
50604 o.path = '/' + file.name;
50607 o.path = controller.info.path + '/' + file.name;
50609 controller.add()(o, controller.list);
50616 DirectoryController.$inject = ['APIEndPoint', '$scope'];
50617 return DirectoryController;
50619 directives.DirectoryController = DirectoryController;
50620 })(directives = app.directives || (app.directives = {}));
50621 })(app || (app = {}));
50625 (function (controllers) {
50626 var Execution = (function () {
50627 function Execution(MyModal, $scope) {
50628 this.MyModal = MyModal;
50629 this.$scope = $scope;
50630 this.commandInfoList = [];
50633 Execution.prototype.add = function () {
50634 this.$scope.$broadcast('close');
50635 var commandInfoList = this.commandInfoList;
50636 var commandInstance = this.MyModal.selectCommand();
50639 .then(function (command) {
50640 commandInfoList.push(new app.declares.CommandInfo(command));
50643 Execution.prototype.open = function () {
50644 var result = this.MyModal.open('SelectCommand');
50645 console.log(result);
50647 Execution.prototype.remove = function (index, list) {
50648 list.splice(index, 1);
50650 Execution.prototype.close = function () {
50651 console.log("close");
50653 Execution.$inject = ['MyModal', '$scope'];
50656 controllers.Execution = Execution;
50657 })(controllers = app.controllers || (app.controllers = {}));
50658 })(app || (app = {}));
50662 (function (controllers) {
50663 var Workspace = (function () {
50664 function Workspace($scope, APIEndPoint, MyModal) {
50665 this.$scope = $scope;
50666 this.APIEndPoint = APIEndPoint;
50667 this.MyModal = MyModal;
50668 this.directoryList = [];
50669 var controller = this;
50670 var directoryList = this.directoryList;
50672 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
50680 directoryList.push(o);
50682 Workspace.prototype.addDirectory = function (info, directoryList) {
50683 directoryList.push(info);
50685 Workspace.prototype.debug = function () {
50686 this.MyModal.preview();
50688 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
50691 controllers.Workspace = Workspace;
50692 })(controllers = app.controllers || (app.controllers = {}));
50693 })(app || (app = {}));
50697 (function (controllers) {
50698 var History = (function () {
50699 function History($scope) {
50700 this.page = "History";
50702 History.$inject = ['$scope'];
50705 controllers.History = History;
50706 })(controllers = app.controllers || (app.controllers = {}));
50707 })(app || (app = {}));
50711 (function (controllers) {
50712 var SelectCommand = (function () {
50713 function SelectCommand($scope, APIEndPoint, $modalInstance) {
50714 this.APIEndPoint = APIEndPoint;
50715 this.$modalInstance = $modalInstance;
50716 var controller = this;
50719 .$promise.then(function (result) {
50720 controller.tags = result.info;
50724 .$promise.then(function (result) {
50725 controller.commands = result.info;
50727 this.currentTag = 'all';
50729 SelectCommand.prototype.changeTag = function (tag) {
50730 this.currentTag = tag;
50732 SelectCommand.prototype.selectCommand = function (command) {
50733 this.$modalInstance.close(command);
50735 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
50736 return SelectCommand;
50738 controllers.SelectCommand = SelectCommand;
50739 })(controllers = app.controllers || (app.controllers = {}));
50740 })(app || (app = {}));
50744 (function (controllers) {
50745 var Preview = (function () {
50746 function Preview($scope, APIEndPoint, $modalInstance) {
50747 this.APIEndPoint = APIEndPoint;
50748 this.$modalInstance = $modalInstance;
50749 var controller = this;
50750 console.log('preview');
50752 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
50755 controllers.Preview = Preview;
50756 })(controllers = app.controllers || (app.controllers = {}));
50757 })(app || (app = {}));
50759 (function (filters) {
50761 return function (commands, tag) {
50763 angular.forEach(commands, function (command) {
50765 angular.forEach(command.tags, function (value) {
50770 result.push(command);
50776 })(filters || (filters = {}));
50780 var appName = 'zephyr';
50781 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
50782 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
50783 $urlRouterProvider.otherwise('/execution');
50784 $locationProvider.html5Mode({
50789 .state('execution', {
50791 templateUrl: 'templates/execution.html',
50792 controller: 'executionController',
50795 .state('workspace', {
50797 templateUrl: 'templates/workspace.html',
50798 controller: 'workspaceController',
50801 .state('history', {
50803 templateUrl: 'templates/history.html',
50804 controller: 'historyController',
50808 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
50809 app.zephyr.service('MyModal', app.services.MyModal);
50810 app.zephyr.service('WebSocket', app.services.WebSocket);
50811 app.zephyr.service('Console', app.services.Console);
50812 app.zephyr.filter('Tag', filters.Tag);
50813 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
50814 app.zephyr.controller('previewController', app.controllers.Preview);
50815 app.zephyr.controller('executionController', app.controllers.Execution);
50816 app.zephyr.controller('workspaceController', app.controllers.Workspace);
50817 app.zephyr.controller('historyController', app.controllers.History);
50818 app.zephyr.controller('commandController', app.directives.CommandController);
50819 app.zephyr.controller('optionController', app.directives.OptionController);
50820 app.zephyr.controller('directoryController', app.directives.DirectoryController);
50821 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
50822 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
50823 app.zephyr.directive('command', app.directives.Command.Factory());
50824 app.zephyr.directive('option', app.directives.Option.Factory());
50825 app.zephyr.directive('directory', app.directives.Directory.Factory());
50826 })(app || (app = {}));
50831 /***/ function(module, exports) {
50836 (function (declares) {
50837 var CommandInfo = (function () {
50838 function CommandInfo(name) {
50841 return CommandInfo;
50843 declares.CommandInfo = CommandInfo;
50844 })(declares = app.declares || (app.declares = {}));
50845 })(app || (app = {}));
50849 (function (services) {
50850 var APIEndPoint = (function () {
50851 function APIEndPoint($resource, $http) {
50852 this.$resource = $resource;
50853 this.$http = $http;
50855 APIEndPoint.prototype.resource = function (endPoint, data) {
50856 var customAction = {
50862 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
50864 return this.$resource(endPoint, {}, { execute: execute });
50866 APIEndPoint.prototype.getOptionControlFile = function (command) {
50867 var endPoint = '/api/v1/optionControlFile/' + command;
50868 return this.resource(endPoint, {}).get();
50870 APIEndPoint.prototype.getFiles = function (fileId) {
50871 var endPoint = '/api/v1/workspace';
50873 endPoint += '/' + fileId;
50875 return this.resource(endPoint, {}).get();
50877 APIEndPoint.prototype.getDirectories = function () {
50878 var endPoint = '/api/v1/all/workspace/directory';
50879 return this.resource(endPoint, {}).get();
50881 APIEndPoint.prototype.getTags = function () {
50882 var endPoint = '/api/v1/tagList';
50883 return this.resource(endPoint, {}).get();
50885 APIEndPoint.prototype.getCommands = function () {
50886 var endPoint = '/api/v1/commandList';
50887 return this.resource(endPoint, {}).get();
50889 APIEndPoint.prototype.execute = function (data) {
50890 var endPoint = '/api/v1/execution';
50891 var fd = new FormData();
50892 fd.append('data', data);
50893 return this.$http.post(endPoint, fd, {
50894 headers: { 'Content-Type': undefined },
50895 transformRequest: angular.identity
50898 APIEndPoint.prototype.debug = function () {
50899 var endPoint = '/api/v1/debug';
50900 return this.$http.get(endPoint);
50902 APIEndPoint.prototype.help = function (command) {
50903 var endPoint = '/api/v1/help/' + command;
50904 return this.$http.get(endPoint);
50906 return APIEndPoint;
50908 services.APIEndPoint = APIEndPoint;
50909 })(services = app.services || (app.services = {}));
50910 })(app || (app = {}));
50914 (function (services) {
50915 var MyModal = (function () {
50916 function MyModal($uibModal) {
50917 this.$uibModal = $uibModal;
50918 this.modalOption = {
50925 MyModal.prototype.open = function (modalName) {
50926 if (modalName === 'SelectCommand') {
50927 this.modalOption.templateUrl = 'templates/select-command.html';
50928 this.modalOption.size = 'lg';
50930 return this.$uibModal.open(this.modalOption);
50932 MyModal.prototype.selectCommand = function () {
50933 this.modalOption.templateUrl = 'templates/select-command.html';
50934 this.modalOption.controller = 'selectCommandController';
50935 this.modalOption.controllerAs = 'c';
50936 this.modalOption.size = 'lg';
50937 return this.$uibModal.open(this.modalOption);
50939 MyModal.prototype.preview = function () {
50940 this.modalOption.templateUrl = 'templates/preview.html';
50941 this.modalOption.controller = 'previewController';
50942 this.modalOption.controllerAs = 'c';
50943 this.modalOption.size = 'lg';
50944 return this.$uibModal.open(this.modalOption);
50946 MyModal.$inject = ['$uibModal'];
50949 services.MyModal = MyModal;
50950 })(services = app.services || (app.services = {}));
50951 })(app || (app = {}));
50955 (function (services) {
50956 var WebSocket = (function () {
50957 function WebSocket($rootScope) {
50958 this.$rootScope = $rootScope;
50959 this.socket = io.connect();
50961 WebSocket.prototype.on = function (eventName, callback) {
50962 var socket = this.socket;
50963 var rootScope = this.$rootScope;
50964 socket.on(eventName, function () {
50965 var args = arguments;
50966 rootScope.$apply(function () {
50967 callback.apply(socket, args);
50971 WebSocket.prototype.emit = function (eventName, data, callback) {
50972 var socket = this.socket;
50973 var rootScope = this.$rootScope;
50974 this.socket.emit(eventName, data, function () {
50975 var args = arguments;
50976 rootScope.$apply(function () {
50978 callback.apply(socket, args);
50984 services.WebSocket = WebSocket;
50985 })(services = app.services || (app.services = {}));
50986 })(app || (app = {}));
50990 (function (services) {
50991 var Console = (function () {
50992 function Console(WebSocket, $rootScope) {
50993 this.WebSocket = WebSocket;
50994 this.$rootScope = $rootScope;
50995 this.WebSocket = WebSocket;
50996 this.$rootScope = $rootScope;
50997 this.directiveIDs = [];
50998 var directiveIDs = this.directiveIDs;
50999 this.WebSocket.on('console', function (d) {
51001 var message = d.message;
51002 if (directiveIDs.indexOf(id) > -1) {
51003 $rootScope.$emit(id, message);
51007 Console.prototype.addDirective = function (id) {
51008 if (!(this.directiveIDs.indexOf(id) > -1)) {
51009 this.directiveIDs.push(id);
51012 Console.prototype.removeDirective = function (id) {
51013 var i = this.directiveIDs.indexOf(id);
51015 this.directiveIDs.splice(i, 1);
51018 Console.prototype.showIDs = function () {
51019 console.log(this.directiveIDs);
51023 services.Console = Console;
51024 })(services = app.services || (app.services = {}));
51025 })(app || (app = {}));
51029 (function (directives) {
51030 var Command = (function () {
51031 function Command() {
51032 this.restrict = 'E';
51033 this.replace = true;
51035 this.controller = 'commandController';
51036 this.controllerAs = 'ctrl';
51037 this.bindToController = {
51043 this.templateUrl = 'templates/command.html';
51045 Command.Factory = function () {
51046 var directive = function () {
51047 return new Command();
51049 directive.$inject = [];
51054 directives.Command = Command;
51055 var CommandController = (function () {
51056 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
51057 this.APIEndPoint = APIEndPoint;
51058 this.$scope = $scope;
51059 this.MyModal = MyModal;
51060 this.WebSocket = WebSocket;
51061 this.$window = $window;
51062 this.$rootScope = $rootScope;
51063 this.Console = Console;
51064 var controller = this;
51066 .getOptionControlFile(this.name)
51068 .then(function (result) {
51069 controller.options = result.info;
51074 .then(function (result) {
51075 controller.dirs = result.info;
51077 this.heading = "[" + this.index + "]: dcdFilePrint";
51078 this.isOpen = true;
51079 this.$scope.$on('close', function () {
51080 controller.isOpen = false;
51084 return Math.floor((1 + Math.random()) * 0x10000)
51088 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
51089 s4() + '-' + s4() + s4() + s4();
51091 this.uuid = guid();
51092 this.Console.addDirective(this.uuid);
51093 this.Console.showIDs();
51095 CommandController.prototype.submit = function () {
51097 angular.forEach(this.options, function (option) {
51099 name: option.option,
51102 angular.forEach(option.arg, function (arg) {
51104 if (typeof arg.input === 'object') {
51105 obj.arguments.push(arg.input.name);
51108 obj.arguments.push(arg.input);
51112 if (obj.arguments.length > 0) {
51117 command: this.name,
51118 workspace: this.workspace.fileId,
51122 .execute(JSON.stringify(execObj))
51123 .then(function (result) {
51124 console.log(result);
51127 CommandController.prototype.removeMySelf = function (index) {
51128 this.$scope.$destroy();
51129 this.Console.removeDirective(this.uuid);
51130 this.remove()(index, this.list);
51131 this.Console.showIDs();
51133 CommandController.prototype.reloadFiles = function () {
51135 var fileId = this.workspace.fileId;
51139 .then(function (result) {
51140 var status = result.status;
51141 if (status === 'success') {
51142 _this.files = result.info;
51145 console.log(result.message);
51149 CommandController.prototype.debug = function () {
51150 var div = angular.element(this.$window.document).find("div");
51153 angular.forEach(div, function (v) {
51154 if (v.className === "panel-body console") {
51157 else if (v.className === "row parameters-console") {
51161 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
51162 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
51163 consoleTag.style.height = consoleHeight;
51164 consoleTag.style.width = consoleWidth;
51166 CommandController.prototype.help = function () {
51169 .then(function (result) {
51170 console.log(result);
51173 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
51174 return CommandController;
51176 directives.CommandController = CommandController;
51177 })(directives = app.directives || (app.directives = {}));
51178 })(app || (app = {}));
51182 (function (directives) {
51183 var HeaderMenu = (function () {
51184 function HeaderMenu() {
51185 this.restrict = 'E';
51186 this.replace = true;
51187 this.templateUrl = 'templates/header-menu.html';
51188 this.controller = 'HeaderMenuController';
51189 this.controllerAs = 'hmc';
51192 HeaderMenu.Factory = function () {
51193 var directive = function () {
51194 return new HeaderMenu();
51200 directives.HeaderMenu = HeaderMenu;
51201 var HeaderMenuController = (function () {
51202 function HeaderMenuController($state) {
51203 this.$state = $state;
51204 this.isExecution = this.$state.current.name === 'execution';
51205 this.isWorkspace = this.$state.current.name === 'workspace';
51206 this.isHistory = this.$state.current.name === 'history';
51208 HeaderMenuController.prototype.transit = function (state) {
51209 this.$state.go(state);
51211 HeaderMenuController.$inject = ['$state'];
51212 return HeaderMenuController;
51214 directives.HeaderMenuController = HeaderMenuController;
51215 })(directives = app.directives || (app.directives = {}));
51216 })(app || (app = {}));
51220 (function (directives) {
51221 var Option = (function () {
51222 function Option() {
51223 this.restrict = 'E';
51224 this.replace = true;
51225 this.controller = 'optionController';
51226 this.bindToController = {
51231 this.templateUrl = 'templates/option.html';
51232 this.controllerAs = 'ctrl';
51234 Option.Factory = function () {
51235 var directive = function () {
51236 return new Option();
51238 directive.$inject = [];
51243 directives.Option = Option;
51244 var OptionController = (function () {
51245 function OptionController() {
51246 var controller = this;
51247 angular.forEach(controller.info.arg, function (arg) {
51248 if (arg.initialValue) {
51249 if (arg.formType === 'number') {
51250 arg.input = parseInt(arg.initialValue);
51253 arg.input = arg.initialValue;
51258 OptionController.$inject = [];
51259 return OptionController;
51261 directives.OptionController = OptionController;
51262 })(directives = app.directives || (app.directives = {}));
51263 })(app || (app = {}));
51267 (function (directives) {
51268 var Directory = (function () {
51269 function Directory() {
51270 this.restrict = 'E';
51271 this.replace = true;
51272 this.controller = 'directoryController';
51273 this.controllerAs = 'ctrl';
51274 this.bindToController = {
51280 this.templateUrl = 'templates/directory.html';
51282 Directory.Factory = function () {
51283 var directive = function () {
51284 return new Directory();
51290 directives.Directory = Directory;
51291 var DirectoryController = (function () {
51292 function DirectoryController(APIEndPoint, $scope) {
51293 this.APIEndPoint = APIEndPoint;
51294 this.$scope = $scope;
51295 var controller = this;
51297 .getFiles(this.info.fileId)
51299 .then(function (result) {
51300 if (result.status === 'success') {
51301 controller.files = result.info;
51302 angular.forEach(result.info, function (file) {
51303 if (file.fileType === '0') {
51305 if (controller.info.path === '/') {
51306 o.path = '/' + file.name;
51309 o.path = controller.info.path + '/' + file.name;
51311 controller.add()(o, controller.list);
51318 DirectoryController.$inject = ['APIEndPoint', '$scope'];
51319 return DirectoryController;
51321 directives.DirectoryController = DirectoryController;
51322 })(directives = app.directives || (app.directives = {}));
51323 })(app || (app = {}));
51327 (function (controllers) {
51328 var Execution = (function () {
51329 function Execution(MyModal, $scope) {
51330 this.MyModal = MyModal;
51331 this.$scope = $scope;
51332 this.commandInfoList = [];
51335 Execution.prototype.add = function () {
51336 this.$scope.$broadcast('close');
51337 var commandInfoList = this.commandInfoList;
51338 var commandInstance = this.MyModal.selectCommand();
51341 .then(function (command) {
51342 commandInfoList.push(new app.declares.CommandInfo(command));
51345 Execution.prototype.open = function () {
51346 var result = this.MyModal.open('SelectCommand');
51347 console.log(result);
51349 Execution.prototype.remove = function (index, list) {
51350 list.splice(index, 1);
51352 Execution.prototype.close = function () {
51353 console.log("close");
51355 Execution.$inject = ['MyModal', '$scope'];
51358 controllers.Execution = Execution;
51359 })(controllers = app.controllers || (app.controllers = {}));
51360 })(app || (app = {}));
51364 (function (controllers) {
51365 var Workspace = (function () {
51366 function Workspace($scope, APIEndPoint, MyModal) {
51367 this.$scope = $scope;
51368 this.APIEndPoint = APIEndPoint;
51369 this.MyModal = MyModal;
51370 this.directoryList = [];
51371 var controller = this;
51372 var directoryList = this.directoryList;
51374 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
51382 directoryList.push(o);
51384 Workspace.prototype.addDirectory = function (info, directoryList) {
51385 directoryList.push(info);
51387 Workspace.prototype.debug = function () {
51388 this.MyModal.preview();
51390 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
51393 controllers.Workspace = Workspace;
51394 })(controllers = app.controllers || (app.controllers = {}));
51395 })(app || (app = {}));
51399 (function (controllers) {
51400 var History = (function () {
51401 function History($scope) {
51402 this.page = "History";
51404 History.$inject = ['$scope'];
51407 controllers.History = History;
51408 })(controllers = app.controllers || (app.controllers = {}));
51409 })(app || (app = {}));
51413 (function (controllers) {
51414 var SelectCommand = (function () {
51415 function SelectCommand($scope, APIEndPoint, $modalInstance) {
51416 this.APIEndPoint = APIEndPoint;
51417 this.$modalInstance = $modalInstance;
51418 var controller = this;
51421 .$promise.then(function (result) {
51422 controller.tags = result.info;
51426 .$promise.then(function (result) {
51427 controller.commands = result.info;
51429 this.currentTag = 'all';
51431 SelectCommand.prototype.changeTag = function (tag) {
51432 this.currentTag = tag;
51434 SelectCommand.prototype.selectCommand = function (command) {
51435 this.$modalInstance.close(command);
51437 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
51438 return SelectCommand;
51440 controllers.SelectCommand = SelectCommand;
51441 })(controllers = app.controllers || (app.controllers = {}));
51442 })(app || (app = {}));
51446 (function (controllers) {
51447 var Preview = (function () {
51448 function Preview($scope, APIEndPoint, $modalInstance) {
51449 this.APIEndPoint = APIEndPoint;
51450 this.$modalInstance = $modalInstance;
51451 var controller = this;
51452 console.log('preview');
51454 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
51457 controllers.Preview = Preview;
51458 })(controllers = app.controllers || (app.controllers = {}));
51459 })(app || (app = {}));
51461 (function (filters) {
51463 return function (commands, tag) {
51465 angular.forEach(commands, function (command) {
51467 angular.forEach(command.tags, function (value) {
51472 result.push(command);
51478 })(filters || (filters = {}));
51482 var appName = 'zephyr';
51483 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
51484 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
51485 $urlRouterProvider.otherwise('/execution');
51486 $locationProvider.html5Mode({
51491 .state('execution', {
51493 templateUrl: 'templates/execution.html',
51494 controller: 'executionController',
51497 .state('workspace', {
51499 templateUrl: 'templates/workspace.html',
51500 controller: 'workspaceController',
51503 .state('history', {
51505 templateUrl: 'templates/history.html',
51506 controller: 'historyController',
51510 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
51511 app.zephyr.service('MyModal', app.services.MyModal);
51512 app.zephyr.service('WebSocket', app.services.WebSocket);
51513 app.zephyr.service('Console', app.services.Console);
51514 app.zephyr.filter('Tag', filters.Tag);
51515 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
51516 app.zephyr.controller('previewController', app.controllers.Preview);
51517 app.zephyr.controller('executionController', app.controllers.Execution);
51518 app.zephyr.controller('workspaceController', app.controllers.Workspace);
51519 app.zephyr.controller('historyController', app.controllers.History);
51520 app.zephyr.controller('commandController', app.directives.CommandController);
51521 app.zephyr.controller('optionController', app.directives.OptionController);
51522 app.zephyr.controller('directoryController', app.directives.DirectoryController);
51523 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
51524 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
51525 app.zephyr.directive('command', app.directives.Command.Factory());
51526 app.zephyr.directive('option', app.directives.Option.Factory());
51527 app.zephyr.directive('directory', app.directives.Directory.Factory());
51528 })(app || (app = {}));