From 6932826429b4382e8b6dca2bc95cdb6718ac420d Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Wed, 26 Jun 2013 21:50:27 +0100 Subject: [PATCH 1/8] Update to latest versions. --- pom.xml | 36 +- src/main/webapp/index.html | 26 +- .../webapp/js/lib/angular-0.9.17.patched.js | 10646 ---------------- .../js/lib/angular-0.9.17.patched.min.js | 116 - src/main/webapp/js/lib/angular-1.0.7.min.js | 163 + src/test/js/lib/angular-mocks-1.0.7.js | 1788 +++ src/test/js/lib/angular-mocks.js | 418 - 7 files changed, 1980 insertions(+), 11213 deletions(-) delete mode 100644 src/main/webapp/js/lib/angular-0.9.17.patched.js delete mode 100644 src/main/webapp/js/lib/angular-0.9.17.patched.min.js create mode 100644 src/main/webapp/js/lib/angular-1.0.7.min.js create mode 100644 src/test/js/lib/angular-mocks-1.0.7.js delete mode 100644 src/test/js/lib/angular-mocks.js diff --git a/pom.xml b/pom.xml index edcd52b..2c7dbb0 100644 --- a/pom.xml +++ b/pom.xml @@ -78,33 +78,29 @@ com.github.searls jasmine-maven-plugin - 1.0.2-beta-2 + 1.3.1.2 - generateManualRunner - resources - testResources test - preparePackage - - ${project.basedir}/src/main/webapp/js - - *.js - - ${project.basedir}/src/test/js - - lib/angular-mocks.js - spec/*.js - - - lib/angular-0.9.17.patched.js - lib/angular-mocks.js - - + + ${project.basedir}/src/main/webapp/js + + *.js + + ${project.basedir}/src/test/js + + lib/angular-mocks-1.0.7.js + spec/*.js + + + lib/angular-1.0.7.min.js + lib/angular-mocks-1.0.7.js + + angular-java-server-midi diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 76a0a56..d409c5e 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -1,24 +1,24 @@ - + Server Side Piano - + - +
Keys - - - @@ -29,17 +29,17 @@
-
- {{key.name}} + ng-style="key.cssPosition" + ng-click="sendMidiCommand(NOTE_ON, key.midiNote)"> + {{key.name}}
-
- {{key.name}} + ng-style="key.cssPosition" + ng-click="sendMidiCommand(NOTE_ON, key.midiNote)"> + {{key.name}}
diff --git a/src/main/webapp/js/lib/angular-0.9.17.patched.js b/src/main/webapp/js/lib/angular-0.9.17.patched.js deleted file mode 100644 index 7a4eaf8..0000000 --- a/src/main/webapp/js/lib/angular-0.9.17.patched.js +++ /dev/null @@ -1,10646 +0,0 @@ -/** - * The MIT License - * - * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -(function(window, document, undefined){ -//////////////////////////////////// - -if (typeof document.getAttribute == $undefined) - document.getAttribute = function() {}; - -/** - * @workInProgress - * @ngdoc function - * @name angular.lowercase - * @function - * - * @description Converts string to lowercase - * @param {string} string String to be lowercased. - * @returns {string} Lowercased string. - */ -var lowercase = function (string){ return isString(string) ? string.toLowerCase() : string; }; - - -/** - * @workInProgress - * @ngdoc function - * @name angular.uppercase - * @function - * - * @description Converts string to uppercase. - * @param {string} string String to be uppercased. - * @returns {string} Uppercased string. - */ -var uppercase = function (string){ return isString(string) ? string.toUpperCase() : string; }; - - -var manualLowercase = function (s) { - return isString(s) - ? s.replace(/[A-Z]/g, function (ch) {return fromCharCode(ch.charCodeAt(0) | 32); }) - : s; -}; -var manualUppercase = function (s) { - return isString(s) - ? s.replace(/[a-z]/g, function (ch) {return fromCharCode(ch.charCodeAt(0) & ~32); }) - : s; -}; - - -// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish -// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods with -// correct but slower alternatives. -if ('i' !== 'I'.toLowerCase()) { - lowercase = manualLowercase; - uppercase = manualUppercase; -} - -function fromCharCode(code) { return String.fromCharCode(code); } - - -var _undefined = undefined, - _null = null, - $$element = '$element', - $$scope = '$scope', - $$validate = '$validate', - $angular = 'angular', - $array = 'array', - $boolean = 'boolean', - $console = 'console', - $date = 'date', - $display = 'display', - $element = 'element', - $function = 'function', - $length = 'length', - $name = 'name', - $none = 'none', - $noop = 'noop', - $null = 'null', - $number = 'number', - $object = 'object', - $string = 'string', - $value = 'value', - $selected = 'selected', - $undefined = 'undefined', - NG_EXCEPTION = 'ng-exception', - NG_VALIDATION_ERROR = 'ng-validation-error', - NOOP = 'noop', - PRIORITY_FIRST = -99999, - PRIORITY_WATCH = -1000, - PRIORITY_LAST = 99999, - PRIORITY = {'FIRST': PRIORITY_FIRST, 'LAST': PRIORITY_LAST, 'WATCH':PRIORITY_WATCH}, - Error = window.Error, - /** holds major version number for IE or NaN for real browsers */ - msie = parseInt((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1], 10), - jqLite, // delay binding since jQuery could be loaded after us. - jQuery, // delay binding - slice = [].slice, - push = [].push, - error = window[$console] - ? bind(window[$console], window[$console]['error'] || noop) - : noop, - - /** @name angular */ - angular = window[$angular] || (window[$angular] = {}), - /** @name angular.markup */ - angularTextMarkup = extensionMap(angular, 'markup'), - /** @name angular.attrMarkup */ - angularAttrMarkup = extensionMap(angular, 'attrMarkup'), - /** @name angular.directive */ - angularDirective = extensionMap(angular, 'directive'), - /** @name angular.widget */ - angularWidget = extensionMap(angular, 'widget', lowercase), - /** @name angular.validator */ - angularValidator = extensionMap(angular, 'validator'), - /** @name angular.fileter */ - angularFilter = extensionMap(angular, 'filter'), - /** @name angular.formatter */ - angularFormatter = extensionMap(angular, 'formatter'), - /** @name angular.service */ - angularService = extensionMap(angular, 'service'), - angularCallbacks = extensionMap(angular, 'callbacks'), - nodeName_, - rngScript = /^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/, - uid = ['0', '0', '0']; - DATE_ISOSTRING_LN = 24; - -/** - * @workInProgress - * @ngdoc function - * @name angular.forEach - * @function - * - * @description - * Invokes the `iterator` function once for each item in `obj` collection. The collection can either - * be an object or an array. The `iterator` function is invoked with `iterator(value, key)`, where - * `value` is the value of an object property or an array element and `key` is the object property - * key or array element index. Optionally, `context` can be specified for the iterator function. - * - * Note: this function was previously known as `angular.foreach`. - * -
-     var values = {name: 'misko', gender: 'male'};
-     var log = [];
-     angular.forEach(values, function(value, key){
-       this.push(key + ': ' + value);
-     }, log);
-     expect(log).toEqual(['name: misko', 'gender:male']);
-   
- * - * @param {Object|Array} obj Object to iterate over. - * @param {function()} iterator Iterator function. - * @param {Object} context Object to become context (`this`) for the iterator function. - * @returns {Objet|Array} Reference to `obj`. - */ -function forEach(obj, iterator, context) { - var key; - if (obj) { - if (isFunction(obj)){ - for (key in obj) { - if (key != 'prototype' && key != $length && key != $name && obj.hasOwnProperty(key)) { - iterator.call(context, obj[key], key); - } - } - } else if (obj.forEach && obj.forEach !== forEach) { - obj.forEach(iterator, context); - } else if (isObject(obj) && isNumber(obj.length)) { - for (key = 0; key < obj.length; key++) - iterator.call(context, obj[key], key); - } else { - for (key in obj) - iterator.call(context, obj[key], key); - } - } - return obj; -} - -function forEachSorted(obj, iterator, context) { - var keys = []; - for (var key in obj) keys.push(key); - keys.sort(); - for ( var i = 0; i < keys.length; i++) { - iterator.call(context, obj[keys[i]], keys[i]); - } - return keys; -} - - -function formatError(arg) { - if (arg instanceof Error) { - if (arg.stack) { - arg = (arg.message && arg.stack.indexOf(arg.message) === -1) ? - 'Error: ' + arg.message + '\n' + arg.stack : arg.stack; - } else if (arg.sourceURL) { - arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; - } - } - return arg; -} - -/** - * @description - * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric - * characters such as '012ABC'. The reason why we are not using simply a number counter is that - * the number string gets longer over time, and it can also overflow, where as the the nextId - * will grow much slower, it is a string, and it will never overflow. - * - * @returns an unique alpha-numeric string - */ -function nextUid() { - var index = uid.length; - var digit; - - while(index) { - index--; - digit = uid[index].charCodeAt(0); - if (digit == 57 /*'9'*/) { - uid[index] = 'A'; - return uid.join(''); - } - if (digit == 90 /*'Z'*/) { - uid[index] = '0'; - } else { - uid[index] = String.fromCharCode(digit + 1); - return uid.join(''); - } - } - uid.unshift('0'); - return uid.join(''); -} - -/** - * @workInProgress - * @ngdoc function - * @name angular.extend - * @function - * - * @description - * Extends the destination object `dst` by copying all of the properties from the `src` object(s) to - * `dst`. You can specify multiple `src` objects. - * - * @param {Object} dst The destination object. - * @param {...Object} src The source object(s). - */ -function extend(dst) { - forEach(arguments, function(obj){ - if (obj !== dst) { - forEach(obj, function(value, key){ - dst[key] = value; - }); - } - }); - return dst; -} - - -function inherit(parent, extra) { - return extend(new (extend(function(){}, {prototype:parent}))(), extra); -} - - -/** - * @workInProgress - * @ngdoc function - * @name angular.noop - * @function - * - * @description - * Empty function that performs no operation whatsoever. This function is useful when writing code - * in the functional style. -
-     function foo(callback) {
-       var result = calculateResult();
-       (callback || angular.noop)(result);
-     }
-   
- */ -function noop() {} - - -/** - * @workInProgress - * @ngdoc function - * @name angular.identity - * @function - * - * @description - * A function that does nothing except for returning its first argument. This function is useful - * when writing code in the functional style. - * -
-     function transformer(transformationFn, value) {
-       return (transformationFn || identity)(value);
-     };
-   
- */ -function identity($) {return $;} - - -function valueFn(value) {return function(){ return value; };} - -function extensionMap(angular, name, transform) { - var extPoint; - return angular[name] || (extPoint = angular[name] = function (name, fn, prop){ - name = (transform || identity)(name); - if (isDefined(fn)) { - extPoint[name] = extend(fn, prop || {}); - } - return extPoint[name]; - }); -} - -/** - * @workInProgress - * @ngdoc function - * @name angular.isUndefined - * @function - * - * @description - * Checks if a reference is undefined. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is undefined. - */ -function isUndefined(value){ return typeof value == $undefined; } - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isDefined - * @function - * - * @description - * Checks if a reference is defined. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is defined. - */ -function isDefined(value){ return typeof value != $undefined; } - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isObject - * @function - * - * @description - * Checks if a reference is an `Object`. Unlike in JavaScript `null`s are not considered to be - * objects. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is an `Object` but not `null`. - */ -function isObject(value){ return value!=null && typeof value == $object;} - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isString - * @function - * - * @description - * Checks if a reference is a `String`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `String`. - */ -function isString(value){ return typeof value == $string;} - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isNumber - * @function - * - * @description - * Checks if a reference is a `Number`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Number`. - */ -function isNumber(value){ return typeof value == $number;} - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isDate - * @function - * - * @description - * Checks if value is a date. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Date`. - */ -function isDate(value){ return value instanceof Date; } - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isArray - * @function - * - * @description - * Checks if a reference is an `Array`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is an `Array`. - */ -function isArray(value) { return value instanceof Array; } - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isFunction - * @function - * - * @description - * Checks if a reference is a `Function`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Function`. - */ -function isFunction(value){ return typeof value == $function;} - - -/** - * Checks if `obj` is a window object. - * - * @private - * @param {*} obj Object to check - * @returns {boolean} True if `obj` is a window obj. - */ -function isWindow(obj) { - return obj && obj.document && obj.location && obj.alert && obj.setInterval; -} - -function isBoolean(value) { return typeof value == $boolean;} -function isTextNode(node) { return nodeName_(node) == '#text'; } -function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; } -function isElement(node) { - return node && - (node.nodeName // we are a direct element - || (node.bind && node.find)); // we have a bind and find method part of jQuery API -} - -/** - * @param str 'key1,key2,...' - * @returns {object} in the form of {key1:true, key2:true, ...} - */ -function makeMap(str){ - var obj = {}, items = str.split(","), i; - for ( i = 0; i < items.length; i++ ) - obj[ items[i] ] = true; - return obj; -} - - - -/** - * HTML class which is the only class which can be used in ng:bind to inline HTML for security reasons. - * @constructor - * @param html raw (unsafe) html - * @param {string=} option if set to 'usafe' then get method will return raw (unsafe/unsanitized) html - */ -function HTML(html, option) { - this.html = html; - this.get = lowercase(option) == 'unsafe' - ? valueFn(html) - : function htmlSanitize() { - var buf = []; - htmlParser(html, htmlSanitizeWriter(buf)); - return buf.join(''); - }; -} - -if (msie < 9) { - nodeName_ = function(element) { - element = element.nodeName ? element : element[0]; - return (element.scopeName && element.scopeName != 'HTML' ) ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; - }; -} else { - nodeName_ = function(element) { - return element.nodeName ? element.nodeName : element[0].nodeName; - }; -} - -function isVisible(element) { - var rect = element[0].getBoundingClientRect(), - width = (rect.width || (rect.right||0 - rect.left||0)), - height = (rect.height || (rect.bottom||0 - rect.top||0)); - return width>0 && height>0; -} - -function map(obj, iterator, context) { - var results = []; - forEach(obj, function(value, index, list) { - results.push(iterator.call(context, value, index, list)); - }); - return results; -} - - -/** - * @ngdoc function - * @name angular.Object.size - * @function - * - * @description - * Determines the number of elements in an array, number of properties of an object or string - * length. - * - * Note: this function is used to augment the Object type in angular expressions. See - * {@link angular.Object} for more info. - * - * @param {Object|Array|string} obj Object, array or string to inspect. - * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object - * @returns {number} The size of `obj` or `0` if `obj` is neither an object or an array. - * - * @example - * - * - * Number of items in array: {{ [1,2].$size() }}
- * Number of items in object: {{ {a:1, b:2, c:3}.$size() }}
- *
- * - * it('should print correct sizes for an array and an object', function() { - * expect(binding('[1,2].$size()')).toBe('2'); - * expect(binding('{a:1, b:2, c:3}.$size()')).toBe('3'); - * }); - * - *
- */ -function size(obj, ownPropsOnly) { - var size = 0, key; - - if (isArray(obj) || isString(obj)) { - return obj.length; - } else if (isObject(obj)){ - for (key in obj) - if (!ownPropsOnly || obj.hasOwnProperty(key)) - size++; - } - - return size; -} - - -function includes(array, obj) { - for ( var i = 0; i < array.length; i++) { - if (obj === array[i]) return true; - } - return false; -} - -function indexOf(array, obj) { - for ( var i = 0; i < array.length; i++) { - if (obj === array[i]) return i; - } - return -1; -} - -function isLeafNode (node) { - if (node) { - switch (node.nodeName) { - case "OPTION": - case "PRE": - case "TITLE": - return true; - } - } - return false; -} - -/** - * @ngdoc function - * @name angular.Object.copy - * @function - * - * @description - * Creates a deep copy of `source`. - * - * If `source` is an object or an array, all of its members will be copied into the `destination` - * object. - * - * If `destination` is not provided and `source` is an object or an array, a copy is created & - * returned, otherwise the `source` is returned. - * - * If `destination` is provided, all of its properties will be deleted. - * - * Note: this function is used to augment the Object type in angular expressions. See - * {@link angular.Object} for more info. - * - * @param {*} source The source to be used to make a copy. - * Can be any type including primitives, `null` and `undefined`. - * @param {(Object|Array)=} destination Optional destination into which the source is copied. If - * provided, must be of the same type as `source`. - * @returns {*} The copy or updated `destination` if `destination` was specified. - * - * @example - * - * - Salutation:
- Name:
- -
- - The master object is NOT equal to the form object. - -
master={{master}}
-
form={{form}}
- *
- * - it('should print that initialy the form object is NOT equal to master', function() { - expect(element('.doc-example-live input[name=master.salutation]').val()).toBe('Hello'); - expect(element('.doc-example-live input[name=master.name]').val()).toBe('world'); - expect(element('.doc-example-live span').css('display')).toBe('inline'); - }); - - it('should make form and master equal when the copy button is clicked', function() { - element('.doc-example-live button').click(); - expect(element('.doc-example-live span').css('display')).toBe('none'); - }); - * - *
- */ -function copy(source, destination){ - if (!destination) { - destination = source; - if (source) { - if (isArray(source)) { - destination = copy(source, []); - } else if (isDate(source)) { - destination = new Date(source.getTime()); - } else if (isObject(source)) { - destination = copy(source, {}); - } - } - } else { - if (isArray(source)) { - while(destination.length) { - destination.pop(); - } - for ( var i = 0; i < source.length; i++) { - destination.push(copy(source[i])); - } - } else { - forEach(destination, function(value, key){ - delete destination[key]; - }); - for ( var key in source) { - destination[key] = copy(source[key]); - } - } - } - return destination; -} - - -/** - * @ngdoc function - * @name angular.Object.equals - * @function - * - * @description - * Determines if two objects or value are equivalent. - * - * To be equivalent, they must pass `==` comparison or be of the same type and have all their - * properties pass `==` comparison. During property comparision properties of `function` type and - * properties with name starting with `$` are ignored. - * - * Supports values types, arrays and objects. - * - * Note: this function is used to augment the Object type in angular expressions. See - * {@link angular.Object} for more info. - * - * @param {*} o1 Object or value to compare. - * @param {*} o2 Object or value to compare. - * @returns {boolean} True if arguments are equal. - * - * @example - * - * - Salutation:
- Name:
-
- - The greeting object is - NOT equal to - {salutation:'Hello', name:'world'}. - -
greeting={{greeting}}
- *
- * - it('should print that initialy greeting is equal to the hardcoded value object', function() { - expect(element('.doc-example-live input[name=greeting.salutation]').val()).toBe('Hello'); - expect(element('.doc-example-live input[name=greeting.name]').val()).toBe('world'); - expect(element('.doc-example-live span').css('display')).toBe('none'); - }); - - it('should say that the objects are not equal when the form is modified', function() { - input('greeting.name').enter('kitty'); - expect(element('.doc-example-live span').css('display')).toBe('inline'); - }); - * - *
- */ -function equals(o1, o2) { - if (o1 == o2) return true; - if (o1 === null || o2 === null) return false; - var t1 = typeof o1, t2 = typeof o2, length, key, keySet; - if (t1 == t2 && t1 == 'object') { - if (o1 instanceof Array) { - if ((length = o1.length) == o2.length) { - for(key=0; key 2 - ? slice.call(arguments, 2, arguments.length) - : []; - if (typeof fn == $function && !(fn instanceof RegExp)) { - return curryArgs.length - ? function() { - return arguments.length - ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0, arguments.length))) - : fn.apply(self, curryArgs); - } - : function() { - return arguments.length - ? fn.apply(self, arguments) - : fn.call(self); - }; - } else { - // in IE, native methods are not functions and so they can not be bound (but they don't need to be) - return fn; - } -} - -function toBoolean(value) { - if (value && value.length !== 0) { - var v = lowercase("" + value); - value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); - } else { - value = false; - } - return value; -} - -function merge(src, dst) { - for ( var key in src) { - var value = dst[key]; - var type = typeof value; - if (type == $undefined) { - dst[key] = fromJson(toJson(src[key])); - } else if (type == 'object' && value.constructor != array && - key.substring(0, 1) != "$") { - merge(src[key], value); - } - } -} - - -/** @name angular.compile */ -function compile(element) { - return new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget) - .compile(element); -} -///////////////////////////////////////////////// - -/** - * Parses an escaped url query string into key-value pairs. - * @returns Object.<(string|boolean)> - */ -function parseKeyValue(/**string*/keyValue) { - var obj = {}, key_value, key; - forEach((keyValue || "").split('&'), function(keyValue){ - if (keyValue) { - key_value = keyValue.split('='); - key = unescape(key_value[0]); - obj[key] = isDefined(key_value[1]) ? unescape(key_value[1]) : true; - } - }); - return obj; -} - -function toKeyValue(obj) { - var parts = []; - forEach(obj, function(value, key) { - parts.push(escape(key) + (value === true ? '' : '=' + escape(value))); - }); - return parts.length ? parts.join('&') : ''; -} - - -/** - * we need our custom mehtod because encodeURIComponent is too agressive and doesn't follow - * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path - * segments: - * segment = *pchar - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * pct-encoded = "%" HEXDIG HEXDIG - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ -function encodeUriSegment(val) { - return encodeUriQuery(val, true). - replace(/%26/gi, '&'). - replace(/%3D/gi, '='). - replace(/%2B/gi, '+'); -} - - -/** - * This method is intended for encoding *key* or *value* parts of query component. We need a custom - * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be - * encoded per http://tools.ietf.org/html/rfc3986: - * query = *( pchar / "/" / "?" ) - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * pct-encoded = "%" HEXDIG HEXDIG - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ -function encodeUriQuery(val, pctEncodeSpaces) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace((pctEncodeSpaces ? null : /%20/g), '+'); -} - - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:autobind - * @element script - * - * @TODO ng:autobind is not a directive!! it should be documented as bootstrap parameter in a - * separate bootstrap section. - * @TODO rename to ng:autobind to ng:autoboot - * - * @description - * - * `ng:autobind` with no parameters tells angular to compile and manage the whole page. - * - * `ng:autobind="[root element ID]"` tells angular to compile and manage part of the docucment, - * starting at "root element ID". - * - * For details on bootstrapping angular, see {@link guide/dev_guide.bootstrap Initializing Angular} - * in the Angular Developer Guide. - */ -function angularInit(config, document){ - var autobind = config.autobind; - - if (autobind) { - var element = isString(autobind) ? document.getElementById(autobind) : document, - scope = compile(element)(createScope({'$config':config})), - $browser = scope.$service('$browser'); - - if (config.css) - $browser.addCss(config.base_url + config.css); - else if(msie<8) - $browser.addJs(config.base_url + config.ie_compat, config.ie_compat_id); - } -} - -function angularJsConfig(document, config) { - bindJQuery(); - var scripts = document.getElementsByTagName("script"), - match; - config = extend({ - ie_compat_id: 'ng-ie-compat' - }, config); - for(var j = 0; j < scripts.length; j++) { - match = (scripts[j].src || "").match(rngScript); - if (match) { - config.base_url = match[1]; - config.ie_compat = match[1] + 'angular-ie-compat' + (match[2] || '') + '.js'; - extend(config, parseKeyValue(match[6])); - eachAttribute(jqLite(scripts[j]), function(value, name){ - if (/^ng:/.exec(name)) { - name = name.substring(3).replace(/-/g, '_'); - value = value || true; - config[name] = value; - } - }); - } - } - return config; -} - -function bindJQuery(){ - // bind to jQuery if present; - jQuery = window.jQuery; - // reset to jQuery or default to us. - if (jQuery) { - jqLite = jQuery; - extend(jQuery.fn, { - scope: JQLitePrototype.scope - }); - } else { - jqLite = jqLiteWrap; - } - angular.element = jqLite; -} - -/** - * throw error of the argument is falsy. - */ -function assertArg(arg, name, reason) { - if (!arg) { - var error = new Error("Argument '" + (name||'?') + "' is " + - (reason || "required")); - throw error; - } -} - -function assertArgFn(arg, name) { - assertArg(isFunction(arg, name, 'not a function')); -} -var array = [].constructor; - -/** - * @workInProgress - * @ngdoc function - * @name angular.toJson - * @function - * - * @description - * Serializes the input into a JSON formated string. - * - * @param {Object|Array|Date|string|number} obj Input to jsonify. - * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. - * @returns {string} Jsonified string representing `obj`. - */ -function toJson(obj, pretty) { - var buf = []; - toJsonArray(buf, obj, pretty ? "\n " : null, []); - return buf.join(''); -} - -/** - * @workInProgress - * @ngdoc function - * @name angular.fromJson - * @function - * - * @description - * Deserializes a string in the JSON format. - * - * @param {string} json JSON string to deserialize. - * @param {boolean} [useNative=false] Use native JSON parser if available - * @returns {Object|Array|Date|string|number} Deserialized thingy. - */ -function fromJson(json, useNative) { - if (!isString(json)) return json; - - var obj; - - try { - if (useNative && window.JSON && window.JSON.parse) { - obj = JSON.parse(json); - return transformDates(obj); - } - return parser(json, true).primary()(); - } catch (e) { - error("fromJson error: ", json, e); - throw e; - } - - // TODO make forEach optionally recursive and remove this function - function transformDates(obj) { - if (isString(obj) && obj.length === DATE_ISOSTRING_LN) { - return angularString.toDate(obj); - } else if (isArray(obj) || isObject(obj)) { - forEach(obj, function(val, name) { - obj[name] = transformDates(val); - }); - } - return obj; - } -} - -angular.toJson = toJson; -angular.fromJson = fromJson; - -function toJsonArray(buf, obj, pretty, stack) { - if (isObject(obj)) { - if (obj === window) { - buf.push('WINDOW'); - return; - } - - if (obj === document) { - buf.push('DOCUMENT'); - return; - } - - if (includes(stack, obj)) { - buf.push('RECURSION'); - return; - } - stack.push(obj); - } - if (obj === null) { - buf.push($null); - } else if (obj instanceof RegExp) { - buf.push(angular.String.quoteUnicode(obj.toString())); - } else if (isFunction(obj)) { - return; - } else if (isBoolean(obj)) { - buf.push('' + obj); - } else if (isNumber(obj)) { - if (isNaN(obj)) { - buf.push($null); - } else { - buf.push('' + obj); - } - } else if (isString(obj)) { - return buf.push(angular.String.quoteUnicode(obj)); - } else if (isObject(obj)) { - if (isArray(obj)) { - buf.push("["); - var len = obj.length; - var sep = false; - for(var i=0; i - //copile the entire window.document and give me the scope bound to this template. - var rootSscope = angular.compile(window.document)(); - - //compile a piece of html - var rootScope2 = angular.compile(''
click me
')(); - - //compile a piece of html and retain reference to both the dom and scope - var template = angular.element('
click me
'), - scoope = angular.compile(view)(); - //at this point template was transformed into a view - - * - * - * @param {string|DOMElement} element Element or HTML to compile into a template function. - * @returns {function([scope][, cloneAttachFn])} a template function which is used to bind template - * (a DOM element/tree) to a scope. Where: - * - * * `scope` - A {@link angular.scope Scope} to bind to. If none specified, then a new - * root scope is created. - * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the - * `template` and call the `cloneAttachFn` function allowing the caller to attach the - * cloned elements to the DOM document at the approriate place. The `cloneAttachFn` is - * called as:
`cloneAttachFn(clonedElement, scope)` where: - * - * * `clonedElement` - is a clone of the original `element` passed into the compiler. - * * `scope` - is the current scope with which the linking function is working with. - * - * Calling the template function returns the scope to which the element is bound to. It is either - * the same scope as the one passed into the template function, or if none were provided it's the - * newly create scope. - * - * If you need access to the bound view, there are two ways to do it: - * - * - If you are not asking the linking function to clone the template, create the DOM element(s) - * before you send them to the compiler and keep this reference around. - *
- *     var view = angular.element('

{{total}}

'), - * scope = angular.compile(view)(); - *
- * - * - if on the other hand, you need the element to be cloned, the view reference from the original - * example would not point to the clone, but rather to the original template that was cloned. In - * this case, you can access the clone via the cloneAttachFn: - *
- *     var original = angular.element('

{{total}}

'), - * scope = someParentScope.$new(), - * clone; - * - * angular.compile(original)(scope, function(clonedElement, scope) { - * clone = clonedElement; - * //attach the clone to DOM document at the right place - * }); - * - * //now we have reference to the cloned DOM via `clone` - *
- * - * - * Compiler Methods For Widgets and Directives: - * - * The following methods are available for use when you write your own widgets, directives, - * and markup. (Recall that the compile function's this is a reference to the compiler.) - * - * `compile(element)` - returns linker - - * Invoke a new instance of the compiler to compile a DOM element and return a linker function. - * You can apply the linker function to the original element or a clone of the original element. - * The linker function returns a scope. - * - * * `comment(commentText)` - returns element - Create a comment element. - * - * * `element(elementName)` - returns element - Create an element by name. - * - * * `text(text)` - returns element - Create a text element. - * - * * `descend([set])` - returns descend state (true or false). Get or set the current descend - * state. If true the compiler will descend to children elements. - * - * * `directives([set])` - returns directive state (true or false). Get or set the current - * directives processing state. The compiler will process directives only when directives set to - * true. - * - * For information on how the compiler works, see the - * {@link guide/dev_guide.compiler Angular HTML Compiler} section of the Developer Guide. - */ -function Compiler(markup, attrMarkup, directives, widgets){ - this.markup = markup; - this.attrMarkup = attrMarkup; - this.directives = directives; - this.widgets = widgets; -} - -Compiler.prototype = { - compile: function(templateElement) { - templateElement = jqLite(templateElement); - var index = 0, - template, - parent = templateElement.parent(); - if (templateElement.length > 1) { - // https://github.com/angular/angular.js/issues/338 - throw Error("Cannot compile multiple element roots: " + - jqLite('
').append(templateElement.clone()).html()); - } - if (parent && parent[0]) { - parent = parent[0]; - for(var i = 0; i < parent.childNodes.length; i++) { - if (parent.childNodes[i] == templateElement[0]) { - index = i; - } - } - } - template = this.templatize(templateElement, index, 0) || new Template(); - return function(scope, cloneConnectFn){ - // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart - // and sometimes changes the structure of the DOM. - var element = cloneConnectFn - ? JQLitePrototype.clone.call(templateElement) // IMPORTANT!!! - : templateElement; - scope = scope || createScope(); - element.data($$scope, scope); - scope.$element = element; - (cloneConnectFn||noop)(element, scope); - template.attach(element, scope); - scope.$eval(); - return scope; - }; - }, - - - /** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:eval-order - * - * @description - * Normally the view is updated from top to bottom. This usually is - * not a problem, but under some circumstances the values for data - * is not available until after the full view is computed. If such - * values are needed before they are computed the order of - * evaluation can be change using ng:eval-order - * - * @element ANY - * @param {integer|string=} [priority=0] priority integer, or FIRST, LAST constant - * - * @example - * try changing the invoice and see that the Total will lag in evaluation - * @example - - -
TOTAL: without ng:eval-order {{ items.$sum('total') | currency }}
-
TOTAL: with ng:eval-order {{ items.$sum('total') | currency }}
- - - - - - - - - - - - - - - - - - - -
QTYDescriptionCostTotal
{{item.total = item.qty * item.cost | currency}}X
add{{ items.$sum('total') | currency }}
-
- - it('should check ng:format', function(){ - expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99'); - expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$9.99'); - input('item.qty').enter('2'); - expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99'); - expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$19.98'); - }); - -
- */ - - templatize: function(element, elementIndex, priority){ - var self = this, - widget, - fn, - directiveFns = self.directives, - descend = true, - directives = true, - elementName = nodeName_(element), - elementNamespace = elementName.indexOf(':') > 0 ? lowercase(elementName).replace(':', '-') : '', - template, - selfApi = { - compile: bind(self, self.compile), - descend: function(value){ if(isDefined(value)) descend = value; return descend;}, - directives: function(value){ if(isDefined(value)) directives = value; return directives;}, - scope: function(value){ if(isDefined(value)) template.newScope = template.newScope || value; return template.newScope;} - }; - try { - priority = element.attr('ng:eval-order') || priority || 0; - } catch (e) { - // for some reason IE throws error under some weird circumstances. so just assume nothing - priority = priority || 0; - } - element.addClass(elementNamespace); - if (isString(priority)) { - priority = PRIORITY[uppercase(priority)] || parseInt(priority, 10); - } - template = new Template(priority); - eachAttribute(element, function(value, name){ - if (!widget) { - if (widget = self.widgets('@' + name)) { - element.addClass('ng-attr-widget'); - widget = bind(selfApi, widget, value, element); - } - } - }); - if (!widget) { - if (widget = self.widgets(elementName)) { - if (elementNamespace) - element.addClass('ng-widget'); - widget = bind(selfApi, widget, element); - } - } - if (widget) { - descend = false; - directives = false; - var parent = element.parent(); - template.addInit(widget.call(selfApi, element)); - if (parent && parent[0]) { - element = jqLite(parent[0].childNodes[elementIndex]); - } - } - if (descend){ - // process markup for text nodes only - for(var i=0, child=element[0].childNodes; - i 1; i++) { - var key = element.shift(); - var newInstance = instance[key]; - if (!newInstance) { - newInstance = {}; - instance[key] = newInstance; - } - instance = newInstance; - } - instance[element.shift()] = value; - return value; -} - -/////////////////////////////////// -var scopeId = 0, - getterFnCache = {}, - compileCache = {}, - JS_KEYWORDS = {}; -forEach( - ("abstract,boolean,break,byte,case,catch,char,class,const,continue,debugger,default," + - "delete,do,double,else,enum,export,extends,false,final,finally,float,for,function,goto," + - "if,implements,import,ininstanceof,intinterface,long,native,new,null,package,private," + - "protected,public,return,short,static,super,switch,synchronized,this,throw,throws," + - "transient,true,try,typeof,var,volatile,void,undefined,while,with").split(/,/), - function(key){ JS_KEYWORDS[key] = true;} -); -function getterFn(path){ - var fn = getterFnCache[path]; - if (fn) return fn; - - var code = 'var l, fn, t;\n'; - forEach(path.split('.'), function(key) { - key = (JS_KEYWORDS[key]) ? '["' + key + '"]' : '.' + key; - code += 'if(!s) return s;\n' + - 'l=s;\n' + - 's=s' + key + ';\n' + - 'if(typeof s=="function" && !(s instanceof RegExp)) s = function(){ return l'+key+'.apply(l, arguments); };\n'; - if (key.charAt(1) == '$') { - // special code for super-imposed functions - var name = key.substr(2); - code += 'if(!s) {\n' + - ' t = angular.Global.typeOf(l);\n' + - ' fn = (angular[t.charAt(0).toUpperCase() + t.substring(1)]||{})["' + name + '"];\n' + - ' if (fn) s = function(){ return fn.apply(l, [l].concat(Array.prototype.slice.call(arguments, 0, arguments.length))); };\n' + - '}\n'; - } - }); - code += 'return s;'; - fn = Function('s', code); - fn["toString"] = function(){ return code; }; - - return getterFnCache[path] = fn; -} - -/////////////////////////////////// - -function expressionCompile(exp){ - if (typeof exp === $function) return exp; - var fn = compileCache[exp]; - if (!fn) { - var p = parser(exp); - var fnSelf = p.statements(); - fn = compileCache[exp] = extend( - function(){ return fnSelf(this);}, - {fnSelf: fnSelf}); - } - return fn; -} - -function errorHandlerFor(element, error) { - elementError(element, NG_EXCEPTION, isDefined(error) ? formatError(error) : error); -} - - -/** - * @workInProgress - * @ngdoc overview - * @name angular.scope - * - * @description - * Scope is a JavaScript object and the execution context for expressions. You can think about - * scopes as JavaScript objects that have extra APIs for registering watchers. A scope is the - * context in which model (from the model-view-controller design pattern) exists. - * - * Angular scope objects provide the following methods: - * - * * {@link angular.scope.$become $become()} - - * * {@link angular.scope.$bind $bind()} - - * * {@link angular.scope.$eval $eval()} - - * * {@link angular.scope.$get $get()} - - * * {@link angular.scope.$new $new()} - - * * {@link angular.scope.$onEval $onEval()} - - * * {@link angular.scope.$service $service()} - - * * {@link angular.scope.$set $set()} - - * * {@link angular.scope.$tryEval $tryEval()} - - * * {@link angular.scope.$watch $watch()} - - * - * For more information about how angular scope objects work, see {@link guide/dev_guide.scopes - * Angular Scope Objects} in the angular Developer Guide. - */ -function createScope(parent, providers, instanceCache) { - function Parent(){} - parent = Parent.prototype = (parent || {}); - var instance = new Parent(); - var evalLists = {sorted:[]}; - var $log, $exceptionHandler; - - extend(instance, { - 'this': instance, - $id: (scopeId++), - $parent: parent, - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$bind - * @function - * - * @description - * Binds a function `fn` to the current scope. See: {@link angular.bind}. - -
-         var scope = angular.scope();
-         var fn = scope.$bind(function(){
-           return this;
-         });
-         expect(fn()).toEqual(scope);
-       
- * - * @param {function()} fn Function to be bound. - */ - $bind: bind(instance, bind, instance), - - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$get - * @function - * - * @description - * Returns the value for `property_chain` on the current scope. Unlike in JavaScript, if there - * are any `undefined` intermediary properties, `undefined` is returned instead of throwing an - * exception. - * -
-         var scope = angular.scope();
-         expect(scope.$get('person.name')).toEqual(undefined);
-         scope.person = {};
-         expect(scope.$get('person.name')).toEqual(undefined);
-         scope.person.name = 'misko';
-         expect(scope.$get('person.name')).toEqual('misko');
-       
- * - * @param {string} property_chain String representing name of a scope property. Optionally - * properties can be chained with `.` (dot), e.g. `'person.name.first'` - * @returns {*} Value for the (nested) property. - */ - $get: bind(instance, getter, instance), - - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$set - * @function - * - * @description - * Assigns a value to a property of the current scope specified via `property_chain`. Unlike in - * JavaScript, if there are any `undefined` intermediary properties, empty objects are created - * and assigned in to them instead of throwing an exception. - * -
-         var scope = angular.scope();
-         expect(scope.person).toEqual(undefined);
-         scope.$set('person.name', 'misko');
-         expect(scope.person).toEqual({name:'misko'});
-         expect(scope.person.name).toEqual('misko');
-       
- * - * @param {string} property_chain String representing name of a scope property. Optionally - * properties can be chained with `.` (dot), e.g. `'person.name.first'` - * @param {*} value Value to assign to the scope property. - */ - $set: bind(instance, setter, instance), - - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$eval - * @function - * - * @description - * Without the `exp` parameter triggers an eval cycle for this scope and its child scopes. - * - * With the `exp` parameter, compiles the expression to a function and calls it with `this` set - * to the current scope and returns the result. In other words, evaluates `exp` as angular - * expression in the context of the current scope. - * - * # Example -
-         var scope = angular.scope();
-         scope.a = 1;
-         scope.b = 2;
-
-         expect(scope.$eval('a+b')).toEqual(3);
-         expect(scope.$eval(function(){ return this.a + this.b; })).toEqual(3);
-
-         scope.$onEval('sum = a+b');
-         expect(scope.sum).toEqual(undefined);
-         scope.$eval();
-         expect(scope.sum).toEqual(3);
-       
- * - * @param {(string|function())=} exp An angular expression to be compiled to a function or a js - * function. - * - * @returns {*} The result of calling compiled `exp` with `this` set to the current scope. - */ - $eval: function(exp) { - var type = typeof exp; - var i, iSize; - var j, jSize; - var queue; - var fn; - if (type == $undefined) { - for ( i = 0, iSize = evalLists.sorted.length; i < iSize; i++) { - for ( queue = evalLists.sorted[i], - jSize = queue.length, - j= 0; j < jSize; j++) { - instance.$tryEval(queue[j].fn, queue[j].handler); - } - } - } else if (type === $function) { - return exp.call(instance); - } else if (type === 'string') { - return expressionCompile(exp).call(instance); - } - }, - - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$tryEval - * @function - * - * @description - * Evaluates the expression in the context of the current scope just like - * {@link angular.scope.$eval} with expression parameter, but also wraps it in a try/catch - * block. - * - * If an exception is thrown then `exceptionHandler` is used to handle the exception. - * - * # Example -
-         var scope = angular.scope();
-         scope.error = function(){ throw 'myerror'; };
-         scope.$exceptionHandler = function(e) {this.lastException = e; };
-
-         expect(scope.$eval('error()'));
-         expect(scope.lastException).toEqual('myerror');
-         this.lastException = null;
-
-         expect(scope.$eval('error()'),  function(e) {this.lastException = e; });
-         expect(scope.lastException).toEqual('myerror');
-
-         var body = angular.element(window.document.body);
-         expect(scope.$eval('error()'), body);
-         expect(body.attr('ng-exception')).toEqual('"myerror"');
-         expect(body.hasClass('ng-exception')).toEqual(true);
-       
- * - * @param {string|function()} expression Angular expression to evaluate. - * @param {(function()|DOMElement)=} exceptionHandler Function to be called or DOMElement to be - * decorated. - * @returns {*} The result of `expression` evaluation. - */ - $tryEval: function (expression, exceptionHandler) { - var type = typeof expression; - try { - if (type == $function) { - return expression.call(instance); - } else if (type == 'string'){ - return expressionCompile(expression).call(instance); - } - } catch (e) { - if ($log) $log.error(e); - if (isFunction(exceptionHandler)) { - exceptionHandler(e); - } else if (exceptionHandler) { - errorHandlerFor(exceptionHandler, e); - } else if (isFunction($exceptionHandler)) { - $exceptionHandler(e); - } - } - }, - - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$watch - * @function - * - * @description - * Registers `listener` as a callback to be executed every time the `watchExp` changes. Be aware - * that the callback gets, by default, called upon registration, this can be prevented via the - * `initRun` parameter. - * - * # Example -
-         var scope = angular.scope();
-         scope.name = 'misko';
-         scope.counter = 0;
-
-         expect(scope.counter).toEqual(0);
-         scope.$watch('name', 'counter = counter + 1');
-         expect(scope.counter).toEqual(1);
-
-         scope.$eval();
-         expect(scope.counter).toEqual(1);
-
-         scope.name = 'adam';
-         scope.$eval();
-         expect(scope.counter).toEqual(2);
-       
- * - * @param {function()|string} watchExp Expression that should be evaluated and checked for - * change during each eval cycle. Can be an angular string expression or a function. - * @param {function()|string} listener Function (or angular string expression) that gets called - * every time the value of the `watchExp` changes. The function will be called with two - * parameters, `newValue` and `oldValue`. - * @param {(function()|DOMElement)=} [exceptionHanlder=angular.service.$exceptionHandler] Handler - * that gets called when `watchExp` or `listener` throws an exception. If a DOMElement is - * specified as handler, the element gets decorated by angular with the information about the - * exception. - * @param {boolean=} [initRun=true] Flag that prevents the first execution of the listener upon - * registration. - * - */ - $watch: function(watchExp, listener, exceptionHandler, initRun) { - var watch = expressionCompile(watchExp), - last = watch.call(instance); - listener = expressionCompile(listener); - function watcher(firstRun){ - var value = watch.call(instance), - // we have to save the value because listener can call ourselves => inf loop - lastValue = last; - if (firstRun || lastValue !== value) { - last = value; - instance.$tryEval(function(){ - return listener.call(instance, value, lastValue); - }, exceptionHandler); - } - } - instance.$onEval(PRIORITY_WATCH, watcher); - if (isUndefined(initRun)) initRun = true; - if (initRun) watcher(true); - }, - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$onEval - * @function - * - * @description - * Evaluates the `expr` expression in the context of the current scope during each - * {@link angular.scope.$eval eval cycle}. - * - * # Example -
-         var scope = angular.scope();
-         scope.counter = 0;
-         scope.$onEval('counter = counter + 1');
-         expect(scope.counter).toEqual(0);
-         scope.$eval();
-         expect(scope.counter).toEqual(1);
-       
- * - * @param {number} [priority=0] Execution priority. Lower priority numbers get executed first. - * @param {string|function()} expr Angular expression or function to be executed. - * @param {(function()|DOMElement)=} [exceptionHandler=angular.service.$exceptionHandler] Handler - * function to call or DOM element to decorate when an exception occurs. - * - */ - $onEval: function(priority, expr, exceptionHandler){ - if (!isNumber(priority)) { - exceptionHandler = expr; - expr = priority; - priority = 0; - } - var evalList = evalLists[priority]; - if (!evalList) { - evalList = evalLists[priority] = []; - evalList.priority = priority; - evalLists.sorted.push(evalList); - evalLists.sorted.sort(function(a,b){return a.priority-b.priority;}); - } - evalList.push({ - fn: expressionCompile(expr), - handler: exceptionHandler - }); - }, - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$become - * @function - * @deprecated This method will be removed before 1.0 - * - * @description - * Modifies the scope to act like an instance of the given class by: - * - * - copying the class's prototype methods - * - applying the class's initialization function to the scope instance (without using the new - * operator) - * - * That makes the scope be a `this` for the given class's methods — effectively an instance of - * the given class with additional (scope) stuff. A scope can later `$become` another class. - * - * `$become` gets used to make the current scope act like an instance of a controller class. - * This allows for use of a controller class in two ways. - * - * - as an ordinary JavaScript class for standalone testing, instantiated using the new - * operator, with no attached view. - * - as a controller for an angular model stored in a scope, "instantiated" by - * `scope.$become(ControllerClass)`. - * - * Either way, the controller's methods refer to the model variables like `this.name`. When - * stored in a scope, the model supports data binding. When bound to a view, {{name}} in the - * HTML template refers to the same variable. - */ - $become: function(Class) { - if (isFunction(Class)) { - instance.constructor = Class; - forEach(Class.prototype, function(fn, name){ - instance[name] = bind(instance, fn); - }); - instance.$service.invoke(instance, Class, slice.call(arguments, 1, arguments.length)); - - //TODO: backwards compatibility hack, remove when we don't depend on init methods - if (isFunction(Class.prototype.init)) { - instance.init(); - } - } - }, - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$new - * @function - * - * @description - * Creates a new {@link angular.scope scope}, that: - * - * - is a child of the current scope - * - will {@link angular.scope.$become $become} of type specified via `constructor` - * - * @param {function()} constructor Constructor function of the type the new scope should assume. - * @returns {Object} The newly created child scope. - * - */ - $new: function(constructor) { - var child = createScope(instance); - child.$become.apply(instance, concat([constructor], arguments, 1)); - instance.$onEval(child.$eval); - return child; - } - - }); - - if (!parent.$root) { - instance.$root = instance; - instance.$parent = instance; - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$service - * @function - * - * @description - * Provides access to angular's dependency injector and - * {@link angular.service registered services}. In general the use of this api is discouraged, - * except for tests and components that currently don't support dependency injection (widgets, - * filters, etc). - * - * @param {string} serviceId String ID of the service to return. - * @returns {*} Value, object or function returned by the service factory function if any. - */ - (instance.$service = createInjector(instance, providers, instanceCache)).eager(); - } - - $log = instance.$service('$log'); - $exceptionHandler = instance.$service('$exceptionHandler'); - - return instance; -} -/** - * @ngdoc function - * @name angular.injector - * @function - * - * @description - * Creates an injector function that can be used for retrieving services as well as for - * dependency injection (see {@link guide/dev_guide.di dependency injection}). - * - * Angular creates an injector automatically for the root scope and it is available as the - * {@link angular.scope.$service $service} property. Creation of the injector automatically creates - * all of the `$eager` {@link angular.service services}. - * - * @param {Object=} [factoryScope={}] `this` for the service factory function. - * @param {Object.=} [factories=angular.service] Map of service factory - * functions. - * @param {Object.=} [instanceCache={}] Place where instances of services are - * saved for reuse. Can also be used to override services specified by `serviceFactory` - * (useful in tests). - * @returns {function()} Injector function: - * - * * `injector(serviceName)`: - * * `serviceName` - `{string=}` - name of the service to retrieve. - * - * The injector function also has these properties: - * - * * an `invoke` property which can be used to invoke methods with dependency-injected arguments. - * `injector.invoke(self, fn, curryArgs)` - * * `self` - "`this`" to be used when invoking the function. - * * `fn` - the function to be invoked. The function may have the `$inject` property which - * lists the set of arguments which should be auto injected - * (see {@link guide.di dependency injection}). - * * `curryArgs(array)` - optional array of arguments to pass to function invocation after the - * injection arguments (also known as curry arguments or currying). - * * an `eager` property which is used to initialize the eager services. - * `injector.eager()` - */ -function createInjector(factoryScope, factories, instanceCache) { - factories = factories || angularService; - instanceCache = instanceCache || {}; - factoryScope = factoryScope || {}; - injector.invoke = invoke; - - injector.eager = function(){ - forEach(factories, function(factory, name){ - if (factory.$eager) - injector(name); - - if (factory.$creation) - throw new Error("Failed to register service '" + name + - "': $creation property is unsupported. Use $eager:true or see release notes."); - }); - }; - return injector; - - function injector(value){ - if (!(value in instanceCache)) { - var factory = factories[value]; - if (!factory) throw Error("Unknown provider for '"+value+"'."); - instanceCache[value] = invoke(factoryScope, factory); - } - return instanceCache[value]; - }; - - function invoke(self, fn, args){ - args = args || []; - var injectNames = injectionArgs(fn); - var i = injectNames.length; - while(i--) { - args.unshift(injector(injectNames[i])); - } - return fn.apply(self, args); - } -} - -/*NOT_PUBLIC_YET - * @ngdoc function - * @name angular.annotate - * @function - * - * @description - * Annotate the function with injection arguments. This is equivalent to setting the `$inject` - * property as described in {@link guide.di dependency injection}. - * - *
- * var MyController = angular.annotate('$location', function($location){ ... });
- * 
- * - * is the same as - * - *
- * var MyController = function($location){ ... };
- * MyController.$inject = ['$location'];
- * 
- * - * @param {String|Array} serviceName... zero or more service names to inject into the - * `annotatedFunction`. - * @param {function} annotatedFunction function to annotate with `$inject` - * functions. - * @returns {function} `annotatedFunction` - */ -function annotate(services, fn) { - if (services instanceof Array) { - fn.$inject = services; - return fn; - } else { - var i = 0, - length = arguments.length - 1, // last one is the destination function - $inject = arguments[length].$inject = []; - for (; i < length; i++) { - $inject.push(arguments[i]); - } - return arguments[length]; // return the last one - } -} - -function angularServiceInject(name, fn, inject, eager) { - angularService(name, fn, {$inject:inject, $eager:eager}); -} - - -/** - * @returns the $inject property of function. If not found the - * the $inject is computed by looking at the toString of function and - * extracting all arguments which and assuming that they are the - * injection names. - */ -var FN_ARGS = /^function\s*[^\(]*\(([^\)]*)\)/m; -var FN_ARG_SPLIT = /,/; -var FN_ARG = /^\s*(.+?)\s*$/; -var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; -function injectionArgs(fn) { - assertArgFn(fn); - if (!fn.$inject) { - var args = fn.$inject = []; - var fnText = fn.toString().replace(STRIP_COMMENTS, ''); - var argDecl = fnText.match(FN_ARGS); - forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ - arg.replace(FN_ARG, function(all, name){ - args.push(name); - }); - }); - } - return fn.$inject; -} -var OPERATORS = { - 'null':function(self){return null;}, - 'true':function(self){return true;}, - 'false':function(self){return false;}, - $undefined:noop, - '+':function(self, a,b){return (isDefined(a)?a:0)+(isDefined(b)?b:0);}, - '-':function(self, a,b){return (isDefined(a)?a:0)-(isDefined(b)?b:0);}, - '*':function(self, a,b){return a*b;}, - '/':function(self, a,b){return a/b;}, - '%':function(self, a,b){return a%b;}, - '^':function(self, a,b){return a^b;}, - '=':noop, - '==':function(self, a,b){return a==b;}, - '!=':function(self, a,b){return a!=b;}, - '<':function(self, a,b){return a':function(self, a,b){return a>b;}, - '<=':function(self, a,b){return a<=b;}, - '>=':function(self, a,b){return a>=b;}, - '&&':function(self, a,b){return a&&b;}, - '||':function(self, a,b){return a||b;}, - '&':function(self, a,b){return a&b;}, -// '|':function(self, a,b){return a|b;}, - '|':function(self, a,b){return b(self, a);}, - '!':function(self, a){return !a;} -}; -var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; - -function lex(text, parseStringsForObjects){ - var dateParseLength = parseStringsForObjects ? DATE_ISOSTRING_LN : -1, - tokens = [], - token, - index = 0, - json = [], - ch, - lastCh = ':'; // can start regexp - - while (index < text.length) { - ch = text.charAt(index); - if (is('"\'')) { - readString(ch); - } else if (isNumber(ch) || is('.') && isNumber(peek())) { - readNumber(); - } else if (isIdent(ch)) { - readIdent(); - // identifiers can only be if the preceding char was a { or , - if (was('{,') && json[0]=='{' && - (token=tokens[tokens.length-1])) { - token.json = token.text.indexOf('.') == -1; - } - } else if (is('(){}[].,;:')) { - tokens.push({ - index:index, - text:ch, - json:(was(':[,') && is('{[')) || is('}]:,') - }); - if (is('{[')) json.unshift(ch); - if (is('}]')) json.shift(); - index++; - } else if (isWhitespace(ch)) { - index++; - continue; - } else { - var ch2 = ch + peek(), - fn = OPERATORS[ch], - fn2 = OPERATORS[ch2]; - if (fn2) { - tokens.push({index:index, text:ch2, fn:fn2}); - index += 2; - } else if (fn) { - tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')}); - index += 1; - } else { - throwError("Unexpected next character ", index, index+1); - } - } - lastCh = ch; - } - return tokens; - - function is(chars) { - return chars.indexOf(ch) != -1; - } - - function was(chars) { - return chars.indexOf(lastCh) != -1; - } - - function peek() { - return index + 1 < text.length ? text.charAt(index + 1) : false; - } - function isNumber(ch) { - return '0' <= ch && ch <= '9'; - } - function isWhitespace(ch) { - return ch == ' ' || ch == '\r' || ch == '\t' || - ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0 - } - function isIdent(ch) { - return 'a' <= ch && ch <= 'z' || - 'A' <= ch && ch <= 'Z' || - '_' == ch || ch == '$'; - } - function isExpOperator(ch) { - return ch == '-' || ch == '+' || isNumber(ch); - } - - function throwError(error, start, end) { - end = end || index; - throw Error("Lexer Error: " + error + " at column" + - (isDefined(start) - ? "s " + start + "-" + index + " [" + text.substring(start, end) + "]" - : " " + end) + - " in expression [" + text + "]."); - } - - function readNumber() { - var number = ""; - var start = index; - while (index < text.length) { - var ch = lowercase(text.charAt(index)); - if (ch == '.' || isNumber(ch)) { - number += ch; - } else { - var peekCh = peek(); - if (ch == 'e' && isExpOperator(peekCh)) { - number += ch; - } else if (isExpOperator(ch) && - peekCh && isNumber(peekCh) && - number.charAt(number.length - 1) == 'e') { - number += ch; - } else if (isExpOperator(ch) && - (!peekCh || !isNumber(peekCh)) && - number.charAt(number.length - 1) == 'e') { - throwError('Invalid exponent'); - } else { - break; - } - } - index++; - } - number = 1 * number; - tokens.push({index:start, text:number, json:true, - fn:function(){return number;}}); - } - function readIdent() { - var ident = ""; - var start = index; - var fn; - while (index < text.length) { - var ch = text.charAt(index); - if (ch == '.' || isIdent(ch) || isNumber(ch)) { - ident += ch; - } else { - break; - } - index++; - } - fn = OPERATORS[ident]; - tokens.push({ - index:start, - text:ident, - json: fn, - fn:fn||extend(getterFn(ident), { - assign:function(self, value){ - return setter(self, ident, value); - } - }) - }); - } - - function readString(quote) { - var start = index; - index++; - var string = ""; - var rawString = quote; - var escape = false; - while (index < text.length) { - var ch = text.charAt(index); - rawString += ch; - if (escape) { - if (ch == 'u') { - var hex = text.substring(index + 1, index + 5); - if (!hex.match(/[\da-f]{4}/i)) - throwError( "Invalid unicode escape [\\u" + hex + "]"); - index += 4; - string += String.fromCharCode(parseInt(hex, 16)); - } else { - var rep = ESCAPE[ch]; - if (rep) { - string += rep; - } else { - string += ch; - } - } - escape = false; - } else if (ch == '\\') { - escape = true; - } else if (ch == quote) { - index++; - tokens.push({index:start, text:rawString, string:string, json:true, - fn:function(){ - return (string.length == dateParseLength) - ? angular['String']['toDate'](string) - : string; - }}); - return; - } else { - string += ch; - } - index++; - } - throwError("Unterminated quote", start); - } -} - -///////////////////////////////////////// - -function parser(text, json){ - var ZERO = valueFn(0), - tokens = lex(text, json), - assignment = _assignment, - assignable = logicalOR, - functionCall = _functionCall, - fieldAccess = _fieldAccess, - objectIndex = _objectIndex, - filterChain = _filterChain, - functionIdent = _functionIdent, - pipeFunction = _pipeFunction; - if(json){ - // The extra level of aliasing is here, just in case the lexer misses something, so that - // we prevent any accidental execution in JSON. - assignment = logicalOR; - functionCall = - fieldAccess = - objectIndex = - assignable = - filterChain = - functionIdent = - pipeFunction = - function (){ throwError("is not valid json", {text:text, index:0}); }; - } - //TODO: Shouldn't all of the public methods have assertAllConsumed? - //TODO: I think these should be public as part of the parser api instead of scope.$eval(). - return { - assignable: assertConsumed(assignable), - primary: assertConsumed(primary), - statements: assertConsumed(statements), - validator: assertConsumed(validator), - formatter: assertConsumed(formatter), - filter: assertConsumed(filter) - }; - - function assertConsumed(fn) { - return function(){ - var value = fn(); - if (tokens.length !== 0) { - throwError("is an unexpected token", tokens[0]); - } - return value; - }; - } - - /////////////////////////////////// - function throwError(msg, token) { - throw Error("Syntax Error: Token '" + token.text + - "' " + msg + " at column " + - (token.index + 1) + " of the expression [" + - text + "] starting at [" + text.substring(token.index) + "]."); - } - - function peekToken() { - if (tokens.length === 0) - throw Error("Unexpected end of expression: " + text); - return tokens[0]; - } - - function peek(e1, e2, e3, e4) { - if (tokens.length > 0) { - var token = tokens[0]; - var t = token.text; - if (t==e1 || t==e2 || t==e3 || t==e4 || - (!e1 && !e2 && !e3 && !e4)) { - return token; - } - } - return false; - } - - function expect(e1, e2, e3, e4){ - var token = peek(e1, e2, e3, e4); - if (token) { - if (json && !token.json) { - index = token.index; - throwError("is not valid json", token); - } - tokens.shift(); - this.currentToken = token; - return token; - } - return false; - } - - function consume(e1){ - if (!expect(e1)) { - throwError("is unexpected, expecting [" + e1 + "]", peek()); - } - } - - function unaryFn(fn, right) { - return function(self) { - return fn(self, right(self)); - }; - } - - function binaryFn(left, fn, right) { - return function(self) { - return fn(self, left(self), right(self)); - }; - } - - function hasTokens () { - return tokens.length > 0; - } - - function statements(){ - var statements = []; - while(true) { - if (tokens.length > 0 && !peek('}', ')', ';', ']')) - statements.push(filterChain()); - if (!expect(';')) { - // optimize for the common case where there is only one statement. - // TODO(size): maybe we should not support multiple statements? - return statements.length == 1 - ? statements[0] - : function (self){ - var value; - for ( var i = 0; i < statements.length; i++) { - var statement = statements[i]; - if (statement) - value = statement(self); - } - return value; - }; - } - } - } - - function _filterChain(){ - var left = expression(); - var token; - while(true) { - if ((token = expect('|'))) { - left = binaryFn(left, token.fn, filter()); - } else { - return left; - } - } - } - - function filter(){ - return pipeFunction(angularFilter); - } - - function validator(){ - return pipeFunction(angularValidator); - } - - function formatter(){ - var token = expect(); - var formatter = angularFormatter[token.text]; - var argFns = []; - if (!formatter) throwError('is not a valid formatter.', token); - while(true) { - if ((token = expect(':'))) { - argFns.push(expression()); - } else { - return valueFn({ - format:invokeFn(formatter.format), - parse:invokeFn(formatter.parse) - }); - } - } - function invokeFn(fn){ - return function(self, input){ - var args = [input]; - for ( var i = 0; i < argFns.length; i++) { - args.push(argFns[i](self)); - } - return fn.apply(self, args); - }; - } - } - - function _pipeFunction(fnScope){ - var fn = functionIdent(fnScope); - var argsFn = []; - var token; - while(true) { - if ((token = expect(':'))) { - argsFn.push(expression()); - } else { - var fnInvoke = function(self, input){ - var args = [input]; - for ( var i = 0; i < argsFn.length; i++) { - args.push(argsFn[i](self)); - } - return fn.apply(self, args); - }; - return function(){ - return fnInvoke; - }; - } - } - } - - function expression(){ - return assignment(); - } - - function _assignment(){ - var left = logicalOR(); - var right; - var token; - if (token = expect('=')) { - if (!left.assign) { - throwError("implies assignment but [" + - text.substring(0, token.index) + "] can not be assigned to", token); - } - right = logicalOR(); - return function(self){ - return left.assign(self, right(self)); - }; - } else { - return left; - } - } - - function logicalOR(){ - var left = logicalAND(); - var token; - while(true) { - if ((token = expect('||'))) { - left = binaryFn(left, token.fn, logicalAND()); - } else { - return left; - } - } - } - - function logicalAND(){ - var left = equality(); - var token; - if ((token = expect('&&'))) { - left = binaryFn(left, token.fn, logicalAND()); - } - return left; - } - - function equality(){ - var left = relational(); - var token; - if ((token = expect('==','!='))) { - left = binaryFn(left, token.fn, equality()); - } - return left; - } - - function relational(){ - var left = additive(); - var token; - if (token = expect('<', '>', '<=', '>=')) { - left = binaryFn(left, token.fn, relational()); - } - return left; - } - - function additive(){ - var left = multiplicative(); - var token; - while(token = expect('+','-')) { - left = binaryFn(left, token.fn, multiplicative()); - } - return left; - } - - function multiplicative(){ - var left = unary(); - var token; - while(token = expect('*','/','%')) { - left = binaryFn(left, token.fn, unary()); - } - return left; - } - - function unary(){ - var token; - if (expect('+')) { - return primary(); - } else if (token = expect('-')) { - return binaryFn(ZERO, token.fn, unary()); - } else if (token = expect('!')) { - return unaryFn(token.fn, unary()); - } else { - return primary(); - } - } - - function _functionIdent(fnScope) { - var token = expect(); - var element = token.text.split('.'); - var instance = fnScope; - var key; - for ( var i = 0; i < element.length; i++) { - key = element[i]; - if (instance) - instance = instance[key]; - } - if (typeof instance != $function) { - throwError("should be a function", token); - } - return instance; - } - - function primary() { - var primary; - if (expect('(')) { - var expression = filterChain(); - consume(')'); - primary = expression; - } else if (expect('[')) { - primary = arrayDeclaration(); - } else if (expect('{')) { - primary = object(); - } else { - var token = expect(); - primary = token.fn; - if (!primary) { - throwError("not a primary expression", token); - } - } - var next; - while (next = expect('(', '[', '.')) { - if (next.text === '(') { - primary = functionCall(primary); - } else if (next.text === '[') { - primary = objectIndex(primary); - } else if (next.text === '.') { - primary = fieldAccess(primary); - } else { - throwError("IMPOSSIBLE"); - } - } - return primary; - } - - function _fieldAccess(object) { - var field = expect().text; - var getter = getterFn(field); - return extend(function (self){ - return getter(object(self)); - }, { - assign:function(self, value){ - return setter(object(self), field, value); - } - }); - } - - function _objectIndex(obj) { - var indexFn = expression(); - consume(']'); - return extend( - function (self){ - var o = obj(self); - var i = indexFn(self); - return (o) ? o[i] : undefined; - }, { - assign:function(self, value){ - return obj(self)[indexFn(self)] = value; - } - }); - } - - function _functionCall(fn) { - var argsFn = []; - if (peekToken().text != ')') { - do { - argsFn.push(expression()); - } while (expect(',')); - } - consume(')'); - return function (self){ - var args = []; - for ( var i = 0; i < argsFn.length; i++) { - args.push(argsFn[i](self)); - } - var fnPtr = fn(self) || noop; - // IE stupidity! - return fnPtr.apply - ? fnPtr.apply(self, args) - : fnPtr(args[0], args[1], args[2], args[3], args[4]); - }; - } - - // This is used with json array declaration - function arrayDeclaration () { - var elementFns = []; - if (peekToken().text != ']') { - do { - elementFns.push(expression()); - } while (expect(',')); - } - consume(']'); - return function (self){ - var array = []; - for ( var i = 0; i < elementFns.length; i++) { - array.push(elementFns[i](self)); - } - return array; - }; - } - - function object () { - var keyValues = []; - if (peekToken().text != '}') { - do { - var token = expect(), - key = token.string || token.text; - consume(":"); - var value = expression(); - keyValues.push({key:key, value:value}); - } while (expect(',')); - } - consume('}'); - return function (self){ - var object = {}; - for ( var i = 0; i < keyValues.length; i++) { - var keyValue = keyValues[i]; - var value = keyValue.value(self); - object[keyValue.key] = value; - } - return object; - }; - } - - function watchDecl () { - var anchorName = expect().text; - consume(":"); - var expressionFn; - if (peekToken().text == '{') { - consume("{"); - expressionFn = statements(); - consume("}"); - } else { - expressionFn = expression(); - } - return function(self) { - return {name:anchorName, fn:expressionFn}; - }; - } -} - - - - - -function Route(template, defaults) { - this.template = template = template + '#'; - this.defaults = defaults || {}; - var urlParams = this.urlParams = {}; - forEach(template.split(/\W/), function(param){ - if (param && template.match(new RegExp(":" + param + "\\W"))) { - urlParams[param] = true; - } - }); -} - -Route.prototype = { - url: function(params) { - var self = this, - url = this.template, - encodedVal; - - params = params || {}; - forEach(this.urlParams, function(_, urlParam){ - encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || ""); - url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1"); - }); - url = url.replace(/\/?#$/, ''); - var query = []; - forEachSorted(params, function(value, key){ - if (!self.urlParams[key]) { - query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); - } - }); - url = url.replace(/\/*$/, ''); - return url + (query.length ? '?' + query.join('&') : ''); - } -}; - -function ResourceFactory(xhr) { - this.xhr = xhr; -} - -ResourceFactory.DEFAULT_ACTIONS = { - 'get': {method:'GET'}, - 'save': {method:'POST'}, - 'query': {method:'GET', isArray:true}, - 'remove': {method:'DELETE'}, - 'delete': {method:'DELETE'} -}; - -ResourceFactory.prototype = { - route: function(url, paramDefaults, actions){ - var self = this; - var route = new Route(url); - actions = extend({}, ResourceFactory.DEFAULT_ACTIONS, actions); - function extractParams(data){ - var ids = {}; - forEach(paramDefaults || {}, function(value, key){ - ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; - }); - return ids; - } - - function Resource(value){ - copy(value || {}, this); - } - - forEach(actions, function(action, name){ - var isPostOrPut = action.method == 'POST' || action.method == 'PUT'; - Resource[name] = function (a1, a2, a3) { - var params = {}; - var data; - var callback = noop; - switch(arguments.length) { - case 3: callback = a3; - case 2: - if (isFunction(a2)) { - callback = a2; - //fallthrough - } else { - params = a1; - data = a2; - break; - } - case 1: - if (isFunction(a1)) callback = a1; - else if (isPostOrPut) data = a1; - else params = a1; - break; - case 0: break; - default: - throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments."; - } - - var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); - self.xhr( - action.method, - route.url(extend({}, action.params || {}, extractParams(data), params)), - data, - function(status, response, clear) { - if (200 <= status && status < 300) { - if (response) { - if (action.isArray) { - value.length = 0; - forEach(response, function(item){ - value.push(new Resource(item)); - }); - } else { - copy(response, value); - } - } - (callback||noop)(value); - } else { - throw {status: status, response:response, message: status + ": " + response}; - } - }, - action.verifyCache); - return value; - }; - - Resource.bind = function(additionalParamDefaults){ - return self.route(url, extend({}, paramDefaults, additionalParamDefaults), actions); - }; - - Resource.prototype['$' + name] = function(a1, a2){ - var params = extractParams(this); - var callback = noop; - switch(arguments.length) { - case 2: params = a1; callback = a2; - case 1: if (typeof a1 == $function) callback = a1; else params = a1; - case 0: break; - default: - throw "Expected between 1-2 arguments [params, callback], got " + arguments.length + " arguments."; - } - var data = isPostOrPut ? this : undefined; - Resource[name].call(this, params, data, callback); - }; - }); - return Resource; - } -}; -////////////////////////////// -// Browser -////////////////////////////// -var XHR = window.XMLHttpRequest || function () { - try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {} - try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {} - try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {} - throw new Error("This browser does not support XMLHttpRequest."); -}; - - -/** - * @private - * @name Browser - * - * @description - * Constructor for the object exposed as $browser service. - * - * This object has two goals: - * - * - hide all the global state in the browser caused by the window object - * - abstract away all the browser specific features and inconsistencies - * - * @param {object} window The global window object. - * @param {object} document jQuery wrapped document. - * @param {object} body jQuery wrapped document.body. - * @param {function()} XHR XMLHttpRequest constructor. - * @param {object} $log console.log or an object with the same interface. - */ -function Browser(window, document, body, XHR, $log) { - var self = this, - rawDocument = document[0], - location = window.location, - setTimeout = window.setTimeout; - - self.isMock = false; - - ////////////////////////////////////////////////////////////// - // XHR API - ////////////////////////////////////////////////////////////// - var idCounter = 0; - var outstandingRequestCount = 0; - var outstandingRequestCallbacks = []; - - - /** - * Executes the `fn` function (supports currying) and decrements the `outstandingRequestCallbacks` - * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. - */ - function completeOutstandingRequest(fn) { - try { - fn.apply(null, slice.call(arguments, 1)); - } finally { - outstandingRequestCount--; - if (outstandingRequestCount === 0) { - while(outstandingRequestCallbacks.length) { - try { - outstandingRequestCallbacks.pop()(); - } catch (e) { - $log.error(e); - } - } - } - } - } - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#xhr - * @methodOf angular.service.$browser - * - * @param {string} method Requested method (get|post|put|delete|head|json) - * @param {string} url Requested url - * @param {?string} post Post data to send (null if nothing to post) - * @param {function(number, string)} callback Function that will be called on response - * @param {object=} header additional HTTP headers to send with XHR. - * Standard headers are: - *
    - *
  • Content-Type: application/x-www-form-urlencoded
  • - *
  • Accept: application/json, text/plain, */*
  • - *
  • X-Requested-With: XMLHttpRequest
  • - *
- * - * @description - * Send ajax request - */ - self.xhr = function(method, url, post, callback, headers) { - outstandingRequestCount ++; - if (lowercase(method) == 'json') { - var callbackId = ("angular_" + Math.random() + '_' + (idCounter++)).replace(/\d\./, ''); - var script = jqLite(rawDocument.createElement('script')) - .attr({type: 'text/javascript', src: url.replace('JSON_CALLBACK', callbackId)}); - window[callbackId] = function(data){ - window[callbackId] = undefined; - script.remove(); - completeOutstandingRequest(callback, 200, data); - }; - body.append(script); - } else { - var xhr = new XHR(); - xhr.open(method, url, true); - forEach(headers, function(value, key) { - if (value) xhr.setRequestHeader(key, value); - }); - xhr.onreadystatechange = function() { - if (xhr.readyState == 4) { - // normalize IE bug (http://bugs.jquery.com/ticket/1450) - var status = xhr.status == 1223 ? 204 : xhr.status || 200; - completeOutstandingRequest(callback, status, xhr.responseText); - } - }; - xhr.send(post || ''); - } - }; - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#notifyWhenNoOutstandingRequests - * @methodOf angular.service.$browser - * - * @param {function()} callback Function that will be called when no outstanding request - */ - self.notifyWhenNoOutstandingRequests = function(callback) { - if (outstandingRequestCount === 0) { - callback(); - } else { - outstandingRequestCallbacks.push(callback); - } - }; - - ////////////////////////////////////////////////////////////// - // Poll Watcher API - ////////////////////////////////////////////////////////////// - var pollFns = [], - pollTimeout; - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#poll - * @methodOf angular.service.$browser - */ - self.poll = function() { - forEach(pollFns, function(pollFn){ pollFn(); }); - }; - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#addPollFn - * @methodOf angular.service.$browser - * - * @param {function()} fn Poll function to add - * - * @description - * Adds a function to the list of functions that poller periodically executes, - * and starts polling if not started yet. - * - * @returns {function()} the added function - */ - self.addPollFn = function(fn) { - if (!pollTimeout) self.startPoller(100, setTimeout); - pollFns.push(fn); - return fn; - }; - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#startPoller - * @methodOf angular.service.$browser - * - * @param {number} interval How often should browser call poll functions (ms) - * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. - * - * @description - * Configures the poller to run in the specified intervals, using the specified - * setTimeout fn and kicks it off. - */ - self.startPoller = function(interval, setTimeout) { - (function check(){ - self.poll(); - pollTimeout = setTimeout(check, interval); - })(); - }; - - ////////////////////////////////////////////////////////////// - // URL API - ////////////////////////////////////////////////////////////// - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#setUrl - * @methodOf angular.service.$browser - * - * @param {string} url New url - * - * @description - * Sets browser's url - */ - self.setUrl = function(url) { - var existingURL = location.href; - if (!existingURL.match(/#/)) existingURL += '#'; - if (!url.match(/#/)) url += '#'; - location.href = url; - }; - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#getUrl - * @methodOf angular.service.$browser - * - * @description - * Get current browser's url - * - * @returns {string} Browser's url - */ - self.getUrl = function() { - return location.href; - }; - - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#onHashChange - * @methodOf angular.service.$browser - * - * @description - * Detects if browser support onhashchange events and register a listener otherwise registers - * $browser poller. The `listener` will then get called when the hash changes. - * - * The listener gets called with either HashChangeEvent object or simple object that also contains - * `oldURL` and `newURL` properties. - * - * NOTE: this api is intended for use only by the $location service. Please use the - * {@link angular.service.$location $location service} to monitor hash changes in angular apps. - * - * @param {function(event)} listener Listener function to be called when url hash changes. - * @return {function()} Returns the registered listener fn - handy if the fn is anonymous. - */ - self.onHashChange = function(listener) { - // IE8 comp mode returns true, but doesn't support hashchange event - var dm = window.document.documentMode; - if ('onhashchange' in window && (isUndefined(dm) || dm >= 8)) { - jqLite(window).bind('hashchange', listener); - } else { - var lastBrowserUrl = self.getUrl(); - - self.addPollFn(function() { - if (lastBrowserUrl != self.getUrl()) { - listener(); - lastBrowserUrl = self.getUrl(); - } - }); - } - return listener; - }; - - ////////////////////////////////////////////////////////////// - // Cookies API - ////////////////////////////////////////////////////////////// - var lastCookies = {}; - var lastCookieString = ''; - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#cookies - * @methodOf angular.service.$browser - * - * @param {string=} name Cookie name - * @param {string=} value Cokkie value - * - * @description - * The cookies method provides a 'private' low level access to browser cookies. - * It is not meant to be used directly, use the $cookie service instead. - * - * The return values vary depending on the arguments that the method was called with as follows: - *
    - *
  • cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it
  • - *
  • cookies(name, value) -> set name to value, if value is undefined delete the cookie
  • - *
  • cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)
  • - *
- * - * @returns {Object} Hash of all cookies (if called without any parameter) - */ - self.cookies = function (name, value) { - var cookieLength, cookieArray, cookie, i, keyValue, index; - - if (name) { - if (value === undefined) { - rawDocument.cookie = escape(name) + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; - } else { - if (isString(value)) { - rawDocument.cookie = escape(name) + '=' + escape(value); - - cookieLength = name.length + value.length + 1; - if (cookieLength > 4096) { - $log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+ - cookieLength + " > 4096 bytes)!"); - } - if (lastCookies.length > 20) { - $log.warn("Cookie '"+ name +"' possibly not set or overflowed because too many cookies " + - "were already set (" + lastCookies.length + " > 20 )"); - } - } - } - } else { - if (rawDocument.cookie !== lastCookieString) { - lastCookieString = rawDocument.cookie; - cookieArray = lastCookieString.split("; "); - lastCookies = {}; - - for (i = 0; i < cookieArray.length; i++) { - cookie = cookieArray[i]; - index = cookie.indexOf('='); - if (index > 0) { //ignore nameless cookies - lastCookies[unescape(cookie.substring(0, index))] = unescape(cookie.substring(index + 1)); - } - } - } - return lastCookies; - } - }; - - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#defer - * @methodOf angular.service.$browser - * @param {function()} fn A function, who's execution should be defered. - * @param {number=} [delay=0] of milliseconds to defer the function execution. - * - * @description - * Executes a fn asynchroniously via `setTimeout(fn, delay)`. - * - * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using - * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed via - * `$browser.defer.flush()`. - * - */ - self.defer = function(fn, delay) { - outstandingRequestCount++; - setTimeout(function() { completeOutstandingRequest(fn); }, delay || 0); - }; - - ////////////////////////////////////////////////////////////// - // Misc API - ////////////////////////////////////////////////////////////// - var hoverListener = noop; - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#hover - * @methodOf angular.service.$browser - * - * @description - * Set hover listener. - * - * @param {function(Object, boolean)} listener Function that will be called when a hover event - * occurs. - */ - self.hover = function(listener) { hoverListener = listener; }; - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#bind - * @methodOf angular.service.$browser - * - * @description - * Register hover function to real browser - */ - self.bind = function() { - document.bind("mouseover", function(event){ - hoverListener(jqLite(msie ? event.srcElement : event.target), true); - return true; - }); - document.bind("mouseleave mouseout click dblclick keypress keyup", function(event){ - hoverListener(jqLite(event.target), false); - return true; - }); - }; - - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#addCss - * @methodOf angular.service.$browser - * - * @param {string} url Url to css file - * @description - * Adds a stylesheet tag to the head. - */ - self.addCss = function(url) { - var link = jqLite(rawDocument.createElement('link')); - link.attr('rel', 'stylesheet'); - link.attr('type', 'text/css'); - link.attr('href', url); - body.append(link); - }; - - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#addJs - * @methodOf angular.service.$browser - * - * @param {string} url Url to js file - * @param {string=} dom_id Optional id for the script tag - * - * @description - * Adds a script tag to the head. - */ - self.addJs = function(url, dom_id) { - var script = jqLite(rawDocument.createElement('script')); - script.attr('type', 'text/javascript'); - script.attr('src', url); - if (dom_id) script.attr('id', dom_id); - body.append(script); - }; -} -/* - * HTML Parser By Misko Hevery (misko@hevery.com) - * based on: HTML Parser By John Resig (ejohn.org) - * Original code by Erik Arvidsson, Mozilla Public License - * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js - * - * // Use like so: - * htmlParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - */ - -// Regular Expressions for parsing tags and attributes -var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, - END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, - ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, - BEGIN_TAG_REGEXP = /^/g, - CDATA_REGEXP = //g, - URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/, - NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character) - -// Empty Elements - HTML 4.01 -var emptyElements = makeMap("area,br,col,hr,img"); - -// Block Elements - HTML 4.01 -var blockElements = makeMap("address,blockquote,center,dd,del,dir,div,dl,dt,"+ - "hr,ins,li,map,menu,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul"); - -// Inline Elements - HTML 4.01 -var inlineElements = makeMap("a,abbr,acronym,b,bdo,big,br,cite,code,del,dfn,em,font,i,img,"+ - "ins,kbd,label,map,q,s,samp,small,span,strike,strong,sub,sup,tt,u,var"); -// Elements that you can, intentionally, leave open -// (and which close themselves) -var closeSelfElements = makeMap("colgroup,dd,dt,li,p,td,tfoot,th,thead,tr"); -// Special Elements (can contain anything) -var specialElements = makeMap("script,style"); -var validElements = extend({}, emptyElements, blockElements, inlineElements, closeSelfElements); - -//Attributes that have href and hence need to be sanitized -var uriAttrs = makeMap("background,href,longdesc,src,usemap"); -var validAttrs = extend({}, uriAttrs, makeMap( - 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ - 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ - 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ - 'scope,scrolling,shape,span,start,summary,target,title,type,'+ - 'valign,value,vspace,width')); - -/** - * @example - * htmlParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - * @param {string} html string - * @param {object} handler - */ -function htmlParser( html, handler ) { - var index, chars, match, stack = [], last = html; - stack.last = function(){ return stack[ stack.length - 1 ]; }; - - while ( html ) { - chars = true; - - // Make sure we're not in a script or style element - if ( !stack.last() || !specialElements[ stack.last() ] ) { - - // Comment - if ( html.indexOf(""); - - if ( index >= 0 ) { - if (handler.comment) handler.comment( html.substring( 4, index ) ); - html = html.substring( index + 3 ); - chars = false; - } - - // end tag - } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { - match = html.match( END_TAG_REGEXP ); - - if ( match ) { - html = html.substring( match[0].length ); - match[0].replace( END_TAG_REGEXP, parseEndTag ); - chars = false; - } - - // start tag - } else if ( BEGIN_TAG_REGEXP.test(html) ) { - match = html.match( START_TAG_REGEXP ); - - if ( match ) { - html = html.substring( match[0].length ); - match[0].replace( START_TAG_REGEXP, parseStartTag ); - chars = false; - } - } - - if ( chars ) { - index = html.indexOf("<"); - - var text = index < 0 ? html : html.substring( 0, index ); - html = index < 0 ? "" : html.substring( index ); - - if (handler.chars) handler.chars( decodeEntities(text) ); - } - - } else { - html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){ - text = text. - replace(COMMENT_REGEXP, "$1"). - replace(CDATA_REGEXP, "$1"); - - if (handler.chars) handler.chars( decodeEntities(text) ); - - return ""; - }); - - parseEndTag( "", stack.last() ); - } - - if ( html == last ) { - throw "Parse Error: " + html; - } - last = html; - } - - // Clean up any remaining tags - parseEndTag(); - - function parseStartTag( tag, tagName, rest, unary ) { - tagName = lowercase(tagName); - if ( blockElements[ tagName ] ) { - while ( stack.last() && inlineElements[ stack.last() ] ) { - parseEndTag( "", stack.last() ); - } - } - - if ( closeSelfElements[ tagName ] && stack.last() == tagName ) { - parseEndTag( "", tagName ); - } - - unary = emptyElements[ tagName ] || !!unary; - - if ( !unary ) - stack.push( tagName ); - - var attrs = {}; - - rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) { - var value = doubleQuotedValue - || singleQoutedValue - || unqoutedValue - || ''; - - attrs[name] = decodeEntities(value); - }); - if (handler.start) handler.start( tagName, attrs, unary ); - } - - function parseEndTag( tag, tagName ) { - var pos = 0, i; - tagName = lowercase(tagName); - if ( tagName ) - // Find the closest opened tag of the same type - for ( pos = stack.length - 1; pos >= 0; pos-- ) - if ( stack[ pos ] == tagName ) - break; - - if ( pos >= 0 ) { - // Close all the open elements, up the stack - for ( i = stack.length - 1; i >= pos; i-- ) - if (handler.end) handler.end( stack[ i ] ); - - // Remove the open elements from the stack - stack.length = pos; - } - } -} - -/** - * decodes all entities into regular string - * @param value - * @returns {string} A string with decoded entities. - */ -var hiddenPre=document.createElement("pre"); -function decodeEntities(value) { - hiddenPre.innerHTML=value.replace(//g, '>'); -} - -/** - * create an HTML/XML writer which writes to buffer - * @param {Array} buf use buf.jain('') to get out sanitized html string - * @returns {object} in the form of { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * } - */ -function htmlSanitizeWriter(buf){ - var ignore = false; - var out = bind(buf, buf.push); - return { - start: function(tag, attrs, unary){ - tag = lowercase(tag); - if (!ignore && specialElements[tag]) { - ignore = tag; - } - if (!ignore && validElements[tag] == true) { - out('<'); - out(tag); - forEach(attrs, function(value, key){ - var lkey=lowercase(key); - if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) { - out(' '); - out(key); - out('="'); - out(encodeEntities(value)); - out('"'); - } - }); - out(unary ? '/>' : '>'); - } - }, - end: function(tag){ - tag = lowercase(tag); - if (!ignore && validElements[tag] == true) { - out(''); - } - if (tag == ignore) { - ignore = false; - } - }, - chars: function(chars){ - if (!ignore) { - out(encodeEntities(chars)); - } - } - }; -} -////////////////////////////////// -//JQLite -////////////////////////////////// - -/** - * @workInProgress - * @ngdoc function - * @name angular.element - * @function - * - * @description - * Wraps a raw DOM element or HTML string as [jQuery](http://jquery.com) element. - * `angular.element` is either an alias for [jQuery](http://api.jquery.com/jQuery/) function if - * jQuery is loaded or a function that wraps the element or string in angular's jQuery lite - * implementation. - * - * Real jQuery always takes precedence (as long as it was loaded before `DOMContentEvent`) - * - * Angular's jQuery lite implementation is a tiny API-compatible subset of jQuery which allows - * angular to manipulate DOM. The jQuery lite implements only a subset of jQuery api, with the - * focus on the most commonly needed functionality and minimal footprint. For this reason only a - * limited number of jQuery methods, arguments and invocation styles are supported. - * - * NOTE: All element references in angular are always wrapped with jQuery (lite) and are never - * raw DOM references. - * - * ## Angular's jQuery lite implements these functions: - * - * - [addClass()](http://api.jquery.com/addClass/) - * - [after()](http://api.jquery.com/after/) - * - [append()](http://api.jquery.com/append/) - * - [attr()](http://api.jquery.com/attr/) - * - [bind()](http://api.jquery.com/bind/) - * - [children()](http://api.jquery.com/children/) - * - [clone()](http://api.jquery.com/clone/) - * - [css()](http://api.jquery.com/css/) - * - [data()](http://api.jquery.com/data/) - * - [hasClass()](http://api.jquery.com/hasClass/) - * - [parent()](http://api.jquery.com/parent/) - * - [remove()](http://api.jquery.com/remove/) - * - [removeAttr()](http://api.jquery.com/removeAttr/) - * - [removeClass()](http://api.jquery.com/removeClass/) - * - [removeData()](http://api.jquery.com/removeData/) - * - [replaceWith()](http://api.jquery.com/replaceWith/) - * - [text()](http://api.jquery.com/text/) - * - [trigger()](http://api.jquery.com/trigger/) - * - * ## Additionally these methods extend the jQuery and are available in both jQuery and jQuery lite - * version: - * - *- `scope()` - retrieves the current angular scope of the element. - * - * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. - * @returns {Object} jQuery object. - */ - -var jqCache = {}, - jqName = 'ng-' + new Date().getTime(), - jqId = 1, - addEventListenerFn = (window.document.addEventListener - ? function(element, type, fn) {element.addEventListener(type, fn, false);} - : function(element, type, fn) {element.attachEvent('on' + type, fn);}), - removeEventListenerFn = (window.document.removeEventListener - ? function(element, type, fn) {element.removeEventListener(type, fn, false); } - : function(element, type, fn) {element.detachEvent('on' + type, fn); }); - -function jqNextId() { return (jqId++); } - - -function getStyle(element) { - var current = {}, style = element[0].style, value, name, i; - if (typeof style.length == 'number') { - for(i = 0; i < style.length; i++) { - name = style[i]; - current[name] = style[name]; - } - } else { - for (name in style) { - value = style[name]; - if (1*name != name && name != 'cssText' && value && typeof value == 'string' && value !='false') - current[name] = value; - } - } - return current; -} - -//TODO: delete me! dead code? -if (msie) { - extend(JQLite.prototype, { - text: function(value) { - var e = this[0]; - // NodeType == 3 is text node - if (e.nodeType == 3) { - if (isDefined(value)) e.nodeValue = value; - return e.nodeValue; - } else { - if (isDefined(value)) e.innerText = value; - return e.innerText; - } - } - }); -} - -///////////////////////////////////////////// -function jqLiteWrap(element) { - if (isString(element) && element.charAt(0) != '<') { - throw new Error('selectors not implemented'); - } - return new JQLite(element); -} - -function JQLite(element) { - if (element instanceof JQLite) { - return element; - } else if (isString(element)) { - var div = document.createElement('div'); - // Read about the NoScope elements here: - // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx - div.innerHTML = '
 
' + element; // IE insanity to make NoScope elements work! - div.removeChild(div.firstChild); // remove the superfluous div - JQLiteAddNodes(this, div.childNodes); - this.remove(); // detach the elements from the temporary DOM div. - } else { - JQLiteAddNodes(this, element); - } -} - -function JQLiteClone(element) { - return element.cloneNode(true); -} - -function JQLiteDealoc(element){ - JQLiteRemoveData(element); - for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { - JQLiteDealoc(children[i]); - } -} - -function JQLiteRemoveData(element) { - var cacheId = element[jqName], - cache = jqCache[cacheId]; - if (cache) { - forEach(cache.bind || {}, function(fn, type){ - removeEventListenerFn(element, type, fn); - }); - delete jqCache[cacheId]; - element[jqName] = undefined; // ie does not allow deletion of attributes on elements. - } -} - -function JQLiteData(element, key, value) { - var cacheId = element[jqName], - cache = jqCache[cacheId || -1]; - if (isDefined(value)) { - if (!cache) { - element[jqName] = cacheId = jqNextId(); - cache = jqCache[cacheId] = {}; - } - cache[key] = value; - } else { - return cache ? cache[key] : null; - } -} - -function JQLiteHasClass(element, selector, _) { - // the argument '_' is important, since it makes the function have 3 arguments, which - // is neede for delegate function to realize the this is a getter. - var className = " " + selector + " "; - return ((" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1); -} - -function JQLiteRemoveClass(element, selector) { - element.className = trim( - (" " + element.className + " ") - .replace(/[\n\t]/g, " ") - .replace(" " + selector + " ", "") - ); -} - -function JQLiteAddClass(element, selector ) { - if (!JQLiteHasClass(element, selector)) { - element.className = trim(element.className + ' ' + selector); - } -} - -function JQLiteAddNodes(root, elements) { - if (elements) { - elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) - ? elements - : [ elements ]; - for(var i=0; i < elements.length; i++) { - root.push(elements[i]); - } - } -} - -////////////////////////////////////////// -// Functions which are declared directly. -////////////////////////////////////////// -var JQLitePrototype = JQLite.prototype = { - ready: function(fn) { - var fired = false; - - function trigger() { - if (fired) return; - fired = true; - fn(); - } - - this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9 - // we can not use jqLite since we are not done loading and jQuery could be loaded later. - jqLiteWrap(window).bind('load', trigger); // fallback to window.onload for others - }, - toString: function(){ - var value = []; - forEach(this, function(e){ value.push('' + e);}); - return '[' + value.join(', ') + ']'; - }, - length: 0, - push: push, - sort: [].sort, - splice: [].splice -}; - -////////////////////////////////////////// -// Functions iterating getter/setters. -// these functions return self on setter and -// value on get. -////////////////////////////////////////// -var SPECIAL_ATTR = makeMap("multiple,selected,checked,disabled,readonly"); - -forEach({ - data: JQLiteData, - - scope: function(element) { - var scope; - while (element && !(scope = jqLite(element).data($$scope))) { - element = element.parentNode; - } - return scope; - }, - - removeAttr: function(element,name) { - element.removeAttribute(name); - }, - - hasClass: JQLiteHasClass, - - css: function(element, name, value) { - if (isDefined(value)) { - element.style[name] = value; - } else { - return element.style[name]; - } - }, - - attr: function(element, name, value){ - if (SPECIAL_ATTR[name]) { - if (isDefined(value)) { - element[name] = !!value; - } else { - return element[name]; - } - } else if (isDefined(value)) { - element.setAttribute(name, value); - } else if (element.getAttribute) { - // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code - // some elements (e.g. Document) don't have get attribute, so return undefined - return element.getAttribute(name, 2); - } - }, - - text: extend((msie < 9) - ? function(element, value) { - // NodeType == 3 is text node - if (element.nodeType == 3) { - if (isUndefined(value)) - return element.nodeValue; - element.nodeValue = value; - } else { - if (isUndefined(value)) - return element.innerText; - element.innerText = value; - } - } - : function(element, value) { - if (isUndefined(value)) { - return element.textContent; - } - element.textContent = value; - }, {$dv:''}), - - val: function(element, value) { - if (isUndefined(value)) { - return element.value; - } - element.value = value; - }, - - html: function(element, value) { - if (isUndefined(value)) { - return element.innerHTML; - } - for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { - JQLiteDealoc(childNodes[i]); - } - element.innerHTML = value; - } -}, function(fn, name){ - /** - * Properties: writes return selection, reads return first value - */ - JQLite.prototype[name] = function(arg1, arg2) { - var i, key; - - if ((fn.length == 2 ? arg1 : arg2) === undefined) { - if (isObject(arg1)) { - // we are a write, but the object properties are the key/values - for(i=0; i < this.length; i++) { - for (key in arg1) { - fn(this[i], key, arg1[key]); - } - } - // return self for chaining - return this; - } else { - // we are a read, so read the first child. - if (this.length) - return fn(this[0], arg1, arg2); - } - } else { - // we are a write, so apply to all children - for(i=0; i < this.length; i++) { - fn(this[i], arg1, arg2); - } - // return self for chaining - return this; - } - return fn.$dv; - }; -}); - -////////////////////////////////////////// -// Functions iterating traversal. -// These functions chain results into a single -// selector. -////////////////////////////////////////// -forEach({ - removeData: JQLiteRemoveData, - - dealoc: JQLiteDealoc, - - bind: function(element, type, fn){ - var bind = JQLiteData(element, 'bind'), - eventHandler; - if (!bind) JQLiteData(element, 'bind', bind = {}); - forEach(type.split(' '), function(type){ - eventHandler = bind[type]; - if (!eventHandler) { - bind[type] = eventHandler = function(event) { - if (!event.preventDefault) { - event.preventDefault = function(){ - event.returnValue = false; //ie - }; - } - if (!event.stopPropagation) { - event.stopPropagation = function() { - event.cancelBubble = true; //ie - }; - } - forEach(eventHandler.fns, function(fn){ - fn.call(element, event); - }); - }; - eventHandler.fns = []; - addEventListenerFn(element, type, eventHandler); - } - eventHandler.fns.push(fn); - }); - }, - - replaceWith: function(element, replaceNode) { - var index, parent = element.parentNode; - JQLiteDealoc(element); - forEach(new JQLite(replaceNode), function(node){ - if (index) { - parent.insertBefore(node, index.nextSibling); - } else { - parent.replaceChild(node, element); - } - index = node; - }); - }, - - children: function(element) { - var children = []; - forEach(element.childNodes, function(element){ - if (element.nodeName != '#text') - children.push(element); - }); - return children; - }, - - append: function(element, node) { - forEach(new JQLite(node), function(child){ - if (element.nodeType === 1) - element.appendChild(child); - }); - }, - - prepend: function(element, node) { - if (element.nodeType === 1) { - var index = element.firstChild; - forEach(new JQLite(node), function(child){ - if (index) { - element.insertBefore(child, index); - } else { - element.appendChild(child); - index = child; - } - }); - } - }, - - remove: function(element) { - JQLiteDealoc(element); - var parent = element.parentNode; - if (parent) parent.removeChild(element); - }, - - after: function(element, newElement) { - var index = element, parent = element.parentNode; - forEach(new JQLite(newElement), function(node){ - parent.insertBefore(node, index.nextSibling); - index = node; - }); - }, - - addClass: JQLiteAddClass, - removeClass: JQLiteRemoveClass, - - toggleClass: function(element, selector, condition) { - if (isUndefined(condition)) { - condition = !JQLiteHasClass(element, selector); - } - (condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector); - }, - - parent: function(element) { - var parent = element.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - - next: function(element) { - return element.nextSibling; - }, - - find: function(element, selector) { - return element.getElementsByTagName(selector); - }, - - clone: JQLiteClone -}, function(fn, name){ - /** - * chaining functions - */ - JQLite.prototype[name] = function(arg1, arg2) { - var value; - for(var i=0; i < this.length; i++) { - if (value == undefined) { - value = fn(this[i], arg1, arg2); - if (value !== undefined) { - // any function which returns a value needs to be wrapped - value = jqLite(value); - } - } else { - JQLiteAddNodes(value, fn(this[i], arg1, arg2)); - } - } - return value == undefined ? this : value; - }; -}); -var angularGlobal = { - 'typeOf':function(obj){ - if (obj === null) return $null; - var type = typeof obj; - if (type == $object) { - if (obj instanceof Array) return $array; - if (isDate(obj)) return $date; - if (obj.nodeType == 1) return $element; - } - return type; - } -}; - - -/** - * @ngdoc overview - * @name angular.Object - * @function - * - * @description - * A namespace for utility functions used to work with JavaScript objects. These functions are - * exposed in two ways: - * - * __* Angular expressions:__ Functions are bound to all objects and augment the Object type. The - * names of these methods are prefixed with the '$' character in order to minimize naming collisions. - * To call a method, invoke the function without the first argument, e.g, `myObject.$foo(param2)`. - * - * __* JavaScript code:__ Functions don't augment the Object type and must be invoked as functions of - * `angular.Object` as `angular.Object.foo(myObject, param2)`. - * - * * {@link angular.Object.copy angular.Object.copy()} - Creates a deep copy of the source parameter - * * {@link angular.Object.equals angular.Object.equals()} - Determines if two objects or values are - * equivalent - * * {@link angular.Object.size angular.Object.size()} - Determines the number of elements in - * strings, arrays, and objects. - */ -var angularCollection = { - 'copy': copy, - 'size': size, - 'equals': equals -}; -var angularObject = { - 'extend': extend -}; - -/** - * @ngdoc overview - * @name angular.Array - * - * @description - * A namespace for utility functions for the manipulation of JavaScript Array objects. - * - * These functions are exposed in two ways: - * - * * __Angular expressions:__ Functions are bound to the Array objects and augment the Array type as - * array methods. The names of these methods are prefixed with $ character to minimize naming - * collisions. To call a method, invoke myArrayObject.$foo(params). - * - * Because Array type is a subtype of the Object type, all angular.Object functions augment - * theArray type in angular expressions as well. - * - * * __JavaScript code:__ Functions don't augment the Array type and must be invoked as functions of - * `angular.Array` as `angular.Array.foo(myArrayObject, params)`. - * - * The following APIs are built-in to the angular Array object: - * - * * {@link angular.Array.add angular.Array.add()} - Optionally adds a new element to an array. - * * {@link angular.Array.count angular.Array.count()} - Determines the number of elements in an - * array. - * * {@link angular.Array.filter angular.Array.filter()} - Returns a subset of items as a new array. - * * {@link angular.Array.indexOf angular.Array.indexOf()} - Determines the index of an array value. - * * {@link angular.Array.limitTo angular.Array.limitTo()} - Creates a new array off the front or - * back of an existing array. - * * {@link angular.Array.orderBy angular.Array.orderBy()} - Orders array elements - * * {@link angular.Array.remove angular.Array.remove()} - Removes array elements - * * {@link angular.Array.sum angular.Array.sum()} - Sums the number elements in an array - */ -var angularArray = { - - - /** - * @ngdoc function - * @name angular.Array.indexOf - * @function - * - * @description - * Determines the index of `value` in `array`. - * - * Note: this function is used to augment the `Array` type in angular expressions. See - * {@link angular.Array} for more info. - * - * @param {Array} array Array to search. - * @param {*} value Value to search for. - * @returns {number} The position of the element in `array`. The position is 0-based. `-1` is returned if the value can't be found. - * - * @example - - -
-
- Index of '{{bookName}}' in the list {{books}} is {{books.$indexOf(bookName)}}. -
- - it('should correctly calculate the initial index', function() { - expect(binding('books.$indexOf(bookName)')).toBe('2'); - }); - - it('should recalculate', function() { - input('bookName').enter('foo'); - expect(binding('books.$indexOf(bookName)')).toBe('-1'); - - input('bookName').enter('Moby Dick'); - expect(binding('books.$indexOf(bookName)')).toBe('0'); - }); - -
- */ - 'indexOf': indexOf, - - - /** - * @ngdoc function - * @name angular.Array.sum - * @function - * - * @description - * This function calculates the sum of all numbers in `array`. If the `expressions` is supplied, - * it is evaluated once for each element in `array` and then the sum of these values is returned. - * - * Note: this function is used to augment the `Array` type in angular expressions. See - * {@link angular.Array} for more info. - * - * @param {Array} array The source array. - * @param {(string|function())=} expression Angular expression or a function to be evaluated for each - * element in `array`. The array element becomes the `this` during the evaluation. - * @returns {number} Sum of items in the array. - * - * @example - - - - - - - - - - - - - - - - - -
QtyDescriptionCostTotal
{{item.qty * item.cost | currency}}[X]
add itemTotal:{{invoice.items.$sum('qty*cost') | currency}}
-
- - //TODO: these specs are lame because I had to work around issues #164 and #167 - it('should initialize and calculate the totals', function() { - expect(repeater('.doc-example-live table tr', 'item in invoice.items').count()).toBe(3); - expect(repeater('.doc-example-live table tr', 'item in invoice.items').row(1)). - toEqual(['$99.50']); - expect(binding("invoice.items.$sum('qty*cost')")).toBe('$99.50'); - expect(binding("invoice.items.$sum('qty*cost')")).toBe('$99.50'); - }); - - it('should add an entry and recalculate', function() { - element('.doc-example-live a:contains("add item")').click(); - using('.doc-example-live tr:nth-child(3)').input('item.qty').enter('20'); - using('.doc-example-live tr:nth-child(3)').input('item.cost').enter('100'); - - expect(repeater('.doc-example-live table tr', 'item in invoice.items').row(2)). - toEqual(['$2,000.00']); - expect(binding("invoice.items.$sum('qty*cost')")).toBe('$2,099.50'); - }); - -
- */ - 'sum':function(array, expression) { - var fn = angular['Function']['compile'](expression); - var sum = 0; - for (var i = 0; i < array.length; i++) { - var value = 1 * fn(array[i]); - if (!isNaN(value)){ - sum += value; - } - } - return sum; - }, - - - /** - * @ngdoc function - * @name angular.Array.remove - * @function - * - * @description - * Modifies `array` by removing an element from it. The element will be looked up using the - * {@link angular.Array.indexOf indexOf} function on the `array` and only the first instance of - * the element will be removed. - * - * Note: this function is used to augment the `Array` type in angular expressions. See - * {@link angular.Array} for more info. - * - * @param {Array} array Array from which an element should be removed. - * @param {*} value Element to be removed. - * @returns {*} The removed element. - * - * @example - - -
    -
  • - {{task}} [X] -
  • -
-
- tasks = {{tasks}} -
- - it('should initialize the task list with for tasks', function() { - expect(repeater('.doc-example-live ul li', 'task in tasks').count()).toBe(4); - expect(repeater('.doc-example-live ul li', 'task in tasks').column('task')). - toEqual(['Learn Angular', 'Read Documentation', 'Check out demos', - 'Build cool applications']); - }); - - it('should initialize the task list with for tasks', function() { - element('.doc-example-live ul li a:contains("X"):first').click(); - expect(repeater('.doc-example-live ul li', 'task in tasks').count()).toBe(3); - - element('.doc-example-live ul li a:contains("X"):last').click(); - expect(repeater('.doc-example-live ul li', 'task in tasks').count()).toBe(2); - - expect(repeater('.doc-example-live ul li', 'task in tasks').column('task')). - toEqual(['Read Documentation', 'Check out demos']); - }); - -
- */ - 'remove':function(array, value) { - var index = indexOf(array, value); - if (index >=0) - array.splice(index, 1); - return value; - }, - - - /** - * @ngdoc function - * @name angular.Array.filter - * @function - * - * @description - * Selects a subset of items from `array` and returns it as a new array. - * - * Note: this function is used to augment the `Array` type in angular expressions. See - * {@link angular.Array} for more info. - * - * @param {Array} array The source array. - * @param {string|Object|function()} expression The predicate to be used for selecting items from - * `array`. - * - * Can be one of: - * - * - `string`: Predicate that results in a substring match using the value of `expression` - * string. All strings or objects with string properties in `array` that contain this string - * will be returned. The predicate can be negated by prefixing the string with `!`. - * - * - `Object`: A pattern object can be used to filter specific properties on objects contained - * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items - * which have property `name` containing "M" and property `phone` containing "1". A special - * property name `$` can be used (as in `{$:"text"}`) to accept a match against any - * property of the object. That's equivalent to the simple substring match with a `string` - * as described above. - * - * - `function`: A predicate function can be used to write arbitrary filters. The function is - * called for each element of `array`. The final result is an array of those elements that - * the predicate returned true for. - * - * @example - - -
- - Search: - - - - - - -
NamePhone
{{friend.name}}{{friend.phone}}
-
- Any:
- Name only
- Phone only
- - - - - - -
NamePhone
{{friend.name}}{{friend.phone}}
-
- - it('should search across all fields when filtering with a string', function() { - input('searchText').enter('m'); - expect(repeater('#searchTextResults tr', 'friend in friends').column('name')). - toEqual(['Mary', 'Mike', 'Adam']); - - input('searchText').enter('76'); - expect(repeater('#searchTextResults tr', 'friend in friends').column('name')). - toEqual(['John', 'Julie']); - }); - - it('should search in specific fields when filtering with a predicate object', function() { - input('search.$').enter('i'); - expect(repeater('#searchObjResults tr', 'friend in friends').column('name')). - toEqual(['Mary', 'Mike', 'Julie']); - }); - -
- */ - 'filter':function(array, expression) { - var predicates = []; - predicates.check = function(value) { - for (var j = 0; j < predicates.length; j++) { - if(!predicates[j](value)) { - return false; - } - } - return true; - }; - var search = function(obj, text){ - if (text.charAt(0) === '!') { - return !search(obj, text.substr(1)); - } - switch (typeof obj) { - case "boolean": - case "number": - case "string": - return ('' + obj).toLowerCase().indexOf(text) > -1; - case "object": - for ( var objKey in obj) { - if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { - return true; - } - } - return false; - case "array": - for ( var i = 0; i < obj.length; i++) { - if (search(obj[i], text)) { - return true; - } - } - return false; - default: - return false; - } - }; - switch (typeof expression) { - case "boolean": - case "number": - case "string": - expression = {$:expression}; - case "object": - for (var key in expression) { - if (key == '$') { - (function(){ - var text = (''+expression[key]).toLowerCase(); - if (!text) return; - predicates.push(function(value) { - return search(value, text); - }); - })(); - } else { - (function(){ - var path = key; - var text = (''+expression[key]).toLowerCase(); - if (!text) return; - predicates.push(function(value) { - return search(getter(value, path), text); - }); - })(); - } - } - break; - case $function: - predicates.push(expression); - break; - default: - return array; - } - var filtered = []; - for ( var j = 0; j < array.length; j++) { - var value = array[j]; - if (predicates.check(value)) { - filtered.push(value); - } - } - return filtered; - }, - - - /** - * @workInProgress - * @ngdoc function - * @name angular.Array.add - * @function - * - * @description - * `add` is a function similar to JavaScript's `Array#push` method, in that it appends a new - * element to an array. The difference is that the value being added is optional and defaults to - * an empty object. - * - * Note: this function is used to augment the `Array` type in angular expressions. See - * {@link angular.Array} for more info. - * - * @param {Array} array The array expand. - * @param {*=} [value={}] The value to be added. - * @returns {Array} The expanded array. - * - * @TODO simplify the example. - * - * @example - * This example shows how an initially empty array can be filled with objects created from user - * input via the `$add` method. - - - [add empty] - [add 'John'] - [add 'Mary'] - -
    -
  • - - - [X] -
  • -
-
people = {{people}}
-
- - beforeEach(function() { - expect(binding('people')).toBe('people = []'); - }); - - it('should create an empty record when "add empty" is clicked', function() { - element('.doc-example-live a:contains("add empty")').click(); - expect(binding('people')).toBe('people = [{\n "name":"",\n "sex":null}]'); - }); - - it('should create a "John" record when "add \'John\'" is clicked', function() { - element('.doc-example-live a:contains("add \'John\'")').click(); - expect(binding('people')).toBe('people = [{\n "name":"John",\n "sex":"male"}]'); - }); - - it('should create a "Mary" record when "add \'Mary\'" is clicked', function() { - element('.doc-example-live a:contains("add \'Mary\'")').click(); - expect(binding('people')).toBe('people = [{\n "name":"Mary",\n "sex":"female"}]'); - }); - - it('should delete a record when "X" is clicked', function() { - element('.doc-example-live a:contains("add empty")').click(); - element('.doc-example-live li a:contains("X"):first').click(); - expect(binding('people')).toBe('people = []'); - }); - -
- */ - 'add':function(array, value) { - array.push(isUndefined(value)? {} : value); - return array; - }, - - - /** - * @ngdoc function - * @name angular.Array.count - * @function - * - * @description - * Determines the number of elements in an array. Optionally it will count only those elements - * for which the `condition` evaluates to `true`. - * - * Note: this function is used to augment the `Array` type in angular expressions. See - * {@link angular.Array} for more info. - * - * @param {Array} array The array to count elements in. - * @param {(function()|string)=} condition A function to be evaluated or angular expression to be - * compiled and evaluated. The element that is currently being iterated over, is exposed to - * the `condition` as `this`. - * @returns {number} Number of elements in the array (for which the condition evaluates to true). - * - * @example - - -

-         
    -
  • - {{item.name}}: points= - -
  • -
-

Number of items which have one point: {{ items.$count('points==1') }}

-

Number of items which have more than one point: {{items.$count('points>1')}}

-
- - it('should calculate counts', function() { - expect(binding('items.$count(\'points==1\')')).toEqual(2); - expect(binding('items.$count(\'points>1\')')).toEqual(1); - }); - - it('should recalculate when updated', function() { - using('.doc-example-live li:first-child').input('item.points').enter('23'); - expect(binding('items.$count(\'points==1\')')).toEqual(1); - expect(binding('items.$count(\'points>1\')')).toEqual(2); - }); - -
- */ - 'count':function(array, condition) { - if (!condition) return array.length; - var fn = angular['Function']['compile'](condition), count = 0; - forEach(array, function(value){ - if (fn(value)) { - count ++; - } - }); - return count; - }, - - - /** - * @ngdoc function - * @name angular.Array.orderBy - * @function - * - * @description - * Orders `array` by the `expression` predicate. - * - * Note: this function is used to augment the `Array` type in angular expressions. See - * {@link angular.Array} for more info. - * - * @param {Array} array The array to sort. - * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be - * used by the comparator to determine the order of elements. - * - * Can be one of: - * - * - `function`: getter function. The result of this function will be sorted using the - * `<`, `=`, `>` operator - * - `string`: angular expression which evaluates to an object to order by, such as 'name' to - * sort by a property called 'name'. Optionally prefixed with `+` or `-` to control ascending - * or descending sort order (e.g. +name or -name). - * - `Array`: array of function or string predicates, such that a first predicate in the array - * is used for sorting, but when the items are equivalent next predicate is used. - * - * @param {boolean=} reverse Reverse the order the array. - * @returns {Array} Sorted copy of the source array. - * - * @example - - -
- -
Sorting predicate = {{predicate}}; reverse = {{reverse}}
-
- [ unsorted ] - - - - - - - - - - - -
Name - (^)Phone NumberAge
{{friend.name}}{{friend.phone}}{{friend.age}}
-
- - it('should be reverse ordered by aged', function() { - expect(binding('predicate')).toBe('Sorting predicate = -age; reverse = '); - expect(repeater('.doc-example-live table', 'friend in friends').column('friend.age')). - toEqual(['35', '29', '21', '19', '10']); - expect(repeater('.doc-example-live table', 'friend in friends').column('friend.name')). - toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']); - }); - - it('should reorder the table when user selects different predicate', function() { - element('.doc-example-live a:contains("Name")').click(); - expect(repeater('.doc-example-live table', 'friend in friends').column('friend.name')). - toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']); - expect(repeater('.doc-example-live table', 'friend in friends').column('friend.age')). - toEqual(['35', '10', '29', '19', '21']); - - element('.doc-example-live a:contains("Phone")').click(); - expect(repeater('.doc-example-live table', 'friend in friends').column('friend.phone')). - toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']); - expect(repeater('.doc-example-live table', 'friend in friends').column('friend.name')). - toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']); - }); - -
- */ - 'orderBy':function(array, sortPredicate, reverseOrder) { - if (!sortPredicate) return array; - sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; - sortPredicate = map(sortPredicate, function(predicate){ - var descending = false, get = predicate || identity; - if (isString(predicate)) { - if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { - descending = predicate.charAt(0) == '-'; - predicate = predicate.substring(1); - } - get = expressionCompile(predicate).fnSelf; - } - return reverseComparator(function(a,b){ - return compare(get(a),get(b)); - }, descending); - }); - var arrayCopy = []; - for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } - return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); - - function comparator(o1, o2){ - for ( var i = 0; i < sortPredicate.length; i++) { - var comp = sortPredicate[i](o1, o2); - if (comp !== 0) return comp; - } - return 0; - } - function reverseComparator(comp, descending) { - return toBoolean(descending) - ? function(a,b){return comp(b,a);} - : comp; - } - function compare(v1, v2){ - var t1 = typeof v1; - var t2 = typeof v2; - if (t1 == t2) { - if (t1 == "string") v1 = v1.toLowerCase(); - if (t1 == "string") v2 = v2.toLowerCase(); - if (v1 === v2) return 0; - return v1 < v2 ? -1 : 1; - } else { - return t1 < t2 ? -1 : 1; - } - } - }, - - - /** - * @ngdoc function - * @name angular.Array.limitTo - * @function - * - * @description - * Creates a new array containing only the first, or last `limit` number of elements of the - * source `array`. - * - * Note: this function is used to augment the `Array` type in angular expressions. See - * {@link angular.Array} for more info. - * - * @param {Array} array Source array to be limited. - * @param {string|Number} limit The length of the returned array. If the number is positive, the - * first `limit` items from the source array will be copied, if the number is negative, the - * last `limit` items will be copied. - * @returns {Array} A new sub-array of length `limit`. - * - * @example - - -
- Limit [1,2,3,4,5,6,7,8,9] to: -

Output: {{ numbers.$limitTo(limit) | json }}

-
-
- - it('should limit the numer array to first three items', function() { - expect(element('.doc-example-live input[name=limit]').val()).toBe('3'); - expect(binding('numbers.$limitTo(limit) | json')).toEqual('[1,2,3]'); - }); - - it('should update the output when -3 is entered', function() { - input('limit').enter(-3); - expect(binding('numbers.$limitTo(limit) | json')).toEqual('[7,8,9]'); - }); - -
- */ - limitTo: function(array, limit) { - limit = parseInt(limit, 10); - var out = [], - i, n; - - if (limit > 0) { - i = 0; - n = limit; - } else { - i = array.length + limit; - n = array.length; - } - - for (; i - -
- {{amount | currency}} -
- - it('should init with 1234.56', function(){ - expect(binding('amount | currency')).toBe('$1,234.56'); - }); - it('should update', function(){ - input('amount').enter('-1234'); - expect(binding('amount | currency')).toBe('$-1,234.00'); - expect(element('.doc-example-live .ng-binding').attr('className')). - toMatch(/ng-format-negative/); - }); - - - */ -angularFilter.currency = function(amount){ - this.$element.toggleClass('ng-format-negative', amount < 0); - return '$' + angularFilter.number.apply(this, [amount, 2]); -}; - -/** - * @workInProgress - * @ngdoc filter - * @name angular.filter.number - * @function - * - * @description - * Formats a number as text. - * - * If the input is not a number empty string is returned. - * - * @param {number|string} number Number to format. - * @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to. - * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. - * - * @example - - - Enter number:
- Default formatting: {{val | number}}
- No fractions: {{val | number:0}}
- Negative number: {{-val | number:4}} -
- - it('should format numbers', function(){ - expect(binding('val | number')).toBe('1,234.57'); - expect(binding('val | number:0')).toBe('1,235'); - expect(binding('-val | number:4')).toBe('-1,234.5679'); - }); - - it('should update', function(){ - input('val').enter('3374.333'); - expect(binding('val | number')).toBe('3,374.33'); - expect(binding('val | number:0')).toBe('3,374'); - expect(binding('-val | number:4')).toBe('-3,374.3330'); - }); - -
- */ -angularFilter.number = function(number, fractionSize){ - if (isNaN(number) || !isFinite(number)) { - return ''; - } - fractionSize = isUndefined(fractionSize)? 2 : fractionSize; - - var isNegative = number < 0, - pow = Math.pow(10, fractionSize), - whole = '' + number, - formatedText = '', - i; - - if (whole.indexOf('e') > -1) return whole; - - number = Math.round(number * pow) / pow; - fraction = ('' + number).split('.'); - whole = fraction[0]; - fraction = fraction[1] || ''; - if (isNegative) { - formatedText = '-'; - whole = whole.substring(1); - } - - - for (i = 0; i < whole.length; i++) { - if ((whole.length - i)%3 === 0 && i !== 0) { - formatedText += ','; - } - formatedText += whole.charAt(i); - } - if (fractionSize) { - while(fraction.length < fractionSize) { - fraction += '0'; - } - formatedText += '.' + fraction.substring(0, fractionSize); - } - return formatedText; -}; - - -function padNumber(num, digits, trim) { - var neg = ''; - if (num < 0) { - neg = '-'; - num = -num; - } - num = '' + num; - while(num.length < digits) num = '0' + num; - if (trim) - num = num.substr(num.length - digits); - return neg + num; -} - - -function dateGetter(name, size, offset, trim) { - return function(date) { - var value = date['get' + name](); - if (offset > 0 || value > -offset) - value += offset; - if (value === 0 && offset == -12 ) value = 12; - return padNumber(value, size, trim); - }; -} - -function dateStrGetter(name, shortForm) { - return function(date) { - var value = date['get' + name](); - - if(name == 'Month') { - value = MONTH[value]; - } else { - value = DAY[value]; - } - - return shortForm ? value.substr(0,3) : value; - }; -} - -var DAY = 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','); - -var MONTH = 'January,February,March,April,May,June,July,August,September,October,November,December'. - split(','); - -var DATE_FORMATS = { - yyyy: dateGetter('FullYear', 4), - yy: dateGetter('FullYear', 2, 0, true), - MMMMM: dateStrGetter('Month'), - MMM: dateStrGetter('Month', true), - MM: dateGetter('Month', 2, 1), - M: dateGetter('Month', 1, 1), - dd: dateGetter('Date', 2), - d: dateGetter('Date', 1), - HH: dateGetter('Hours', 2), - H: dateGetter('Hours', 1), - hh: dateGetter('Hours', 2, -12), - h: dateGetter('Hours', 1, -12), - mm: dateGetter('Minutes', 2), - m: dateGetter('Minutes', 1), - ss: dateGetter('Seconds', 2), - s: dateGetter('Seconds', 1), - EEEE: dateStrGetter('Day'), - EEE: dateStrGetter('Day', true), - a: function(date){return date.getHours() < 12 ? 'am' : 'pm';}, - Z: function(date){ - var offset = date.getTimezoneOffset(); - return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2); - } -}; - - -var DATE_FORMATS_SPLIT = /([^yMdHhmsaZE]*)(E+|y+|M+|d+|H+|h+|m+|s+|a|Z)(.*)/; -var NUMBER_STRING = /^\d+$/; - - -/** - * @workInProgress - * @ngdoc filter - * @name angular.filter.date - * @function - * - * @description - * Formats `date` to a string based on the requested `format`. - * - * `format` string can be composed of the following elements: - * - * * `'yyyy'`: 4 digit representation of year e.g. 2010 - * * `'yy'`: 2 digit representation of year, padded (00-99) - * * `'MMMMM'`: Month in year (January‒December) - * * `'MMM'`: Month in year (Jan - Dec) - * * `'MM'`: Month in year, padded (01‒12) - * * `'M'`: Month in year (1‒12) - * * `'dd'`: Day in month, padded (01‒31) - * * `'d'`: Day in month (1-31) - * * `'EEEE'`: Day in Week,(Sunday‒Saturday) - * * `'EEE'`: Day in Week, (Sun-Sat) - * * `'HH'`: Hour in day, padded (00‒23) - * * `'H'`: Hour in day (0-23) - * * `'hh'`: Hour in am/pm, padded (01‒12) - * * `'h'`: Hour in am/pm, (1-12) - * * `'mm'`: Minute in hour, padded (00‒59) - * * `'m'`: Minute in hour (0-59) - * * `'ss'`: Second in minute, padded (00‒59) - * * `'s'`: Second in minute (0‒59) - * * `'a'`: am/pm marker - * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200‒1200) - * - * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or - * number) or ISO 8601 extended datetime string (yyyy-MM-ddTHH:mm:ss.SSSZ). - * @param {string=} format Formatting rules. If not specified, Date#toLocaleDateString is used. - * @returns {string} Formatted string or the input if input is not recognized as date/millis. - * - * @example - - - {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}: - {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
- {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}: - {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
-
- - it('should format date', function(){ - expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")). - toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/); - expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")). - toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(am|pm)/); - }); - -
- */ -angularFilter.date = function(date, format) { - if (isString(date)) { - if (NUMBER_STRING.test(date)) { - date = parseInt(date, 10); - } else { - date = angularString.toDate(date); - } - } - - if (isNumber(date)) { - date = new Date(date); - } - - if (!isDate(date)) { - return date; - } - - var text = date.toLocaleDateString(), fn; - if (format && isString(format)) { - text = ''; - var parts = [], match; - while(format) { - match = DATE_FORMATS_SPLIT.exec(format); - if (match) { - parts = concat(parts, match, 1); - format = parts.pop(); - } else { - parts.push(format); - format = null; - } - } - forEach(parts, function(value){ - fn = DATE_FORMATS[value]; - text += fn ? fn(date) : value; - }); - } - return text; -}; - - -/** - * @workInProgress - * @ngdoc filter - * @name angular.filter.json - * @function - * - * @description - * Allows you to convert a JavaScript object into JSON string. - * - * This filter is mostly useful for debugging. When using the double curly {{value}} notation - * the binding is automatically converted to JSON. - * - * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. - * @returns {string} JSON string. - * - * @css ng-monospace Always applied to the encapsulating element. - * - * @example: - - - -
{{ obj | json }}
-
- - it('should jsonify filtered objects', function() { - expect(binding('obj | json')).toBe('{\n "a":1,\n "b":[]}'); - }); - - it('should update', function() { - input('objTxt').enter('[1, 2, 3]'); - expect(binding('obj | json')).toBe('[1,2,3]'); - }); - -
- * - */ -angularFilter.json = function(object) { - this.$element.addClass("ng-monospace"); - return toJson(object, true); -}; - - -/** - * @workInProgress - * @ngdoc filter - * @name angular.filter.lowercase - * @function - * - * @see angular.lowercase - */ -angularFilter.lowercase = lowercase; - - -/** - * @workInProgress - * @ngdoc filter - * @name angular.filter.uppercase - * @function - * - * @see angular.uppercase - */ -angularFilter.uppercase = uppercase; - - -/** - * @workInProgress - * @ngdoc filter - * @name angular.filter.html - * @function - * - * @description - * Prevents the input from getting escaped by angular. By default the input is sanitized and - * inserted into the DOM as is. - * - * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are - * then serialized back to properly escaped html string. This means that no unsafe input can make - * it into the returned string, however since our parser is more strict than a typical browser - * parser, it's possible that some obscure input, which would be recognized as valid HTML by a - * browser, won't make it through the sanitizer. - * - * If you hate your users, you may call the filter with optional 'unsafe' argument, which bypasses - * the html sanitizer, but makes your application vulnerable to XSS and other attacks. Using this - * option is strongly discouraged and should be used only if you absolutely trust the input being - * filtered and you can't get the content through the sanitizer. - * - * @param {string} html Html input. - * @param {string=} option If 'unsafe' then do not sanitize the HTML input. - * @returns {string} Sanitized or raw html. - * - * @example - - - Snippet: - - - - - - - - - - - - - - - - - - - - - -
FilterSourceRendered
html filter -
<div ng:bind="snippet | html">
</div>
-
-
-
no filter
<div ng:bind="snippet">
</div>
unsafe html filter
<div ng:bind="snippet | html:'unsafe'">
</div>
-
- - it('should sanitize the html snippet ', function(){ - expect(using('#html-filter').binding('snippet | html')). - toBe('

an html\nclick here\nsnippet

'); - }); - - it('should escape snippet without any filter', function() { - expect(using('#escaped-html').binding('snippet')). - toBe("<p style=\"color:blue\">an html\n" + - "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + - "snippet</p>"); - }); - - it('should inline raw snippet if filtered as unsafe', function() { - expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")). - toBe("

an html\n" + - "click here\n" + - "snippet

"); - }); - - it('should update', function(){ - input('snippet').enter('new text'); - expect(using('#html-filter').binding('snippet | html')).toBe('new text'); - expect(using('#escaped-html').binding('snippet')).toBe("new <b>text</b>"); - expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).toBe('new text'); - }); -
-
- */ -angularFilter.html = function(html, option){ - return new HTML(html, option); -}; - - -/** - * @workInProgress - * @ngdoc filter - * @name angular.filter.linky - * @function - * - * @description - * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and - * plane email address links. - * - * @param {string} text Input text. - * @returns {string} Html-linkified text. - * - * @example - - - Snippet: - - - - - - - - - - - - - - - - -
FilterSourceRendered
linky filter -
<div ng:bind="snippet | linky">
</div>
-
-
-
no filter
<div ng:bind="snippet">
</div>
-
- - it('should linkify the snippet with urls', function(){ - expect(using('#linky-filter').binding('snippet | linky')). - toBe('Pretty text with some links:\n' + - 'http://angularjs.org/,\n' + - 'us@somewhere.org,\n' + - 'another@somewhere.org,\n' + - 'and one more: ftp://127.0.0.1/.'); - }); - - it ('should not linkify snippet without the linky filter', function() { - expect(using('#escaped-html').binding('snippet')). - toBe("Pretty text with some links:\n" + - "http://angularjs.org/,\n" + - "mailto:us@somewhere.org,\n" + - "another@somewhere.org,\n" + - "and one more: ftp://127.0.0.1/."); - }); - - it('should update', function(){ - input('snippet').enter('new http://link.'); - expect(using('#linky-filter').binding('snippet | linky')). - toBe('new http://link.'); - expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); - }); - -
- */ -//TODO: externalize all regexps -angularFilter.linky = function(text){ - if (!text) return text; - var URL = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/; - var match; - var raw = text; - var html = []; - var writer = htmlSanitizeWriter(html); - var url; - var i; - while (match=raw.match(URL)) { - // We can not end in these as they are sometimes found at the end of the sentence - url = match[0]; - // if we did not match ftp/http/mailto then assume mailto - if (match[2]==match[3]) url = 'mailto:' + url; - i = match.index; - writer.chars(raw.substr(0, i)); - writer.start('a', {href:url}); - writer.chars(match[0].replace(/^mailto:/, '')); - writer.end('a'); - raw = raw.substring(i + match[0].length); - } - writer.chars(raw); - return new HTML(html.join('')); -}; -/** - * @workInProgress - * @ngdoc overview - * @name angular.formatter - * @description - * - * Formatters are used for translating data formats between those used in for display and those used - * for storage. - * - * Following is the list of built-in angular formatters: - * - * * {@link angular.formatter.boolean boolean} - Formats user input in boolean format - * * {@link angular.formatter.index index} - Manages indexing into an HTML select widget - * * {@link angular.formatter.json json} - Formats user input in JSON format - * * {@link angular.formatter.list list} - Formats user input string as an array - * * {@link angular.formatter.number} - Formats user input strings as a number - * * {@link angular.formatter.trim} - Trims extras spaces from end of user input - * - * For more information about how angular formatters work, and how to create your own formatters, - * see {@link guide/dev_guide.templates.formatters Understanding Angular Formatters} in the angular - * Developer Guide. - */ - -function formatter(format, parse) {return {'format':format, 'parse':parse || format};} -function toString(obj) { - return (isDefined(obj) && obj !== null) ? "" + obj : obj; -} - -var NUMBER = /^\s*[-+]?\d*(\.\d*)?\s*$/; - -angularFormatter.noop = formatter(identity, identity); - -/** - * @workInProgress - * @ngdoc formatter - * @name angular.formatter.json - * - * @description - * Formats the user input as JSON text. - * - * @returns {?string} A JSON string representation of the model. - * - * @example - - -
- -
data={{data}}
-
-
- - it('should format json', function(){ - expect(binding('data')).toEqual('data={\n \"name\":\"misko\",\n \"project\":\"angular\"}'); - input('data').enter('{}'); - expect(binding('data')).toEqual('data={\n }'); - }); - -
- */ -angularFormatter.json = formatter(toJson, function(value){ - return fromJson(value || 'null'); -}); - -/** - * @workInProgress - * @ngdoc formatter - * @name angular.formatter.boolean - * - * @description - * Use boolean formatter if you wish to store the data as boolean. - * - * @returns {boolean} Converts to `true` unless user enters (blank), `f`, `false`, `0`, `no`, `[]`. - * - * @example - - - Enter truthy text: - - -
value={{value}}
-
- - it('should format boolean', function(){ - expect(binding('value')).toEqual('value=false'); - input('value').enter('truthy'); - expect(binding('value')).toEqual('value=true'); - }); - -
- */ -angularFormatter['boolean'] = formatter(toString, toBoolean); - -/** - * @workInProgress - * @ngdoc formatter - * @name angular.formatter.number - * - * @description - * Use number formatter if you wish to convert the user entered string to a number. - * - * @returns {number} Number from the parsed string. - * - * @example - - - Enter valid number: - -
value={{value}}
-
- - it('should format numbers', function(){ - expect(binding('value')).toEqual('value=1234'); - input('value').enter('5678'); - expect(binding('value')).toEqual('value=5678'); - }); - -
- */ -angularFormatter.number = formatter(toString, function(obj){ - if (obj == null || NUMBER.exec(obj)) { - return obj===null || obj === '' ? null : 1*obj; - } else { - throw "Not a number"; - } -}); - -/** - * @workInProgress - * @ngdoc formatter - * @name angular.formatter.list - * - * @description - * Use list formatter if you wish to convert the user entered string to an array. - * - * @returns {Array} Array parsed from the entered string. - * - * @example - - - Enter a list of items: - - -
value={{value}}
-
- - it('should format lists', function(){ - expect(binding('value')).toEqual('value=["chair","table"]'); - this.addFutureAction('change to XYZ', function($window, $document, done){ - $document.elements('.doc-example-live :input:last').val(',,a,b,').trigger('change'); - done(); - }); - expect(binding('value')).toEqual('value=["a","b"]'); - }); - -
- */ -angularFormatter.list = formatter( - function(obj) { return obj ? obj.join(", ") : obj; }, - function(value) { - var list = []; - forEach((value || '').split(','), function(item){ - item = trim(item); - if (item) list.push(item); - }); - return list; - } -); - -/** - * @workInProgress - * @ngdoc formatter - * @name angular.formatter.trim - * - * @description - * Use trim formatter if you wish to trim extra spaces in user text. - * - * @returns {String} Trim excess leading and trailing space. - * - * @example - - - Enter text with leading/trailing spaces: - - -
value={{value|json}}
-
- - it('should format trim', function(){ - expect(binding('value')).toEqual('value="book"'); - this.addFutureAction('change to XYZ', function($window, $document, done){ - $document.elements('.doc-example-live :input:last').val(' text ').trigger('change'); - done(); - }); - expect(binding('value')).toEqual('value="text"'); - }); - -
- */ -angularFormatter.trim = formatter( - function(obj) { return obj ? trim("" + obj) : ""; } -); -/** - * @workInProgress - * @ngdoc overview - * @name angular.validator - * @description - * - * Most of the built-in angular validators are used to check user input against defined types or - * patterns. You can easily create your own custom validators as well. - * - * Following is the list of built-in angular validators: - * - * * {@link angular.validator.asynchronous asynchronous()} - Provides asynchronous validation via a - * callback function. - * * {@link angular.validator.date date()} - Checks user input against default date format: - * "MM/DD/YYYY" - * * {@link angular.validator.email email()} - Validates that user input is a well-formed email - * address. - * * {@link angular.validator.integer integer()} - Validates that user input is an integer - * * {@link angular.validator.json json()} - Validates that user input is valid JSON - * * {@link angular.validator.number number()} - Validates that user input is a number - * * {@link angular.validator.phone phone()} - Validates that user input matches the pattern - * "1(123)123-1234" - * * {@link angular.validator.regexp regexp()} - Restricts valid input to a specified regular - * expression pattern - * * {@link angular.validator.url url()} - Validates that user input is a well-formed URL. - * - * For more information about how angular validators work, and how to create your own validators, - * see {@link guide/dev_guide.templates.validators Understanding Angular Validators} in the angular - * Developer Guide. - */ - -extend(angularValidator, { - 'noop': function() { return null; }, - - /** - * @workInProgress - * @ngdoc validator - * @name angular.validator.regexp - * @description - * Use regexp validator to restrict the input to any Regular Expression. - * - * @param {string} value value to validate - * @param {string|regexp} expression regular expression. - * @param {string=} msg error message to display. - * @css ng-validation-error - * - * @example - - - - Enter valid SSN: -
- -
-
- - it('should invalidate non ssn', function(){ - var textBox = element('.doc-example-live :input'); - expect(textBox.attr('className')).not().toMatch(/ng-validation-error/); - expect(textBox.val()).toEqual('123-45-6789'); - input('ssn').enter('123-45-67890'); - expect(textBox.attr('className')).toMatch(/ng-validation-error/); - }); - -
- * - */ - 'regexp': function(value, regexp, msg) { - if (!value.match(regexp)) { - return msg || - "Value does not match expected format " + regexp + "."; - } else { - return null; - } - }, - - /** - * @workInProgress - * @ngdoc validator - * @name angular.validator.number - * @description - * Use number validator to restrict the input to numbers with an - * optional range. (See integer for whole numbers validator). - * - * @param {string} value value to validate - * @param {int=} [min=MIN_INT] minimum value. - * @param {int=} [max=MAX_INT] maximum value. - * @css ng-validation-error - * - * @example - - - Enter number:
- Enter number greater than 10:
- Enter number between 100 and 200:
-
- - it('should invalidate number', function(){ - var n1 = element('.doc-example-live :input[name=n1]'); - expect(n1.attr('className')).not().toMatch(/ng-validation-error/); - input('n1').enter('1.x'); - expect(n1.attr('className')).toMatch(/ng-validation-error/); - var n2 = element('.doc-example-live :input[name=n2]'); - expect(n2.attr('className')).not().toMatch(/ng-validation-error/); - input('n2').enter('9'); - expect(n2.attr('className')).toMatch(/ng-validation-error/); - var n3 = element('.doc-example-live :input[name=n3]'); - expect(n3.attr('className')).not().toMatch(/ng-validation-error/); - input('n3').enter('201'); - expect(n3.attr('className')).toMatch(/ng-validation-error/); - }); - -
- * - */ - 'number': function(value, min, max) { - var num = 1 * value; - if (num == value) { - if (typeof min != $undefined && num < min) { - return "Value can not be less than " + min + "."; - } - if (typeof min != $undefined && num > max) { - return "Value can not be greater than " + max + "."; - } - return null; - } else { - return "Not a number"; - } - }, - - /** - * @workInProgress - * @ngdoc validator - * @name angular.validator.integer - * @description - * Use number validator to restrict the input to integers with an - * optional range. (See integer for whole numbers validator). - * - * @param {string} value value to validate - * @param {int=} [min=MIN_INT] minimum value. - * @param {int=} [max=MAX_INT] maximum value. - * @css ng-validation-error - * - * @example - - - Enter integer:
- Enter integer equal or greater than 10:
- Enter integer between 100 and 200 (inclusive):
-
- - it('should invalidate integer', function(){ - var n1 = element('.doc-example-live :input[name=n1]'); - expect(n1.attr('className')).not().toMatch(/ng-validation-error/); - input('n1').enter('1.1'); - expect(n1.attr('className')).toMatch(/ng-validation-error/); - var n2 = element('.doc-example-live :input[name=n2]'); - expect(n2.attr('className')).not().toMatch(/ng-validation-error/); - input('n2').enter('10.1'); - expect(n2.attr('className')).toMatch(/ng-validation-error/); - var n3 = element('.doc-example-live :input[name=n3]'); - expect(n3.attr('className')).not().toMatch(/ng-validation-error/); - input('n3').enter('100.1'); - expect(n3.attr('className')).toMatch(/ng-validation-error/); - }); - -
- */ - 'integer': function(value, min, max) { - var numberError = angularValidator['number'](value, min, max); - if (numberError) return numberError; - if (!("" + value).match(/^\s*[\d+]*\s*$/) || value != Math.round(value)) { - return "Not a whole number"; - } - return null; - }, - - /** - * @workInProgress - * @ngdoc validator - * @name angular.validator.date - * @description - * Use date validator to restrict the user input to a valid date - * in format in format MM/DD/YYYY. - * - * @param {string} value value to validate - * @css ng-validation-error - * - * @example - - - Enter valid date: - - - - it('should invalidate date', function(){ - var n1 = element('.doc-example-live :input'); - expect(n1.attr('className')).not().toMatch(/ng-validation-error/); - input('text').enter('123/123/123'); - expect(n1.attr('className')).toMatch(/ng-validation-error/); - }); - - - * - */ - 'date': function(value) { - var fields = /^(\d\d?)\/(\d\d?)\/(\d\d\d\d)$/.exec(value); - var date = fields ? new Date(fields[3], fields[1]-1, fields[2]) : 0; - return (date && - date.getFullYear() == fields[3] && - date.getMonth() == fields[1]-1 && - date.getDate() == fields[2]) - ? null - : "Value is not a date. (Expecting format: 12/31/2009)."; - }, - - /** - * @workInProgress - * @ngdoc validator - * @name angular.validator.email - * @description - * Use email validator if you wist to restrict the user input to a valid email. - * - * @param {string} value value to validate - * @css ng-validation-error - * - * @example - - - Enter valid email: - - - - it('should invalidate email', function(){ - var n1 = element('.doc-example-live :input'); - expect(n1.attr('className')).not().toMatch(/ng-validation-error/); - input('text').enter('a@b.c'); - expect(n1.attr('className')).toMatch(/ng-validation-error/); - }); - - - * - */ - 'email': function(value) { - if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) { - return null; - } - return "Email needs to be in username@host.com format."; - }, - - /** - * @workInProgress - * @ngdoc validator - * @name angular.validator.phone - * @description - * Use phone validator to restrict the input phone numbers. - * - * @param {string} value value to validate - * @css ng-validation-error - * - * @example - - - Enter valid phone number: - - - - it('should invalidate phone', function(){ - var n1 = element('.doc-example-live :input'); - expect(n1.attr('className')).not().toMatch(/ng-validation-error/); - input('text').enter('+12345678'); - expect(n1.attr('className')).toMatch(/ng-validation-error/); - }); - - - * - */ - 'phone': function(value) { - if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) { - return null; - } - if (value.match(/^\+\d{2,3} (\(\d{1,5}\))?[\d ]+\d$/)) { - return null; - } - return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly."; - }, - - /** - * @workInProgress - * @ngdoc validator - * @name angular.validator.url - * @description - * Use phone validator to restrict the input URLs. - * - * @param {string} value value to validate - * @css ng-validation-error - * - * @example - - - Enter valid URL: - - - - it('should invalidate url', function(){ - var n1 = element('.doc-example-live :input'); - expect(n1.attr('className')).not().toMatch(/ng-validation-error/); - input('text').enter('abc://server/path'); - expect(n1.attr('className')).toMatch(/ng-validation-error/); - }); - - - * - */ - 'url': function(value) { - if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) { - return null; - } - return "URL needs to be in http://server[:port]/path format."; - }, - - /** - * @workInProgress - * @ngdoc validator - * @name angular.validator.json - * @description - * Use json validator if you wish to restrict the user input to a valid JSON. - * - * @param {string} value value to validate - * @css ng-validation-error - * - * @example - - - - - - it('should invalidate json', function(){ - var n1 = element('.doc-example-live :input'); - expect(n1.attr('className')).not().toMatch(/ng-validation-error/); - input('json').enter('{name}'); - expect(n1.attr('className')).toMatch(/ng-validation-error/); - }); - - - * - */ - 'json': function(value) { - try { - fromJson(value); - return null; - } catch (e) { - return e.toString(); - } - }, - - /** - * @workInProgress - * @ngdoc validator - * @name angular.validator.asynchronous - * @description - * Use asynchronous validator if the validation can not be computed - * immediately, but is provided through a callback. The widget - * automatically shows a spinning indicator while the validity of - * the widget is computed. This validator caches the result. - * - * @param {string} value value to validate - * @param {function(inputToValidate,validationDone)} validate function to call to validate the state - * of the input. - * @param {function(data)=} [update=noop] function to call when state of the - * validator changes - * - * @paramDescription - * The `validate` function (specified by you) is called as - * `validate(inputToValidate, validationDone)`: - * - * * `inputToValidate`: value of the input box. - * * `validationDone`: `function(error, data){...}` - * * `error`: error text to display if validation fails - * * `data`: data object to pass to update function - * - * The `update` function is optionally specified by you and is - * called by on input change. Since the - * asynchronous validator caches the results, the update - * function can be called without a call to `validate` - * function. The function is called as `update(data)`: - * - * * `data`: data object as passed from validate function - * - * @css ng-input-indicator-wait, ng-validation-error - * - * @example - - - - This input is validated asynchronously: -
- -
-
- - it('should change color in delayed way', function(){ - var textBox = element('.doc-example-live :input'); - expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/); - expect(textBox.attr('className')).not().toMatch(/ng-validation-error/); - input('text').enter('X'); - expect(textBox.attr('className')).toMatch(/ng-input-indicator-wait/); - sleep(.6); - expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/); - expect(textBox.attr('className')).toMatch(/ng-validation-error/); - }); - -
- * - */ - /* - * cache is attached to the element - * cache: { - * inputs : { - * 'user input': { - * response: server response, - * error: validation error - * }, - * current: 'current input' - * } - * - */ - 'asynchronous': function(input, asynchronousFn, updateFn) { - if (!input) return; - var scope = this; - var element = scope.$element; - var cache = element.data('$asyncValidator'); - if (!cache) { - element.data('$asyncValidator', cache = {inputs:{}}); - } - - cache.current = input; - - var inputState = cache.inputs[input], - $invalidWidgets = scope.$service('$invalidWidgets'); - - if (!inputState) { - cache.inputs[input] = inputState = { inFlight: true }; - $invalidWidgets.markInvalid(scope.$element); - element.addClass('ng-input-indicator-wait'); - asynchronousFn(input, function(error, data) { - inputState.response = data; - inputState.error = error; - inputState.inFlight = false; - if (cache.current == input) { - element.removeClass('ng-input-indicator-wait'); - $invalidWidgets.markValid(element); - } - element.data($$validate)(); - scope.$service('$updateView')(); - }); - } else if (inputState.inFlight) { - // request in flight, mark widget invalid, but don't show it to user - $invalidWidgets.markInvalid(scope.$element); - } else { - (updateFn||noop)(inputState.response); - } - return inputState.error; - } - -}); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$cookieStore - * @requires $cookies - * - * @description - * Provides a key-value (string-object) storage, that is backed by session cookies. - * Objects put or retrieved from this storage are automatically serialized or - * deserialized by angular's toJson/fromJson. - * @example - */ -angularServiceInject('$cookieStore', function($store) { - - return { - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$cookieStore#get - * @methodOf angular.service.$cookieStore - * - * @description - * Returns the value of given cookie key - * - * @param {string} key Id to use for lookup. - * @returns {Object} Deserialized cookie value. - */ - get: function(key) { - return fromJson($store[key]); - }, - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$cookieStore#put - * @methodOf angular.service.$cookieStore - * - * @description - * Sets a value for given cookie key - * - * @param {string} key Id for the `value`. - * @param {Object} value Value to be stored. - */ - put: function(key, value) { - $store[key] = toJson(value); - }, - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$cookieStore#remove - * @methodOf angular.service.$cookieStore - * - * @description - * Remove given cookie - * - * @param {string} key Id of the key-value pair to delete. - */ - remove: function(key) { - delete $store[key]; - } - }; - -}, ['$cookies']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$cookies - * @requires $browser - * - * @description - * Provides read/write access to browser's cookies. - * - * Only a simple Object is exposed and by adding or removing properties to/from - * this object, new cookies are created/deleted at the end of current $eval. - * - * @example - */ -angularServiceInject('$cookies', function($browser) { - var rootScope = this, - cookies = {}, - lastCookies = {}, - lastBrowserCookies, - runEval = false; - - //creates a poller fn that copies all cookies from the $browser to service & inits the service - $browser.addPollFn(function() { - var currentCookies = $browser.cookies(); - if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl - lastBrowserCookies = currentCookies; - copy(currentCookies, lastCookies); - copy(currentCookies, cookies); - if (runEval) rootScope.$eval(); - } - })(); - - runEval = true; - - //at the end of each eval, push cookies - //TODO: this should happen before the "delayed" watches fire, because if some cookies are not - // strings or browser refuses to store some cookies, we update the model in the push fn. - this.$onEval(PRIORITY_LAST, push); - - return cookies; - - - /** - * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. - */ - function push(){ - var name, - value, - browserCookies, - updated; - - //delete any cookies deleted in $cookies - for (name in lastCookies) { - if (isUndefined(cookies[name])) { - $browser.cookies(name, undefined); - } - } - - //update all cookies updated in $cookies - for(name in cookies) { - value = cookies[name]; - if (!isString(value)) { - if (isDefined(lastCookies[name])) { - cookies[name] = lastCookies[name]; - } else { - delete cookies[name]; - } - } else if (value !== lastCookies[name]) { - $browser.cookies(name, value); - updated = true; - } - } - - //verify what was actually stored - if (updated){ - updated = false; - browserCookies = $browser.cookies(); - - for (name in cookies) { - if (cookies[name] !== browserCookies[name]) { - //delete or reset all cookies that the browser dropped from $cookies - if (isUndefined(browserCookies[name])) { - delete cookies[name]; - } else { - cookies[name] = browserCookies[name]; - } - updated = true; - } - } - } - } -}, ['$browser']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$defer - * @requires $browser - * @requires $exceptionHandler - * @requires $updateView - * - * @description - * Delegates to {@link angular.service.$browser $browser.defer}, but wraps the `fn` function - * into a try/catch block and delegates any exceptions to - * {@link angular.service.$exceptionHandler $exceptionHandler} service. - * - * In tests you can use `$browser.defer.flush()` to flush the queue of deferred functions. - * - * @param {function()} fn A function, who's execution should be deferred. - * @param {number=} [delay=0] of milliseconds to defer the function execution. - */ -angularServiceInject('$defer', function($browser, $exceptionHandler, $updateView) { - return function(fn, delay) { - $browser.defer(function() { - try { - fn(); - } catch(e) { - $exceptionHandler(e); - } finally { - $updateView(); - } - }, delay); - }; -}, ['$browser', '$exceptionHandler', '$updateView']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$document - * @requires $window - * - * @description - * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document` - * element. - */ -angularServiceInject("$document", function(window){ - return jqLite(window.document); -}, ['$window']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$exceptionHandler - * @requires $log - * - * @description - * Any uncaught exception in angular expressions is delegated to this service. - * The default implementation simply delegates to `$log.error` which logs it into - * the browser console. - * - * In unit tests, if `angular-mocks.js` is loaded, this service is overriden by - * {@link angular.mock.service.$exceptionHandler mock $exceptionHandler} - * - * @example - */ -var $exceptionHandlerFactory; //reference to be used only in tests -angularServiceInject('$exceptionHandler', $exceptionHandlerFactory = function($log){ - return function(e) { - $log.error(e); - }; -}, ['$log']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$hover - * @requires $browser - * @requires $document - * - * @description - * - * @example - */ -angularServiceInject("$hover", function(browser, document) { - var tooltip, self = this, error, width = 300, arrowWidth = 10, body = jqLite(document[0].body); - browser.hover(function(element, show){ - if (show && (error = element.attr(NG_EXCEPTION) || element.attr(NG_VALIDATION_ERROR))) { - if (!tooltip) { - tooltip = { - callout: jqLite('
'), - arrow: jqLite('
'), - title: jqLite('
'), - content: jqLite('
') - }; - tooltip.callout.append(tooltip.arrow); - tooltip.callout.append(tooltip.title); - tooltip.callout.append(tooltip.content); - body.append(tooltip.callout); - } - var docRect = body[0].getBoundingClientRect(), - elementRect = element[0].getBoundingClientRect(), - leftSpace = docRect.right - elementRect.right - arrowWidth; - tooltip.title.text(element.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error..."); - tooltip.content.text(error); - if (leftSpace < width) { - tooltip.arrow.addClass('ng-arrow-right'); - tooltip.arrow.css({left: (width + 1)+'px'}); - tooltip.callout.css({ - position: 'fixed', - left: (elementRect.left - arrowWidth - width - 4) + "px", - top: (elementRect.top - 3) + "px", - width: width + "px" - }); - } else { - tooltip.arrow.addClass('ng-arrow-left'); - tooltip.callout.css({ - position: 'fixed', - left: (elementRect.right + arrowWidth) + "px", - top: (elementRect.top - 3) + "px", - width: width + "px" - }); - } - } else if (tooltip) { - tooltip.callout.remove(); - tooltip = null; - } - }); -}, ['$browser', '$document'], true); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$invalidWidgets - * - * @description - * Keeps references to all invalid widgets found during validation. - * Can be queried to find whether there are any invalid widgets currently displayed. - * - * @example - */ -angularServiceInject("$invalidWidgets", function(){ - var invalidWidgets = []; - - - /** Remove an element from the array of invalid widgets */ - invalidWidgets.markValid = function(element){ - var index = indexOf(invalidWidgets, element); - if (index != -1) - invalidWidgets.splice(index, 1); - }; - - - /** Add an element to the array of invalid widgets */ - invalidWidgets.markInvalid = function(element){ - var index = indexOf(invalidWidgets, element); - if (index === -1) - invalidWidgets.push(element); - }; - - - /** Return count of all invalid widgets that are currently visible */ - invalidWidgets.visible = function() { - var count = 0; - forEach(invalidWidgets, function(widget){ - count = count + (isVisible(widget) ? 1 : 0); - }); - return count; - }; - - - /* At the end of each eval removes all invalid widgets that are not part of the current DOM. */ - this.$onEval(PRIORITY_LAST, function() { - for(var i = 0; i < invalidWidgets.length;) { - var widget = invalidWidgets[i]; - if (isOrphan(widget[0])) { - invalidWidgets.splice(i, 1); - if (widget.dealoc) widget.dealoc(); - } else { - i++; - } - } - }); - - - /** - * Traverses DOM element's (widget's) parents and considers the element to be an orphant if one of - * it's parents isn't the current window.document. - */ - function isOrphan(widget) { - if (widget == window.document) return false; - var parent = widget.parentNode; - return !parent || isOrphan(parent); - } - - return invalidWidgets; -}); -var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/, - HASH_MATCH = /^([^\?]*)?(\?([^\?]*))?$/, - DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21}; - -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$location - * @requires $browser - * - * @property {string} href The full URL of the current location. - * @property {string} protocol The protocol part of the URL (e.g. http or https). - * @property {string} host The host name, ip address or FQDN of the current location. - * @property {number} port The port number of the current location (e.g. 80, 443, 8080). - * @property {string} path The path of the current location (e.g. /myapp/inbox). - * @property {Object.} search Map of query parameters (e.g. {user:"foo", page:23}). - * @property {string} hash The fragment part of the URL of the current location (e.g. #foo). - * @property {string} hashPath Similar to `path`, but located in the `hash` fragment - * (e.g. ../foo#/some/path => /some/path). - * @property {Object.} hashSearch Similar to `search` but located in `hash` - * fragment (e.g. .../foo#/some/path?hashQuery=param => {hashQuery: "param"}). - * - * @description - * Parses the browser location url and makes it available to your application. - * Any changes to the url are reflected into `$location` service and changes to - * `$location` are reflected in the browser location url. - * - * Notice that using browser's forward/back buttons changes the $location. - * - * @example - - -
- test hash| - reset hash
- -
$location = {{$location}}
-
-
- - it('should initialize the input field', function() { - expect(using('.doc-example-live').input('$location.hash').val()). - toBe('!/api/angular.service.$location'); - }); - - - it('should bind $location.hash to the input field', function() { - using('.doc-example-live').input('$location.hash').enter('foo'); - expect(browser().location().hash()).toBe('foo'); - }); - - - it('should set the hash to a test string with test link is presed', function() { - using('.doc-example-live').element('#ex-test').click(); - expect(using('.doc-example-live').input('$location.hash').val()). - toBe('myPath?name=misko'); - }); - - it('should reset $location when reset link is pressed', function() { - using('.doc-example-live').input('$location.hash').enter('foo'); - using('.doc-example-live').element('#ex-reset').click(); - expect(using('.doc-example-live').input('$location.hash').val()). - toBe('!/api/angular.service.$location'); - }); - - -
- */ -angularServiceInject("$location", function($browser) { - var scope = this, - location = {update:update, updateHash: updateHash}, - lastLocation = {}; - - $browser.onHashChange(function() { //register - update($browser.getUrl()); - copy(location, lastLocation); - scope.$eval(); - })(); //initialize - - this.$onEval(PRIORITY_FIRST, sync); - this.$onEval(PRIORITY_LAST, updateBrowser); - - return location; - - // PUBLIC METHODS - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$location#update - * @methodOf angular.service.$location - * - * @description - * Updates the location object. - * - * Does not immediately update the browser. Instead the browser is updated at the end of $eval() - * cycle. - * - *
-       $location.update('http://www.angularjs.org/path#hash?search=x');
-       $location.update({host: 'www.google.com', protocol: 'https'});
-       $location.update({hashPath: '/path', hashSearch: {a: 'b', x: true}});
-     
- * - * @param {string|Object} href Full href as a string or object with properties - */ - function update(href) { - if (isString(href)) { - extend(location, parseHref(href)); - } else { - if (isDefined(href.hash)) { - extend(href, isString(href.hash) ? parseHash(href.hash) : href.hash); - } - - extend(location, href); - - if (isDefined(href.hashPath || href.hashSearch)) { - location.hash = composeHash(location); - } - - location.href = composeHref(location); - } - } - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$location#updateHash - * @methodOf angular.service.$location - * - * @description - * Updates the hash fragment part of the url. - * - * @see update() - * - *
-       scope.$location.updateHash('/hp')
-         ==> update({hashPath: '/hp'})
-       scope.$location.updateHash({a: true, b: 'val'})
-         ==> update({hashSearch: {a: true, b: 'val'}})
-       scope.$location.updateHash('/hp', {a: true})
-         ==> update({hashPath: '/hp', hashSearch: {a: true}})
-     
- * - * @param {string|Object} path A hashPath or hashSearch object - * @param {Object=} search A hashSearch object - */ - function updateHash(path, search) { - var hash = {}; - - if (isString(path)) { - hash.hashPath = path; - hash.hashSearch = search || {}; - } else - hash.hashSearch = path; - - hash.hash = composeHash(hash); - - update({hash: hash}); - } - - - // INNER METHODS - - /** - * Synchronizes all location object properties. - * - * User is allowed to change properties, so after property change, - * location object is not in consistent state. - * - * Properties are synced with the following precedence order: - * - * - `$location.href` - * - `$location.hash` - * - everything else - * - * Keep in mind that if the following code is executed: - * - * scope.$location.href = 'http://www.angularjs.org/path#a/b' - * - * immediately afterwards all other properties are still the old ones... - * - * This method checks the changes and update location to the consistent state - */ - function sync() { - if (!equals(location, lastLocation)) { - if (location.href != lastLocation.href) { - update(location.href); - return; - } - if (location.hash != lastLocation.hash) { - var hash = parseHash(location.hash); - updateHash(hash.hashPath, hash.hashSearch); - } else { - location.hash = composeHash(location); - location.href = composeHref(location); - } - update(location.href); - } - } - - - /** - * If location has changed, update the browser - * This method is called at the end of $eval() phase - */ - function updateBrowser() { - sync(); - - if ($browser.getUrl() != location.href) { - $browser.setUrl(location.href); - copy(location, lastLocation); - } - } - - /** - * Compose href string from a location object - * - * @param {Object} loc The location object with all properties - * @return {string} Composed href - */ - function composeHref(loc) { - var url = toKeyValue(loc.search); - var port = (loc.port == DEFAULT_PORTS[loc.protocol] ? null : loc.port); - - return loc.protocol + '://' + loc.host + - (port ? ':' + port : '') + loc.path + - (url ? '?' + url : '') + (loc.hash ? '#' + loc.hash : ''); - } - - /** - * Compose hash string from location object - * - * @param {Object} loc Object with hashPath and hashSearch properties - * @return {string} Hash string - */ - function composeHash(loc) { - var hashSearch = toKeyValue(loc.hashSearch); - //TODO: temporary fix for issue #158 - return escape(loc.hashPath).replace(/%21/gi, '!').replace(/%3A/gi, ':').replace(/%24/gi, '$') + - (hashSearch ? '?' + hashSearch : ''); - } - - /** - * Parse href string into location object - * - * @param {string} href - * @return {Object} The location object - */ - function parseHref(href) { - var loc = {}; - var match = URL_MATCH.exec(href); - - if (match) { - loc.href = href.replace(/#$/, ''); - loc.protocol = match[1]; - loc.host = match[3] || ''; - loc.port = match[5] || DEFAULT_PORTS[loc.protocol] || null; - loc.path = match[6] || ''; - loc.search = parseKeyValue(match[8]); - loc.hash = match[10] || ''; - - extend(loc, parseHash(loc.hash)); - } - - return loc; - } - - /** - * Parse hash string into object - * - * @param {string} hash - */ - function parseHash(hash) { - var h = {}; - var match = HASH_MATCH.exec(hash); - - if (match) { - h.hash = hash; - h.hashPath = unescape(match[1] || ''); - h.hashSearch = parseKeyValue(match[3]); - } - - return h; - } -}, ['$browser']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$log - * @requires $window - * - * @description - * Simple service for logging. Default implementation writes the message - * into the browser's console (if present). - * - * The main purpose of this service is to simplify debugging and troubleshooting. - * - * @example - - -

Reload this page with open console, enter text and hit the log button...

- Message: - - - - - -
- - -
- */ -var $logFactory; //reference to be used only in tests -angularServiceInject("$log", $logFactory = function($window){ - return { - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$log#log - * @methodOf angular.service.$log - * - * @description - * Write a log message - */ - log: consoleLog('log'), - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$log#warn - * @methodOf angular.service.$log - * - * @description - * Write a warning message - */ - warn: consoleLog('warn'), - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$log#info - * @methodOf angular.service.$log - * - * @description - * Write an information message - */ - info: consoleLog('info'), - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$log#error - * @methodOf angular.service.$log - * - * @description - * Write an error message - */ - error: consoleLog('error') - }; - - function consoleLog(type) { - var console = $window.console || {}; - var logFn = console[type] || console.log || noop; - if (logFn.apply) { - return function(){ - var args = []; - forEach(arguments, function(arg){ - args.push(formatError(arg)); - }); - return logFn.apply(console, args); - }; - } else { - // we are IE, in which case there is nothing we can do - return logFn; - } - } -}, ['$window']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$resource - * @requires $xhr.cache - * - * @description - * A factory which creates a resource object that lets you interact with - * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. - * - * The returned resource object has action methods which provide high-level behaviors without - * the need to interact with the low level {@link angular.service.$xhr $xhr} service or - * raw XMLHttpRequest. - * - * @param {string} url A parameterized URL template with parameters prefixed by `:` as in - * `/user/:username`. - * - * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in - * `actions` methods. - * - * Each key value in the parameter object is first bound to url template if present and then any - * excess keys are appended to the url search query after the `?`. - * - * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in - * URL `/path/greet?salutation=Hello`. - * - * If the parameter value is prefixed with `@` then the value of that parameter is extracted from - * the data object (useful for non-GET operations). - * - * @param {Object.=} actions Hash with declaration of custom action that should extend the - * default set of resource actions. The declaration should be created in the following format: - * - * {action1: {method:?, params:?, isArray:?, verifyCache:?}, - * action2: {method:?, params:?, isArray:?, verifyCache:?}, - * ...} - * - * Where: - * - * - `action` – {string} – The name of action. This name becomes the name of the method on your - * resource object. - * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, - * and `JSON` (also known as JSONP). - * - `params` – {object=} – Optional set of pre-bound parameters for this action. - * - isArray – {boolean=} – If true then the returned object for this action is an array, see - * `returns` section. - * - verifyCache – {boolean=} – If true then whenever cache hit occurs, the object is returned and - * an async request will be made to the server and the resources as well as the cache will be - * updated when the response is received. - * - * @returns {Object} A resource "class" object with methods for the default set of resource actions - * optionally extended with custom `actions`. The default set contains these actions: - * - * { 'get': {method:'GET'}, - * 'save': {method:'POST'}, - * 'query': {method:'GET', isArray:true}, - * 'remove': {method:'DELETE'}, - * 'delete': {method:'DELETE'} }; - * - * Calling these methods invoke an {@link angular.service.$xhr} with the specified http method, - * destination and parameters. When the data is returned from the server then the object is an - * instance of the resource class `save`, `remove` and `delete` actions are available on it as - * methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read, - * update, delete) on server-side data like this: - *
-        var User = $resource('/user/:userId', {userId:'@id'});
-        var user = User.get({userId:123}, function(){
-          user.abc = true;
-          user.$save();
-        });
-     
- * - * It is important to realize that invoking a $resource object method immediately returns an - * empty reference (object or array depending on `isArray`). Once the data is returned from the - * server the existing reference is populated with the actual data. This is a useful trick since - * usually the resource is assigned to a model which is then rendered by the view. Having an empty - * object results in no rendering, once the data arrives from the server then the object is - * populated with the data and the view automatically re-renders itself showing the new data. This - * means that in most case one never has to write a callback function for the action methods. - * - * The action methods on the class object or instance object can be invoked with the following - * parameters: - * - * - HTTP GET "class" actions: `Resource.action([parameters], [callback])` - * - non-GET "class" actions: `Resource.action(postData, [parameters], [callback])` - * - non-GET instance actions: `instance.$action([parameters], [callback])` - * - * - * @example - * - * # Credit card resource - * - *
-     // Define CreditCard class
-     var CreditCard = $resource('/user/:userId/card/:cardId',
-      {userId:123, cardId:'@id'}, {
-       charge: {method:'POST', params:{charge:true}}
-      });
-
-     // We can retrieve a collection from the server
-     var cards = CreditCard.query();
-     // GET: /user/123/card
-     // server returns: [ {id:456, number:'1234', name:'Smith'} ];
-
-     var card = cards[0];
-     // each item is an instance of CreditCard
-     expect(card instanceof CreditCard).toEqual(true);
-     card.name = "J. Smith";
-     // non GET methods are mapped onto the instances
-     card.$save();
-     // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
-     // server returns: {id:456, number:'1234', name: 'J. Smith'};
-
-     // our custom method is mapped as well.
-     card.$charge({amount:9.99});
-     // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
-     // server returns: {id:456, number:'1234', name: 'J. Smith'};
-
-     // we can create an instance as well
-     var newCard = new CreditCard({number:'0123'});
-     newCard.name = "Mike Smith";
-     newCard.$save();
-     // POST: /user/123/card {number:'0123', name:'Mike Smith'}
-     // server returns: {id:789, number:'01234', name: 'Mike Smith'};
-     expect(newCard.id).toEqual(789);
- * 
- * - * The object returned from this function execution is a resource "class" which has "static" method - * for each action in the definition. - * - * Calling these methods invoke `$xhr` on the `url` template with the given `method` and `params`. - * When the data is returned from the server then the object is an instance of the resource type and - * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD - * operations (create, read, update, delete) on server-side data. - -
-     var User = $resource('/user/:userId', {userId:'@id'});
-     var user = User.get({userId:123}, function(){
-       user.abc = true;
-       user.$save();
-     });
-   
- * - * It's worth noting that the callback for `get`, `query` and other method gets passed in the - * response that came from the server, so one could rewrite the above example as: - * -
-     var User = $resource('/user/:userId', {userId:'@id'});
-     User.get({userId:123}, function(u){
-       u.abc = true;
-       u.$save();
-     });
-   
- - * # Buzz client - - Let's look at what a buzz client created with the `$resource` service looks like: - - - - -
- - -
-
-

- - {{item.actor.name}} - Expand replies: {{item.links.replies[0].count}} -

- {{item.object.content | html}} -
- - {{reply.actor.name}}: {{reply.content | html}} -
-
-
-
- - -
- */ -angularServiceInject('$resource', function($xhr){ - var resource = new ResourceFactory($xhr); - return bind(resource, resource.route); -}, ['$xhr.cache']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$route - * @requires $location - * - * @property {Object} current Reference to the current route definition. - * @property {Array.} routes Array of all configured routes. - * - * @description - * Watches `$location.hashPath` and tries to map the hash to an existing route - * definition. It is used for deep-linking URLs to controllers and views (HTML partials). - * - * The `$route` service is typically used in conjunction with {@link angular.widget.ng:view ng:view} - * widget. - * - * @example - This example shows how changing the URL hash causes the $route - to match a route against the URL, and the [[ng:include]] pulls in the partial. - Try changing the URL in the input box to see changes. - - - - - - Chose: - Moby | - Moby: Ch1 | - Gatsby | - Gatsby: Ch4
- -
$location={{$location}}
-
$route.current.template={{$route.current.template}}
-
$route.current.params={{$route.current.params}}
-
$route.current.scope.name={{$route.current.scope.name}}
-
- -
- - -
- */ -angularServiceInject('$route', function(location, $updateView) { - var routes = {}, - onChange = [], - matcher = switchRouteMatcher, - parentScope = this, - dirty = 0, - $route = { - routes: routes, - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$route#onChange - * @methodOf angular.service.$route - * - * @param {function()} fn Function that will be called when `$route.current` changes. - * @returns {function()} The registered function. - * - * @description - * Register a handler function that will be called when route changes - */ - onChange: function(fn) { - onChange.push(fn); - return fn; - }, - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$route#parent - * @methodOf angular.service.$route - * - * @param {Scope} [scope=rootScope] Scope to be used as parent for newly created - * `$route.current.scope` scopes. - * - * @description - * Sets a scope to be used as the parent scope for scopes created on route change. If not - * set, defaults to the root scope. - */ - parent: function(scope) { - if (scope) parentScope = scope; - }, - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$route#when - * @methodOf angular.service.$route - * - * @param {string} path Route path (matched against `$location.hash`) - * @param {Object} params Mapping information to be assigned to `$route.current` on route - * match. - * - * Object properties: - * - * - `controller` – `{function()=}` – Controller fn that should be associated with newly - * created scope. - * - `template` – `{string=}` – path to an html template that should be used by - * {@link angular.widget.ng:view ng:view} or - * {@link angular.widget.ng:include ng:include} widgets. - * - `redirectTo` – {(string|function())=} – value to update - * {@link angular.service.$location $location} hash with and trigger route redirection. - * - * If `redirectTo` is a function, it will be called with the following parameters: - * - * - `{Object.}` - route parameters extracted from the current - * `$location.hashPath` by applying the current route template. - * - `{string}` - current `$location.hash` - * - `{string}` - current `$location.hashPath` - * - `{string}` - current `$location.hashSearch` - * - * The custom `redirectTo` function is expected to return a string which will be used - * to update `$location.hash`. - * - * @returns {Object} route object - * - * @description - * Adds a new route definition to the `$route` service. - */ - when:function (path, params) { - if (isUndefined(path)) return routes; //TODO(im): remove - not needed! - var route = routes[path]; - if (!route) route = routes[path] = {}; - if (params) extend(route, params); - dirty++; - return route; - }, - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$route#otherwise - * @methodOf angular.service.$route - * - * @description - * Sets route definition that will be used on route change when no other route definition - * is matched. - * - * @param {Object} params Mapping information to be assigned to `$route.current`. - */ - otherwise: function(params) { - $route.when(null, params); - }, - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$route#reload - * @methodOf angular.service.$route - * - * @description - * Causes `$route` service to reload (and recreate the `$route.current` scope) upon the next - * eval even if {@link angular.service.$location $location} hasn't changed. - */ - reload: function() { - dirty++; - } - }; - - - function switchRouteMatcher(on, when, dstName) { - var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$', - params = [], - dst = {}; - forEach(when.split(/\W/), function(param){ - if (param) { - var paramRegExp = new RegExp(":" + param + "([\\W])"); - if (regex.match(paramRegExp)) { - regex = regex.replace(paramRegExp, "([^\/]*)$1"); - params.push(param); - } - } - }); - var match = on.match(new RegExp(regex)); - if (match) { - forEach(params, function(name, index){ - dst[name] = match[index + 1]; - }); - if (dstName) this.$set(dstName, dst); - } - return match ? dst : null; - } - - - function updateRoute(){ - var childScope, routeParams, pathParams, segmentMatch, key, redir; - - $route.current = null; - forEach(routes, function(rParams, rPath) { - if (!pathParams) { - if (pathParams = matcher(location.hashPath, rPath)) { - routeParams = rParams; - } - } - }); - - // "otherwise" fallback - routeParams = routeParams || routes[null]; - - if(routeParams) { - if (routeParams.redirectTo) { - if (isString(routeParams.redirectTo)) { - // interpolate the redirectTo string - redir = {hashPath: '', - hashSearch: extend({}, location.hashSearch, pathParams)}; - - forEach(routeParams.redirectTo.split(':'), function(segment, i) { - if (i==0) { - redir.hashPath += segment; - } else { - segmentMatch = segment.match(/(\w+)(.*)/); - key = segmentMatch[1]; - redir.hashPath += pathParams[key] || location.hashSearch[key]; - redir.hashPath += segmentMatch[2] || ''; - delete redir.hashSearch[key]; - } - }); - } else { - // call custom redirectTo function - redir = {hash: routeParams.redirectTo(pathParams, location.hash, location.hashPath, - location.hashSearch)}; - } - - location.update(redir); - $updateView(); //TODO this is to work around the $location<=>$browser issues - return; - } - - childScope = createScope(parentScope); - $route.current = extend({}, routeParams, { - scope: childScope, - params: extend({}, location.hashSearch, pathParams) - }); - } - - //fire onChange callbacks - forEach(onChange, parentScope.$tryEval); - - if (childScope) { - childScope.$become($route.current.controller); - } - } - - - this.$watch(function(){return dirty + location.hash;}, updateRoute); - - return $route; -}, ['$location', '$updateView']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$updateView - * @requires $browser - * - * @description - * Calling `$updateView` enqueues the eventual update of the view. (Update the DOM to reflect the - * model). The update is eventual, since there are often multiple updates to the model which may - * be deferred. The default update delayed is 25 ms. This means that the view lags the model by - * that time. (25ms is small enough that it is perceived as instantaneous by the user). The delay - * can be adjusted by setting the delay property of the service. - * - *
angular.service('$updateView').delay = 10
- * - * The delay is there so that multiple updates to the model which occur sufficiently close - * together can be merged into a single update. - * - * You don't usually call '$updateView' directly since angular does it for you in most cases, - * but there are some cases when you need to call it. - * - * - `$updateView()` called automatically by angular: - * - Your Application Controllers: Your controller code is called by angular and hence - * angular is aware that you may have changed the model. - * - Your Services: Your service is usually called by your controller code, hence same rules - * apply. - * - May need to call `$updateView()` manually: - * - Widgets / Directives: If you listen to any DOM events or events on any third party - * libraries, then angular is not aware that you may have changed state state of the - * model, and hence you need to call '$updateView()' manually. - * - 'setTimeout'/'XHR': If you call 'setTimeout' (instead of {@link angular.service.$defer}) - * or 'XHR' (instead of {@link angular.service.$xhr}) then you may be changing the model - * without angular knowledge and you may need to call '$updateView()' directly. - * - * NOTE: if you wish to update the view immediately (without delay), you can do so by calling - * {@link angular.scope.$eval} at any time from your code: - *
scope.$root.$eval()
- * - * In unit-test mode the update is instantaneous and synchronous to simplify writing tests. - * - */ - -function serviceUpdateViewFactory($browser){ - var rootScope = this; - var scheduled; - function update(){ - scheduled = false; - rootScope.$eval(); - } - return $browser.isMock ? update : function(){ - if (!scheduled) { - scheduled = true; - $browser.defer(update, serviceUpdateViewFactory.delay); - } - }; -} -serviceUpdateViewFactory.delay = 25; - -angularServiceInject('$updateView', serviceUpdateViewFactory, ['$browser']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$window - * - * @description - * A reference to the browser's `window` object. While `window` - * is globally available in JavaScript, it causes testability problems, because - * it is a global variable. In angular we always refer to it through the - * `$window` service, so it may be overriden, removed or mocked for testing. - * - * All expressions are evaluated with respect to current scope so they don't - * suffer from window globality. - * - * @example - - - - - - - - - */ -angularServiceInject("$window", bind(window, identity, window)); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$xhr.bulk - * @requires $xhr - * @requires $xhr.error - * @requires $log - * - * @description - * - * @example - */ -angularServiceInject('$xhr.bulk', function($xhr, $error, $log){ - var requests = [], - scope = this; - function bulkXHR(method, url, post, callback) { - if (isFunction(post)) { - callback = post; - post = null; - } - var currentQueue; - forEach(bulkXHR.urls, function(queue){ - if (isFunction(queue.match) ? queue.match(url) : queue.match.exec(url)) { - currentQueue = queue; - } - }); - if (currentQueue) { - if (!currentQueue.requests) currentQueue.requests = []; - currentQueue.requests.push({method: method, url: url, data:post, callback:callback}); - } else { - $xhr(method, url, post, callback); - } - } - bulkXHR.urls = {}; - bulkXHR.flush = function(callback){ - forEach(bulkXHR.urls, function(queue, url){ - var currentRequests = queue.requests; - if (currentRequests && currentRequests.length) { - queue.requests = []; - queue.callbacks = []; - $xhr('POST', url, {requests:currentRequests}, function(code, response){ - forEach(response, function(response, i){ - try { - if (response.status == 200) { - (currentRequests[i].callback || noop)(response.status, response.response); - } else { - $error(currentRequests[i], response); - } - } catch(e) { - $log.error(e); - } - }); - (callback || noop)(); - }); - scope.$eval(); - } - }); - }; - this.$onEval(PRIORITY_LAST, bulkXHR.flush); - return bulkXHR; -}, ['$xhr', '$xhr.error', '$log']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$xhr.cache - * @function - * @requires $xhr - * - * @description - * Acts just like the {@link angular.service.$xhr $xhr} service but caches responses for `GET` - * requests. All cache misses are delegated to the $xhr service. - * - * @property {function()} delegate Function to delegate all the cache misses to. Defaults to - * the {@link angular.service.$xhr $xhr} service. - * @property {object} data The hashmap where all cached entries are stored. - * - * @param {string} method HTTP method. - * @param {string} url Destination URL. - * @param {(string|Object)=} post Request body. - * @param {function(number, (string|Object))} callback Response callback. - * @param {boolean=} [verifyCache=false] If `true` then a result is immediately returned from cache - * (if present) while a request is sent to the server for a fresh response that will update the - * cached entry. The `callback` function will be called when the response is received. - * @param {boolean=} [sync=false] in case of cache hit execute `callback` synchronously. - */ -angularServiceInject('$xhr.cache', function($xhr, $defer, $log){ - var inflight = {}, self = this; - function cache(method, url, post, callback, verifyCache, sync){ - if (isFunction(post)) { - callback = post; - post = null; - } - if (method == 'GET') { - var data, dataCached; - if (dataCached = cache.data[url]) { - - if (sync) { - callback(200, copy(dataCached.value)); - } else { - $defer(function() { callback(200, copy(dataCached.value)); }); - } - - if (!verifyCache) - return; - } - - if (data = inflight[url]) { - data.callbacks.push(callback); - } else { - inflight[url] = {callbacks: [callback]}; - cache.delegate(method, url, post, function(status, response){ - if (status == 200) - cache.data[url] = { value: response }; - var callbacks = inflight[url].callbacks; - delete inflight[url]; - forEach(callbacks, function(callback){ - try { - (callback||noop)(status, copy(response)); - } catch(e) { - $log.error(e); - } - }); - }); - } - - } else { - cache.data = {}; - cache.delegate(method, url, post, callback); - } - } - cache.data = {}; - cache.delegate = $xhr; - return cache; -}, ['$xhr.bulk', '$defer', '$log']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$xhr.error - * @function - * @requires $log - * - * @description - * Error handler for {@link angular.service.$xhr $xhr service}. An application can replaces this - * service with one specific for the application. The default implementation logs the error to - * {@link angular.service.$log $log.error}. - * - * @param {Object} request Request object. - * - * The object has the following properties - * - * - `method` – `{string}` – The http request method. - * - `url` – `{string}` – The request destination. - * - `data` – `{(string|Object)=} – An optional request body. - * - `callback` – `{function()}` – The callback function - * - * @param {Object} response Response object. - * - * The response object has the following properties: - * - * - status – {number} – Http status code. - * - body – {string|Object} – Body of the response. - * - * @example - - - fetch a non-existent file and log an error in the console: - - - - */ -angularServiceInject('$xhr.error', function($log){ - return function(request, response){ - $log.error('ERROR: XHR: ' + request.url, request, response); - }; -}, ['$log']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$xhr - * @function - * @requires $browser $xhr delegates all XHR requests to the `$browser.xhr()`. A mock version - * of the $browser exists which allows setting expectaitions on XHR requests - * in your tests - * @requires $xhr.error $xhr delegates all non `2xx` response code to this service. - * @requires $log $xhr delegates all exceptions to `$log.error()`. - * @requires $updateView After a server response the view needs to be updated for data-binding to - * take effect. - * - * @description - * Generates an XHR request. The $xhr service delegates all requests to - * {@link angular.service.$browser $browser.xhr()} and adds error handling and security features. - * While $xhr service provides nicer api than raw XmlHttpRequest, it is still considered a lower - * level api in angular. For a higher level abstraction that utilizes `$xhr`, please check out the - * {@link angular.service.$resource $resource} service. - * - * # Error handling - * All XHR responses with response codes other then `2xx` are delegated to - * {@link angular.service.$xhr.error $xhr.error}. The `$xhr.error` can intercept the request - * and process it in application specific way, or resume normal execution by calling the - * request callback method. - * - * # HTTP Headers - * The $xhr service will automatically add certain http headers to all requests. These defaults can - * be fully configured by accessing the `$xhr.defaults.headers` configuration object, which - * currently contains this default configuration: - * - * - `$xhr.defaults.headers.common` (headers that are common for all requests): - * - `Accept: application/json, text/plain, *\/*` - * - `X-Requested-With: XMLHttpRequest` - * - `$xhr.defaults.headers.post` (header defaults for HTTP POST requests): - * - `Content-Type: application/x-www-form-urlencoded` - * - * To add or overwrite these defaults, simple add or remove a property from this configuration - * object. To add headers for an HTTP method other than POST, simple create a new object with name - * equal to the lowercased http method name, e.g. `$xhr.defaults.headers.get['My-Header']='value'`. - * - * - * # Security Considerations - * When designing web applications your design needs to consider security threats from - * {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx - * JSON Vulnerability} and {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF}. - * Both server and the client must cooperate in order to eliminate these threats. Angular comes - * pre-configured with strategies that address these issues, but for this to work backend server - * cooperation is required. - * - * ## JSON Vulnerability Protection - * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx - * JSON Vulnerability} allows third party web-site to turn your JSON resource URL into - * {@link http://en.wikipedia.org/wiki/JSON#JSONP JSONP} request under some conditions. To - * counter this your server can prefix all JSON requests with following string `")]}',\n"`. - * Angular will automatically strip the prefix before processing it as JSON. - * - * For example if your server needs to return: - *
- * ['one','two']
- * 
- * - * which is vulnerable to attack, your server can return: - *
- * )]}',
- * ['one','two']
- * 
- * - * angular will strip the prefix, before processing the JSON. - * - * - * ## Cross Site Request Forgery (XSRF) Protection - * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which an - * unauthorized site can gain your user's private data. Angular provides following mechanism to - * counter XSRF. When performing XHR requests, the $xhr service reads a token from a cookie - * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that - * runs on your domain could read the cookie, your server can be assured that the XHR came from - * JavaScript running on your domain. - * - * To take advantage of this, your server needs to set a token in a JavaScript readable session - * cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the server - * can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure that only - * JavaScript running on your domain could have read the token. The token must be unique for each - * user and must be verifiable by the server (to prevent the JavaScript making up its own tokens). - * We recommend that the token is a digest of your site's authentication cookie with - * {@link http://en.wikipedia.org/wiki/Rainbow_table salt for added security}. - * - * @param {string} method HTTP method to use. Valid values are: `GET`, `POST`, `PUT`, `DELETE`, and - * `JSON`. `JSON` is a special case which causes a - * [JSONP](http://en.wikipedia.org/wiki/JSON#JSONP) cross domain request using script tag - * insertion. - * @param {string} url Relative or absolute URL specifying the destination of the request. For - * `JSON` requests, `url` should include `JSON_CALLBACK` string to be replaced with a name of an - * angular generated callback function. - * @param {(string|Object)=} post Request content as either a string or an object to be stringified - * as JSON before sent to the server. - * @param {function(number, (string|Object))} callback A function to be called when the response is - * received. The callback will be called with: - * - * - {number} code [HTTP status code](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) of - * the response. This will currently always be 200, since all non-200 responses are routed to - * {@link angular.service.$xhr.error} service. - * - {string|Object} response Response object as string or an Object if the response was in JSON - * format. - * - * @example - - - -
- -
- - - sample - buzz -
code={{code}}
-
response={{response}}
-
-
-
- */ -angularServiceInject('$xhr', function($browser, $error, $log, $updateView){ - - var xhrHeaderDefaults = { - common: { - "Accept": "application/json, text/plain, */*", - "X-Requested-With": "XMLHttpRequest" - }, - post: {'Content-Type': 'application/x-www-form-urlencoded'}, - get: {}, // all these empty properties are needed so that client apps can just do: - head: {}, // $xhr.defaults.headers.head.foo="bar" without having to create head object - put: {}, // it also means that if we add a header for these methods in the future, it - 'delete': {}, // won't be easily silently lost due to an object assignment. - patch: {} - }; - - function xhr(method, url, post, callback){ - if (isFunction(post)) { - callback = post; - post = null; - } - if (post && isObject(post)) { - post = toJson(post); - } - - $browser.xhr(method, url, post, function(code, response){ - try { - if (isString(response)) { - if (response.match(/^\)\]\}',\n/)) response=response.substr(6); - if (/^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) { - response = fromJson(response, true); - } - } - if (200 <= code && code < 300) { - callback(code, response); - } else { - $error( - {method: method, url:url, data:post, callback:callback}, - {status: code, body:response}); - } - } catch (e) { - $log.error(e); - } finally { - $updateView(); - } - }, extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']}, - xhrHeaderDefaults.common, - xhrHeaderDefaults[lowercase(method)])); - }; - - xhr.defaults = {headers: xhrHeaderDefaults}; - - return xhr; -}, ['$browser', '$xhr.error', '$log', '$updateView']); -/** - * @workInProgress - * @ngdoc overview - * @name angular.directive - * @description - * - * Custom attributes for DOM elements. Directives modify the behavior of the element they are - * specified in, but are not intended to add elements to the DOM as are - * {@link angular.widget widgets}. - * - * Following is the list of built-in angular directives: - * - * * {@link angular.directive.ng:bind ng:bind} - Creates a data-binding between HTML text value and - * data model. - * * {@link angular.directive.ng:bind-attr ng:bind-attr} - Creates a data-binding as in `ng:bind`, - * but uses JSON key / value pairs. - * * {@link angular.directive.ng:bind-template ng:bind-template} - Replaces text value of an element - * with a specified template. - * * {@link angular.directive.ng:change ng:change} - Executes an expression when the value of an - * input widget changes. - * * {@link angular.directive.ng:class ng:class} - Conditionally set CSS class on an element. - * * {@link angular.directive.ng:class-even ng:class-even} - Like `ng:class`, but works in - * conjunction with {@link angular.widget.@ng:repeat} to affect even rows in a collection. - * * {@link angular.directive.ng:class-odd ng:class-odd} - Like `ng:class`, but works with {@link - * angular.widget.@ng:repeat} to affect odd rows. - * * {@link angular.directive.ng:click ng:click} - Executes custom behavior when element is clicked. - * * {@link angular.directive.ng:controller ng:controller} - Creates a scope object linked to the - * DOM element and assigns behavior to the scope. - * * {@link angular.directive.ng:eval ng:eval} - Executes a binding but blocks output. - * * {@link angular.directive.ng:eval-order ng:eval-order} - Change evaluation order when updating - * the view. - * * {@link angular.directive.ng:hide ng:hide} - Conditionally hides a portion of HTML. - * * {@link angular.directive.ng:href ng:href} - Places an href in the angular namespace. - * * {@link angular.directive.ng:init} - Initialization tasks run before a template is executed. - * * {@link angular.directive.ng:show ng:show} - Conditionally displays a portion of HTML. - * * {@link angular.directive.ng:src ng:src} - Places a `src` attribute into the angular namespace. - * * {@link angular.directive.ng:style ng:style} - Conditionally set CSS styles on an element. - * * {@link angular.directive.ng:submit} - Binds angular expressions to `onSubmit` events. - * - * For more information about how angular directives work, and how to create your own directives, - * see {@link guide/dev_guide.compiler.directives Understanding Angular Directives} in the angular - * Developer Guide. - */ - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:init - * - * @description - * The `ng:init` attribute specifies initialization tasks to be executed - * before the template enters execution mode during bootstrap. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. - * - * @example - - -
- {{greeting}} {{person}}! -
-
- - it('should check greeting', function(){ - expect(binding('greeting')).toBe('Hello'); - expect(binding('person')).toBe('World'); - }); - -
- */ -angularDirective("ng:init", function(expression){ - return function(element){ - this.$tryEval(expression, element); - }; -}); - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:controller - * - * @description - * The `ng:controller` directive assigns behavior to a scope. This is a key aspect of how angular - * supports the principles behind the Model-View-Controller design pattern. - * - * MVC components in angular: - * - * * Model — The Model is data in scope properties; scopes are attached to the DOM. - * * View — The template (HTML with data bindings) is rendered into the View. - * * Controller — The `ng:controller` directive specifies a Controller class; the class has - * methods that typically express the business logic behind the application. - * - * Note that an alternative way to define controllers is via the `{@link angular.service.$route}` - * service. - * - * @element ANY - * @param {expression} expression Name of a globally accessible constructor function or an - * {@link guide/dev_guide.expressions expression} that on the current scope evaluates to a - * constructor function. - * - * @example - * Here is a simple form for editing user contact information. Adding, removing, clearing, and - * greeting are methods declared on the controller (see source tab). These methods can - * easily be called from the angular markup. Notice that the scope becomes the `this` for the - * controller's instance. This allows for easy access to the view data from the controller. Also - * notice that any changes to the data are automatically reflected in the View without the need - * for a manual update. - - - -
- Name: - [ greet ]
- Contact: -
    -
  • - - - [ clear - | X ] -
  • -
  • [ add ]
  • -
-
-
- - it('should check controller', function(){ - expect(element('.doc-example-live div>:input').val()).toBe('John Smith'); - expect(element('.doc-example-live li[ng\\:repeat-index="0"] input').val()).toBe('408 555 1212'); - expect(element('.doc-example-live li[ng\\:repeat-index="1"] input').val()).toBe('john.smith@example.org'); - element('.doc-example-live li:first a:contains("clear")').click(); - expect(element('.doc-example-live li:first input').val()).toBe(''); - element('.doc-example-live li:last a:contains("add")').click(); - expect(element('.doc-example-live li[ng\\:repeat-index="2"] input').val()).toBe('yourname@example.org'); - }); - -
- */ -angularDirective("ng:controller", function(expression){ - this.scope(true); - return function(element){ - var controller = getter(window, expression, true) || getter(this, expression, true); - if (!controller) - throw "Can not find '"+expression+"' controller."; - if (!isFunction(controller)) - throw "Reference '"+expression+"' is not a class."; - this.$become(controller); - }; -}); - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:eval - * - * @description - * The `ng:eval` allows you to execute a binding which has side effects - * without displaying the result to the user. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. - * - * @example - * Notice that `{{` `obj.multiplied = obj.a * obj.b` `}}` has a side effect of assigning - * a value to `obj.multiplied` and displaying the result to the user. Sometimes, - * however, it is desirable to execute a side effect without showing the value to - * the user. In such a case `ng:eval` allows you to execute code without updating - * the display. - - - - * - = {{obj.multiplied = obj.a * obj.b}}
- - - - obj.divide = {{obj.divide}}
- obj.updateCount = {{obj.updateCount}} -
- - it('should check eval', function(){ - expect(binding('obj.divide')).toBe('3'); - expect(binding('obj.updateCount')).toBe('2'); - input('obj.a').enter('12'); - expect(binding('obj.divide')).toBe('6'); - expect(binding('obj.updateCount')).toBe('3'); - }); - -
- */ -angularDirective("ng:eval", function(expression){ - return function(element){ - this.$onEval(expression, element); - }; -}); - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:bind - * - * @description - * The `ng:bind` attribute asks angular to replace the text content of this - * HTML element with the value of the given expression, and to keep the text - * content up to date when the expression's value changes. Usually you would - * just write `{{ expression }}` and let angular compile it into - * `` at bootstrap time. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. - * - * @example - * You can try it right here: enter text in the text box and watch the greeting change. - - - Enter name:
- Hello ! -
- - it('should check ng:bind', function(){ - expect(using('.doc-example-live').binding('name')).toBe('Whirled'); - using('.doc-example-live').input('name').enter('world'); - expect(using('.doc-example-live').binding('name')).toBe('world'); - }); - -
- */ -angularDirective("ng:bind", function(expression, element){ - element.addClass('ng-binding'); - return function(element) { - var lastValue = noop, lastError = noop; - this.$onEval(function() { - var error, value, html, isHtml, isDomElement, - oldElement = this.hasOwnProperty($$element) ? this.$element : undefined; - this.$element = element; - value = this.$tryEval(expression, function(e){ - error = formatError(e); - }); - this.$element = oldElement; - // If we are HTML than save the raw HTML data so that we don't - // recompute sanitization since it is expensive. - // TODO: turn this into a more generic way to compute this - if (isHtml = (value instanceof HTML)) - value = (html = value).html; - if (lastValue === value && lastError == error) return; - isDomElement = isElement(value); - if (!isHtml && !isDomElement && isObject(value)) { - value = toJson(value, true); - } - if (value != lastValue || error != lastError) { - lastValue = value; - lastError = error; - elementError(element, NG_EXCEPTION, error); - if (error) value = error; - if (isHtml) { - element.html(html.get()); - } else if (isDomElement) { - element.html(''); - element.append(value); - } else { - element.text(value == undefined ? '' : value); - } - } - }, element); - }; -}); - -var bindTemplateCache = {}; -function compileBindTemplate(template){ - var fn = bindTemplateCache[template]; - if (!fn) { - var bindings = []; - forEach(parseBindings(template), function(text){ - var exp = binding(text); - bindings.push(exp - ? function(element){ - var error, value = this.$tryEval(exp, function(e){ - error = toJson(e); - }); - elementError(element, NG_EXCEPTION, error); - return error ? error : value; - } - : function() { - return text; - }); - }); - bindTemplateCache[template] = fn = function(element, prettyPrintJson){ - var parts = [], self = this, - oldElement = this.hasOwnProperty($$element) ? self.$element : undefined; - self.$element = element; - for ( var i = 0; i < bindings.length; i++) { - var value = bindings[i].call(self, element); - if (isElement(value)) - value = ''; - else if (isObject(value)) - value = toJson(value, prettyPrintJson); - parts.push(value); - } - self.$element = oldElement; - return parts.join(''); - }; - } - return fn; -} - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:bind-template - * - * @description - * The `ng:bind-template` attribute specifies that the element - * text should be replaced with the template in ng:bind-template. - * Unlike ng:bind the ng:bind-template can contain multiple `{{` `}}` - * expressions. (This is required since some HTML elements - * can not have SPAN elements such as TITLE, or OPTION to name a few. - * - * @element ANY - * @param {string} template of form - * {{ expression }} to eval. - * - * @example - * Try it here: enter text in text box and watch the greeting change. - - - Salutation:
- Name:
-

-     
- - it('should check ng:bind', function(){ - expect(using('.doc-example-live').binding('{{salutation}} {{name}}')). - toBe('Hello World!'); - using('.doc-example-live').input('salutation').enter('Greetings'); - using('.doc-example-live').input('name').enter('user'); - expect(using('.doc-example-live').binding('{{salutation}} {{name}}')). - toBe('Greetings user!'); - }); - -
- */ -angularDirective("ng:bind-template", function(expression, element){ - element.addClass('ng-binding'); - var templateFn = compileBindTemplate(expression); - return function(element) { - var lastValue; - this.$onEval(function() { - var value = templateFn.call(this, element, true); - if (value != lastValue) { - element.text(value); - lastValue = value; - } - }, element); - }; -}); - -var REMOVE_ATTRIBUTES = { - 'disabled':'disabled', - 'readonly':'readOnly', - 'checked':'checked', - 'selected':'selected', - 'multiple':'multiple' -}; -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:bind-attr - * - * @description - * The `ng:bind-attr` attribute specifies that - * {@link guide/dev_guide.templates.databinding databindings} should be created between element - * attributes and given expressions. Unlike `ng:bind` the `ng:bind-attr` contains a JSON key value - * pairs representing which attributes need to be mapped to which - * {@link guide/dev_guide.expressions expressions}. - * - * You don't usually write the `ng:bind-attr` in the HTML since embedding - * {{expression}} into the attribute directly as the attribute value is - * preferred. The attributes get translated into `` at - * compile time. - * - * This HTML snippet is preferred way of working with `ng:bind-attr` - *
- *   Google
- * 
- * - * The above gets translated to bellow during bootstrap time. - *
- *   Google
- * 
- * - * @element ANY - * @param {string} attribute_json a JSON key-value pairs representing - * the attributes to replace. Each key matches the attribute - * which needs to be replaced. Each value is a text template of - * the attribute with embedded - * {{expression}}s. Any number of - * key-value pairs can be specified. - * - * @example - * Try it here: enter text in text box and click Google. - - - Google for: - - Google - - - it('should check ng:bind-attr', function(){ - expect(using('.doc-example-live').element('a').attr('href')). - toBe('http://www.google.com/search?q=AngularJS'); - using('.doc-example-live').input('query').enter('google'); - expect(using('.doc-example-live').element('a').attr('href')). - toBe('http://www.google.com/search?q=google'); - }); - - - */ -angularDirective("ng:bind-attr", function(expression){ - return function(element){ - var lastValue = {}; - this.$onEval(function(){ - var values = this.$eval(expression); - for(var key in values) { - var value = compileBindTemplate(values[key]).call(this, element), - specialName = REMOVE_ATTRIBUTES[lowercase(key)]; - if (lastValue[key] !== value) { - lastValue[key] = value; - if (specialName) { - if (toBoolean(value)) { - element.attr(specialName, specialName); - element.attr('ng-' + specialName, value); - } else { - element.removeAttr(specialName); - element.removeAttr('ng-' + specialName); - } - (element.data($$validate)||noop)(); - } else { - element.attr(key, value); - } - } - } - }, element); - }; -}); - - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:click - * - * @description - * The ng:click allows you to specify custom behavior when - * element is clicked. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval upon click. - * - * @example - - - - count: {{count}} - - - it('should check ng:click', function(){ - expect(binding('count')).toBe('0'); - element('.doc-example-live :button').click(); - expect(binding('count')).toBe('1'); - }); - - - */ -/* - * A directive that allows creation of custom onclick handlers that are defined as angular - * expressions and are compiled and executed within the current scope. - * - * Events that are handled via these handler are always configured not to propagate further. - * - * TODO: maybe we should consider allowing users to control event propagation in the future. - */ -angularDirective("ng:click", function(expression, element){ - return annotate('$updateView', function($updateView, element){ - var self = this; - element.bind('click', function(event){ - self.$tryEval(expression, element); - $updateView(); - event.stopPropagation(); - }); - }); -}); - - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:submit - * - * @description - * Enables binding angular expressions to onsubmit events. - * - * Additionally it prevents the default action (which for form means sending the request to the - * server and reloading the current page). - * - * @element form - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. - * - * @example - - -
- Enter text and hit enter: - -
-
list={{list}}
-
- - it('should check ng:submit', function(){ - expect(binding('list')).toBe('list=[]'); - element('.doc-example-live form input').click(); - this.addFutureAction('submit from', function($window, $document, done) { - $window.angular.element( - $document.elements('.doc-example-live form')). - trigger('submit'); - done(); - }); - expect(binding('list')).toBe('list=["hello"]'); - }); - -
- */ -angularDirective("ng:submit", function(expression, element) { - return annotate('$updateView', function($updateView, element) { - var self = this; - element.bind('submit', function(event) { - self.$tryEval(expression, element); - $updateView(); - event.preventDefault(); - }); - }); -}); - - -function ngClass(selector) { - return function(expression, element){ - var existing = element[0].className + ' '; - return function(element){ - this.$onEval(function(){ - if (selector(this.$index)) { - var value = this.$eval(expression); - if (isArray(value)) value = value.join(' '); - element[0].className = trim(existing + value); - } - }, element); - }; - }; -} - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:class - * - * @description - * The `ng:class` allows you to set CSS class on HTML element - * conditionally. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. - * - * @example - - - - -
- Sample Text      -
- - it('should check ng:class', function(){ - expect(element('.doc-example-live span').attr('className')).not(). - toMatch(/ng-input-indicator-wait/); - - using('.doc-example-live').element(':button:first').click(); - - expect(element('.doc-example-live span').attr('className')). - toMatch(/ng-input-indicator-wait/); - - using('.doc-example-live').element(':button:last').click(); - - expect(element('.doc-example-live span').attr('className')).not(). - toMatch(/ng-input-indicator-wait/); - }); - -
- */ -angularDirective("ng:class", ngClass(function(){return true;})); - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:class-odd - * - * @description - * The `ng:class-odd` and `ng:class-even` works exactly as - * `ng:class`, except it works in conjunction with `ng:repeat` - * and takes affect only on odd (even) rows. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. Must be - * inside `ng:repeat`. - * - * @example - - -
    -
  1. - - {{name}}       - -
  2. -
-
- - it('should check ng:class-odd and ng:class-even', function(){ - expect(element('.doc-example-live li:first span').attr('className')). - toMatch(/ng-format-negative/); - expect(element('.doc-example-live li:last span').attr('className')). - toMatch(/ng-input-indicator-wait/); - }); - -
- */ -angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;})); - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:class-even - * - * @description - * The `ng:class-odd` and `ng:class-even` works exactly as - * `ng:class`, except it works in conjunction with `ng:repeat` - * and takes affect only on odd (even) rows. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. Must be - * inside `ng:repeat`. - * - * @example - - -
    -
  1. - - {{name}}       - -
  2. -
-
- - it('should check ng:class-odd and ng:class-even', function(){ - expect(element('.doc-example-live li:first span').attr('className')). - toMatch(/ng-format-negative/); - expect(element('.doc-example-live li:last span').attr('className')). - toMatch(/ng-input-indicator-wait/); - }); - -
- */ -angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;})); - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:show - * - * @description - * The `ng:show` and `ng:hide` directives show or hide a portion of the DOM tree (HTML) - * conditionally. - * - * @element ANY - * @param {expression} expression If the {@link guide/dev_guide.expressions expression} is truthy - * then the element is shown or hidden respectively. - * - * @example - - - Click me:
- Show: I show up when your checkbox is checked.
- Hide: I hide when your checkbox is checked. -
- - it('should check ng:show / ng:hide', function(){ - expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); - expect(element('.doc-example-live span:last:visible').count()).toEqual(1); - - input('checked').check(); - - expect(element('.doc-example-live span:first:visible').count()).toEqual(1); - expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); - }); - -
- */ -angularDirective("ng:show", function(expression, element){ - return function(element){ - this.$onEval(function(){ - element.css($display, toBoolean(this.$eval(expression)) ? '' : $none); - }, element); - }; -}); - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:hide - * - * @description - * The `ng:hide` and `ng:show` directives hide or show a portion - * of the HTML conditionally. - * - * @element ANY - * @param {expression} expression If the {@link guide/dev_guide.expressions expression} truthy then - * the element is shown or hidden respectively. - * - * @example - - - Click me:
- Show: I show up when you checkbox is checked?
- Hide: I hide when you checkbox is checked? -
- - it('should check ng:show / ng:hide', function(){ - expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); - expect(element('.doc-example-live span:last:visible').count()).toEqual(1); - - input('checked').check(); - - expect(element('.doc-example-live span:first:visible').count()).toEqual(1); - expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); - }); - -
- */ -angularDirective("ng:hide", function(expression, element){ - return function(element){ - this.$onEval(function(){ - element.css($display, toBoolean(this.$eval(expression)) ? $none : ''); - }, element); - }; -}); - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:style - * - * @description - * The ng:style allows you to set CSS style on an HTML element conditionally. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} which evals to an - * object whose keys are CSS style names and values are corresponding values for those CSS - * keys. - * - * @example - - - - -
- Sample Text -
myStyle={{myStyle}}
-
- - it('should check ng:style', function(){ - expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); - element('.doc-example-live :button[value=set]').click(); - expect(element('.doc-example-live span').css('color')).toBe('red'); - element('.doc-example-live :button[value=clear]').click(); - expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); - }); - -
- */ -angularDirective("ng:style", function(expression, element){ - return function(element){ - var resetStyle = getStyle(element); - this.$onEval(function(){ - var style = this.$eval(expression) || {}, key, mergedStyle = {}; - for(key in style) { - if (resetStyle[key] === undefined) resetStyle[key] = ''; - mergedStyle[key] = style[key]; - } - for(key in resetStyle) { - mergedStyle[key] = mergedStyle[key] || resetStyle[key]; - } - element.css(mergedStyle); - }, element); - }; -}); - -/** - * @workInProgress - * @ngdoc overview - * @name angular.markup - * @description - * - * Angular markup transforms content of DOM elements or portions of this content into other text or - * DOM elements for further compilation. - * - * Markup extensions do not themselves produce linking functions. Think of markup as a way to - * produce shorthand for a {@link angular.widget widget} or a {@link angular.directive directive}. - * - * The most prominent example of an markup in angular is the built-in double curly markup - * `{{expression}}`, which is a shorthand for ``. - * - * Create custom markup like this: - * - *
- *   angular.markup('newMarkup', function(text, textNode, parentElement){
- *     //tranformation code
- *   });
- * 
- * - * For more information about angular markup, see {@link guide/dev_guide.compiler.markup - * Understanding Angular Markup} in the angular Developer Guide. - */ - -/** - * @workInProgress - * @ngdoc overview - * @name angular.attrMarkup - * @description - * - * Attribute markup extends the angular compiler in a very similar way as {@link angular.markup} - * except that it allows you to modify the state of the attribute text rather then the content of a - * node. - * - * Create custom attribute markup like this: - * - *
- *   angular.attrMarkup('newAttrMarkup', function(attrValue, attrName, element){
- *     //tranformation code
- *   });
- * 
- * - * For more information about angular attribute markup, see {@link guide/dev_guide.compiler.markup - * Understanding Angular Markup} in the angular Developer Guide. - */ - -function parseBindings(string) { - var results = []; - var lastIndex = 0; - var index; - while((index = string.indexOf('{{', lastIndex)) > -1) { - if (lastIndex < index) - results.push(string.substr(lastIndex, index - lastIndex)); - lastIndex = index; - - index = string.indexOf('}}', index); - index = index < 0 ? string.length : index + 2; - - results.push(string.substr(lastIndex, index - lastIndex)); - lastIndex = index; - } - if (lastIndex != string.length) - results.push(string.substr(lastIndex, string.length - lastIndex)); - return results.length === 0 ? [ string ] : results; -} - -function binding(string) { - var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); - return binding ? binding[1] : null; -} - -function hasBindings(bindings) { - return bindings.length > 1 || binding(bindings[0]) !== null; -} - -angularTextMarkup('{{}}', function(text, textNode, parentElement) { - var bindings = parseBindings(text), - self = this; - if (hasBindings(bindings)) { - if (isLeafNode(parentElement[0])) { - parentElement.attr('ng:bind-template', text); - } else { - var cursor = textNode, newElement; - forEach(parseBindings(text), function(text){ - var exp = binding(text); - if (exp) { - newElement = jqLite(''); - newElement.attr('ng:bind', exp); - } else { - newElement = jqLite(document.createTextNode(text)); - } - if (msie && text.charAt(0) == ' ') { - newElement = jqLite(' '); - var nbsp = newElement.html(); - newElement.text(text.substr(1)); - newElement.html(nbsp + newElement.html()); - } - cursor.after(newElement); - cursor = newElement; - }); - textNode.remove(); - } - } -}); - -/** - * This tries to normalize the behavior of value attribute across browsers. If value attribute is - * not specified, then specify it to be that of the text. - */ -angularTextMarkup('option', function(text, textNode, parentElement){ - if (lowercase(nodeName_(parentElement)) == 'option') { - if (msie <= 7) { - // In IE7 The issue is that there is no way to see if the value was specified hence - // we have to resort to parsing HTML; - htmlParser(parentElement[0].outerHTML, { - start: function(tag, attrs) { - if (isUndefined(attrs.value)) { - parentElement.attr('value', text); - } - } - }); - } else if (parentElement[0].getAttribute('value') == null) { - // jQuery does normalization on 'value' so we have to bypass it. - parentElement.attr('value', text); - } - } -}); - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:href - * - * @description - * Using markup like {{hash}} in an href attribute makes - * the page open to a wrong URL, ff the user clicks that link before - * angular has a chance to replace the {{hash}} with actual URL, the - * link will be broken and will most likely return a 404 error. - * The `ng:href` solves this problem by placing the `href` in the - * `ng:` namespace. - * - * The buggy way to write it: - *
- * 
- * 
- * - * The correct way to write it: - *
- * 
- * 
- * - * @element ANY - * @param {template} template any string which can contain `{{}}` markup. - * - * @example - * This example uses `link` variable inside `href` attribute: - - -
-
link 1 (link, don't reload)
- link 2 (link, don't reload)
- link 3 (link, reload!)
- anchor (link, don't reload)
- anchor (no link)
- link (link, change hash) - - - it('should execute ng:click but not reload when href without value', function() { - element('#link-1').click(); - expect(input('value').val()).toEqual('1'); - expect(element('#link-1').attr('href')).toBe(""); - }); - - it('should execute ng:click but not reload when href empty string', function() { - element('#link-2').click(); - expect(input('value').val()).toEqual('2'); - expect(element('#link-2').attr('href')).toBe(""); - }); - - it('should execute ng:click and change url when ng:href specified', function() { - element('#link-3').click(); - expect(input('value').val()).toEqual('3'); - expect(element('#link-3').attr('href')).toBe("#123"); - expect(browser().location().hash()).toEqual('123'); - }); - - it('should execute ng:click but not reload when href empty string and name specified', function() { - element('#link-4').click(); - expect(input('value').val()).toEqual('4'); - expect(element('#link-4').attr('href')).toBe(""); - }); - - it('should execute ng:click but not reload when no href but name specified', function() { - element('#link-5').click(); - expect(input('value').val()).toEqual('5'); - expect(element('#link-5').attr('href')).toBe(undefined); - }); - - it('should only change url when only ng:href', function() { - input('value').enter('6'); - element('#link-6').click(); - expect(browser().location().hash()).toEqual('/6'); - expect(element('#link-6').attr('href')).toBe("#/6"); - }); - - - */ - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:src - * - * @description - * Using markup like `{{hash}}` in a `src` attribute doesn't - * work right: The browser will fetch from the URL with the literal - * text `{{hash}}` until replaces the expression inside - * `{{hash}}`. The `ng:src` attribute solves this problem by placing - * the `src` attribute in the `ng:` namespace. - * - * The buggy way to write it: - *
- * 
- * 
- * - * The correct way to write it: - *
- * 
- * 
- * - * @element ANY - * @param {template} template any string which can contain `{{}}` markup. - */ - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:disabled - * - * @description - * - * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: - *
- * 
- * - *
- *
- * - * the HTML specs do not require browsers preserve the special attributes such as disabled.(The presense of them means true and absense means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce ng:disabled. - * - * @example - - - Click me to toggle:
- -
- - it('should toggle button', function() { - expect(element('.doc-example-live :button').attr('disabled')).toBeFalsy(); - input('checked').check(); - expect(element('.doc-example-live :button').attr('disabled')).toBeTruthy(); - }); - -
- * - * @element ANY - * @param {template} template any string which can contain '{{}}' markup. - */ - - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:checked - * - * @description - * the HTML specs do not require browsers preserve the special attributes such as checked.(The presense of them means true and absense means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce ng:checked. - * @example - - - Check me to check both:
- -
- - it('should check both checkBoxes', function() { - expect(element('.doc-example-live #checkSlave').attr('checked')).toBeFalsy(); - input('master').check(); - expect(element('.doc-example-live #checkSlave').attr('checked')).toBeTruthy(); - }); - -
- * - * @element ANY - * @param {template} template any string which can contain '{{}}' markup. - */ - - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:multiple - * - * @description - * the HTML specs do not require browsers preserve the special attributes such as multiple.(The presense of them means true and absense means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce ng:multiple. - * - * @example - - - Check me check multiple:
- -
- - it('should toggle multiple', function() { - expect(element('.doc-example-live #select').attr('multiple')).toBeFalsy(); - input('checked').check(); - expect(element('.doc-example-live #select').attr('multiple')).toBeTruthy(); - }); - -
- * - * @element ANY - * @param {template} template any string which can contain '{{}}' markup. - */ - - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:readonly - * - * @description - * the HTML specs do not require browsers preserve the special attributes such as readonly.(The presense of them means true and absense means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce ng:readonly. - * @example - - - Check me to make text readonly:
- -
- - it('should toggle readonly attr', function() { - expect(element('.doc-example-live :text').attr('readonly')).toBeFalsy(); - input('checked').check(); - expect(element('.doc-example-live :text').attr('readonly')).toBeTruthy(); - }); - -
- * - * @element ANY - * @param {template} template any string which can contain '{{}}' markup. - */ - - -/** -* @workInProgress -* @ngdoc directive -* @name angular.directive.ng:selected -* -* @description -* the HTML specs do not require browsers preserve the special attributes such as selected.(The presense of them means true and absense means false) -* This prevents the angular compiler from correctly retrieving the binding expression. -* To solve this problem, we introduce ng:selected. -* @example - - - Check me to select:
- -
- - it('should select Greetings!', function() { - expect(element('.doc-example-live #greet').attr('selected')).toBeFalsy(); - input('checked').check(); - expect(element('.doc-example-live #greet').attr('selected')).toBeTruthy(); - }); - -
-* @element ANY -* @param {template} template any string which can contain '{{}}' markup. -*/ - - -var NG_BIND_ATTR = 'ng:bind-attr'; -var SPECIAL_ATTRS = {}; - -forEach('src,href,checked,disabled,multiple,readonly,selected'.split(','), function(name) { - SPECIAL_ATTRS['ng:' + name] = name; -}); - -angularAttrMarkup('{{}}', function(value, name, element){ - // don't process existing attribute markup - if (angularDirective(name) || angularDirective("@" + name)) return; - if (msie && name == 'src') - value = decodeURI(value); - var bindings = parseBindings(value), - bindAttr; - if (hasBindings(bindings)) { - element.removeAttr(name); - bindAttr = fromJson(element.attr(NG_BIND_ATTR) || "{}"); - bindAttr[SPECIAL_ATTRS[name] || name] = value; - element.attr(NG_BIND_ATTR, toJson(bindAttr)); - } -}); -/** - * @workInProgress - * @ngdoc overview - * @name angular.widget - * @description - * - * Widgets are custom DOM elements. An angular widget can be either a custom - * attribute that modifies an existing DOM elements or an entirely new DOM element. - * - * Following is the list of built-in angular widgets: - * - * * {@link angular.widget.@ng:format ng:format} - Formats data for display to user and for storage. - * * {@link angular.widget.@ng:non-bindable ng:non-bindable} - Blocks angular from processing an - * HTML element. - * * {@link angular.widget.@ng:repeat ng:repeat} - Creates and manages a collection of cloned HTML - * elements. - * * {@link angular.widget.@ng:required ng:required} - Verifies presence of user input. - * * {@link angular.widget.@ng:validate ng:validate} - Validates content of user input. - * * {@link angular.widget.HTML HTML} - Standard HTML processed by angular. - * * {@link angular.widget.ng:view ng:view} - Works with $route to "include" partial templates - * * {@link angular.widget.ng:switch ng:switch} - Conditionally changes DOM structure - * * {@link angular.widget.ng:include ng:include} - Includes an external HTML fragment - * - * For more information about angular widgets, see {@link guide/dev_guide.compiler.widgets - * Understanding Angular Widgets} in the angular Developer Guide. - */ - -/** - * @workInProgress - * @ngdoc widget - * @name angular.widget.HTML - * - * @description - * The most common widgets you will use will be in the form of the - * standard HTML set. These widgets are bound using the `name` attribute - * to an expression. In addition they can have `ng:validate`, `ng:required`, - * `ng:format`, `ng:change` attribute to further control their behavior. - * - * @usageContent - * see example below for usage - * - * - * - {{input2|json}} - - - radio - String - - <input type="radio" name="input3" value="A">
- <input type="radio" name="input3" value="B"> -
- - - - - {{input3|json}} - - - checkbox - Boolean - <input type="checkbox" name="input4" value="checked"> - - {{input4|json}} - - - pulldown - String - - <select name="input5">
-   <option value="c">C</option>
-   <option value="d">D</option>
- </select>
-
- - - - {{input5|json}} - - - multiselect - Array - - <select name="input6" multiple size="4">
-   <option value="e">E</option>
-   <option value="f">F</option>
- </select>
-
- - - - {{input6|json}} - - - - - - it('should exercise text', function(){ - input('input1').enter('Carlos'); - expect(binding('input1')).toEqual('"Carlos"'); - }); - it('should exercise textarea', function(){ - input('input2').enter('Carlos'); - expect(binding('input2')).toEqual('"Carlos"'); - }); - it('should exercise radio', function(){ - expect(binding('input3')).toEqual('null'); - input('input3').select('A'); - expect(binding('input3')).toEqual('"A"'); - input('input3').select('B'); - expect(binding('input3')).toEqual('"B"'); - }); - it('should exercise checkbox', function(){ - expect(binding('input4')).toEqual('false'); - input('input4').check(); - expect(binding('input4')).toEqual('true'); - }); - it('should exercise pulldown', function(){ - expect(binding('input5')).toEqual('"c"'); - select('input5').option('d'); - expect(binding('input5')).toEqual('"d"'); - }); - it('should exercise multiselect', function(){ - expect(binding('input6')).toEqual('[]'); - select('input6').options('e'); - expect(binding('input6')).toEqual('["e"]'); - select('input6').options('e', 'f'); - expect(binding('input6')).toEqual('["e","f"]'); - }); - - - */ - -function modelAccessor(scope, element) { - var expr = element.attr('name'); - var exprFn, assignFn; - if (expr) { - exprFn = parser(expr).assignable(); - assignFn = exprFn.assign; - if (!assignFn) throw new Error("Expression '" + expr + "' is not assignable."); - return { - get: function() { - return exprFn(scope); - }, - set: function(value) { - if (value !== undefined) { - return scope.$tryEval(function(){ - assignFn(scope, value); - }, element); - } - } - }; - } -} - -function modelFormattedAccessor(scope, element) { - var accessor = modelAccessor(scope, element), - formatterName = element.attr('ng:format') || NOOP, - formatter = compileFormatter(formatterName); - if (accessor) { - return { - get: function() { - return formatter.format(scope, accessor.get()); - }, - set: function(value) { - return accessor.set(formatter.parse(scope, value)); - } - }; - } -} - -function compileValidator(expr) { - return parser(expr).validator()(); -} - -function compileFormatter(expr) { - return parser(expr).formatter()(); -} - -/** - * @workInProgress - * @ngdoc widget - * @name angular.widget.@ng:validate - * - * @description - * The `ng:validate` attribute widget validates the user input. If the input does not pass - * validation, the `ng-validation-error` CSS class and the `ng:error` attribute are set on the input - * element. Check out {@link angular.validator validators} to find out more. - * - * @param {string} validator The name of a built-in or custom {@link angular.validator validator} to - * to be used. - * - * @element INPUT - * @css ng-validation-error - * - * @example - * This example shows how the input element becomes red when it contains invalid input. Correct - * the input to make the error disappear. - * - - - I don't validate: -
- - I need an integer or nothing: -
-
- - it('should check ng:validate', function(){ - expect(element('.doc-example-live :input:last').attr('className')). - toMatch(/ng-validation-error/); - - input('value').enter('123'); - expect(element('.doc-example-live :input:last').attr('className')). - not().toMatch(/ng-validation-error/); - }); - -
- */ -/** - * @workInProgress - * @ngdoc widget - * @name angular.widget.@ng:required - * - * @description - * The `ng:required` attribute widget validates that the user input is present. It is a special case - * of the {@link angular.widget.@ng:validate ng:validate} attribute widget. - * - * @element INPUT - * @css ng-validation-error - * - * @example - * This example shows how the input element becomes red when it contains invalid input. Correct - * the input to make the error disappear. - * - - - I cannot be blank:
-
- - it('should check ng:required', function(){ - expect(element('.doc-example-live :input').attr('className')).toMatch(/ng-validation-error/); - input('value').enter('123'); - expect(element('.doc-example-live :input').attr('className')).not().toMatch(/ng-validation-error/); - }); - -
- */ -/** - * @workInProgress - * @ngdoc widget - * @name angular.widget.@ng:format - * - * @description - * The `ng:format` attribute widget formats stored data to user-readable text and parses the text - * back to the stored form. You might find this useful for example if you collect user input in a - * text field but need to store the data in the model as a list. Check out - * {@link angular.formatter formatters} to learn more. - * - * @param {string} formatter The name of the built-in or custom {@link angular.formatter formatter} - * to be used. - * - * @element INPUT - * - * @example - * This example shows how the user input is converted from a string and internally represented as an - * array. - * - - - Enter a comma separated list of items: - -
list={{list}}
-
- - it('should check ng:format', function(){ - expect(binding('list')).toBe('list=["table","chairs","plate"]'); - input('list').enter(',,, a ,,,'); - expect(binding('list')).toBe('list=["a"]'); - }); - -
- */ -function valueAccessor(scope, element) { - var validatorName = element.attr('ng:validate') || NOOP, - validator = compileValidator(validatorName), - requiredExpr = element.attr('ng:required'), - formatterName = element.attr('ng:format') || NOOP, - formatter = compileFormatter(formatterName), - format, parse, lastError, required, - invalidWidgets = scope.$service('$invalidWidgets') || {markValid:noop, markInvalid:noop}; - if (!validator) throw "Validator named '" + validatorName + "' not found."; - format = formatter.format; - parse = formatter.parse; - if (requiredExpr) { - scope.$watch(requiredExpr, function(newValue) { - required = newValue; - validate(); - }); - } else { - required = requiredExpr === ''; - } - - element.data($$validate, validate); - return { - get: function(){ - if (lastError) - elementError(element, NG_VALIDATION_ERROR, null); - try { - var value = parse(scope, element.val()); - validate(); - return value; - } catch (e) { - lastError = e; - elementError(element, NG_VALIDATION_ERROR, e); - } - }, - set: function(value) { - var oldValue = element.val(), - newValue = format(scope, value); - if (oldValue != newValue) { - element.val(newValue || ''); // needed for ie - } - validate(); - } - }; - - function validate() { - var value = trim(element.val()); - if (element[0].disabled || element[0].readOnly) { - elementError(element, NG_VALIDATION_ERROR, null); - invalidWidgets.markValid(element); - } else { - var error, validateScope = inherit(scope, {$element:element}); - error = required && !value - ? 'Required' - : (value ? validator(validateScope, value) : null); - elementError(element, NG_VALIDATION_ERROR, error); - lastError = error; - if (error) { - invalidWidgets.markInvalid(element); - } else { - invalidWidgets.markValid(element); - } - } - } -} - -function checkedAccessor(scope, element) { - var domElement = element[0], elementValue = domElement.value; - return { - get: function(){ - return !!domElement.checked; - }, - set: function(value){ - domElement.checked = toBoolean(value); - } - }; -} - -function radioAccessor(scope, element) { - var domElement = element[0]; - return { - get: function(){ - return domElement.checked ? domElement.value : null; - }, - set: function(value){ - domElement.checked = value == domElement.value; - } - }; -} - -function optionsAccessor(scope, element) { - var formatterName = element.attr('ng:format') || NOOP, - formatter = compileFormatter(formatterName); - return { - get: function(){ - var values = []; - forEach(element[0].options, function(option){ - if (option.selected) values.push(formatter.parse(scope, option.value)); - }); - return values; - }, - set: function(values){ - var keys = {}; - forEach(values, function(value){ - keys[formatter.format(scope, value)] = true; - }); - forEach(element[0].options, function(option){ - option.selected = keys[option.value]; - }); - } - }; -} - -function noopAccessor() { return { get: noop, set: noop }; } - -/* - * TODO: refactor - * - * The table bellow is not quite right. In some cases the formatter is on the model side - * and in some cases it is on the view side. This is a historical artifact - * - * The concept of model/view accessor is useful for anyone who is trying to develop UI, and - * so it should be exposed to others. There should be a form object which keeps track of the - * accessors and also acts as their factory. It should expose it as an object and allow - * the validator to publish errors to it, so that the the error messages can be bound to it. - * - */ -var textWidget = inputWidget('keydown change', modelAccessor, valueAccessor, initWidgetValue(), true), - buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop), - INPUT_TYPE = { - 'text': textWidget, - 'textarea': textWidget, - 'hidden': textWidget, - 'password': textWidget, - 'button': buttonWidget, - 'submit': buttonWidget, - 'reset': buttonWidget, - 'image': buttonWidget, - 'checkbox': inputWidget('click', modelFormattedAccessor, checkedAccessor, initWidgetValue(false)), - 'radio': inputWidget('click', modelFormattedAccessor, radioAccessor, radioInit), - 'select-one': inputWidget('change', modelAccessor, valueAccessor, initWidgetValue(null)), - 'select-multiple': inputWidget('change', modelAccessor, optionsAccessor, initWidgetValue([])) -// 'file': fileWidget??? - }; - - -function initWidgetValue(initValue) { - return function (model, view) { - var value = view.get(); - if (!value && isDefined(initValue)) { - value = copy(initValue); - } - if (isUndefined(model.get()) && isDefined(value)) { - model.set(value); - } - }; -} - -function radioInit(model, view, element) { - var modelValue = model.get(), viewValue = view.get(), input = element[0]; - input.checked = false; - input.name = this.$id + '@' + input.name; - if (isUndefined(modelValue)) { - model.set(modelValue = null); - } - if (modelValue == null && viewValue !== null) { - model.set(viewValue); - } - view.set(modelValue); -} - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:change - * - * @description - * The directive executes an expression whenever the input widget changes. - * - * @element INPUT - * @param {expression} expression to execute. - * - * @example - * @example - - -
- - changeCount {{textCount}}
- - changeCount {{checkboxCount}}
-
- - it('should check ng:change', function(){ - expect(binding('textCount')).toBe('0'); - expect(binding('checkboxCount')).toBe('0'); - - using('.doc-example-live').input('text').enter('abc'); - expect(binding('textCount')).toBe('1'); - expect(binding('checkboxCount')).toBe('0'); - - - using('.doc-example-live').input('checkbox').check(); - expect(binding('textCount')).toBe('1'); - expect(binding('checkboxCount')).toBe('1'); - }); - -
- */ -function inputWidget(events, modelAccessor, viewAccessor, initFn, textBox) { - return annotate('$updateView', '$defer', function($updateView, $defer, element) { - var scope = this, - model = modelAccessor(scope, element), - view = viewAccessor(scope, element), - action = element.attr('ng:change') || '', - lastValue; - if (model) { - initFn.call(scope, model, view, element); - this.$eval(element.attr('ng:init')||''); - element.bind(events, function(event){ - function handler(){ - var value = view.get(); - if (!textBox || value != lastValue) { - model.set(value); - lastValue = model.get(); - scope.$tryEval(action, element); - $updateView(); - } - } - event.type == 'keydown' ? $defer(handler) : handler(); - }); - scope.$watch(model.get, function(value){ - if (lastValue !== value) { - view.set(lastValue = value); - } - }); - } - }); -} - -function inputWidgetSelector(element){ - this.directives(true); - this.descend(true); - return INPUT_TYPE[lowercase(element[0].type)] || noop; -} - -angularWidget('input', inputWidgetSelector); -angularWidget('textarea', inputWidgetSelector); -angularWidget('button', inputWidgetSelector); - -/** - * @workInProgress - * @ngdoc directive - * @name angular.directive.ng:options - * - * @description - * Dynamically generate a list of ` - - - - url = {{url}} -
- - - - it('should load date filter', function(){ - expect(element('.doc-example-live ng\\:include').text()).toMatch(/angular\.filter\.date/); - }); - it('should change to html filter', function(){ - select('url').option('api/angular.filter.html.html'); - expect(element('.doc-example-live ng\\:include').text()).toMatch(/angular\.filter\.html/); - }); - it('should change to blank', function(){ - select('url').option(''); - expect(element('.doc-example-live ng\\:include').text()).toEqual(''); - }); - - - */ -angularWidget('ng:include', function(element){ - var compiler = this, - srcExp = element.attr("src"), - scopeExp = element.attr("scope") || '', - onloadExp = element[0].getAttribute('onload') || ''; //workaround for jquery bug #7537 - if (element[0]['ng:compiled']) { - this.descend(true); - this.directives(true); - } else { - element[0]['ng:compiled'] = true; - return extend(function(xhr, element){ - var scope = this, childScope; - var changeCounter = 0; - var preventRecursion = false; - function incrementChange(){ changeCounter++;} - this.$watch(srcExp, incrementChange); - this.$watch(scopeExp, incrementChange); - - // note that this propagates eval to the current childScope, where childScope is dynamically - // bound (via $route.onChange callback) to the current scope created by $route - scope.$onEval(function(){ - if (childScope && !preventRecursion) { - preventRecursion = true; - try { - childScope.$eval(); - } finally { - preventRecursion = false; - } - } - }); - this.$watch(function(){return changeCounter;}, function(){ - var src = this.$eval(srcExp), - useScope = this.$eval(scopeExp); - - if (src) { - xhr('GET', src, null, function(code, response){ - element.html(response); - childScope = useScope || createScope(scope); - compiler.compile(element)(childScope); - scope.$eval(onloadExp); - }, false, true); - } else { - childScope = null; - element.html(''); - } - }); - }, {$inject:['$xhr.cache']}); - } -}); - -/** - * @workInProgress - * @ngdoc widget - * @name angular.widget.ng:switch - * - * @description - * Conditionally change the DOM structure. - * - * @usageContent - * ... - * ... - * ... - * ... - * - * @param {*} on expression to match against ng:switch-when. - * @paramDescription - * On child elments add: - * - * * `ng:switch-when`: the case statement to match against. If match then this - * case will be displayed. - * * `ng:switch-default`: the default case when no other casses match. - * - * @example - - - - switch={{switch}} - - -
Settings Div
- Home Span - default -
- -
- - it('should start in settings', function(){ - expect(element('.doc-example-live ng\\:switch').text()).toEqual('Settings Div'); - }); - it('should change to home', function(){ - select('switch').option('home'); - expect(element('.doc-example-live ng\\:switch').text()).toEqual('Home Span'); - }); - it('should select deafault', function(){ - select('switch').option('other'); - expect(element('.doc-example-live ng\\:switch').text()).toEqual('default'); - }); - -
- */ -//TODO(im): remove all the code related to using and inline equals -var ngSwitch = angularWidget('ng:switch', function (element){ - var compiler = this, - watchExpr = element.attr("on"), - usingExpr = (element.attr("using") || 'equals'), - usingExprParams = usingExpr.split(":"), - usingFn = ngSwitch[usingExprParams.shift()], - changeExpr = element.attr('change') || '', - cases = []; - if (!usingFn) throw "Using expression '" + usingExpr + "' unknown."; - if (!watchExpr) throw "Missing 'on' attribute."; - eachNode(element, function(caseElement){ - var when = caseElement.attr('ng:switch-when'); - var switchCase = { - change: changeExpr, - element: caseElement, - template: compiler.compile(caseElement) - }; - if (isString(when)) { - switchCase.when = function(scope, value){ - var args = [value, when]; - forEach(usingExprParams, function(arg){ - args.push(arg); - }); - return usingFn.apply(scope, args); - }; - cases.unshift(switchCase); - } else if (isString(caseElement.attr('ng:switch-default'))) { - switchCase.when = valueFn(true); - cases.push(switchCase); - } - }); - - // this needs to be here for IE - forEach(cases, function(_case){ - _case.element.remove(); - }); - - element.html(''); - return function(element){ - var scope = this, childScope; - this.$watch(watchExpr, function(value){ - var found = false; - element.html(''); - childScope = createScope(scope); - forEach(cases, function(switchCase){ - if (!found && switchCase.when(childScope, value)) { - found = true; - childScope.$tryEval(switchCase.change, element); - switchCase.template(childScope, function(caseElement){ - element.append(caseElement); - }); - } - }); - }); - scope.$onEval(function(){ - if (childScope) childScope.$eval(); - }); - }; -}, { - equals: function(on, when) { - return ''+on == when; - } -}); - - -/* - * Modifies the default behavior of html A tag, so that the default action is prevented when href - * attribute is empty. - * - * The reasoning for this change is to allow easy creation of action links with ng:click without - * changing the location or causing page reloads, e.g.: - * Save - */ -angularWidget('a', function() { - this.descend(true); - this.directives(true); - - return function(element) { - var hasNgHref = ((element.attr('ng:bind-attr') || '').indexOf('"href":') !== -1); - - // turn link into a link in IE - // but only if it doesn't have name attribute, in which case it's an anchor - if (!hasNgHref && !element.attr('name') && !element.attr('href')) { - element.attr('href', ''); - } - - if (element.attr('href') === '' && !hasNgHref) { - element.bind('click', function(event){ - event.preventDefault(); - }); - } - }; -}); - - -/** - * @workInProgress - * @ngdoc widget - * @name angular.widget.@ng:repeat - * - * @description - * The `ng:repeat` widget instantiates a template once per item from a collection. The collection is - * enumerated with the `ng:repeat-index` attribute, starting from 0. Each template instance gets - * its own scope, where the given loop variable is set to the current collection item, and `$index` - * is set to the item index or key. - * - * Special properties are exposed on the local scope of each template instance, including: - * - * * `$index` – `{number}` – iterator offset of the repeated element (0..length-1) - * * `$position` – `{string}` – position of the repeated element in the iterator. One of: - * * `'first'`, - * * `'middle'` - * * `'last'` - * - * Note: Although `ng:repeat` looks like a directive, it is actually an attribute widget. - * - * @element ANY - * @param {string} repeat_expression The expression indicating how to enumerate a collection. Two - * formats are currently supported: - * - * * `variable in expression` – where variable is the user defined loop variable and `expression` - * is a scope expression giving the collection to enumerate. - * - * For example: `track in cd.tracks`. - * - * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, - * and `expression` is the scope expression giving the collection to enumerate. - * - * For example: `(name, age) in {'adam':10, 'amalie':12}`. - * - * @example - * This example initializes the scope to a list of names and - * then uses `ng:repeat` to display every person: - - -
- I have {{friends.length}} friends. They are: -
    -
  • - [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. -
  • -
-
-
- - it('should check ng:repeat', function(){ - var r = using('.doc-example-live').repeater('ul li'); - expect(r.count()).toBe(2); - expect(r.row(0)).toEqual(["1","John","25"]); - expect(r.row(1)).toEqual(["2","Mary","28"]); - }); - -
- */ -angularWidget('@ng:repeat', function(expression, element){ - element.removeAttr('ng:repeat'); - element.replaceWith(jqLite('')); - var linker = this.compile(element); - return function(iterStartElement){ - var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), - lhs, rhs, valueIdent, keyIdent; - if (! match) { - throw Error("Expected ng:repeat in form of '_item_ in _collection_' but got '" + - expression + "'."); - } - lhs = match[1]; - rhs = match[2]; - match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); - if (!match) { - throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" + - keyValue + "'."); - } - valueIdent = match[3] || match[1]; - keyIdent = match[2]; - - var children = [], currentScope = this; - this.$onEval(function(){ - var index = 0, - childCount = children.length, - lastIterElement = iterStartElement, - collection = this.$tryEval(rhs, iterStartElement), - collectionLength = size(collection, true), - fragment = (element[0].nodeName != 'OPTION') ? document.createDocumentFragment() : null, - addFragment, - childScope, - key; - - for (key in collection) { - if (collection.hasOwnProperty(key)) { - if (index < childCount) { - // reuse existing child - childScope = children[index]; - childScope[valueIdent] = collection[key]; - if (keyIdent) childScope[keyIdent] = key; - lastIterElement = childScope.$element; - childScope.$position = index == 0 - ? 'first' - : (index == collectionLength - 1 ? 'last' : 'middle'); - childScope.$eval(); - } else { - // grow children - childScope = createScope(currentScope); - childScope[valueIdent] = collection[key]; - if (keyIdent) childScope[keyIdent] = key; - childScope.$index = index; - childScope.$position = index == 0 - ? 'first' - : (index == collectionLength - 1 ? 'last' : 'middle'); - children.push(childScope); - linker(childScope, function(clone){ - clone.attr('ng:repeat-index', index); - - if (fragment) { - fragment.appendChild(clone[0]); - addFragment = true; - } else { - //temporarily preserve old way for option element - lastIterElement.after(clone); - lastIterElement = clone; - } - }); - } - index ++; - } - } - - //attach new nodes buffered in doc fragment - if (addFragment) { - lastIterElement.after(jqLite(fragment)); - } - - // shrink children - while(children.length > index) { - children.pop().$element.remove(); - } - }, iterStartElement); - }; -}); - - -/** - * @workInProgress - * @ngdoc widget - * @name angular.widget.@ng:non-bindable - * - * @description - * Sometimes it is necessary to write code which looks like bindings but which should be left alone - * by angular. Use `ng:non-bindable` to make angular ignore a chunk of HTML. - * - * NOTE: `ng:non-bindable` looks like a directive, but is actually an attribute widget. - * - * @element ANY - * - * @example - * In this example there are two location where a siple binding (`{{}}`) is present, but the one - * wrapped in `ng:non-bindable` is left alone. - * - * @example - - -
Normal: {{1 + 2}}
-
Ignored: {{1 + 2}}
-
- - it('should check ng:non-bindable', function(){ - expect(using('.doc-example-live').binding('1 + 2')).toBe('3'); - expect(using('.doc-example-live').element('div:last').text()). - toMatch(/1 \+ 2/); - }); - -
- */ -angularWidget("@ng:non-bindable", noop); - - -/** - * @ngdoc widget - * @name angular.widget.ng:view - * - * @description - * # Overview - * `ng:view` is a widget that complements the {@link angular.service.$route $route} service by - * including the rendered template of the current route into the main layout (`index.html`) file. - * Every time the current route changes, the included view changes with it according to the - * configuration of the `$route` service. - * - * This widget provides functionality similar to {@link angular.widget.ng:include ng:include} when - * used like this: - * - * - * - * - * # Advantages - * Compared to `ng:include`, `ng:view` offers these advantages: - * - * - shorter syntax - * - more efficient execution - * - doesn't require `$route` service to be available on the root scope - * - * - * @example - - - -
- overview | bootstrap | undefined
- The view is included below: -
- -
-
- - -
- */ -angularWidget('ng:view', function(element) { - var compiler = this; - - if (!element[0]['ng:compiled']) { - element[0]['ng:compiled'] = true; - return annotate('$xhr.cache', '$route', function($xhr, $route, element){ - var parentScope = this, - childScope; - - $route.onChange(function(){ - var src; - - if ($route.current) { - src = $route.current.template; - childScope = $route.current.scope; - } - - if (src) { - //xhr's callback must be async, see commit history for more info - $xhr('GET', src, function(code, response){ - element.html(response); - compiler.compile(element)(childScope); - }); - } else { - element.html(''); - } - })(); //initialize the state forcefully, it's possible that we missed the initial - //$route#onChange already - - // note that this propagates eval to the current childScope, where childScope is dynamically - // bound (via $route.onChange callback) to the current scope created by $route - parentScope.$onEval(function() { - if (childScope) { - childScope.$eval(); - } - }); - }); - } else { - this.descend(true); - this.directives(true); - } -}); -var browserSingleton; -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$browser - * @requires $log - * - * @description - * Represents the browser. - */ -angularService('$browser', function($log){ - if (!browserSingleton) { - browserSingleton = new Browser(window, jqLite(window.document), jqLite(window.document.body), - XHR, $log); - browserSingleton.bind(); - } - return browserSingleton; -}, {$inject:['$log']}); - -extend(angular, { - // disabled for now until we agree on public name - //'annotate': annotate, - 'element': jqLite, - 'compile': compile, - 'scope': createScope, - 'copy': copy, - 'extend': extend, - 'equals': equals, - 'forEach': forEach, - 'injector': createInjector, - 'noop':noop, - 'bind':bind, - 'toJson': toJson, - 'fromJson': fromJson, - 'identity':identity, - 'isUndefined': isUndefined, - 'isDefined': isDefined, - 'isString': isString, - 'isFunction': isFunction, - 'isObject': isObject, - 'isNumber': isNumber, - 'isArray': isArray -}); - -//try to bind to jquery now so that one can write angular.element().read() -//but we will rebind on bootstrap again. -bindJQuery(); - - - - jqLiteWrap(document).ready(function(){ - angularInit(angularJsConfig(document), document); - }); - -})(window, document); -angular.element(document).find('head').append(''); \ No newline at end of file diff --git a/src/main/webapp/js/lib/angular-0.9.17.patched.min.js b/src/main/webapp/js/lib/angular-0.9.17.patched.min.js deleted file mode 100644 index aaf65bd..0000000 --- a/src/main/webapp/js/lib/angular-0.9.17.patched.min.js +++ /dev/null @@ -1,116 +0,0 @@ -(function(y,J,B){function k(a,b,c){var d;if(a)if(t(a))for(d in a)d!="prototype"&&d!=bc&&d!=cc&&a.hasOwnProperty(d)&&b.call(c,a[d],d);else if(a.forEach&&a.forEach!==k)a.forEach(b,c);else if(K(a)&&fa(a.length))for(d=0;d2?ia.call(arguments, -2,arguments.length):[];return typeof b==T&&!(b instanceof RegExp)?c.length?function(){return arguments.length?b.apply(a,c.concat(ia.call(arguments,0,arguments.length))):b.apply(a,c)}:function(){return arguments.length?b.apply(a,arguments):b.call(a)}:b}function ja(a){a&&a.length!==0?(a=A(""+a),a=!(a=="f"||a=="0"||a=="false"||a=="no"||a=="n"||a=="[]")):a=!1;return a}function pb(a){return(new qb(Pa,rb,C,M)).compile(a)}function Qa(a){var b={},c,d;k((a||"").split("&"),function(a){a&&(c=a.split("="),d= -unescape(c[0]),b[d]=n(c[1])?unescape(c[1]):!0)});return b}function sb(a){var b=[];k(a,function(a,d){b.push(escape(d)+(a===!0?"":"="+escape(a)))});return b.length?b.join("&"):""}function Ra(a,b){return encodeURIComponent(a).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(b?null:/%20/g,"+")}function kc(a,b){tb();var c=a.getElementsByTagName("script"),d;b=m({ie_compat_id:"ng-ie-compat"},b);for(var e=0;e1;d++){var e=b.shift(),g=a[e];g||(g={},a[e]=g);a=g}return a[b.shift()]=c}function wb(a){var b=xb[a];if(b)return b;var c="var l, fn, t;\n";k(a.split("."),function(a){a=yb[a]?'["'+a+'"]':"."+a;c+="if(!s) return s;\nl=s;\ns=s"+a+';\nif(typeof s=="function" && !(s instanceof RegExp)) s = function(){ return l'+a+".apply(l, arguments); };\n";a.charAt(1)=="$"&&(a=a.substr(2),c+='if(!s) {\n t = angular.Global.typeOf(l);\n fn = (angular[t.charAt(0).toUpperCase() + t.substring(1)]||{})["'+ -a+'"];\n if (fn) s = function(){ return fn.apply(l, [l].concat(Array.prototype.slice.call(arguments, 0, arguments.length))); };\n}\n')});c+="return s;";b=Function("s",c);b.toString=function(){return c};return xb[a]=b}function V(a){if(typeof a===T)return a;var b=zb[a];if(!b){var c=ca(a).statements();b=zb[a]=m(function(){return c(this)},{fnSelf:c})}return b}function W(a,b,c){function d(){}a=d.prototype=a||{};var e=new d,g={sorted:[]},f,h;m(e,{"this":e,$id:pc++,$parent:a,$bind:F(e,F,e),$get:F(e,oa, -e),$set:F(e,Xa,e),$eval:function(a){var b=typeof a,c,d,f;if(b==ga){a=0;for(b=g.sorted.length;a0){var e= -p[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(a,c,e,f){if(a=g(a,c,e,f)){if(b&&!a.json)index=a.index,d("is not valid json",a);p.shift();return this.currentToken=a}return!1}function h(a){f(a)||d("is unexpected, expecting ["+a+"]",g())}function i(a,b){return function(c){return a(c,b(c))}}function j(a,b,c){return function(d){return b(d,a(d),c(d))}}function u(){return A(O)}function r(){for(var a=q(),b;;)if(b=f("||"))a=j(a,b.fn,q());else return a}function q(){var a= -v(),b;if(b=f("&&"))a=j(a,b.fn,q());return a}function v(){var a=l(),b;if(b=f("==","!="))a=j(a,b.fn,v());return a}function l(){var a;a=k();for(var b;b=f("+","-");)a=j(a,b.fn,k());if(b=f("<",">","<=",">="))a=j(a,b.fn,l());return a}function k(){for(var a=w(),b;b=f("*","/","%");)a=j(a,b.fn,w());return a}function w(){var a;return f("+")?L():(a=f("-"))?j(Ba,a.fn,w()):(a=f("!"))?i(a.fn,w()):L()}function L(){var a;if(f("("))a=t(),h(")");else if(f("["))a=za();else if(f("{"))a=Aa();else{var b=f();(a=b.fn)|| -d("not a primary expression",b)}for(;b=f("(","[",".");)b.text==="("?a=x(a):b.text==="["?a=y(a):b.text==="."?a=Cb(a):d("IMPOSSIBLE");return a}function za(){var a=[];if(e().text!="]"){do a.push(o());while(f(","))}h("]");return function(b){for(var c=[],d=0;d0&&!g("}",")",";","]")&&a.push(t()),!f(";"))return a.length==1?a[0]:function(b){for(var c,d=0;d=8))p(a).bind("hashchange", -b);else{var d=f.getUrl();f.addPollFn(function(){d!=f.getUrl()&&(b(),d=f.getUrl())})}return b};var pa={},w="";f.cookies=function(a,b){var c,d,f,g;if(a)if(b===B)h.cookie=escape(a)+"=;expires=Thu, 01 Jan 1970 00:00:00 GMT";else{if(x(b))h.cookie=escape(a)+"="+escape(b),c=a.length+b.length+1,c>4096&&e.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+c+" > 4096 bytes)!"),pa.length>20&&e.warn("Cookie '"+a+"' possibly not set or overflowed because too many cookies were already set ("+ -pa.length+" > 20 )")}else{if(h.cookie!==w){w=h.cookie;c=w.split("; ");pa={};for(f=0;f0&&(pa[unescape(d.substring(0,g))]=unescape(d.substring(g+1)))}return pa}};f.defer=function(a,b){r++;j(function(){g(a)},b||0)};var L=s;f.hover=function(a){L=a};f.bind=function(){b.bind("mouseover",function(a){L(p(U?a.srcElement:a.target),!0);return!0});b.bind("mouseleave mouseout click dblclick keypress keyup",function(a){L(p(a.target),!1);return!0})};f.addCss=function(a){var b= -p(h.createElement("link"));b.attr("rel","stylesheet");b.attr("type","text/css");b.attr("href",a);c.append(b)};f.addJs=function(a,b){var d=p(h.createElement("script"));d.attr("type","text/javascript");d.attr("src",a);b&&d.attr("id",b);c.append(d)}}function lb(a,b){function c(a,c,e,g){c=A(c);if(Eb[c])for(;f.last()&&Fb[f.last()];)d("",f.last());Gb[c]&&f.last()==c&&d("",c);(g=Hb[c]||!!g)||f.push(c);var h={};e.replace(zc,function(a,b,c,d,e){h[b]=ab(c||d||e||"")});b.start&&b.start(c,h,g)}function d(a,c){var d= -0,e;if(c=A(c))for(d=f.length-1;d>=0;d--)if(f[d]==c)break;if(d>=0){for(e=f.length-1;e>=d;e--)b.end&&b.end(f[e]);f.length=d}}var e,g,f=[],h=a;for(f.last=function(){return f[f.length-1]};a;){g=!0;if(!f.last()||!Ib[f.last()]){if(a.indexOf("<\!--")===0)e=a.indexOf("--\>"),e>=0&&(b.comment&&b.comment(a.substring(4,e)),a=a.substring(e+3),g=!1);else if(Ac.test(a)){if(e=a.match(Jb))a=a.substring(e[0].length),e[0].replace(Jb,d),g=!1}else if(Bc.test(a)&&(e=a.match(Kb)))a=a.substring(e[0].length),e[0].replace(Kb, -c),g=!1;g&&(e=a.indexOf("<"),g=e<0?a:a.substring(0,e),a=e<0?"":a.substring(e),b.chars&&b.chars(ab(g)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,c){c=c.replace(Cc,"$1").replace(Dc,"$1");b.chars&&b.chars(ab(c));return""}),d("",f.last());if(a==h)throw"Parse Error: "+a;h=a}d()}function ab(a){bb.innerHTML=a.replace(//g,">")}function mb(a){var b=!1,c=F(a,a.push);return{start:function(a,e,g){a=A(a);!b&&Ib[a]&&(b=a);!b&&Mb[a]==!0&&(c("<"),c(a),k(e,function(a,b){var d=A(b);if(Fc[d]==!0&&(Nb[d]!==!0||a.match(Gc)))c(" "),c(b),c('="'),c(Lb(a)),c('"')}),c(g?"/>":">"))},end:function(a){a=A(a);!b&&Mb[a]==!0&&(c(""));a==b&&(b=!1)},chars:function(a){b||c(Lb(a))}}}function Hc(a){var b={};a=a[0].style;var c,d;if(typeof a.length=="number")for(c=0;c "+a;b.removeChild(b.firstChild);cb(this,b.childNodes);this.remove()}else cb(this,a)}function qa(a){Ob(a);var b=0;for(a=a.childNodes||[];b-1}function Pb(a,b){a.className=ha((" "+a.className+" ").replace(/[\n\t]/g," ").replace(" "+b+" ",""))}function Qb(a,b){if(!eb(a,b))a.className=ha(a.className+" "+b)}function cb(a,b){if(b){b=!b.nodeName&&n(b.length)&&(!b||!b.document||!b.location|| -!b.alert||!b.setInterval)?b:[b];for(var c=0;c0||e>-c)e+=c;e===0&&c==-12&&(e=12); -return Q(e,b,d)}}function Fa(a,b){return function(c){c=c["get"+a]();c=a=="Month"?Kc[c]:Lc[c];return b?c.substr(0,3):c}}function ka(a,b){return{format:a,parse:b||a}}function Sb(a){return n(a)&&a!==null?""+a:a}function gb(a){function b(){d=!1;c.$eval()}var c=this,d;return a.isMock?b:function(){d||(d=!0,a.defer(b,gb.delay))}}function Tb(a){var b=Ub[a];if(!b){var c=[];k(Ga(a),function(a){var b=Ha(a);c.push(b?function(a){var c,d=this.$tryEval(b,function(a){c=N(a)});aa(a,xa,c);return c?c:d}:function(){return a})}); -Ub[a]=b=function(a,b){var g=[],f=this.hasOwnProperty(Vb)?this.$element:B;this.$element=a;for(var h=0;h-1;)c< -d&&b.push(a.substr(c,d-c)),c=d,d=a.indexOf("}}",d),d=d<0?a.length:d+2,b.push(a.substr(c,d-c)),c=d;c!=a.length&&b.push(a.substr(c,a.length-c));return b.length===0?[a]:b}function Ha(a){return(a=a.replace(/\n/gm," ").match(/^\{\{(.*)\}\}$/))?a[1]:null}function ra(a,b){var c=b.attr("name"),d,e;if(c){d=ca(c).assignable();e=d.assign;if(!e)throw new G("Expression '"+c+"' is not assignable.");return{get:function(){return d(a)},set:function(c){if(c!==B)return a.$tryEval(function(){e(a,c)},b)}}}}function Wb(a, -b){var c=ra(a,b),d=b.attr("ng:format")||Ia,e=ca(d).formatter()();if(c)return{get:function(){return e.format(a,c.get())},set:function(b){return c.set(e.parse(a,b))}}}function Xb(a,b){function c(){var c=ha(b.val());if(b[0].disabled||b[0].readOnly)aa(b,sa,null),r.markValid(b);else{var d=ec(a,{$element:b});c=u&&!c?"Required":c?e(d,c):null;aa(b,sa,c);(j=c)?r.markInvalid(b):r.markValid(b)}}var d=b.attr("ng:validate")||Ia,e=ca(d).validator()(),g=b.attr("ng:required"),f=b.attr("ng:format")||Ia;f=ca(f).formatter()(); -var h,i,j,u,r=a.$service("$invalidWidgets")||{markValid:s,markInvalid:s};if(!e)throw"Validator named '"+d+"' not found.";h=f.format;i=f.parse;g?a.$watch(g,function(a){u=a;c()}):u=g==="";b.data(ib,c);return{get:function(){j&&aa(b,sa,null);try{var d=i(a,b.val());c();return d}catch(e){j=e,aa(b,sa,e)}},set:function(d){var e=b.val();d=h(a,d);e!=d&&b.val(d||"");c()}}}function Yb(){return{get:s,set:s}}function Ja(a){return function(b,c){var d=c.get();!d&&n(a)&&(d=E(a));z(b.get())&&n(d)&&b.set(d)}}function la(a, -b,c,d,e){return ya("$updateView","$defer",function(g,f,h){var i=this,j=b(i,h),u=c(i,h),r=h.attr("ng:change")||"",q;j&&(d.call(i,j,u,h),this.$eval(h.attr("ng:init")||""),h.bind(a,function(a){function b(){var a=u.get();if(!e||a!=q)j.set(a),q=j.get(),i.$tryEval(r,h),g()}a.type=="keydown"?f(b):b()}),i.$watch(j.get,function(a){q!==a&&u.set(q=a)}))})}function Ka(a){this.directives(!0);this.descend(!0);return Mc[A(a[0].type)]||s}if(typeof J.getAttribute==ga)J.getAttribute=function(){};var A=function(a){return x(a)? -a.toLowerCase():a},jb=function(a){return x(a)?a.toUpperCase():a},Vb="$element",ib="$validate",oc="boolean",T="function",bc="length",cc="name",wa="null",gc="number",kb="object",fc="string",ga="undefined",xa="ng-exception",sa="ng-validation-error",Ia="noop",Ab=-1E3,Nc={FIRST:-99999,LAST:99999,WATCH:Ab},G=y.Error,U=parseInt((/msie (\d+)/.exec(A(navigator.userAgent))||[])[1],10),p,Sa,ia=[].slice,I=[].push,nc=y.console?F(y.console,y.console.error||s):s,o=y.angular||(y.angular={}),Pa=S(o,"markup"),rb=S(o, -"attrMarkup"),C=S(o,"directive"),M=S(o,"widget",A),$a=S(o,"validator"),O=S(o,"filter"),da=S(o,"formatter"),Ya=S(o,"service");S(o,"callbacks");var na,lc=/^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/,R=["0","0","0"];DATE_ISOSTRING_LN=24;na=U<9?function(a){a=a.nodeName?a:a[0];return a.scopeName&&a.scopeName!="HTML"?jb(a.scopeName+":"+a.nodeName):a.nodeName}:function(a){return a.nodeName?a.nodeName:a[0].nodeName};o.toJson=N;o.fromJson=ba;Wa.prototype={attach:function(a,b){var c={};this.collectInits(a, -c,b);Ma(c,function(a){k(a,function(a){a()})})},collectInits:function(a,b,c){var d=b[this.priority],e=c;d||(b[this.priority]=d=[]);this.newScope&&(e=W(c),c.$onEval(e.$eval),a.data("$scope",e));k(this.inits,function(b){d.push(function(){e.$tryEval(function(){return e.$service.invoke(e,b,[a])},a)})});var g=a[0].childNodes,f=this.children,h=this.paths,i=h.length;for(c=0;c1)throw G("Cannot compile multiple element roots: "+p("
").append(a.clone()).html());if(d&&d[0]){d=d[0];for(var e=0;e0?A(j).replace(":","-"):"",r,q={compile:F(d,d.compile),descend:function(a){n(a)&&(h=a);return h},directives:function(a){n(a)&&(i=a);return i},scope:function(a){if(n(a))r.newScope=r.newScope||a;return r.newScope}};try{c=a.attr("ng:eval-order")||c||0}catch(v){c=c||0}a.addClass(u);x(c)&&(c=Nc[jb(c)]||parseInt(c,10));r=new Wa(c);va(a,function(b,c){if(!e&&(e=d.widgets("@"+c)))a.addClass("ng-attr-widget"), -e=F(q,e,b,a)});if(!e&&(e=d.widgets(j)))u&&a.addClass("ng-widget"),e=F(q,e,a);e&&(i=h=!1,j=a.parent(),r.addInit(e.call(q,a)),j&&j[0]&&(a=p(j[0].childNodes[b])));if(h)for(var l=0,o=a[0].childNodes;l":function(a,b,c){return b>c},"<=":function(a,b,c){return b<=c},">=":function(a,b,c){return b>=c},"&&":function(a,b,c){return b&&c},"||":function(a,b,c){return b||c},"&":function(a,b,c){return b&c},"|":function(a,b,c){return c(a,b)},"!":function(a,b){return!b}},xc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'};Db.prototype={url:function(a){var b=this,c=this.template,d;a=a||{};k(this.urlParams,function(e,f){d=Ra(a[f]||b.defaults[f]||"",!0).replace(/%26/gi, -"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+");c=c.replace(RegExp(":"+f+"(\\W)"),d+"$1")});c=c.replace(/\/?#$/,"");var e=[];Ma(a,function(a,c){b.urlParams[c]||e.push(Ra(c)+"="+Ra(a))});c=c.replace(/\/*$/,"");return c+(e.length?"?"+e.join("&"):"")}};Ca.DEFAULT_ACTIONS={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}};Ca.prototype={route:function(a,b,c){function d(a){var c={};k(b||{},function(b,d){c[d]=b.charAt&&b.charAt(0)== -"@"?oa(a,b.substr(1)):b});return c}function e(a){E(a||{},this)}var g=this,f=new Db(a);c=m({},Ca.DEFAULT_ACTIONS,c);k(c,function(h,i){var j=h.method=="POST"||h.method=="PUT";e[i]=function(a,b,c){var i={},l,p=s;switch(arguments.length){case 3:p=c;case 2:if(t(b))p=b;else{i=a;l=b;break}case 1:t(a)?p=a:j?l=a:i=a;break;case 0:break;default:throw"Expected between 0-3 arguments [params, data, callback], got "+arguments.length+" arguments.";}var w=this instanceof e?this:h.isArray?[]:new e(l);g.xhr(h.method, -f.url(m({},h.params||{},d(l),i)),l,function(a,b){if(200<=a&&a<300){if(b)h.isArray?(w.length=0,k(b,function(a){w.push(new e(a))})):E(b,w);(p||s)(w)}else throw{status:a,response:b,message:a+": "+b};},h.verifyCache);return w};e.bind=function(d){return g.route(a,m({},b,d),c)};e.prototype["$"+i]=function(a,b){var c=d(this),f=s;switch(arguments.length){case 2:c=a,f=b;case 1:typeof a==T?f=a:c=a;case 0:break;default:throw"Expected between 1-2 arguments [params, callback], got "+arguments.length+" arguments."; -}e[i].call(this,c,j?this:B,f)}});return e}};var Oc=y.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(b){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(c){}throw new G("This browser does not support XMLHttpRequest.");},Kb=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,Jb=/^<\s*\/\s*([\w:-]+)[^>]*>/,zc=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, -Bc=/^/g,Dc=//g,Gc=/^((ftp|https?):\/\/|mailto:|#)/,Ec=/([^\#-~| |!])/g,Hb=Z("area,br,col,hr,img"),Eb=Z("address,blockquote,center,dd,del,dir,div,dl,dt,hr,ins,li,map,menu,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul"),Fb=Z("a,abbr,acronym,b,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,q,s,samp,small,span,strike,strong,sub,sup,tt,u,var"),Gb=Z("colgroup,dd,dt,li,p,td,tfoot,th,thead,tr"),Ib=Z("script,style"),Mb=m({},Hb,Eb,Fb, -Gb),Nb=Z("background,href,longdesc,src,usemap"),Fc=m({},Nb,Z("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),bb=J.createElement("pre"),Ea={},Da="ng-"+(new Date).getTime(),Jc=1,Pc=y.document.addEventListener?function(a,b,c){a.addEventListener(b,c,!1)}:function(a, -b,c){a.attachEvent("on"+b,c)},Ic=y.document.removeEventListener?function(a,b,c){a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent("on"+b,c)};U&&m(P.prototype,{text:function(a){var b=this[0];if(b.nodeType==3){if(n(a))b.nodeValue=a;return b.nodeValue}else{if(n(a))b.innerText=a;return b.innerText}}});var ub=P.prototype={ready:function(a){function b(){c||(c=!0,a())}var c=!1;this.bind("DOMContentLoaded",b);Ta(y).bind("load",b)},toString:function(){var a=[];k(this,function(b){a.push(""+b)}); -return"["+a.join(", ")+"]"},length:0,push:I,sort:[].sort,splice:[].splice},Qc=Z("multiple,selected,checked,disabled,readonly");k({data:db,scope:function(a){for(var b;a&&!(b=p(a).data("$scope"));)a=a.parentNode;return b},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:eb,css:function(a,b,c){if(n(c))a.style[b]=c;else return a.style[b]},attr:function(a,b,c){if(Qc[b])if(n(c))a[b]=!!c;else return a[b];else if(n(c))a.setAttribute(b,c);else if(a.getAttribute)return a.getAttribute(b,2)},text:m(U< -9?function(a,b){if(a.nodeType==3){if(z(b))return a.nodeValue;a.nodeValue=b}else{if(z(b))return a.innerText;a.innerText=b}}:function(a,b){if(z(b))return a.textContent;a.textContent=b},{$dv:""}),val:function(a,b){if(z(b))return a.value;a.value=b},html:function(a,b){if(z(b))return a.innerHTML;for(var c=0,d=a.childNodes;c=0&&a.splice(c,1);return b},filter:function(a,b){var c=[];c.check=function(a){for(var b=0;b-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;c0?(d=0,e=b):(d=a.length+b,e=a.length);for(;d-1)return e;a=Math.round(a*d)/d;fraction=(""+a).split(".");e=fraction[0];fraction=fraction[1]||"";c&&(g="-",e=e.substring(1));for(c=0;c]/,c=a,d=[],e=mb(d),g,f;a=c.match(b);)g=a[0],a[2]==a[3]&&(g="mailto:"+g),f=a.index,e.chars(c.substr(0,f)),e.start("a",{href:g}),e.chars(a[0].replace(/^mailto:/,"")),e.end("a"),c=c.substring(f+a[0].length);e.chars(c);return new Oa(d.join(""))};var Xc=/^\s*[-+]?\d*(\.\d*)?\s*$/;da.noop=ka($,$);da.json=ka(N,function(a){return ba(a||"null")});da["boolean"]=ka(Sb,ja);da.number=ka(Sb,function(a){if(a==null||Xc.exec(a))return a=== -null||a===""?null:1*a;else throw"Not a number";});da.list=ka(function(a){return a?a.join(", "):a},function(a){var b=[];k((a||"").split(","),function(a){(a=ha(a))&&b.push(a)});return b});da.trim=ka(function(a){return a?ha(""+a):""});m($a,{noop:function(){return null},regexp:function(a,b,c){return a.match(b)?null:c||"Value does not match expected format "+b+"."},number:function(a,b,c){var d=1*a;if(d==a){if(typeof b!=ga&&dc)return"Value can not be greater than "+ -c+".";return null}else return"Not a number"},integer:function(a,b,c){if(b=$a.number(a,b,c))return b;if(!(""+a).match(/^\s*[\d+]*\s*$/)||a!=Math.round(a))return"Not a whole number";return null},date:function(a){var b=(a=/^(\d\d?)\/(\d\d?)\/(\d\d\d\d)$/.exec(a))?new Date(a[3],a[1]-1,a[2]):0;return b&&b.getFullYear()==a[3]&&b.getMonth()==a[1]-1&&b.getDate()==a[2]?null:"Value is not a date. (Expecting format: 12/31/2009)."},email:function(a){if(a.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/))return null; -return"Email needs to be in username@host.com format."},phone:function(a){if(a.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/))return null;if(a.match(/^\+\d{2,3} (\(\d{1,5}\))?[\d ]+\d$/))return null;return"Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly."},url:function(a){if(a.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/))return null;return"URL needs to be in http://server[:port]/path format."},json:function(a){try{return ba(a), -null}catch(b){return b.toString()}},asynchronous:function(a,b,c){if(a){var d=this,e=d.$element,g=e.data("$asyncValidator");g||e.data("$asyncValidator",g={inputs:{}});g.current=a;var f=g.inputs[a],h=d.$service("$invalidWidgets");f?f.inFlight?h.markInvalid(d.$element):(c||s)(f.response):(g.inputs[a]=f={inFlight:!0},h.markInvalid(d.$element),e.addClass("ng-input-indicator-wait"),b(a,function(b,c){f.response=c;f.error=b;f.inFlight=!1;g.current==a&&(e.removeClass("ng-input-indicator-wait"),h.markValid(e)); -e.data(ib)();d.$service("$updateView")()}));return f.error}}});D("$cookieStore",function(a){return{get:function(b){return ba(a[b])},put:function(b,c){a[b]=N(c)},remove:function(b){delete a[b]}}},["$cookies"]);D("$cookies",function(a){var b=this,c={},d={},e,g=!1;a.addPollFn(function(){var f=a.cookies();e!=f&&(e=f,E(f,d),E(f,c),g&&b.$eval())})();g=!0;this.$onEval(99999,function(){var b,e,g;for(b in d)z(c[b])&&a.cookies(b,B);for(b in c)e=c[b],x(e)?e!==d[b]&&(a.cookies(b,e),g=!0):n(d[b])?c[b]=d[b]:delete c[b]; -if(g)for(b in e=a.cookies(),c)c[b]!==e[b]&&(z(e[b])?delete c[b]:c[b]=e[b])});return c},["$browser"]);D("$defer",function(a,b,c){return function(d,e){a.defer(function(){try{d()}catch(a){b(a)}finally{c()}},e)}},["$browser","$exceptionHandler","$updateView"]);D("$document",function(a){return p(a.document)},["$window"]);D("$exceptionHandler",function(a){return function(b){a.error(b)}},["$log"]);D("$hover",function(a,b){var c,d,e=p(b[0].body);a.hover(function(a,b){if(b&&(d=a.attr(xa)||a.attr(sa))){c|| -(c={callout:p('
'),arrow:p("
"),title:p('
'),content:p('
')},c.callout.append(c.arrow),c.callout.append(c.title),c.callout.append(c.content),e.append(c.callout));var h=e[0].getBoundingClientRect(),i=a[0].getBoundingClientRect();h=h.right-i.right-10;c.title.text(a.hasClass("ng-exception")?"EXCEPTION:":"Validation error...");c.content.text(d);h<300?(c.arrow.addClass("ng-arrow-right"),c.arrow.css({left:"301px"}), -c.callout.css({position:"fixed",left:i.left-10-300-4+"px",top:i.top-3+"px",width:"300px"})):(c.arrow.addClass("ng-arrow-left"),c.callout.css({position:"fixed",left:i.right+10+"px",top:i.top-3+"px",width:"300px"}))}else c&&(c.callout.remove(),c=null)})},["$browser","$document"],!0);D("$invalidWidgets",function(){function a(b){if(b==y.document)return!1;b=b.parentNode;return!b||a(b)}var b=[];b.markValid=function(a){a=ua(b,a);a!=-1&&b.splice(a,1)};b.markInvalid=function(a){ua(b,a)===-1&&b.push(a)};b.visible= -function(){var a=0;k(b,function(b){var e=a;b=b[0].getBoundingClientRect();var g=b.height||b.bottom||0-b.top||0;a=e+((b.width||b.right||0-b.left||0)>0&&g>0?1:0)});return a};this.$onEval(99999,function(){for(var c=0;c1||Ha(d[0])!==null)if(ic(c[0]))c.attr("ng:bind-template",a);else{var e=b,g;k(Ga(a),function(a){var b=Ha(a);b?(g=p(""),g.attr("ng:bind",b)):g=p(J.createTextNode(a));U&&a.charAt(0)==" "&&(g=p(" "),b= -g.html(),g.text(a.substr(1)),g.html(b+g.html()));e.after(g);e=g});b.remove()}});Pa("option",function(a,b,c){A(na(c))=="option"&&(U<=7?lb(c[0].outerHTML,{start:function(b,e){z(e.value)&&c.attr("value",a)}}):c[0].getAttribute("value")==null&&c.attr("value",a))});var ac={};k("src,href,checked,disabled,multiple,readonly,selected".split(","),function(a){ac["ng:"+a]=a});rb("{{}}",function(a,b,c){if(!C(b)&&!C("@"+b)){U&&b=="src"&&(a=decodeURI(a));var d=Ga(a);if(d.length>1||Ha(d[0])!==null)c.removeAttr(b), -d=ba(c.attr("ng:bind-attr")||"{}"),d[ac[b]||b]=a,c.attr("ng:bind-attr",N(d))}});I=la("keydown change",ra,Xb,Ja(),!0);X=la("click",Yb,Yb,s);var Mc={text:I,textarea:I,hidden:I,password:I,button:X,submit:X,reset:X,image:X,checkbox:la("click",Wb,function(a,b){var c=b[0];return{get:function(){return!!c.checked},set:function(a){c.checked=ja(a)}}},Ja(!1)),radio:la("click",Wb,function(a,b){var c=b[0];return{get:function(){return c.checked?c.value:null},set:function(a){c.checked=a==c.value}}},function(a,b, -c){var d=a.get(),e=b.get();c=c[0];c.checked=!1;c.name=this.$id+"@"+c.name;z(d)&&a.set(d=null);d==null&&e!==null&&a.set(e);b.set(d)}),"select-one":la("change",ra,Xb,Ja(null)),"select-multiple":la("change",ra,function(a,b){var c=b.attr("ng:format")||Ia,d=ca(c).formatter()();return{get:function(){var c=[];k(b[0].options,function(b){b.selected&&c.push(d.parse(a,b.value))});return c},set:function(c){var g={};k(c,function(b){g[d.format(a,b)]=!0});k(b[0].options,function(a){a.selected=g[a.value]})}}},Ja([]))}; -M("input",Ka);M("textarea",Ka);M("button",Ka);var ad=/^(.*)\s+for\s+([\$\w][\$\w\d]*)\s+in\s+(.*)$/;M("select",function(a){this.descend(!0);this.directives(!0);var b=a.attr("multiple"),c=a.attr("ng:options"),d;if(!c)return Ka.call(this,a);if(!(d=c.match(ad)))throw G("Expected ng:options in form of '_expresion_ for _item_ in _collection_' but got '"+c+"'.");var e=V(d[1]).fnSelf,g=d[2],f=V(d[3]).fnSelf,h=p(J.createElement("option"));return function(c){var d=this,u=[],r=[],q=b?{}:!1,v=h.clone().val(""), -l=h.clone().val("?"),o=ra(d,a);k(c.children(),function(a){a.value==""&&(v=!1)});c.bind("change",function(){var a=f(d)||[],e=c.val(),g,h;if(b){e=[];g=0;for(h=u.length;gm?(n= -u[m],d!=r[m]&&n.text(r[m]=d)):(k||(k=J.createDocumentFragment()),r.push(d),u.push(n=h.clone()),n.attr("value",m).text(d),k.appendChild(n[0])),b){if(q[m]!=(d=t.remove(y)))n[0].selected=!!(q[m]=d)}else s==y&&(t=m);for(k&&c.append(p(k));u.length>m;)u.pop().remove(),delete q[u.length];b||(t===""&&s&&(t="?"),q!==t&&(v&&(q==""&&v.remove(),t===""&&c.prepend(v)),l&&(q=="?"&&l.remove(),t==="?"&&c.prepend(l)),c.val(q=t)))})}});M("ng:include",function(a){var b=this,c=a.attr("src"),d=a.attr("scope")||"",e=a[0].getAttribute("onload")|| -"";if(a[0]["ng:compiled"])this.descend(!0),this.directives(!0);else return a[0]["ng:compiled"]=!0,m(function(a,f){function h(){k++}var i=this,j,k=0,r=!1;this.$watch(c,h);this.$watch(d,h);i.$onEval(function(){if(j&&!r){r=!0;try{j.$eval()}finally{r=!1}}});this.$watch(function(){return k},function(){var h=this.$eval(c),k=this.$eval(d);h?a("GET",h,null,function(a,c){f.html(c);j=k||W(i);b.compile(f)(j);i.$eval(e)},!1,!0):(j=null,f.html(""))})},{$inject:["$xhr.cache"]})});var bd=M("ng:switch",function(a){var b= -this,c=a.attr("on"),d=a.attr("using")||"equals",e=d.split(":"),g=bd[e.shift()],f=a.attr("change")||"",h=[];if(!g)throw"Using expression '"+d+"' unknown.";if(!c)throw"Missing 'on' attribute.";vb(a,function(a){var c=a.attr("ng:switch-when"),d={change:f,element:a,template:b.compile(a)};if(x(c))d.when=function(a,b){var d=[b,c];k(e,function(a){d.push(a)});return g.apply(a,d)},h.unshift(d);else if(x(a.attr("ng:switch-default")))d.when=ta(!0),h.push(d)});k(h,function(a){a.element.remove()});a.html("");return function(a){var b= -this,d;this.$watch(c,function(c){var e=!1;a.html("");d=W(b);k(h,function(b){!e&&b.when(d,c)&&(e=!0,d.$tryEval(b.change,a),b.template(d,function(b){a.append(b)}))})});b.$onEval(function(){d&&d.$eval()})}},{equals:function(a,b){return""+a==b}});M("a",function(){this.descend(!0);this.directives(!0);return function(a){var b=(a.attr("ng:bind-attr")||"").indexOf('"href":')!==-1;!b&&!a.attr("name")&&!a.attr("href")&&a.attr("href","");a.attr("href")===""&&!b&&a.bind("click",function(a){a.preventDefault()})}}); -M("@ng:repeat",function(a,b){b.removeAttr("ng:repeat");b.replaceWith(p("<\!-- ng:repeat: "+a+" --\>"));var c=this.compile(b);return function(d){var e=a.match(/^\s*(.+)\s+in\s+(.*)\s*$/),g,f,h,i;if(!e)throw G("Expected ng:repeat in form of '_item_ in _collection_' but got '"+a+"'.");g=e[1];f=e[2];e=g.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);if(!e)throw G("'item' in 'item in collection' should be identifier or (key, value) but got '"+keyValue+"'.");h=e[3]||e[1];i=e[2];var j=[],k=this;this.$onEval(function(){var a= -0,e=j.length,g=d,l=this.$tryEval(f,d),m=nb(l,!0),o=b[0].nodeName!="OPTION"?J.createDocumentFragment():null,t,n,s;for(s in l)if(l.hasOwnProperty(s))aa;)j.pop().$element.remove()}, -d)}});M("@ng:non-bindable",s);M("ng:view",function(a){var b=this;if(a[0]["ng:compiled"])this.descend(!0),this.directives(!0);else return a[0]["ng:compiled"]=!0,ya("$xhr.cache","$route",function(a,d,e){var g;d.onChange(function(){var f;if(d.current)f=d.current.template,g=d.current.scope;f?a("GET",f,function(a,c){e.html(c);b.compile(e)(g)}):e.html("")})();this.$onEval(function(){g&&g.$eval()})})});var La;Ya("$browser",function(a){La||(La=new yc(y,p(y.document),p(y.document.body),Oc,a),La.bind());return La}, -{$inject:["$log"]});m(o,{element:p,compile:pb,scope:W,copy:E,extend:m,equals:ma,forEach:k,injector:Bb,noop:s,bind:F,toJson:N,fromJson:ba,identity:$,isUndefined:z,isDefined:n,isString:x,isFunction:t,isObject:K,isNumber:fa,isArray:Y});tb();Ta(J).ready(function(){var a=kc(J),b=J,c=a.autobind;c&&(b=x(c)?b.getElementById(c):b,b=pb(b)(W({$config:a})).$service("$browser"),a.css?b.addCss(a.base_url+a.css):U<8&&b.addJs(a.base_url+a.ie_compat,a.ie_compat_id))})})(window,document);angular.element(document).find("head").append(''); diff --git a/src/main/webapp/js/lib/angular-1.0.7.min.js b/src/main/webapp/js/lib/angular-1.0.7.min.js new file mode 100644 index 0000000..2b22068 --- /dev/null +++ b/src/main/webapp/js/lib/angular-1.0.7.min.js @@ -0,0 +1,163 @@ +/* + AngularJS v1.0.7 + (c) 2010-2012 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(P,T,q){'use strict';function m(b,a,c){var d;if(b)if(H(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==m)b.forEach(a,c);else if(!b||typeof b.length!=="number"?0:typeof b.hasOwnProperty!="function"&&typeof b.constructor!="function"||b instanceof K||ca&&b instanceof ca||wa.call(b)!=="[object Object]"||typeof b.callee==="function")for(d=0;d=0&&b.splice(c,1);return a}function U(b,a){if(oa(b)||b&&b.$evalAsync&&b.$watch)throw Error("Can't copy Window or Scope");if(a){if(b===a)throw Error("Can't copy equivalent objects or arrays");if(E(b))for(var c=a.length=0;c2?ha.call(arguments,2):[];return H(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ha.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function ic(b,a){var c=a;/^\$+/.test(b)?c=q:oa(a)?c="$WINDOW":a&&T===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function da(b,a){return JSON.stringify(b, +ic,a?" ":null)}function pb(b){return B(b)?JSON.parse(b):b}function Ua(b){b&&b.length!==0?(b=z(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;return b}function pa(b){b=u(b).clone();try{b.html("")}catch(a){}var c=u("
").append(b).html();try{return b[0].nodeType===3?z(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+z(b)})}catch(d){return z(c)}}function Va(b){var a={},c,d;m((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]), +a[d]=y(c[1])?decodeURIComponent(c[1]):!0)});return a}function qb(b){var a=[];m(b,function(b,d){a.push(Wa(d,!0)+(b===!0?"":"="+Wa(b,!0)))});return a.length?a.join("&"):""}function Xa(b){return Wa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function jc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,h=["ng:app","ng-app","x-ng-app", +"data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;m(h,function(a){h[a]=!0;c(T.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(m(b.querySelectorAll("."+a),c),m(b.querySelectorAll("."+a+"\\:"),c),m(b.querySelectorAll("["+a+"]"),c))});m(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):m(a.attributes,function(b){if(!e&&h[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}function rb(b,a){var c=function(){b=u(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement", +b)}]);a.unshift("ng");var c=sb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(P&&!d.test(P.name))return c();P.name=P.name.replace(d,"");Ya.resumeBootstrap=function(b){m(b,function(b){a.push(b)});c()}}function Za(b,a){a=a||"_";return b.replace(kc,function(b,d){return(d?a:"")+b.toLowerCase()})}function $a(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"required")); +return b}function qa(b,a,c){c&&E(b)&&(b=b[b.length-1]);$a(H(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function lc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,d,e){return function(){b[e||"push"]([c,d,arguments]);return k}}if(!e)throw Error("No module: "+d);var b=[],c=[],j=a("$injector", +"invoke"),k={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:j,run:function(a){c.push(a);return this}};g&&j(g);return k})}})}function tb(b){return b.replace(mc,function(a,b,d,e){return e?d.toUpperCase(): +d}).replace(nc,"Moz$1")}function ab(b,a){function c(){var e;for(var b=[this],c=a,h,f,i,j,k,l;b.length;){h=b.shift();f=0;for(i=h.length;f 
"+b;a.removeChild(a.firstChild);bb(this,a.childNodes);this.remove()}else bb(this,b)}function cb(b){return b.cloneNode(!0)}function ra(b){ub(b);for(var a=0,b=b.childNodes||[];a-1}function xb(b,a){a&&m(a.split(" "),function(a){b.className=Q((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+Q(a)+" "," "))})} +function yb(b,a){a&&m(a.split(" "),function(a){if(!Ca(b,a))b.className=Q(b.className+" "+Q(a))})}function bb(b,a){if(a)for(var a=!a.nodeName&&y(a.length)&&!oa(a)?a:[a],c=0;c4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!")}else{if(i.cookie!==$){$=i.cookie;d=$.split("; ");r={};for(f=0;f0&&(a=unescape(e.substring(0,j)),r[a]===q&&(r[a]=unescape(e.substring(j+1))))}return r}};f.defer=function(a,b){var c; +p++;c=l(function(){delete o[c];e(a)},b||0);o[c]=!0;return c};f.defer.cancel=function(a){return o[a]?(delete o[a],n(a),e(C),!0):!1}}function wc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new vc(b,d,a,c)}]}function xc(){this.$get=function(){function b(b,d){function e(a){if(a!=l){if(n){if(n==a)n=a.n}else n=a;g(a.n,a.p);g(a,l);l=a;l.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error("cacheId "+b+" taken");var h=0,f=v({},d,{id:b}),i={},j=d&& +d.capacity||Number.MAX_VALUE,k={},l=null,n=null;return a[b]={put:function(a,b){var c=k[a]||(k[a]={key:a});e(c);w(b)||(a in i||h++,i[a]=b,h>j&&this.remove(n.key))},get:function(a){var b=k[a];if(b)return e(b),i[a]},remove:function(a){var b=k[a];if(b){if(b==l)l=b.p;if(b==n)n=b.n;g(b.n,b.p);delete k[a];delete i[a];h--}},removeAll:function(){i={};h=0;k={};l=n=null},destroy:function(){k=f=i=null;delete a[b]},info:function(){return v({},f,{size:h})}}}var a={};b.info=function(){var b={};m(a,function(a,e){b[e]= +a.info()});return b};b.get=function(b){return a[b]};return b}}function yc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Db(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ",h=/^\s*(https?|ftp|mailto|file):/;this.directive=function i(d,e){B(d)?($a(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];m(a[d], +function(a){try{var g=b.invoke(a);if(H(g))g={compile:I(g)};else if(!g.compile&&g.link)g.compile=I(g.link);g.priority=g.priority||0;g.name=g.name||d;g.require=g.require||g.controller&&g.name;g.restrict=g.restrict||"A";e.push(g)}catch(h){c(h)}});return e}])),a[d].push(e)):m(d,nb(i));return this};this.urlSanitizationWhitelist=function(a){return y(a)?(h=a,this):h};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document",function(b, +j,k,l,n,o,p,s,t){function x(a,b,c){a instanceof u||(a=u(a));m(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\S+/)&&(a[c]=u(b).wrap("").parent()[0])});var d=A(a,b,a,c);return function(b,c){$a(b,"scope");for(var e=c?ua.clone.call(a):a,j=0,g=e.length;jr.priority)break;if(Y=r.scope)ta("isolated scope",J,r,D),L(Y)&&(M(D,"ng-isolate-scope"),J=r),M(D,"ng-scope"),s=s||r;F=r.name;if(Y=r.controller)y=y||{},ta("'"+F+"' controller",y[F],r,D),y[F]=r;if(Y=r.transclude)ta("transclusion",ja,r,D),ja=r,l=r.priority,Y=="element"?(W=u(b),D=c.$$element=u(T.createComment(" "+ +F+": "+c[F]+" ")),b=D[0],C(e,u(W[0]),b),V=x(W,d,l)):(W=u(cb(b)).contents(),D.html(""),V=x(W,d));if(Y=r.template)if(ta("template",A,r,D),A=r,Y=Fb(Y),r.replace){W=u("
"+Q(Y)+"
").contents();b=W[0];if(W.length!=1||b.nodeType!==1)throw Error(g+Y);C(e,D,b);F={$attr:{}};a=a.concat(N(b,a.splice(v+1,a.length-(v+1)),F));$(c,F);z=a.length}else D.html(Y);if(r.templateUrl)ta("template",A,r,D),A=r,i=R(a.splice(v,a.length-v),i,D,c,e,r.replace,V),z=a.length;else if(r.compile)try{w=r.compile(D,c,V),H(w)? +j(null,w):w&&j(w.pre,w.post)}catch(G){k(G,pa(D))}if(r.terminal)i.terminal=!0,l=Math.max(l,r.priority)}i.scope=s&&s.scope;i.transclude=ja&&V;return i}function r(d,e,g,j){var h=!1;if(a.hasOwnProperty(e))for(var o,e=b.get(e+c),l=0,p=e.length;lo.priority)&&o.restrict.indexOf(g)!=-1)d.push(o),h=!0}catch(n){k(n)}return h}function $(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b, +function(b,g){g=="class"?(M(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):g=="style"?e.attr("style",e.attr("style")+";"+b):g.charAt(0)!="$"&&!a.hasOwnProperty(g)&&(a[g]=b,d[g]=c[g])})}function R(a,b,c,d,e,j,h){var i=[],k,o,p=c[0],t=a.shift(),s=v({},t,{controller:null,templateUrl:null,transclude:null,scope:null});c.html("");l.get(t.templateUrl,{cache:n}).success(function(l){var n,t,l=Fb(l);if(j){t=u("
"+Q(l)+"
").contents();n=t[0];if(t.length!=1||n.nodeType!==1)throw Error(g+l);l={$attr:{}}; +C(e,c,n);N(n,a,l);$(d,l)}else n=p,c.html(l);a.unshift(s);k=J(a,n,d,h);for(o=A(c[0].childNodes,h);i.length;){var r=i.pop(),l=i.pop();t=i.pop();var ia=i.pop(),D=n;t!==p&&(D=cb(n),C(l,u(t),D));k(function(){b(o,ia,D,e,r)},ia,D,e,r)}i=null}).error(function(a,b,c,d){throw Error("Failed to load template: "+d.url);});return function(a,c,d,e,g){i?(i.push(c),i.push(d),i.push(e),i.push(g)):k(function(){b(o,c,d,e,g)},c,d,e,g)}}function F(a,b){return b.priority-a.priority}function ta(a,b,c,d){if(b)throw Error("Multiple directives ["+ +b.name+", "+c.name+"] asking for "+a+" on: "+pa(d));}function y(a,b){var c=j(b,!0);c&&a.push({priority:0,compile:I(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);M(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function V(a,b,c,d){var e=j(c,!0);e&&b.push({priority:100,compile:I(function(a,b,c){b=c.$$observers||(c.$$observers={});d==="class"&&(e=j(c[d],!0));c[d]=q;(b[d]||(b[d]=[])).$$inter=!0;(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e, +function(a){c.$set(d,a)})})})}function C(a,b,c){var d=b[0],e=d.parentNode,g,j;if(a){g=0;for(j=a.length;g +0){var e=R[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b,c,d,f){return(b=h(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),R.shift(),b):!1}function i(a){f(a)||e("is unexpected, expecting ["+a+"]",h())}function j(a,b){return function(c,d){return a(c,d,b)}}function k(a,b,c){return function(d,e){return b(d,e,a,c)}}function l(){for(var a=[];;)if(R.length>0&&!h("}",")",";","]")&&a.push(w()),!f(";"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e","<=",">="))a=k(a,b.fn,t());return a}function x(){for(var a=m(),b;b=f("*","/","%");)a=k(a,b.fn,m());return a}function m(){var a;return f("+")?A():(a=f("-"))?k(r,a.fn,m()):(a=f("!"))?j(a.fn,m()):A()}function A(){var a;if(f("("))a=w(),i(")");else if(f("["))a=N();else if(f("{"))a=J();else{var b=f();(a=b.fn)||e("not a primary expression",b)}for(var c;b=f("(","[",".");)b.text==="("?(a=y(a,c),c=null):b.text==="["?(c=a,a=V(a)):b.text==="."?(c=a,a=u(a)):e("IMPOSSIBLE");return a}function N(){var a= +[];if(g().text!="]"){do a.push(F());while(f(","))}i("]");return function(b,c){for(var d=[],e=0;e1;d++){var e=a.shift(),g=b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]= +c}function gb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,h=0;h7),hasEvent:function(c){if(c=="input"&&Z==9)return!1;if(w(a[c])){var e=b.document.createElement("div");a[c]="on"+c in e}return a[c]},csp:!1}}]}function Vc(){this.$get=I(P)}function Ob(b){var a={},c,d,e;if(!b)return a;m(b.split("\n"),function(b){e=b.indexOf(":");c=z(Q(b.substr(0, +e)));d=Q(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Pb(b){var a=L(b)?b:q;return function(c){a||(a=Ob(b));return c?a[z(c)]||null:a}}function Qb(b,a,c){if(H(c))return c(b,a);m(c,function(c){b=c(b,a)});return b}function Wc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){B(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=pb(d,!0)));return d}],transformRequest:[function(a){return L(a)&&wa.apply(a)!=="[object File]"?da(a):a}], +headers:{common:{Accept:"application/json, text/plain, */*","X-Requested-With":"XMLHttpRequest"},post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}}},e=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,i,j,k){function l(a){function c(a){var b=v({},a,{data:Qb(a.data,a.headers,f)});return 200<=a.status&&a.status<300?b:j.reject(b)}a.method=la(a.method);var e=a.transformRequest|| +d.transformRequest,f=a.transformResponse||d.transformResponse,g=d.headers,g=v({"X-XSRF-TOKEN":b.cookies()["XSRF-TOKEN"]},g.common,g[z(a.method)],a.headers),e=Qb(a.data,Pb(g),e),i;w(a.data)&&delete g["Content-Type"];i=n(a,e,g);i=i.then(c,c);m(s,function(a){i=a(i)});i.success=function(b){i.then(function(c){b(c.data,c.status,c.headers,a)});return i};i.error=function(b){i.then(null,function(c){b(c.data,c.status,c.headers,a)});return i};return i}function n(b,c,d){function e(a,b,c){m&&(200<=a&&a<300?m.put(q, +[a,b,Ob(c)]):m.remove(q));f(b,a,c);i.$apply()}function f(a,c,d){c=Math.max(c,0);(200<=c&&c<300?k.resolve:k.reject)({data:a,status:c,headers:Pb(d),config:b})}function h(){var a=za(l.pendingRequests,b);a!==-1&&l.pendingRequests.splice(a,1)}var k=j.defer(),n=k.promise,m,s,q=o(b.url,b.params);l.pendingRequests.push(b);n.then(h,h);b.cache&&b.method=="GET"&&(m=L(b.cache)?b.cache:p);if(m)if(s=m.get(q))if(s.then)return s.then(h,h),s;else E(s)?f(s[1],s[0],U(s[2])):f(s,200,{});else m.put(q,n);s||a(b.method, +q,c,e,d,b.timeout,b.withCredentials);return n}function o(a,b){if(!b)return a;var c=[];fc(b,function(a,b){a==null||a==q||(L(a)&&(a=da(a)),c.push(encodeURIComponent(b)+"="+encodeURIComponent(a)))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var p=c("$http"),s=[];m(e,function(a){s.push(B(a)?k.get(a):k.invoke(a))});l.pendingRequests=[];(function(a){m(arguments,function(a){l[a]=function(b,c){return l(v(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){m(arguments,function(a){l[a]= +function(b,c,d){return l(v(d||{},{method:a,url:b,data:c}))}})})("post","put");l.defaults=d;return l}]}function Xc(){this.$get=["$browser","$window","$document",function(b,a,c){return Yc(b,Zc,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]}function Yc(b,a,c,d,e,g){function h(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;Z?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror= +d;e.body.appendChild(c)}return function(e,i,j,k,l,n,o){function p(a,c,d,e){c=(i.match(Hb)||["",g])[1]=="file"?d?200:404:c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(C)}b.$$incOutstandingRequestCount();i=i||b.url();if(z(e)=="jsonp"){var s="_"+(d.counter++).toString(36);d[s]=function(a){d[s].data=a};h(i.replace("JSON_CALLBACK","angular.callbacks."+s),function(){d[s].data?p(k,200,d[s].data):p(k,-2);delete d[s]})}else{var t=new a;t.open(e,i,!0);m(l,function(a,b){a&&t.setRequestHeader(b,a)}); +var q;t.onreadystatechange=function(){if(t.readyState==4){var a=t.getAllResponseHeaders(),b=["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified","Pragma"];a||(a="",m(b,function(b){var c=t.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));p(k,q||t.status,t.responseText,a)}};if(o)t.withCredentials=!0;t.send(j||"");n>0&&c(function(){q=-1;t.abort()},n)}}}function $c(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0, +maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","), +AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function ad(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,i){var j=c.defer(),k=j.promise,l=y(i)&&!i,f=a.defer(function(){try{j.resolve(e())}catch(a){j.reject(a),d(a)}l||b.$apply()},f),i=function(){delete g[k.$$timeoutId]}; +k.$$timeoutId=f;g[f]=j;k.then(i,i);return k}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Rb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Sb);a("date",Tb);a("filter",bd);a("json",cd);a("limitTo",dd);a("lowercase",ed);a("number",Ub);a("orderBy",Vb);a("uppercase",fd)}function bd(){return function(b, +a){if(!E(b))return b;var c=[];c.check=function(a){for(var b=0;b-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;ce+1?h="0":(f=h,j=!0)}if(!j){h=(h.split(Xb)[1]||"").length;w(e)&&(e=Math.min(Math.max(a.minFrac,h),a.maxFrac));var h=Math.pow(10,e),b=Math.round(b*h)/h,b=(""+b).split(Xb),h=b[0],b=b[1]||"",j=0,k=a.lgSize, +l=a.gSize;if(h.length>=k+l)for(var j=h.length-k,n=0;n0||e> +-c)e+=c;e===0&&c==-12&&(e=12);return jb(e,a,d)}}function Ja(b,a){return function(c,d){var e=c["get"+b](),g=la(a?"SHORT"+b:b);return d[g][e]}}function Tb(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,h=0;b[9]&&(g=G(b[9]+b[10]),h=G(b[9]+b[11]));a.setUTCFullYear(G(b[1]),G(b[2])-1,G(b[3]));a.setUTCHours(G(b[4]||0)-g,G(b[5]||0)-h,G(b[6]||0),G(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c, +e){var g="",h=[],f,i,e=e||"mediumDate",e=b.DATETIME_FORMATS[e]||e;B(c)&&(c=gd.test(c)?G(c):a(c));Qa(c)&&(c=new Date(c));if(!na(c))return c;for(;e;)(i=hd.exec(e))?(h=h.concat(ha.call(i,1)),e=h.pop()):(h.push(e),e=null);m(h,function(a){f=id[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function cd(){return function(b){return da(b,!0)}}function dd(){return function(b,a){if(!(b instanceof Array))return b;var a=G(a),c=[],d,e;if(!b||!(b instanceof Array))return c; +a>b.length?a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;dn?(d.$setValidity("maxlength",!1),q):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(c);d.$formatters.push(c)}}function kb(b,a){b="ngClass"+b;return S(function(c,d,e){function g(b){if(a===!0||c.$index%2===a)i&&!fa(b,i)&&h(i),f(b);i=U(b)}function h(a){L(a)&& +!E(a)&&(a=Ra(a,function(a,b){if(a)return b}));d.removeClass(E(a)?a.join(" "):a)}function f(a){L(a)&&!E(a)&&(a=Ra(a,function(a,b){if(a)return b}));a&&d.addClass(E(a)?a.join(" "):a)}var i=q;c.$watch(e[b],g,!0);e.$observe("class",function(){var a=c.$eval(e[b]);g(a,a)});b!=="ngClass"&&c.$watch("$index",function(d,g){var i=d&1;i!==g&1&&(i===a?f(c.$eval(e[b])):h(c.$eval(e[b])))})})}var z=function(b){return B(b)?b.toLowerCase():b},la=function(b){return B(b)?b.toUpperCase():b},Z=G((/msie (\d+)/.exec(z(navigator.userAgent))|| +[])[1]),u,ca,ha=[].slice,Pa=[].push,wa=Object.prototype.toString,Ya=P.angular||(P.angular={}),sa,fb,aa=["0","0","0"];C.$inject=[];ma.$inject=[];fb=Z<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?la(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var kc=/[A-Z]/g,jd={full:"1.0.7",major:1,minor:0,dot:7,codeName:"monochromatic-rainbow"},Ba=K.cache={},Aa=K.expando="ng-"+(new Date).getTime(),oc=1,$b=P.document.addEventListener? +function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},db=P.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},mc=/([\:\-\_]+(.))/g,nc=/^moz([A-Z])/,ua=K.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;this.bind("DOMContentLoaded",a);K(P).bind("load",a)},toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?u(this[b]):u(this[this.length+ +b])},length:0,push:Pa,sort:[].sort,splice:[].splice},Ea={};m("multiple,selected,checked,disabled,readOnly,required".split(","),function(b){Ea[z(b)]=b});var Bb={};m("input,select,option,textarea,button,form".split(","),function(b){Bb[la(b)]=!0});m({data:wb,inheritedData:Da,scope:function(b){return Da(b,"$scope")},controller:zb,injector:function(b){return Da(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ca,css:function(b,a,c){a=tb(a);if(y(c))b.style[a]=c;else{var d;Z<=8&&(d= +b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];Z<=8&&(d=d===""?q:d);return d}},attr:function(b,a,c){var d=z(a);if(Ea[d])if(y(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||C).specified?d:q;else if(y(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?q:b},prop:function(b,a,c){if(y(c))b[a]=c;else return b[a]},text:v(Z<9?function(b,a){if(b.nodeType==1){if(w(a))return b.innerText; +b.innerText=a}else{if(w(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(w(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(w(a))return b.value;b.value=a},html:function(b,a){if(w(a))return b.innerHTML;for(var c=0,d=b.childNodes;c":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Mc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},ib={},Zc=P.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw Error("This browser does not support XMLHttpRequest."); +};Rb.$inject=["$provide"];Sb.$inject=["$locale"];Ub.$inject=["$locale"];var Xb=".",id={yyyy:O("FullYear",4),yy:O("FullYear",2,0,!0),y:O("FullYear",1),MMMM:Ja("Month"),MMM:Ja("Month",!0),MM:O("Month",2,1),M:O("Month",1,1),dd:O("Date",2),d:O("Date",1),HH:O("Hours",2),H:O("Hours",1),hh:O("Hours",2,-12),h:O("Hours",1,-12),mm:O("Minutes",2),m:O("Minutes",1),ss:O("Seconds",2),s:O("Seconds",1),EEEE:Ja("Day"),EEE:Ja("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){var a= +-1*a.getTimezoneOffset(),c=a>=0?"+":"";c+=jb(Math[a>0?"floor":"ceil"](a/60),2)+jb(Math.abs(a%60),2);return c}},hd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,gd=/^\d+$/;Tb.$inject=["$locale"];var ed=I(z),fd=I(la);Vb.$inject=["$parse"];var kd=I({restrict:"E",compile:function(a,c){Z<=8&&(!c.href&&!c.name&&c.$set("href",""),a.append(T.createComment("IE fix")));return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),lb={};m(Ea,function(a, +c){var d=ea("ng-"+c);lb[d]=function(){return{priority:100,compile:function(){return function(a,g,h){a.$watch(h[d],function(a){h.$set(c,!!a)})}}}}});m(["src","href"],function(a){var c=ea("ng-"+a);lb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),Z&&e.prop(a,g[a]))})}}}});var Ma={$addControl:C,$removeControl:C,$setValidity:C,$setDirty:C};Yb.$inject=["$element","$attrs","$scope"];var Pa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E", +controller:Yb,compile:function(){return{pre:function(a,d,h,f){if(!h.action){var i=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};$b(d[0],"submit",i);d.bind("$destroy",function(){c(function(){db(d[0],"submit",i)},0,!1)})}var j=d.parent().controller("form"),k=h.name||h.ngForm;k&&(a[k]=f);j&&d.bind("$destroy",function(){j.$removeControl(f);k&&(a[k]=q);v(f,Ma)})}}}};return a?v(U(d),{restrict:"EAC"}):d}]},ld=Pa(),md=Pa(!0),nd=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/, +od=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,pd=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,bc={text:Oa,number:function(a,c,d,e,g,h){Oa(a,c,d,e,g,h);e.$parsers.push(function(a){var c=X(a);return c||pd.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),q)});e.$formatters.push(function(a){return X(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!X(a)&&ai?(e.$setValidity("max",!1),q):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return X(a)||Qa(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),q)})},url:function(a,c,d,e,g,h){Oa(a,c,d,e,g,h);a=function(a){return X(a)||nd.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a, +c,d,e,g,h){Oa(a,c,d,e,g,h);a=function(a){return X(a)||od.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){w(d.name)&&c.attr("name",xa());c.bind("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,h=d.ngFalseValue;B(g)||(g=!0);B(h)||(h=!1);c.bind("click", +function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:h})},hidden:C,button:C,submit:C,reset:C},cc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,h){h&&(bc[z(g.type)]||bc.text)(d,e,g,h,c,a)}}}],La="ng-valid",Ka="ng-invalid",Na="ng-pristine",Zb="ng-dirty",qd=["$scope","$exceptionHandler","$attrs","$element","$parse", +function(a,c,d,e,g){function h(a,c){c=c?"-"+Za(c,"-"):"";e.removeClass((a?Ka:La)+c).addClass((a?La:Ka)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),i=f.assign;if(!i)throw Error(Eb+d.ngModel+" ("+pa(e)+")");this.$render=C;var j=e.inheritedData("$formController")||Ma,k=0,l=this.$error={};e.addClass(Na);h(!0);this.$setValidity=function(a, +c){if(l[a]!==!c){if(c){if(l[a]&&k--,!k)h(!0),this.$valid=!0,this.$invalid=!1}else h(!1),this.$invalid=!0,this.$valid=!1,k++;l[a]=!c;h(c,a);j.$setValidity(a,c,this)}};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(Na).addClass(Zb),j.$setDirty();m(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,i(a,d),m(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};var n=this;a.$watch(function(){var c= +f(a);if(n.$modelValue!==c){var d=n.$formatters,e=d.length;for(n.$modelValue=c;e--;)c=d[e](c);if(n.$viewValue!==c)n.$viewValue=c,n.$render()}})}],rd=function(){return{require:["ngModel","^?form"],controller:qd,link:function(a,c,d,e){var g=e[0],h=e[1]||Ma;h.$addControl(g);c.bind("$destroy",function(){h.$removeControl(g)})}}},sd=I({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),dc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required= +!0;var g=function(a){if(d.required&&(X(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},td=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&m(a.split(g),function(a){a&&c.push(Q(a))});return c});e.$formatters.push(function(a){return E(a)?a.join(", "): +q})}}},ud=/^(true|false|\d+)$/,vd=function(){return{priority:100,compile:function(a,c){return ud.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},wd=S(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==q?"":a)})}),xd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate", +function(a){d.text(a)})}}],yd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],zd=kb("",!0),Ad=kb("Odd",0),Bd=kb("Even",1),Cd=S({compile:function(a,c){c.$set("ngCloak",q);a.removeClass("ng-cloak")}}),Dd=[function(){return{scope:!0,controller:"@"}}],Ed=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],ec={};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave".split(" "), +function(a){var c=ea("ng-"+a);ec[c]=["$parse",function(d){return function(e,g,h){var f=d(h[c]);g.bind(z(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Fd=S(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),Gd=["$http","$templateCache","$anchorScroll","$compile",function(a,c,d,e){return{restrict:"ECA",terminal:!0,compile:function(g,h){var f=h.ngInclude||h.src,i=h.onload||"",j=h.autoscroll;return function(g,h){var n=0,o,p=function(){o&&(o.$destroy(),o=null);h.html("")}; +g.$watch(f,function(f){var m=++n;f?a.get(f,{cache:c}).success(function(a){m===n&&(o&&o.$destroy(),o=g.$new(),h.html(a),e(h.contents())(o),y(j)&&(!j||g.$eval(j))&&d(),o.$emit("$includeContentLoaded"),g.$eval(i))}).error(function(){m===n&&p()}):p()})}}}}],Hd=S({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Id=S({terminal:!0,priority:1E3}),Jd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,h){var f=h.count,i=g.attr(h.$attr.when),j=h.offset|| +0,k=e.$eval(i),l={},n=c.startSymbol(),o=c.endSymbol();m(k,function(a,e){l[e]=c(a.replace(d,n+f+"-"+j+o))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(c in k||(c=a.pluralCat(c-j)),l[c](e,g,!0))},function(a){g.text(a)})}}}],Kd=S({transclude:"element",priority:1E3,terminal:!0,compile:function(a,c,d){return function(a,c,h){var f=h.ngRepeat,h=f.match(/^\s*(.+)\s+in\s+(.*)\s*$/),i,j,k;if(!h)throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '"+f+"'.");f= +h[1];i=h[2];h=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!h)throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '"+f+"'.");j=h[3]||h[1];k=h[2];var l=new eb;a.$watch(function(a){var e,f,h=a.$eval(i),m=c,q=new eb,y,A,u,w,r,v;if(E(h))r=h||[];else{r=[];for(u in h)h.hasOwnProperty(u)&&u.charAt(0)!="$"&&r.push(u);r.sort()}y=r.length-1;e=0;for(f=r.length;ez;)u.pop().element.remove()}for(;r.length> +x;)r.pop()[0].element.remove()}var i;if(!(i=s.match(d)))throw Error("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+s+"'.");var j=c(i[2]||i[1]),k=i[4]||i[6],l=i[5],m=c(i[3]||""),n=c(i[2]?i[1]:k),o=c(i[7]),r=[[{element:f,label:""}]];t&&(a(t)(e),t.removeClass("ng-scope"),t.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=o(e)||[],d={},h,i,j,m,s,t;if(p){i=[];m=0;for(t=r.length;m@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}'); diff --git a/src/test/js/lib/angular-mocks-1.0.7.js b/src/test/js/lib/angular-mocks-1.0.7.js new file mode 100644 index 0000000..41217c7 --- /dev/null +++ b/src/test/js/lib/angular-mocks-1.0.7.js @@ -0,0 +1,1788 @@ +/** + * @license AngularJS v1.0.7 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + * + * TODO(vojta): wrap whole file into closure during build + */ + +/** + * @ngdoc overview + * @name angular.mock + * @description + * + * Namespace from 'angular-mocks.js' which contains testing related code. + */ +angular.mock = {}; + +/** + * ! This is a private undocumented service ! + * + * @name ngMock.$browser + * + * @description + * This service is a mock implementation of {@link ng.$browser}. It provides fake + * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, + * cookies, etc... + * + * The api of this service is the same as that of the real {@link ng.$browser $browser}, except + * that there are several helper methods available which can be used in tests. + */ +angular.mock.$BrowserProvider = function() { + this.$get = function(){ + return new angular.mock.$Browser(); + }; +}; + +angular.mock.$Browser = function() { + var self = this; + + this.isMock = true; + self.$$url = "http://server/"; + self.$$lastUrl = self.$$url; // used by url polling fn + self.pollFns = []; + + // TODO(vojta): remove this temporary api + self.$$completeOutstandingRequest = angular.noop; + self.$$incOutstandingRequestCount = angular.noop; + + + // register url polling fn + + self.onUrlChange = function(listener) { + self.pollFns.push( + function() { + if (self.$$lastUrl != self.$$url) { + self.$$lastUrl = self.$$url; + listener(self.$$url); + } + } + ); + + return listener; + }; + + self.cookieHash = {}; + self.lastCookieHash = {}; + self.deferredFns = []; + self.deferredNextId = 0; + + self.defer = function(fn, delay) { + delay = delay || 0; + self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); + self.deferredFns.sort(function(a,b){ return a.time - b.time;}); + return self.deferredNextId++; + }; + + + self.defer.now = 0; + + + self.defer.cancel = function(deferId) { + var fnIndex; + + angular.forEach(self.deferredFns, function(fn, index) { + if (fn.id === deferId) fnIndex = index; + }); + + if (fnIndex !== undefined) { + self.deferredFns.splice(fnIndex, 1); + return true; + } + + return false; + }; + + + /** + * @name ngMock.$browser#defer.flush + * @methodOf ngMock.$browser + * + * @description + * Flushes all pending requests and executes the defer callbacks. + * + * @param {number=} number of milliseconds to flush. See {@link #defer.now} + */ + self.defer.flush = function(delay) { + if (angular.isDefined(delay)) { + self.defer.now += delay; + } else { + if (self.deferredFns.length) { + self.defer.now = self.deferredFns[self.deferredFns.length-1].time; + } else { + throw Error('No deferred tasks to be flushed'); + } + } + + while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { + self.deferredFns.shift().fn(); + } + }; + /** + * @name ngMock.$browser#defer.now + * @propertyOf ngMock.$browser + * + * @description + * Current milliseconds mock time. + */ + + self.$$baseHref = ''; + self.baseHref = function() { + return this.$$baseHref; + }; +}; +angular.mock.$Browser.prototype = { + +/** + * @name ngMock.$browser#poll + * @methodOf ngMock.$browser + * + * @description + * run all fns in pollFns + */ + poll: function poll() { + angular.forEach(this.pollFns, function(pollFn){ + pollFn(); + }); + }, + + addPollFn: function(pollFn) { + this.pollFns.push(pollFn); + return pollFn; + }, + + url: function(url, replace) { + if (url) { + this.$$url = url; + return this; + } + + return this.$$url; + }, + + cookies: function(name, value) { + if (name) { + if (value == undefined) { + delete this.cookieHash[name]; + } else { + if (angular.isString(value) && //strings only + value.length <= 4096) { //strict cookie storage limits + this.cookieHash[name] = value; + } + } + } else { + if (!angular.equals(this.cookieHash, this.lastCookieHash)) { + this.lastCookieHash = angular.copy(this.cookieHash); + this.cookieHash = angular.copy(this.cookieHash); + } + return this.cookieHash; + } + }, + + notifyWhenNoOutstandingRequests: function(fn) { + fn(); + } +}; + + +/** + * @ngdoc object + * @name ngMock.$exceptionHandlerProvider + * + * @description + * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed + * into the `$exceptionHandler`. + */ + +/** + * @ngdoc object + * @name ngMock.$exceptionHandler + * + * @description + * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed + * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration + * information. + * + * + *
+ *   describe('$exceptionHandlerProvider', function() {
+ *
+ *     it('should capture log messages and exceptions', function() {
+ *
+ *       module(function($exceptionHandlerProvider) {
+ *         $exceptionHandlerProvider.mode('log');
+ *       });
+ *
+ *       inject(function($log, $exceptionHandler, $timeout) {
+ *         $timeout(function() { $log.log(1); });
+ *         $timeout(function() { $log.log(2); throw 'banana peel'; });
+ *         $timeout(function() { $log.log(3); });
+ *         expect($exceptionHandler.errors).toEqual([]);
+ *         expect($log.assertEmpty());
+ *         $timeout.flush();
+ *         expect($exceptionHandler.errors).toEqual(['banana peel']);
+ *         expect($log.log.logs).toEqual([[1], [2], [3]]);
+ *       });
+ *     });
+ *   });
+ * 
+ */ + +angular.mock.$ExceptionHandlerProvider = function() { + var handler; + + /** + * @ngdoc method + * @name ngMock.$exceptionHandlerProvider#mode + * @methodOf ngMock.$exceptionHandlerProvider + * + * @description + * Sets the logging mode. + * + * @param {string} mode Mode of operation, defaults to `rethrow`. + * + * - `rethrow`: If any errors are passed into the handler in tests, it typically + * means that there is a bug in the application or test, so this mock will + * make these tests fail. + * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an + * array of errors in `$exceptionHandler.errors`, to allow later assertion of them. + * See {@link ngMock.$log#assertEmpty assertEmpty()} and + * {@link ngMock.$log#reset reset()} + */ + this.mode = function(mode) { + switch(mode) { + case 'rethrow': + handler = function(e) { + throw e; + }; + break; + case 'log': + var errors = []; + + handler = function(e) { + if (arguments.length == 1) { + errors.push(e); + } else { + errors.push([].slice.call(arguments, 0)); + } + }; + + handler.errors = errors; + break; + default: + throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); + } + }; + + this.$get = function() { + return handler; + }; + + this.mode('rethrow'); +}; + + +/** + * @ngdoc service + * @name ngMock.$log + * + * @description + * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays + * (one array per logging level). These arrays are exposed as `logs` property of each of the + * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. + * + */ +angular.mock.$LogProvider = function() { + + function concat(array1, array2, index) { + return array1.concat(Array.prototype.slice.call(array2, index)); + } + + + this.$get = function () { + var $log = { + log: function() { $log.log.logs.push(concat([], arguments, 0)); }, + warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, + info: function() { $log.info.logs.push(concat([], arguments, 0)); }, + error: function() { $log.error.logs.push(concat([], arguments, 0)); } + }; + + /** + * @ngdoc method + * @name ngMock.$log#reset + * @methodOf ngMock.$log + * + * @description + * Reset all of the logging arrays to empty. + */ + $log.reset = function () { + /** + * @ngdoc property + * @name ngMock.$log#log.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#log}. + * + * @example + *
+       * $log.log('Some Log');
+       * var first = $log.log.logs.unshift();
+       * 
+ */ + $log.log.logs = []; + /** + * @ngdoc property + * @name ngMock.$log#warn.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#warn}. + * + * @example + *
+       * $log.warn('Some Warning');
+       * var first = $log.warn.logs.unshift();
+       * 
+ */ + $log.warn.logs = []; + /** + * @ngdoc property + * @name ngMock.$log#info.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#info}. + * + * @example + *
+       * $log.info('Some Info');
+       * var first = $log.info.logs.unshift();
+       * 
+ */ + $log.info.logs = []; + /** + * @ngdoc property + * @name ngMock.$log#error.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#error}. + * + * @example + *
+       * $log.log('Some Error');
+       * var first = $log.error.logs.unshift();
+       * 
+ */ + $log.error.logs = []; + }; + + /** + * @ngdoc method + * @name ngMock.$log#assertEmpty + * @methodOf ngMock.$log + * + * @description + * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown. + */ + $log.assertEmpty = function() { + var errors = []; + angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) { + angular.forEach($log[logLevel].logs, function(log) { + angular.forEach(log, function (logItem) { + errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); + }); + }); + }); + if (errors.length) { + errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + + "log message was not checked and removed:"); + errors.push(''); + throw new Error(errors.join('\n---------\n')); + } + }; + + $log.reset(); + return $log; + }; +}; + + +(function() { + var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; + + function jsonStringToDate(string){ + var match; + if (match = string.match(R_ISO8061_STR)) { + var date = new Date(0), + tzHour = 0, + tzMin = 0; + if (match[9]) { + tzHour = int(match[9] + match[10]); + tzMin = int(match[9] + match[11]); + } + date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); + date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); + return date; + } + return string; + } + + function int(str) { + return parseInt(str, 10); + } + + function padNumber(num, digits, trim) { + var neg = ''; + if (num < 0) { + neg = '-'; + num = -num; + } + num = '' + num; + while(num.length < digits) num = '0' + num; + if (trim) + num = num.substr(num.length - digits); + return neg + num; + } + + + /** + * @ngdoc object + * @name angular.mock.TzDate + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. + * + * Mock of the Date type which has its timezone specified via constructor arg. + * + * The main purpose is to create Date-like instances with timezone fixed to the specified timezone + * offset, so that we can test code that depends on local timezone settings without dependency on + * the time zone settings of the machine where the code is running. + * + * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) + * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* + * + * @example + * !!!! WARNING !!!!! + * This is not a complete Date object so only methods that were implemented can be called safely. + * To make matters worse, TzDate instances inherit stuff from Date via a prototype. + * + * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is + * incomplete we might be missing some non-standard methods. This can result in errors like: + * "Date.prototype.foo called on incompatible Object". + * + *
+   * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
+   * newYearInBratislava.getTimezoneOffset() => -60;
+   * newYearInBratislava.getFullYear() => 2010;
+   * newYearInBratislava.getMonth() => 0;
+   * newYearInBratislava.getDate() => 1;
+   * newYearInBratislava.getHours() => 0;
+   * newYearInBratislava.getMinutes() => 0;
+   * 
+ * + */ + angular.mock.TzDate = function (offset, timestamp) { + var self = new Date(0); + if (angular.isString(timestamp)) { + var tsStr = timestamp; + + self.origDate = jsonStringToDate(timestamp); + + timestamp = self.origDate.getTime(); + if (isNaN(timestamp)) + throw { + name: "Illegal Argument", + message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" + }; + } else { + self.origDate = new Date(timestamp); + } + + var localOffset = new Date(timestamp).getTimezoneOffset(); + self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; + self.date = new Date(timestamp + self.offsetDiff); + + self.getTime = function() { + return self.date.getTime() - self.offsetDiff; + }; + + self.toLocaleDateString = function() { + return self.date.toLocaleDateString(); + }; + + self.getFullYear = function() { + return self.date.getFullYear(); + }; + + self.getMonth = function() { + return self.date.getMonth(); + }; + + self.getDate = function() { + return self.date.getDate(); + }; + + self.getHours = function() { + return self.date.getHours(); + }; + + self.getMinutes = function() { + return self.date.getMinutes(); + }; + + self.getSeconds = function() { + return self.date.getSeconds(); + }; + + self.getTimezoneOffset = function() { + return offset * 60; + }; + + self.getUTCFullYear = function() { + return self.origDate.getUTCFullYear(); + }; + + self.getUTCMonth = function() { + return self.origDate.getUTCMonth(); + }; + + self.getUTCDate = function() { + return self.origDate.getUTCDate(); + }; + + self.getUTCHours = function() { + return self.origDate.getUTCHours(); + }; + + self.getUTCMinutes = function() { + return self.origDate.getUTCMinutes(); + }; + + self.getUTCSeconds = function() { + return self.origDate.getUTCSeconds(); + }; + + self.getUTCMilliseconds = function() { + return self.origDate.getUTCMilliseconds(); + }; + + self.getDay = function() { + return self.date.getDay(); + }; + + // provide this method only on browsers that already have it + if (self.toISOString) { + self.toISOString = function() { + return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + + padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + + padNumber(self.origDate.getUTCDate(), 2) + 'T' + + padNumber(self.origDate.getUTCHours(), 2) + ':' + + padNumber(self.origDate.getUTCMinutes(), 2) + ':' + + padNumber(self.origDate.getUTCSeconds(), 2) + '.' + + padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' + } + } + + //hide all methods not implemented in this mock that the Date prototype exposes + var unimplementedMethods = ['getMilliseconds', 'getUTCDay', + 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', + 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', + 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', + 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', + 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; + + angular.forEach(unimplementedMethods, function(methodName) { + self[methodName] = function() { + throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); + }; + }); + + return self; + }; + + //make "tzDateInstance instanceof Date" return true + angular.mock.TzDate.prototype = Date.prototype; +})(); + + +/** + * @ngdoc function + * @name angular.mock.dump + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available function. + * + * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging. + * + * This method is also available on window, where it can be used to display objects on debug console. + * + * @param {*} object - any object to turn into string. + * @return {string} a serialized string of the argument + */ +angular.mock.dump = function(object) { + return serialize(object); + + function serialize(object) { + var out; + + if (angular.isElement(object)) { + object = angular.element(object); + out = angular.element('
'); + angular.forEach(object, function(element) { + out.append(angular.element(element).clone()); + }); + out = out.html(); + } else if (angular.isArray(object)) { + out = []; + angular.forEach(object, function(o) { + out.push(serialize(o)); + }); + out = '[ ' + out.join(', ') + ' ]'; + } else if (angular.isObject(object)) { + if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { + out = serializeScope(object); + } else if (object instanceof Error) { + out = object.stack || ('' + object.name + ': ' + object.message); + } else { + out = angular.toJson(object, true); + } + } else { + out = String(object); + } + + return out; + } + + function serializeScope(scope, offset) { + offset = offset || ' '; + var log = [offset + 'Scope(' + scope.$id + '): {']; + for ( var key in scope ) { + if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { + log.push(' ' + key + ': ' + angular.toJson(scope[key])); + } + } + var child = scope.$$childHead; + while(child) { + log.push(serializeScope(child, offset + ' ')); + child = child.$$nextSibling; + } + log.push('}'); + return log.join('\n' + offset); + } +}; + +/** + * @ngdoc object + * @name ngMock.$httpBackend + * @description + * Fake HTTP backend implementation suitable for unit testing applications that use the + * {@link ng.$http $http service}. + * + * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less + * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. + * + * During unit testing, we want our unit tests to run quickly and have no external dependencies so + * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or + * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is + * to verify whether a certain request has been sent or not, or alternatively just let the + * application make requests, respond with pre-trained responses and assert that the end result is + * what we expect it to be. + * + * This mock implementation can be used to respond with static or dynamic responses via the + * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). + * + * When an Angular application needs some data from a server, it calls the $http service, which + * sends the request to a real server using $httpBackend service. With dependency injection, it is + * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify + * the requests and respond with some testing data without sending a request to real server. + * + * There are two ways to specify what test data should be returned as http responses by the mock + * backend when the code under test makes http requests: + * + * - `$httpBackend.expect` - specifies a request expectation + * - `$httpBackend.when` - specifies a backend definition + * + * + * # Request Expectations vs Backend Definitions + * + * Request expectations provide a way to make assertions about requests made by the application and + * to define responses for those requests. The test will fail if the expected requests are not made + * or they are made in the wrong order. + * + * Backend definitions allow you to define a fake backend for your application which doesn't assert + * if a particular request was made or not, it just returns a trained response if a request is made. + * The test will pass whether or not the request gets made during testing. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Request expectationsBackend definitions
Syntax.expect(...).respond(...).when(...).respond(...)
Typical usagestrict unit testsloose (black-box) unit testing
Fulfills multiple requestsNOYES
Order of requests mattersYESNO
Request requiredYESNO
Response requiredoptional (see below)YES
+ * + * In cases where both backend definitions and request expectations are specified during unit + * testing, the request expectations are evaluated first. + * + * If a request expectation has no response specified, the algorithm will search your backend + * definitions for an appropriate response. + * + * If a request didn't match any expectation or if the expectation doesn't have the response + * defined, the backend definitions are evaluated in sequential order to see if any of them match + * the request. The response from the first matched definition is returned. + * + * + * # Flushing HTTP requests + * + * The $httpBackend used in production, always responds to requests with responses asynchronously. + * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are + * hard to write, follow and maintain. At the same time the testing mock, can't respond + * synchronously because that would change the execution of the code under test. For this reason the + * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending + * requests and thus preserving the async api of the backend, while allowing the test to execute + * synchronously. + * + * + * # Unit testing with mock $httpBackend + * + *
+   // controller
+   function MyController($scope, $http) {
+     $http.get('/auth.py').success(function(data) {
+       $scope.user = data;
+     });
+
+     this.saveMessage = function(message) {
+       $scope.status = 'Saving...';
+       $http.post('/add-msg.py', message).success(function(response) {
+         $scope.status = '';
+       }).error(function() {
+         $scope.status = 'ERROR!';
+       });
+     };
+   }
+
+   // testing controller
+   var $httpBackend;
+
+   beforeEach(inject(function($injector) {
+     $httpBackend = $injector.get('$httpBackend');
+
+     // backend definition common for all tests
+     $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
+   }));
+
+
+   afterEach(function() {
+     $httpBackend.verifyNoOutstandingExpectation();
+     $httpBackend.verifyNoOutstandingRequest();
+   });
+
+
+   it('should fetch authentication token', function() {
+     $httpBackend.expectGET('/auth.py');
+     var controller = scope.$new(MyController);
+     $httpBackend.flush();
+   });
+
+
+   it('should send msg to server', function() {
+     // now you don’t care about the authentication, but
+     // the controller will still send the request and
+     // $httpBackend will respond without you having to
+     // specify the expectation and response for this request
+     $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
+
+     var controller = scope.$new(MyController);
+     $httpBackend.flush();
+     controller.saveMessage('message content');
+     expect(controller.status).toBe('Saving...');
+     $httpBackend.flush();
+     expect(controller.status).toBe('');
+   });
+
+
+   it('should send auth header', function() {
+     $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
+       // check if the header was send, if it wasn't the expectation won't
+       // match the request and the test will fail
+       return headers['Authorization'] == 'xxx';
+     }).respond(201, '');
+
+     var controller = scope.$new(MyController);
+     controller.saveMessage('whatever');
+     $httpBackend.flush();
+   });
+   
+ */ +angular.mock.$HttpBackendProvider = function() { + this.$get = [createHttpBackendMock]; +}; + +/** + * General factory function for $httpBackend mock. + * Returns instance for unit testing (when no arguments specified): + * - passing through is disabled + * - auto flushing is disabled + * + * Returns instance for e2e testing (when `$delegate` and `$browser` specified): + * - passing through (delegating request to real backend) is enabled + * - auto flushing is enabled + * + * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) + * @param {Object=} $browser Auto-flushing enabled if specified + * @return {Object} Instance of $httpBackend mock + */ +function createHttpBackendMock($delegate, $browser) { + var definitions = [], + expectations = [], + responses = [], + responsesPush = angular.bind(responses, responses.push); + + function createResponse(status, data, headers) { + if (angular.isFunction(status)) return status; + + return function() { + return angular.isNumber(status) + ? [status, data, headers] + : [200, status, data]; + }; + } + + // TODO(vojta): change params to: method, url, data, headers, callback + function $httpBackend(method, url, data, callback, headers) { + var xhr = new MockXhr(), + expectation = expectations[0], + wasExpected = false; + + function prettyPrint(data) { + return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) + ? data + : angular.toJson(data); + } + + if (expectation && expectation.match(method, url)) { + if (!expectation.matchData(data)) + throw Error('Expected ' + expectation + ' with different data\n' + + 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); + + if (!expectation.matchHeaders(headers)) + throw Error('Expected ' + expectation + ' with different headers\n' + + 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + + prettyPrint(headers)); + + expectations.shift(); + + if (expectation.response) { + responses.push(function() { + var response = expectation.response(method, url, data, headers); + xhr.$$respHeaders = response[2]; + callback(response[0], response[1], xhr.getAllResponseHeaders()); + }); + return; + } + wasExpected = true; + } + + var i = -1, definition; + while ((definition = definitions[++i])) { + if (definition.match(method, url, data, headers || {})) { + if (definition.response) { + // if $browser specified, we do auto flush all requests + ($browser ? $browser.defer : responsesPush)(function() { + var response = definition.response(method, url, data, headers); + xhr.$$respHeaders = response[2]; + callback(response[0], response[1], xhr.getAllResponseHeaders()); + }); + } else if (definition.passThrough) { + $delegate(method, url, data, callback, headers); + } else throw Error('No response defined !'); + return; + } + } + throw wasExpected ? + Error('No response defined !') : + Error('Unexpected request: ' + method + ' ' + url + '\n' + + (expectation ? 'Expected ' + expectation : 'No more request expected')); + } + + /** + * @ngdoc method + * @name ngMock.$httpBackend#when + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + */ + $httpBackend.when = function(method, url, data, headers) { + var definition = new MockHttpExpectation(method, url, data, headers), + chain = { + respond: function(status, data, headers) { + definition.response = createResponse(status, data, headers); + } + }; + + if ($browser) { + chain.passThrough = function() { + definition.passThrough = true; + }; + } + + definitions.push(definition); + return chain; + }; + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenGET + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenHEAD + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenDELETE + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenPOST + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenPUT + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenJSONP + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + createShortMethods('when'); + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expect + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current expectation. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + */ + $httpBackend.expect = function(method, url, data, headers) { + var expectation = new MockHttpExpectation(method, url, data, headers); + expectations.push(expectation); + return { + respond: function(status, data, headers) { + expectation.response = createResponse(status, data, headers); + } + }; + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectGET + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for GET requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. See #expect for more info. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectHEAD + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for HEAD requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectDELETE + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for DELETE requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectPOST + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for POST requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectPUT + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for PUT requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectPATCH + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for PATCH requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectJSONP + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for JSONP requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + createShortMethods('expect'); + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#flush + * @methodOf ngMock.$httpBackend + * @description + * Flushes all pending requests using the trained responses. + * + * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, + * all pending requests will be flushed. If there are no pending requests when the flush method + * is called an exception is thrown (as this typically a sign of programming error). + */ + $httpBackend.flush = function(count) { + if (!responses.length) throw Error('No pending request to flush !'); + + if (angular.isDefined(count)) { + while (count--) { + if (!responses.length) throw Error('No more pending request to flush !'); + responses.shift()(); + } + } else { + while (responses.length) { + responses.shift()(); + } + } + $httpBackend.verifyNoOutstandingExpectation(); + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#verifyNoOutstandingExpectation + * @methodOf ngMock.$httpBackend + * @description + * Verifies that all of the requests defined via the `expect` api were made. If any of the + * requests were not made, verifyNoOutstandingExpectation throws an exception. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + *
+   *   afterEach($httpBackend.verifyExpectations);
+   * 
+ */ + $httpBackend.verifyNoOutstandingExpectation = function() { + if (expectations.length) { + throw Error('Unsatisfied requests: ' + expectations.join(', ')); + } + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#verifyNoOutstandingRequest + * @methodOf ngMock.$httpBackend + * @description + * Verifies that there are no outstanding requests that need to be flushed. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + *
+   *   afterEach($httpBackend.verifyNoOutstandingRequest);
+   * 
+ */ + $httpBackend.verifyNoOutstandingRequest = function() { + if (responses.length) { + throw Error('Unflushed requests: ' + responses.length); + } + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#resetExpectations + * @methodOf ngMock.$httpBackend + * @description + * Resets all request expectations, but preserves all backend definitions. Typically, you would + * call resetExpectations during a multiple-phase test when you want to reuse the same instance of + * $httpBackend mock. + */ + $httpBackend.resetExpectations = function() { + expectations.length = 0; + responses.length = 0; + }; + + return $httpBackend; + + + function createShortMethods(prefix) { + angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { + $httpBackend[prefix + method] = function(url, headers) { + return $httpBackend[prefix](method, url, undefined, headers) + } + }); + + angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { + $httpBackend[prefix + method] = function(url, data, headers) { + return $httpBackend[prefix](method, url, data, headers) + } + }); + } +} + +function MockHttpExpectation(method, url, data, headers) { + + this.data = data; + this.headers = headers; + + this.match = function(m, u, d, h) { + if (method != m) return false; + if (!this.matchUrl(u)) return false; + if (angular.isDefined(d) && !this.matchData(d)) return false; + if (angular.isDefined(h) && !this.matchHeaders(h)) return false; + return true; + }; + + this.matchUrl = function(u) { + if (!url) return true; + if (angular.isFunction(url.test)) return url.test(u); + return url == u; + }; + + this.matchHeaders = function(h) { + if (angular.isUndefined(headers)) return true; + if (angular.isFunction(headers)) return headers(h); + return angular.equals(headers, h); + }; + + this.matchData = function(d) { + if (angular.isUndefined(data)) return true; + if (data && angular.isFunction(data.test)) return data.test(d); + if (data && !angular.isString(data)) return angular.toJson(data) == d; + return data == d; + }; + + this.toString = function() { + return method + ' ' + url; + }; +} + +function MockXhr() { + + // hack for testing $http, $httpBackend + MockXhr.$$lastInstance = this; + + this.open = function(method, url, async) { + this.$$method = method; + this.$$url = url; + this.$$async = async; + this.$$reqHeaders = {}; + this.$$respHeaders = {}; + }; + + this.send = function(data) { + this.$$data = data; + }; + + this.setRequestHeader = function(key, value) { + this.$$reqHeaders[key] = value; + }; + + this.getResponseHeader = function(name) { + // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last + var header = this.$$respHeaders[name]; + if (header) return header; + + name = angular.lowercase(name); + header = this.$$respHeaders[name]; + if (header) return header; + + header = undefined; + angular.forEach(this.$$respHeaders, function(headerVal, headerName) { + if (!header && angular.lowercase(headerName) == name) header = headerVal; + }); + return header; + }; + + this.getAllResponseHeaders = function() { + var lines = []; + + angular.forEach(this.$$respHeaders, function(value, key) { + lines.push(key + ': ' + value); + }); + return lines.join('\n'); + }; + + this.abort = angular.noop; +} + + +/** + * @ngdoc function + * @name ngMock.$timeout + * @description + * + * This service is just a simple decorator for {@link ng.$timeout $timeout} service + * that adds a "flush" method. + */ + +/** + * @ngdoc method + * @name ngMock.$timeout#flush + * @methodOf ngMock.$timeout + * @description + * + * Flushes the queue of pending tasks. + */ + +/** + * + */ +angular.mock.$RootElementProvider = function() { + this.$get = function() { + return angular.element('
'); + } +}; + +/** + * @ngdoc overview + * @name ngMock + * @description + * + * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful + * mocks to the {@link AUTO.$injector $injector}. + */ +angular.module('ngMock', ['ng']).provider({ + $browser: angular.mock.$BrowserProvider, + $exceptionHandler: angular.mock.$ExceptionHandlerProvider, + $log: angular.mock.$LogProvider, + $httpBackend: angular.mock.$HttpBackendProvider, + $rootElement: angular.mock.$RootElementProvider +}).config(function($provide) { + $provide.decorator('$timeout', function($delegate, $browser) { + $delegate.flush = function() { + $browser.defer.flush(); + }; + return $delegate; + }); +}); + + +/** + * @ngdoc overview + * @name ngMockE2E + * @description + * + * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. + * Currently there is only one mock present in this module - + * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. + */ +angular.module('ngMockE2E', ['ng']).config(function($provide) { + $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); +}); + +/** + * @ngdoc object + * @name ngMockE2E.$httpBackend + * @description + * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of + * applications that use the {@link ng.$http $http service}. + * + * *Note*: For fake http backend implementation suitable for unit testing please see + * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. + * + * This implementation can be used to respond with static or dynamic responses via the `when` api + * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the + * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch + * templates from a webserver). + * + * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application + * is being developed with the real backend api replaced with a mock, it is often desirable for + * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch + * templates or static files from the webserver). To configure the backend with this behavior + * use the `passThrough` request handler of `when` instead of `respond`. + * + * Additionally, we don't want to manually have to flush mocked out requests like we do during unit + * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests + * automatically, closely simulating the behavior of the XMLHttpRequest object. + * + * To setup the application to run with this http backend, you have to create a module that depends + * on the `ngMockE2E` and your application modules and defines the fake backend: + * + *
+ *   myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
+ *   myAppDev.run(function($httpBackend) {
+ *     phones = [{name: 'phone1'}, {name: 'phone2'}];
+ *
+ *     // returns the current list of phones
+ *     $httpBackend.whenGET('/phones').respond(phones);
+ *
+ *     // adds a new phone to the phones array
+ *     $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
+ *       phones.push(angular.fromJSON(data));
+ *     });
+ *     $httpBackend.whenGET(/^\/templates\//).passThrough();
+ *     //...
+ *   });
+ * 
+ * + * Afterwards, bootstrap your app with this new module. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#when + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` + * handler, will be pass through to the real backend (an XHR request will be made to the + * server. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenGET + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenHEAD + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenDELETE + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenPOST + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenPUT + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenPATCH + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for PATCH requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenJSONP + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ +angular.mock.e2e = {}; +angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$browser', createHttpBackendMock]; + + +angular.mock.clearDataCache = function() { + var key, + cache = angular.element.cache; + + for(key in cache) { + if (cache.hasOwnProperty(key)) { + var handle = cache[key].handle; + + handle && angular.element(handle.elem).unbind(); + delete cache[key]; + } + } +}; + + +window.jstestdriver && (function(window) { + /** + * Global method to output any number of objects into JSTD console. Useful for debugging. + */ + window.dump = function() { + var args = []; + angular.forEach(arguments, function(arg) { + args.push(angular.mock.dump(arg)); + }); + jstestdriver.console.log.apply(jstestdriver.console, args); + if (window.console) { + window.console.log.apply(window.console, args); + } + }; +})(window); + + +window.jasmine && (function(window) { + + afterEach(function() { + var spec = getCurrentSpec(); + var injector = spec.$injector; + + spec.$injector = null; + spec.$modules = null; + + if (injector) { + injector.get('$rootElement').unbind(); + injector.get('$browser').pollFns.length = 0; + } + + angular.mock.clearDataCache(); + + // clean up jquery's fragment cache + angular.forEach(angular.element.fragments, function(val, key) { + delete angular.element.fragments[key]; + }); + + MockXhr.$$lastInstance = null; + + angular.forEach(angular.callbacks, function(val, key) { + delete angular.callbacks[key]; + }); + angular.callbacks.counter = 0; + }); + + function getCurrentSpec() { + return jasmine.getEnv().currentSpec; + } + + function isSpecRunning() { + var spec = getCurrentSpec(); + return spec && spec.queue.running; + } + + /** + * @ngdoc function + * @name angular.mock.module + * @description + * + * *NOTE*: This function is also published on window for easy access.
+ * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. + * + * This function registers a module configuration code. It collects the configuration information + * which will be used when the injector is created by {@link angular.mock.inject inject}. + * + * See {@link angular.mock.inject inject} for usage example + * + * @param {...(string|Function)} fns any number of modules which are represented as string + * aliases or as anonymous module initialization functions. The modules are used to + * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. + */ + window.module = angular.mock.module = function() { + var moduleFns = Array.prototype.slice.call(arguments, 0); + return isSpecRunning() ? workFn() : workFn; + ///////////////////// + function workFn() { + var spec = getCurrentSpec(); + if (spec.$injector) { + throw Error('Injector already created, can not register a module!'); + } else { + var modules = spec.$modules || (spec.$modules = []); + angular.forEach(moduleFns, function(module) { + modules.push(module); + }); + } + } + }; + + /** + * @ngdoc function + * @name angular.mock.inject + * @description + * + * *NOTE*: This function is also published on window for easy access.
+ * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. + * + * The inject function wraps a function into an injectable function. The inject() creates new + * instance of {@link AUTO.$injector $injector} per test, which is then used for + * resolving references. + * + * See also {@link angular.mock.module module} + * + * Example of what a typical jasmine tests looks like with the inject method. + *
+   *
+   *   angular.module('myApplicationModule', [])
+   *       .value('mode', 'app')
+   *       .value('version', 'v1.0.1');
+   *
+   *
+   *   describe('MyApp', function() {
+   *
+   *     // You need to load modules that you want to test,
+   *     // it loads only the "ng" module by default.
+   *     beforeEach(module('myApplicationModule'));
+   *
+   *
+   *     // inject() is used to inject arguments of all given functions
+   *     it('should provide a version', inject(function(mode, version) {
+   *       expect(version).toEqual('v1.0.1');
+   *       expect(mode).toEqual('app');
+   *     }));
+   *
+   *
+   *     // The inject and module method can also be used inside of the it or beforeEach
+   *     it('should override a version and test the new version is injected', function() {
+   *       // module() takes functions or strings (module aliases)
+   *       module(function($provide) {
+   *         $provide.value('version', 'overridden'); // override version here
+   *       });
+   *
+   *       inject(function(version) {
+   *         expect(version).toEqual('overridden');
+   *       });
+   *     ));
+   *   });
+   *
+   * 
+ * + * @param {...Function} fns any number of functions which will be injected using the injector. + */ + window.inject = angular.mock.inject = function() { + var blockFns = Array.prototype.slice.call(arguments, 0); + var errorForStack = new Error('Declaration Location'); + return isSpecRunning() ? workFn() : workFn; + ///////////////////// + function workFn() { + var spec = getCurrentSpec(); + var modules = spec.$modules || []; + modules.unshift('ngMock'); + modules.unshift('ng'); + var injector = spec.$injector; + if (!injector) { + injector = spec.$injector = angular.injector(modules); + } + for(var i = 0, ii = blockFns.length; i < ii; i++) { + try { + injector.invoke(blockFns[i] || angular.noop, this); + } catch (e) { + if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; + throw e; + } finally { + errorForStack = null; + } + } + } + }; +})(window); diff --git a/src/test/js/lib/angular-mocks.js b/src/test/js/lib/angular-mocks.js deleted file mode 100644 index 0b5c35b..0000000 --- a/src/test/js/lib/angular-mocks.js +++ /dev/null @@ -1,418 +0,0 @@ -/** - * The MIT License - * - * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - - -/* - - NUGGGGGH MUST TONGUE WANGS - \ - ..... - C C / - /< / - ___ __________/_#__=o - /(- /(\_\________ \ - \ ) \ )_ \o \ - /|\ /|\ |' | - | _| - /o __\ - / ' | - / / | - /_/\______| - ( _( < - \ \ \ - \ \ | - \____\____\ - ____\_\__\_\ - /` /` o\ - |___ |_______|.. . b'ger - - - IN THE FINAL BUILD THIS FILE DOESN'T HAVE DIRECT ACCESS TO GLOBAL FUNCTIONS - DEFINED IN Angular.js YOU *MUST* REFER TO THEM VIA angular OBJECT - (e.g. angular.forEach(...)) AND MAKE SURE THAT THE GIVEN FUNCTION IS EXPORTED - TO THE angular NAMESPACE in AngularPublic.js - - */ - - -/** - * @workInProgress - * @ngdoc overview - * @name angular.mock - * @description - * - * The `angular.mock` object is a namespace for all built-in mock services that ship with angular. - * It automatically replaces real services if the `angular-mocks.js` file is loaded after - * `angular.js` and before any tests. - * - * Built-in mocks: - * - * * {@link angular.mock.service.$browser $browser } - A mock implementation of the browser. - * * {@link angular.mock.service.$exceptionHandler $exceptionHandler } - A mock implementation of the - * angular service exception handler. - * * {@link angular.mock.service.$log $log } - A mock implementation of the angular service log. - */ -angular.mock = {}; - - -/** - * @workInProgress - * @ngdoc service - * @name angular.mock.service.$browser - */ -function MockBrowser() { - var self = this, - expectations = {}, - requests = []; - - this.isMock = true; - self.url = "http://server"; - self.lastUrl = self.url; // used by url polling fn - self.pollFns = []; - - - // register url polling fn - - self.onHashChange = function(listener) { - self.pollFns.push( - function() { - if (self.lastUrl != self.url) { - self.lastUrl = self.url; - listener(); - } - } - ); - - return listener; - }; - - - self.xhr = function(method, url, data, callback, headers) { - headers = headers || {}; - if (data && angular.isObject(data)) data = angular.toJson(data); - if (data && angular.isString(data)) url += "|" + data; - var expect = expectations[method] || {}; - var expectation = expect[url]; - if (!expectation) { - throw new Error("Unexpected request for method '" + method + "' and url '" + url + "'."); - } - requests.push(function(){ - angular.forEach(expectation.headers, function(value, key){ - if (headers[key] !== value) { - throw new Error("Missing HTTP request header: " + key + ": " + value); - } - }); - callback(expectation.code, expectation.response); - }); - }; - self.xhr.expectations = expectations; - self.xhr.requests = requests; - self.xhr.expect = function(method, url, data, headers) { - if (data && angular.isObject(data)) data = angular.toJson(data); - if (data && angular.isString(data)) url += "|" + data; - var expect = expectations[method] || (expectations[method] = {}); - return { - respond: function(code, response) { - if (!angular.isNumber(code)) { - response = code; - code = 200; - } - expect[url] = {code:code, response:response, headers: headers || {}}; - } - }; - }; - self.xhr.expectGET = angular.bind(self, self.xhr.expect, 'GET'); - self.xhr.expectPOST = angular.bind(self, self.xhr.expect, 'POST'); - self.xhr.expectDELETE = angular.bind(self, self.xhr.expect, 'DELETE'); - self.xhr.expectPUT = angular.bind(self, self.xhr.expect, 'PUT'); - self.xhr.expectJSON = angular.bind(self, self.xhr.expect, 'JSON'); - self.xhr.flush = function() { - if (requests.length == 0) { - throw new Error("No xhr requests to be flushed!"); - } - - while(requests.length) { - requests.pop()(); - } - }; - - self.cookieHash = {}; - self.lastCookieHash = {}; - self.deferredFns = []; - - self.defer = function(fn, delay) { - delay = delay || 0; - self.deferredFns.push({time:(self.defer.now + delay), fn:fn}); - self.deferredFns.sort(function(a,b){ return a.time - b.time;}); - }; - - self.defer.now = 0; - - self.defer.flush = function(delay) { - if (angular.isDefined(delay)) { - self.defer.now += delay; - } else { - if (self.deferredFns.length) { - self.defer.now = self.deferredFns[self.deferredFns.length-1].time; - } - } - - while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { - self.deferredFns.shift().fn(); - } - }; -} -MockBrowser.prototype = { - - poll: function poll(){ - angular.forEach(this.pollFns, function(pollFn){ - pollFn(); - }); - }, - - addPollFn: function(pollFn) { - this.pollFns.push(pollFn); - return pollFn; - }, - - hover: function(onHover) { - }, - - getUrl: function(){ - return this.url; - }, - - setUrl: function(url){ - this.url = url; - }, - - cookies: function(name, value) { - if (name) { - if (value == undefined) { - delete this.cookieHash[name]; - } else { - if (angular.isString(value) && //strings only - value.length <= 4096) { //strict cookie storage limits - this.cookieHash[name] = value; - } - } - } else { - if (!angular.equals(this.cookieHash, this.lastCookieHash)) { - this.lastCookieHash = angular.copy(this.cookieHash); - this.cookieHash = angular.copy(this.cookieHash); - } - return this.cookieHash; - } - }, - - addJs: function(){} -}; - -angular.service('$browser', function(){ - return new MockBrowser(); -}); - - -/** - * @workInProgress - * @ngdoc service - * @name angular.mock.service.$exceptionHandler - * - * @description - * Mock implementation of {@link angular.service.$exceptionHandler} that rethrows any error passed - * into `$exceptionHandler`. If any errors are are passed into the handler in tests, it typically - * means that there is a bug in the application or test, so this mock will make these tests fail. - * - * See {@link angular.mock} for more info on angular mocks. - */ -angular.service('$exceptionHandler', function() { - return function(e) { throw e;}; -}); - - -/** - * @workInProgress - * @ngdoc service - * @name angular.mock.service.$log - * - * @description - * Mock implementation of {@link angular.service.$log} that gathers all logged messages in arrays - * (one array per logging level). These arrays are exposed as `logs` property of each of the - * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. - * - * See {@link angular.mock} for more info on angular mocks. - */ -angular.service('$log', MockLogFactory); - -function MockLogFactory() { - var $log = { - log: function(){ $log.log.logs.push(arguments); }, - warn: function(){ $log.warn.logs.push(arguments); }, - info: function(){ $log.info.logs.push(arguments); }, - error: function(){ $log.error.logs.push(arguments); } - }; - - $log.log.logs = []; - $log.warn.logs = []; - $log.info.logs = []; - $log.error.logs = []; - - return $log; -} - - -/** - * Mock of the Date type which has its timezone specified via constroctor arg. - * - * The main purpose is to create Date-like instances with timezone fixed to the specified timezone - * offset, so that we can test code that depends on local timezone settings without dependency on - * the time zone settings of the machine where the code is running. - * - * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) - * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* - * - * @example - * !!!! WARNING !!!!! - * This is not a complete Date object so only methods that were implemented can be called safely. - * To make matters worse, TzDate instances inherit stuff from Date via a prototype. - * - * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is - * incomplete we might be missing some non-standard methods. This can result in errors like: - * "Date.prototype.foo called on incompatible Object". - * - *
- * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
- * newYearInBratislava.getTimezoneOffset() => -60;
- * newYearInBratislava.getFullYear() => 2010;
- * newYearInBratislava.getMonth() => 0;
- * newYearInBratislava.getDate() => 1;
- * newYearInBratislava.getHours() => 0;
- * newYearInBratislava.getMinutes() => 0;
- * 
- * - */ -function TzDate(offset, timestamp) { - if (angular.isString(timestamp)) { - var tsStr = timestamp; - - this.origDate = angular.String.toDate(timestamp); - - timestamp = this.origDate.getTime(); - if (isNaN(timestamp)) - throw { - name: "Illegal Argument", - message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" - }; - } else { - this.origDate = new Date(timestamp); - } - - var localOffset = new Date(timestamp).getTimezoneOffset(); - this.offsetDiff = localOffset*60*1000 - offset*1000*60*60; - this.date = new Date(timestamp + this.offsetDiff); - - this.getTime = function() { - return this.date.getTime() - this.offsetDiff; - }; - - this.toLocaleDateString = function() { - return this.date.toLocaleDateString(); - }; - - this.getFullYear = function() { - return this.date.getFullYear(); - }; - - this.getMonth = function() { - return this.date.getMonth(); - }; - - this.getDate = function() { - return this.date.getDate(); - }; - - this.getHours = function() { - return this.date.getHours(); - }; - - this.getMinutes = function() { - return this.date.getMinutes(); - }; - - this.getSeconds = function() { - return this.date.getSeconds(); - }; - - this.getTimezoneOffset = function() { - return offset * 60; - }; - - this.getUTCFullYear = function() { - return this.origDate.getUTCFullYear(); - }; - - this.getUTCMonth = function() { - return this.origDate.getUTCMonth(); - }; - - this.getUTCDate = function() { - return this.origDate.getUTCDate(); - }; - - this.getUTCHours = function() { - return this.origDate.getUTCHours(); - }; - - this.getUTCMinutes = function() { - return this.origDate.getUTCMinutes(); - }; - - this.getUTCSeconds = function() { - return this.origDate.getUTCSeconds(); - }; - - this.getDay = function() { - return this.origDate.getDay(); - }; - - //hide all methods not implemented in this mock that the Date prototype exposes - var unimplementedMethods = ['getMilliseconds', 'getTime', 'getUTCDay', - 'getUTCMilliseconds', 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', - 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', - 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', - 'setYear', 'toDateString', 'toJSON', 'toGMTString', 'toLocaleFormat', 'toLocaleString', - 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; - - angular.forEach(unimplementedMethods, function(methodName) { - this[methodName] = function() { - throw { - name: "MethodNotImplemented", - message: "Method '" + methodName + "' is not implemented in the TzDate mock" - }; - }; - }); -} - -//make "tzDateInstance instanceof Date" return true -TzDate.prototype = Date.prototype; From 5a7efb2164cdfbdbe1da98379e18d844a47df010 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Wed, 26 Jun 2013 22:21:18 +0100 Subject: [PATCH 2/8] Fix location of angular sources and add jQuery. --- pom.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 2c7dbb0..b390b05 100644 --- a/pom.xml +++ b/pom.xml @@ -93,13 +93,14 @@ ${project.basedir}/src/test/js - lib/angular-mocks-1.0.7.js spec/*.js - lib/angular-1.0.7.min.js - lib/angular-mocks-1.0.7.js + http://code.jquery.com/jquery-1.9.1.js + ${project.basedir}/src/main/webapp/js/lib/angular-1.0.7.min.js + ${project.basedir}/src/test/js/lib/angular-mocks-1.0.7.js + From d2fad51c9cedc0958f7d9e5703df14fbbdb4dc72 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 27 Jun 2013 10:53:50 +0100 Subject: [PATCH 3/8] Add gitignore for eclipse artifacts. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e2a3069..942598d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -/target +target *~ +.* From 724fb6fe83101961d704043da63f94e094edba85 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 27 Jun 2013 11:41:06 +0100 Subject: [PATCH 4/8] Frontend piano now displays correctly. --- src/main/webapp/index.html | 12 ++++---- src/main/webapp/js/controllers.js | 49 +++++++++++++++---------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index d409c5e..4d45a2c 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -1,5 +1,5 @@ - + Server Side Piano @@ -11,31 +11,31 @@
Keys - - - - Note names + Note names
-
{{key.name}}
-
diff --git a/src/main/webapp/js/controllers.js b/src/main/webapp/js/controllers.js index 6dfac2c..07d13c8 100644 --- a/src/main/webapp/js/controllers.js +++ b/src/main/webapp/js/controllers.js @@ -1,7 +1,6 @@ -function PianoCtrl($xhr) { - $xhr.defaults.headers.post['Content-Type'] = 'application/json'; - var scope = this; +angular.module('piano', []); +function PianoCtrl($scope, $http) { var genKeyboard = function(startOctave, endOctave) { if (startOctave < 0 || endOctave > 8 || startOctave >= endOctave) throw "Bad arguments"; var notes = [ @@ -25,39 +24,39 @@ function PianoCtrl($xhr) { }); } return keys; - } + }; var keys = genOctave(startOctave, 9, 12); // A, Bb, B for (var oct=startOctave+1; oct < endOctave; oct++) { keys = keys.concat( genOctave(oct, 0, 12) ); // Full octave } return keys.concat( genOctave(endOctave, 0, 1) ); // C - } + }; - scope.NOTE_ON = 144; - scope.NOTE_OFF = 128; - scope.sendMidiCommand = function(command, midiNote) { + $scope.NOTE_ON = 144; + $scope.NOTE_OFF = 128; + $scope.sendMidiCommand = function(command, midiNote) { var midiMessage = { command: command, - channel: scope.channel, + channel: $scope.channel, note: midiNote, - velocity: scope.velocity - } + velocity: $scope.velocity + }; var timeStart = new Date().getTime(); - $xhr('POST', '/data/midi/send', midiMessage, function(code, response) { - scope.latency = new Date().getTime() - timeStart; + $http.post('data/midi/send', midiMessage).success(function(response, code) { + $scope.latency = new Date().getTime() - timeStart; }); - } + }; - scope.startOctave = 2; - scope.endOctave = 6; - scope.resetKeyboard = function() { - scope.keyboard = genKeyboard(scope.startOctave, scope.endOctave); - } - scope.resetKeyboard(); + $scope.startOctave = 2; + $scope.endOctave = 6; + $scope.resetKeyboard = function() { + $scope.keyboard = genKeyboard($scope.startOctave, $scope.endOctave); + }; + $scope.resetKeyboard(); - scope.latency = 0; - scope.showNames = true; - scope.channel = 0; - scope.velocity = 60; + $scope.latency = 0; + $scope.showNames = true; + $scope.channel = 0; + $scope.velocity = 60; } -PianoCtrl.$inject = ['$xhr']; \ No newline at end of file +PianoCtrl.$inject = ['$scope', '$http']; \ No newline at end of file From d823d92270b4ccb602716e967b199fafc062e90f Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 27 Jun 2013 12:05:38 +0100 Subject: [PATCH 5/8] Tests now work for angular-js 1.0.7 --- src/test/js/spec/controllers-spec.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/test/js/spec/controllers-spec.js b/src/test/js/spec/controllers-spec.js index 1dea1b0..34e369a 100644 --- a/src/test/js/spec/controllers-spec.js +++ b/src/test/js/spec/controllers-spec.js @@ -1,21 +1,23 @@ describe('PianoCtrl', function() { - var scope, controller; + var scope, ctrl, $httpBackend; beforeEach(function(){ this.addMatchers({ toBeGreaterThanOrEqualTo: function(expected) { return this.actual >= expected; } }); - scope = angular.scope(); - controller = scope.$new(PianoCtrl); + inject(function(_$httpBackend_, $rootScope, $controller) { + $httpBackend = _$httpBackend_; + scope = $rootScope.$new(); + controller = $controller(PianoCtrl, {$scope: scope}); + }); }); it('should measure latency on each send', function() { - var xhr = scope.$service('$browser').xhr; - var data = {channel:0, command:144, note:77, velocity:60}; - xhr.expectPOST('/data/midi/send', data).respond(data); - controller.latency = -1; - controller.sendMidiCommand(controller.NOTE_ON, 77); - xhr.flush(); - expect(controller.latency).toBeGreaterThanOrEqualTo(0); + var data = {command:144, channel:0, note:77, velocity:60}; + $httpBackend.expectPOST('data/midi/send', data).respond(data); + scope.latency = -1; + scope.sendMidiCommand(scope.NOTE_ON, 77); + $httpBackend.flush(); + expect(scope.latency).toBeGreaterThanOrEqualTo(0); }); }) \ No newline at end of file From 42406dffd8f3eb4627d5036c303269447fe63358 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 27 Jun 2013 14:34:43 +0100 Subject: [PATCH 6/8] Move jasmine test reports into the surefire report directory. --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index b390b05..1ad84c7 100644 --- a/pom.xml +++ b/pom.xml @@ -88,6 +88,7 @@ ${project.basedir}/src/main/webapp/js + ${project.build.directory}/surefire-reports *.js From 3de51eac67cac846ce5f86c3470a22e2ea0f6095 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 27 Jun 2013 14:52:56 +0100 Subject: [PATCH 7/8] Alter surefire plugin to pick up Jasmine tests. --- pom.xml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1ad84c7..3e2976e 100644 --- a/pom.xml +++ b/pom.xml @@ -60,10 +60,24 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + test + + + package + + + org.apache.maven.plugins maven-compiler-plugin - 2.0.2 + 2.5.1 1.6 1.6 From ecb7087eac5dff070e72c51e5e128ff9bbe99710 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 27 Jun 2013 16:30:34 +0100 Subject: [PATCH 8/8] Pushed surefire test after the jasmine test --- pom.xml | 202 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 107 insertions(+), 95 deletions(-) diff --git a/pom.xml b/pom.xml index 3e2976e..11cd0be 100644 --- a/pom.xml +++ b/pom.xml @@ -1,105 +1,91 @@ - 4.0.0 - com.example.ng.piano - angular-java-server-midi - war - 0.1-SNAPSHOT - angular-java-server-midi Java EE 6 Webapp - http://maven.apache.org - - - java.net2 - Repository hosting the jee6 artifacts - http://download.java.net/maven/2 - - - - - javax - javaee-web-api - 6.0 - provided - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 + com.example.ng.piano + angular-java-server-midi + war + 0.1-SNAPSHOT + angular-java-server-midi Java EE 6 Webapp + http://maven.apache.org + + + java.net2 + Repository hosting the jee6 artifacts + http://download.java.net/maven/2 + + + + + javax + javaee-web-api + 6.0 + provided + - - - com.sun.jersey - jersey-server - 1.7 - + + + com.sun.jersey + jersey-server + 1.7 + - - - org.codehaus.jackson - jackson-core-asl - 1.8.1 - - - org.codehaus.jackson - jackson-jaxrs - 1.8.1 - - - org.codehaus.jackson - jackson-xc - 1.8.1 - - - org.codehaus.jackson - jackson-jaxrs - 1.8.1 - + + + org.codehaus.jackson + jackson-core-asl + 1.8.1 + + + org.codehaus.jackson + jackson-jaxrs + 1.8.1 + + + org.codehaus.jackson + jackson-xc + 1.8.1 + + + org.codehaus.jackson + jackson-jaxrs + 1.8.1 + - - junit - junit - 4.8.2 - test - - - - - + + junit + junit + 4.8.2 + test + + + + + org.apache.maven.plugins - maven-surefire-plugin - 2.12 + maven-compiler-plugin + 2.5.1 + + 1.6 + 1.6 + + + + org.mortbay.jetty + jetty-maven-plugin + 7.0.2.v20100331 + + + + com.github.searls + jasmine-maven-plugin + 1.3.1.2 test - - package - - - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - 1.6 - 1.6 - - - - org.mortbay.jetty - jetty-maven-plugin - 7.0.2.v20100331 - - - - com.github.searls - jasmine-maven-plugin - 1.3.1.2 - - - - test - - - ${project.basedir}/src/main/webapp/js ${project.build.directory}/surefire-reports @@ -115,11 +101,37 @@ ${project.basedir}/src/main/webapp/js/lib/angular-1.0.7.min.js ${project.basedir}/src/test/js/lib/angular-mocks-1.0.7.js - + + + + + maven-surefire-plugin + + + + surefire-test + test + + + test + + + + ${skipTests} + + + + + + true - - - angular-java-server-midi - + + + angular-java-server-midi +