From ee0209783ded1a7ab7fa9dfae34b30ba1add67c7 Mon Sep 17 00:00:00 2001 From: Dan Barber Date: Fri, 22 Aug 2014 17:40:15 +0100 Subject: [PATCH] Update AngularJS. --- assets/javascripts/libs/angular-resource.js | 70 +- assets/javascripts/libs/angular.js | 3813 ++++++++++++------- 2 files changed, 2388 insertions(+), 1495 deletions(-) mode change 100644 => 100755 assets/javascripts/libs/angular-resource.js mode change 100644 => 100755 assets/javascripts/libs/angular.js diff --git a/assets/javascripts/libs/angular-resource.js b/assets/javascripts/libs/angular-resource.js old mode 100644 new mode 100755 index 9c14d9a..a2b1b52 --- a/assets/javascripts/libs/angular-resource.js +++ b/assets/javascripts/libs/angular-resource.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.2.14 + * @license AngularJS v1.2.22 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ @@ -99,10 +99,12 @@ function shallowClearAndCopy(src, dst) { * 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). + * If the parameter value is prefixed with `@` then the value for that parameter will be extracted + * from the corresponding property on the `data` object (provided when calling an action method). For + * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam` + * will be `data.someProp`. * - * @param {Object.<Object>=} actions Hash with declaration of custom action that should extend + * @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 format of {@link * ng.$http#usage_parameters $http.config}: * @@ -114,8 +116,8 @@ function shallowClearAndCopy(src, dst) { * * - **`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 `JSONP`. + * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, + * `DELETE`, `JSONP`, etc). * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of * the parameter value is a function, it will be executed every time when a param value needs to * be obtained for a request (unless the param was overridden). @@ -204,6 +206,9 @@ function shallowClearAndCopy(src, dst) { * On failure, the promise is resolved with the {@link ng.$http http response} object, without * the `resource` property. * + * If an interceptor object was provided, the promise will instead be resolved with the value + * returned by the interceptor. + * * - `$resolved`: `true` after first server interaction is completed (either with success or * rejection), `false` before that. Knowing if the Resource has been resolved is useful in * data-binding. @@ -258,7 +263,7 @@ function shallowClearAndCopy(src, dst) { ```js var User = $resource('/user/:userId', {userId:'@id'}); - var user = User.get({userId:123}, function() { + User.get({userId:123}, function(user) { user.abc = true; user.$save(); }); @@ -278,24 +283,34 @@ function shallowClearAndCopy(src, dst) { }); }); ``` + * + * You can also access the raw `$http` promise via the `$promise` property on the object returned + * + ``` + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}) + .$promise.then(function(user) { + $scope.user = user; + }); + ``` * # Creating a custom 'PUT' request * In this example we create a custom method on our resource to make a PUT request * ```js - * var app = angular.module('app', ['ngResource', 'ngRoute']); + * var app = angular.module('app', ['ngResource', 'ngRoute']); * - * // Some APIs expect a PUT request in the format URL/object/ID - * // Here we are creating an 'update' method - * app.factory('Notes', ['$resource', function($resource) { + * // Some APIs expect a PUT request in the format URL/object/ID + * // Here we are creating an 'update' method + * app.factory('Notes', ['$resource', function($resource) { * return $resource('/notes/:id', null, * { * 'update': { method:'PUT' } * }); - * }]); + * }]); * - * // In our controller we get the ID from the URL using ngRoute and $routeParams - * // We pass in $routeParams and our Notes factory along with $scope - * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', + * // In our controller we get the ID from the URL using ngRoute and $routeParams + * // We pass in $routeParams and our Notes factory along with $scope + * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', function($scope, $routeParams, Notes) { * // First get a note object from the factory * var note = Notes.get({ id:$routeParams.id }); @@ -305,7 +320,7 @@ function shallowClearAndCopy(src, dst) { * Notes.update({ id:$id }, note); * * // This will PUT /notes/ID with the note object in the request payload - * }]); + * }]); * ``` */ angular.module('ngResource', ['ng']). @@ -514,23 +529,32 @@ angular.module('ngResource', ['ng']). extend({}, extractParams(data, action.params || {}), params), action.url); - var promise = $http(httpConfig).then(function(response) { + var promise = $http(httpConfig).then(function (response) { var data = response.data, - promise = value.$promise; + promise = value.$promise; if (data) { // Need to convert action.isArray to boolean in case it is undefined // jshint -W018 if (angular.isArray(data) !== (!!action.isArray)) { - throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' + - 'response to contain an {0} but got an {1}', - action.isArray?'array':'object', angular.isArray(data)?'array':'object'); + throw $resourceMinErr('badcfg', + 'Error in resource configuration. Expected ' + + 'response to contain an {0} but got an {1}', + action.isArray ? 'array' : 'object', + angular.isArray(data) ? 'array' : 'object'); } // jshint +W018 if (action.isArray) { value.length = 0; - forEach(data, function(item) { - value.push(new Resource(item)); + forEach(data, function (item) { + if (typeof item === "object") { + value.push(new Resource(item)); + } else { + // Valid JSON values may be string literals, and these should not be converted + // into objects. These items will not have access to the Resource prototype + // methods, but unfortunately there + value.push(item); + } }); } else { shallowClearAndCopy(data, value); diff --git a/assets/javascripts/libs/angular.js b/assets/javascripts/libs/angular.js old mode 100644 new mode 100755 index 8314668..8ddf726 --- a/assets/javascripts/libs/angular.js +++ b/assets/javascripts/libs/angular.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.2.14 + * @license AngularJS v1.2.22 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ @@ -68,7 +68,7 @@ function minErr(module) { return match; }); - message = message + '\nhttp://errors.angularjs.org/1.2.14/' + + message = message + '\nhttp://errors.angularjs.org/1.2.22/' + (module ? module + '/' : '') + code; for (i = 2; i < arguments.length; i++) { message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + @@ -80,88 +80,88 @@ function minErr(module) { } /* We need to tell jshint what variables are being exported */ -/* global - -angular, - -msie, - -jqLite, - -jQuery, - -slice, - -push, - -toString, - -ngMinErr, - -_angular, - -angularModule, - -nodeName_, - -uid, - - -lowercase, - -uppercase, - -manualLowercase, - -manualUppercase, - -nodeName_, - -isArrayLike, - -forEach, - -sortedKeys, - -forEachSorted, - -reverseParams, - -nextUid, - -setHashKey, - -extend, - -int, - -inherit, - -noop, - -identity, - -valueFn, - -isUndefined, - -isDefined, - -isObject, - -isString, - -isNumber, - -isDate, - -isArray, - -isFunction, - -isRegExp, - -isWindow, - -isScope, - -isFile, - -isBoolean, - -trim, - -isElement, - -makeMap, - -map, - -size, - -includes, - -indexOf, - -arrayRemove, - -isLeafNode, - -copy, - -shallowCopy, - -equals, - -csp, - -concat, - -sliceArgs, - -bind, - -toJsonReplacer, - -toJson, - -fromJson, - -toBoolean, - -startingTag, - -tryDecodeURIComponent, - -parseKeyValue, - -toKeyValue, - -encodeUriSegment, - -encodeUriQuery, - -angularInit, - -bootstrap, - -snake_case, - -bindJQuery, - -assertArg, - -assertArgFn, - -assertNotHasOwnProperty, - -getter, - -getBlockElements, - -hasOwnProperty, +/* global angular: true, + msie: true, + jqLite: true, + jQuery: true, + slice: true, + push: true, + toString: true, + ngMinErr: true, + angularModule: true, + nodeName_: true, + uid: true, + VALIDITY_STATE_PROPERTY: true, + lowercase: true, + uppercase: true, + manualLowercase: true, + manualUppercase: true, + nodeName_: true, + isArrayLike: true, + forEach: true, + sortedKeys: true, + forEachSorted: true, + reverseParams: true, + nextUid: true, + setHashKey: true, + extend: true, + int: true, + inherit: true, + noop: true, + identity: true, + valueFn: true, + isUndefined: true, + isDefined: true, + isObject: true, + isString: true, + isNumber: true, + isDate: true, + isArray: true, + isFunction: true, + isRegExp: true, + isWindow: true, + isScope: true, + isFile: true, + isBlob: true, + isBoolean: true, + isPromiseLike: true, + trim: true, + isElement: true, + makeMap: true, + map: true, + size: true, + includes: true, + indexOf: true, + arrayRemove: true, + isLeafNode: true, + copy: true, + shallowCopy: true, + equals: true, + csp: true, + concat: true, + sliceArgs: true, + bind: true, + toJsonReplacer: true, + toJson: true, + fromJson: true, + toBoolean: true, + startingTag: true, + tryDecodeURIComponent: true, + parseKeyValue: true, + toKeyValue: true, + encodeUriSegment: true, + encodeUriQuery: true, + angularInit: true, + bootstrap: true, + snake_case: true, + bindJQuery: true, + assertArg: true, + assertArgFn: true, + assertNotHasOwnProperty: true, + getter: true, + getBlockElements: true, + hasOwnProperty: true, */ //////////////////////////////////// @@ -181,11 +181,15 @@ function minErr(module) { *
*/ +// The name of a form control's ValidityState property. +// This is used so that it's possible for internal tests to create mock ValidityStates. +var VALIDITY_STATE_PROPERTY = 'validity'; + /** * @ngdoc function * @name angular.lowercase * @module ng - * @function + * @kind function * * @description Converts the specified string to lowercase. * @param {string} string String to be converted to lowercase. @@ -198,7 +202,7 @@ var hasOwnProperty = Object.prototype.hasOwnProperty; * @ngdoc function * @name angular.uppercase * @module ng - * @function + * @kind function * * @description Converts the specified string to uppercase. * @param {string} string String to be converted to uppercase. @@ -239,8 +243,6 @@ var /** holds major version number for IE or NaN for real browsers */ toString = Object.prototype.toString, ngMinErr = minErr('ng'), - - _angular = window.angular, /** @name angular */ angular = window.angular || (window.angular = {}), angularModule, @@ -282,7 +284,7 @@ function isArrayLike(obj) { * @ngdoc function * @name angular.forEach * @module ng - * @function + * @kind function * * @description * Invokes the `iterator` function once for each item in `obj` collection, which can be either an @@ -296,7 +298,7 @@ function isArrayLike(obj) { ```js var values = {name: 'misko', gender: 'male'}; var log = []; - angular.forEach(values, function(value, key){ + angular.forEach(values, function(value, key) { this.push(key + ': ' + value); }, log); expect(log).toEqual(['name: misko', 'gender: male']); @@ -310,7 +312,7 @@ function isArrayLike(obj) { function forEach(obj, iterator, context) { var key; if (obj) { - if (isFunction(obj)){ + if (isFunction(obj)) { for (key in obj) { // Need to check if hasOwnProperty exists, // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function @@ -318,11 +320,12 @@ function forEach(obj, iterator, context) { iterator.call(context, obj[key], key); } } - } else if (obj.forEach && obj.forEach !== forEach) { - obj.forEach(iterator, context); - } else if (isArrayLike(obj)) { - for (key = 0; key < obj.length; key++) + } else if (isArray(obj) || isArrayLike(obj)) { + for (key = 0; key < obj.length; key++) { iterator.call(context, obj[key], key); + } + } else if (obj.forEach && obj.forEach !== forEach) { + obj.forEach(iterator, context); } else { for (key in obj) { if (obj.hasOwnProperty(key)) { @@ -411,7 +414,7 @@ function setHashKey(obj, h) { * @ngdoc function * @name angular.extend * @module ng - * @function + * @kind function * * @description * Extends the destination object `dst` by copying all of the properties from the `src` object(s) @@ -423,9 +426,9 @@ function setHashKey(obj, h) { */ function extend(dst) { var h = dst.$$hashKey; - forEach(arguments, function(obj){ + forEach(arguments, function(obj) { if (obj !== dst) { - forEach(obj, function(value, key){ + forEach(obj, function(value, key) { dst[key] = value; }); } @@ -448,7 +451,7 @@ function inherit(parent, extra) { * @ngdoc function * @name angular.noop * @module ng - * @function + * @kind function * * @description * A function that performs no operations. This function can be useful when writing code in the @@ -468,7 +471,7 @@ noop.$inject = []; * @ngdoc function * @name angular.identity * @module ng - * @function + * @kind function * * @description * A function that returns its first argument. This function is useful when writing code in the @@ -490,7 +493,7 @@ function valueFn(value) {return function() {return value;};} * @ngdoc function * @name angular.isUndefined * @module ng - * @function + * @kind function * * @description * Determines if a reference is undefined. @@ -505,7 +508,7 @@ function isUndefined(value){return typeof value === 'undefined';} * @ngdoc function * @name angular.isDefined * @module ng - * @function + * @kind function * * @description * Determines if a reference is defined. @@ -520,7 +523,7 @@ function isDefined(value){return typeof value !== 'undefined';} * @ngdoc function * @name angular.isObject * @module ng - * @function + * @kind function * * @description * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not @@ -536,7 +539,7 @@ function isObject(value){return value != null && typeof value === 'object';} * @ngdoc function * @name angular.isString * @module ng - * @function + * @kind function * * @description * Determines if a reference is a `String`. @@ -551,7 +554,7 @@ function isString(value){return typeof value === 'string';} * @ngdoc function * @name angular.isNumber * @module ng - * @function + * @kind function * * @description * Determines if a reference is a `Number`. @@ -566,7 +569,7 @@ function isNumber(value){return typeof value === 'number';} * @ngdoc function * @name angular.isDate * @module ng - * @function + * @kind function * * @description * Determines if a value is a date. @@ -574,7 +577,7 @@ function isNumber(value){return typeof value === 'number';} * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Date`. */ -function isDate(value){ +function isDate(value) { return toString.call(value) === '[object Date]'; } @@ -583,7 +586,7 @@ function isDate(value){ * @ngdoc function * @name angular.isArray * @module ng - * @function + * @kind function * * @description * Determines if a reference is an `Array`. @@ -591,16 +594,20 @@ function isDate(value){ * @param {*} value Reference to check. * @returns {boolean} True if `value` is an `Array`. */ -function isArray(value) { - return toString.call(value) === '[object Array]'; -} - +var isArray = (function() { + if (!isFunction(Array.isArray)) { + return function(value) { + return toString.call(value) === '[object Array]'; + }; + } + return Array.isArray; +})(); /** * @ngdoc function * @name angular.isFunction * @module ng - * @function + * @kind function * * @description * Determines if a reference is a `Function`. @@ -645,11 +652,21 @@ function isFile(obj) { } +function isBlob(obj) { + return toString.call(obj) === '[object Blob]'; +} + + function isBoolean(value) { return typeof value === 'boolean'; } +function isPromiseLike(obj) { + return obj && isFunction(obj.then); +} + + var trim = (function() { // native trim is way faster: http://jsperf.com/angular-trim-test // but IE doesn't have it... :-( @@ -669,7 +686,7 @@ var trim = (function() { * @ngdoc function * @name angular.isElement * @module ng - * @function + * @kind function * * @description * Determines if a reference is a DOM element (or wrapped jQuery element). @@ -687,7 +704,7 @@ function isElement(node) { * @param str 'key1,key2,...' * @returns {object} in the form of {key1:true, key2:true, ...} */ -function makeMap(str){ +function makeMap(str) { var obj = {}, items = str.split(","), i; for ( i = 0; i < items.length; i++ ) obj[ items[i] ] = true; @@ -734,7 +751,7 @@ function size(obj, ownPropsOnly) { if (isArray(obj) || isString(obj)) { return obj.length; - } else if (isObject(obj)){ + } else if (isObject(obj)) { for (key in obj) if (!ownPropsOnly || obj.hasOwnProperty(key)) count++; @@ -780,7 +797,7 @@ function isLeafNode (node) { * @ngdoc function * @name angular.copy * @module ng - * @function + * @kind function * * @description * Creates a deep copy of `source`, which should be an object or an array. @@ -798,9 +815,9 @@ function isLeafNode (node) { * @returns {*} The copy or updated `destination`, if `destination` was specified. * * @example - + -
+
Name:
E-mail:
@@ -814,26 +831,27 @@ function isLeafNode (node) {
*/ -function copy(source, destination){ +function copy(source, destination, stackSource, stackDest) { if (isWindow(source) || isScope(source)) { throw ngMinErr('cpws', "Can't copy! Making copies of Window or Scope instances is not supported."); @@ -843,52 +861,83 @@ function copy(source, destination){ destination = source; if (source) { if (isArray(source)) { - destination = copy(source, []); + destination = copy(source, [], stackSource, stackDest); } else if (isDate(source)) { destination = new Date(source.getTime()); } else if (isRegExp(source)) { - destination = new RegExp(source.source); + destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); + destination.lastIndex = source.lastIndex; } else if (isObject(source)) { - destination = copy(source, {}); + destination = copy(source, {}, stackSource, stackDest); } } } else { if (source === destination) throw ngMinErr('cpi', "Can't copy! Source and destination are identical."); + + stackSource = stackSource || []; + stackDest = stackDest || []; + + if (isObject(source)) { + var index = indexOf(stackSource, source); + if (index !== -1) return stackDest[index]; + + stackSource.push(source); + stackDest.push(destination); + } + + var result; if (isArray(source)) { destination.length = 0; for ( var i = 0; i < source.length; i++) { - destination.push(copy(source[i])); + result = copy(source[i], null, stackSource, stackDest); + if (isObject(source[i])) { + stackSource.push(source[i]); + stackDest.push(result); + } + destination.push(result); } } else { var h = destination.$$hashKey; - forEach(destination, function(value, key){ + forEach(destination, function(value, key) { delete destination[key]; }); for ( var key in source) { - destination[key] = copy(source[key]); + result = copy(source[key], null, stackSource, stackDest); + if (isObject(source[key])) { + stackSource.push(source[key]); + stackDest.push(result); + } + destination[key] = result; } setHashKey(destination,h); } + } return destination; } /** - * Create a shallow copy of an object + * Creates a shallow copy of an object, an array or a primitive */ function shallowCopy(src, dst) { - dst = dst || {}; + if (isArray(src)) { + dst = dst || []; - for(var key in src) { - // shallowCopy is only ever called by $compile nodeLinkFn, which has control over src - // so we don't need to worry about using our custom hasOwnProperty here - if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { - dst[key] = src[key]; + for ( var i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } else if (isObject(src)) { + dst = dst || {}; + + for (var key in src) { + if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { + dst[key] = src[key]; + } } } - return dst; + return dst || src; } @@ -896,7 +945,7 @@ function shallowCopy(src, dst) { * @ngdoc function * @name angular.equals * @module ng - * @function + * @kind function * * @description * Determines if two objects or two values are equivalent. Supports value types, regular @@ -908,7 +957,7 @@ function shallowCopy(src, dst) { * * Both objects or values are of the same type and all of their properties are equal by * comparing them with `angular.equals`. * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) - * * Both values represent the same regular expression (In JavasScript, + * * Both values represent the same regular expression (In JavaScript, * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual * representation matches). * @@ -961,12 +1010,25 @@ function equals(o1, o2) { return false; } +var csp = function() { + if (isDefined(csp.isActive_)) return csp.isActive_; + + var active = !!(document.querySelector('[ng-csp]') || + document.querySelector('[data-ng-csp]')); + + if (!active) { + try { + /* jshint -W031, -W054 */ + new Function(''); + /* jshint +W031, +W054 */ + } catch (e) { + active = true; + } + } + + return (csp.isActive_ = active); +}; -function csp() { - return (document.securityPolicy && document.securityPolicy.isActive) || - (document.querySelector && - !!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]'))); -} function concat(array1, array2, index) { @@ -983,7 +1045,7 @@ function sliceArgs(args, startIndex) { * @ngdoc function * @name angular.bind * @module ng - * @function + * @kind function * * @description * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for @@ -1039,7 +1101,7 @@ function toJsonReplacer(key, value) { * @ngdoc function * @name angular.toJson * @module ng - * @function + * @kind function * * @description * Serializes input into a JSON-formatted string. Properties with leading $ characters will be @@ -1059,7 +1121,7 @@ function toJson(obj, pretty) { * @ngdoc function * @name angular.fromJson * @module ng - * @function + * @kind function * * @description * Deserializes a JSON string. @@ -1136,13 +1198,13 @@ function tryDecodeURIComponent(value) { */ function parseKeyValue(/**string*/keyValue) { var obj = {}, key_value, key; - forEach((keyValue || "").split('&'), function(keyValue){ + forEach((keyValue || "").split('&'), function(keyValue) { if ( keyValue ) { - key_value = keyValue.split('='); + key_value = keyValue.replace(/\+/g,'%20').split('='); key = tryDecodeURIComponent(key_value[0]); if ( isDefined(key) ) { var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; - if (!obj[key]) { + if (!hasOwnProperty.call(obj, key)) { obj[key] = val; } else if(isArray(obj[key])) { obj[key].push(val); @@ -1314,7 +1376,42 @@ function angularInit(element, bootstrap) { * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. * They must use {@link ng.directive:ngApp ngApp}. * - * @param {Element} element DOM element which is the root of angular application. + * Angular will detect if it has been loaded into the browser more than once and only allow the + * first loaded script to be bootstrapped and will report a warning to the browser console for + * each of the subsequent scripts. This prevents strange results in applications, where otherwise + * multiple instances of Angular try to work on the DOM. + * + * + * + * + *
+ * + * + * + * + * + * + * + *
{{heading}}
{{fill}}
+ *
+ *
+ * + * var app = angular.module('multi-bootstrap', []) + * + * .controller('BrokenTable', function($scope) { + * $scope.headings = ['One', 'Two', 'Three']; + * $scope.fillings = [[1, 2, 3], ['A', 'B', 'C'], [7, 8, 9]]; + * }); + * + * + * it('should only insert one table cell for each item in $scope.fillings', function() { + * expect(element.all(by.css('td')).count()) + * .toBe(9); + * }); + * + *
+ * + * @param {DOMElement} element DOM element which is the root of angular application. * @param {Array=} modules an array of modules to load into the application. * Each item in the array should be the name of a predefined module or a (DI annotated) * function that will be invoked by the injector as a run block. @@ -1363,7 +1460,7 @@ function bootstrap(element, modules) { } var SNAKE_CASE_REGEXP = /[A-Z]/g; -function snake_case(name, separator){ +function snake_case(name, separator) { separator = separator || '_'; return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { return (pos ? separator : '') + letter.toLowerCase(); @@ -1373,8 +1470,9 @@ function snake_case(name, separator){ function bindJQuery() { // bind to jQuery if present; jQuery = window.jQuery; - // reset to jQuery or default to us. - if (jQuery) { + // Use jQuery if it exists with proper functionality, otherwise default to us. + // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support. + if (jQuery && jQuery.fn.on) { jqLite = jQuery; extend(jQuery.fn, { scope: JQLitePrototype.scope, @@ -1410,7 +1508,7 @@ function assertArgFn(arg, name, acceptArrayAnnotation) { } assertArg(isFunction(arg), name, 'not a function, got ' + - (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); + (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); return arg; } @@ -1520,7 +1618,7 @@ function setupModuleLoader(window) { * * # Module * - * A module is a collection of services, directives, filters, and configuration information. + * A module is a collection of services, directives, controllers, filters, and configuration information. * `angular.module` is used to configure the {@link auto.$injector $injector}. * * ```js @@ -1531,16 +1629,16 @@ function setupModuleLoader(window) { * myModule.value('appName', 'MyCoolApp'); * * // configure existing services inside initialization blocks. - * myModule.config(function($locationProvider) { + * myModule.config(['$locationProvider', function($locationProvider) { * // Configure existing providers * $locationProvider.hashPrefix('!'); - * }); + * }]); * ``` * * Then you can create an injector and load your modules like this: * * ```js - * var injector = angular.injector(['ng', 'MyModule']) + * var injector = angular.injector(['ng', 'myModule']) * ``` * * However it's more likely that you'll just use @@ -1548,9 +1646,9 @@ function setupModuleLoader(window) { * {@link angular.bootstrap} to simplify this process for you. * * @param {!string} name The name of the module to create or retrieve. - * @param {Array.=} requires If specified then new module is being created. If - * unspecified then the the module is being retrieved for further configuration. - * @param {Function} configFn Optional configuration function for the module. Same as + * @param {!Array.=} requires If specified then new module is being created. If + * unspecified then the module is being retrieved for further configuration. + * @param {Function=} configFn Optional configuration function for the module. Same as * {@link angular.Module#config Module#config()}. * @returns {module} new module with the {@link angular.Module} api. */ @@ -1590,7 +1688,6 @@ function setupModuleLoader(window) { * @ngdoc property * @name angular.Module#requires * @module ng - * @propertyOf angular.Module * @returns {Array.} List of module names which must be loaded before this module. * @description * Holds the list of modules which the injector will load before the current module is @@ -1602,7 +1699,6 @@ function setupModuleLoader(window) { * @ngdoc property * @name angular.Module#name * @module ng - * @propertyOf angular.Module * @returns {string} Name of the module. * @description */ @@ -1744,6 +1840,8 @@ function setupModuleLoader(window) { * configuration. * @description * Use this method to register work which needs to be performed on module loading. + * For more about how to configure services, see + * {@link providers#providers_provider-recipe Provider Recipe}. */ config: config, @@ -1787,12 +1885,11 @@ function setupModuleLoader(window) { } -/* global - angularModule: true, - version: true, +/* global angularModule: true, + version: true, - $LocaleProvider, - $CompileProvider, + $LocaleProvider, + $CompileProvider, htmlAnchorDirective, inputDirective, @@ -1880,11 +1977,11 @@ function setupModuleLoader(window) { * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.2.14', // all of these placeholder strings will be replaced by grunt's + full: '1.2.22', // all of these placeholder strings will be replaced by grunt's major: 1, // package task minor: 2, - dot: 14, - codeName: 'feisty-cryokinesis' + dot: 22, + codeName: 'finicky-pleasure' }; @@ -1897,11 +1994,11 @@ function publishExternalAPI(angular){ 'element': jqLite, 'forEach': forEach, 'injector': createInjector, - 'noop':noop, - 'bind':bind, + 'noop': noop, + 'bind': bind, 'toJson': toJson, 'fromJson': fromJson, - 'identity':identity, + 'identity': identity, 'isUndefined': isUndefined, 'isDefined': isDefined, 'isString': isString, @@ -2008,12 +2105,10 @@ function publishExternalAPI(angular){ ]); } -/* global - - -JQLitePrototype, - -addEventListenerFn, - -removeEventListenerFn, - -BOOLEAN_ATTR +/* global JQLitePrototype: true, + addEventListenerFn: true, + removeEventListenerFn: true, + BOOLEAN_ATTR: true */ ////////////////////////////////// @@ -2024,7 +2119,7 @@ function publishExternalAPI(angular){ * @ngdoc function * @name angular.element * @module ng - * @function + * @kind function * * @description * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. @@ -2106,8 +2201,9 @@ function publishExternalAPI(angular){ * @returns {Object} jQuery object. */ +JQLite.expando = 'ng339'; + var jqCache = JQLite.cache = {}, - jqName = JQLite.expando = 'ng-' + new Date().getTime(), jqId = 1, addEventListenerFn = (window.document.addEventListener ? function(element, type, fn) {element.addEventListener(type, fn, false);} @@ -2187,6 +2283,75 @@ function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArgu } } +var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; +var HTML_REGEXP = /<|&#?\w+;/; +var TAG_NAME_REGEXP = /<([\w:]+)/; +var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; + +var wrapMap = { + 'option': [1, ''], + + 'thead': [1, '', '
'], + 'col': [2, '', '
'], + 'tr': [2, '', '
'], + 'td': [3, '', '
'], + '_default': [0, "", ""] +}; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +function jqLiteIsTextNode(html) { + return !HTML_REGEXP.test(html); +} + +function jqLiteBuildFragment(html, context) { + var elem, tmp, tag, wrap, + fragment = context.createDocumentFragment(), + nodes = [], i, j, jj; + + if (jqLiteIsTextNode(html)) { + // Convert non-html into a text node + nodes.push(context.createTextNode(html)); + } else { + tmp = fragment.appendChild(context.createElement('div')); + // Convert html into DOM nodes + tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); + wrap = wrapMap[tag] || wrapMap._default; + tmp.innerHTML = '
 
' + + wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1>") + wrap[2]; + tmp.removeChild(tmp.firstChild); + + // Descend through wrappers to the right content + i = wrap[0]; + while (i--) { + tmp = tmp.lastChild; + } + + for (j=0, jj=tmp.childNodes.length; j + +
+ + + + +

Cached Values

+
+ + : + +
+ +

Cache Info

+
+ + : + +
+
+
+ + angular.module('cacheExampleApp', []). + controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { + $scope.keys = []; + $scope.cache = $cacheFactory('cacheId'); + $scope.put = function(key, value) { + $scope.cache.put(key, value); + $scope.keys.push(key); + }; + }]); + + + p { + margin: 10px 0 3px; + } + + */ function $CacheFactoryProvider() { @@ -4583,8 +4818,65 @@ function $CacheFactoryProvider() { freshEnd = null, staleEnd = null; + /** + * @ngdoc type + * @name $cacheFactory.Cache + * + * @description + * A cache object used to store and retrieve data, primarily used by + * {@link $http $http} and the {@link ng.directive:script script} directive to cache + * templates and other data. + * + * ```js + * angular.module('superCache') + * .factory('superCache', ['$cacheFactory', function($cacheFactory) { + * return $cacheFactory('super-cache'); + * }]); + * ``` + * + * Example test: + * + * ```js + * it('should behave like a cache', inject(function(superCache) { + * superCache.put('key', 'value'); + * superCache.put('another key', 'another value'); + * + * expect(superCache.info()).toEqual({ + * id: 'super-cache', + * size: 2 + * }); + * + * superCache.remove('another key'); + * expect(superCache.get('another key')).toBeUndefined(); + * + * superCache.removeAll(); + * expect(superCache.info()).toEqual({ + * id: 'super-cache', + * size: 0 + * }); + * })); + * ``` + */ return caches[cacheId] = { + /** + * @ngdoc method + * @name $cacheFactory.Cache#put + * @kind function + * + * @description + * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be + * retrieved later, and incrementing the size of the cache if the key was not already + * present in the cache. If behaving like an LRU cache, it will also remove stale + * entries from the set. + * + * It will not insert undefined values into the cache. + * + * @param {string} key the key under which the cached data is stored. + * @param {*} value the value to store alongside the key. If it is undefined, the key + * will not be stored. + * @returns {*} the value stored. + */ put: function(key, value) { if (capacity < Number.MAX_VALUE) { var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); @@ -4603,7 +4895,17 @@ function $CacheFactoryProvider() { return value; }, - + /** + * @ngdoc method + * @name $cacheFactory.Cache#get + * @kind function + * + * @description + * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. + * + * @param {string} key the key of the data to be retrieved + * @returns {*} the value stored. + */ get: function(key) { if (capacity < Number.MAX_VALUE) { var lruEntry = lruHash[key]; @@ -4617,6 +4919,16 @@ function $CacheFactoryProvider() { }, + /** + * @ngdoc method + * @name $cacheFactory.Cache#remove + * @kind function + * + * @description + * Removes an entry from the {@link $cacheFactory.Cache Cache} object. + * + * @param {string} key the key of the entry to be removed + */ remove: function(key) { if (capacity < Number.MAX_VALUE) { var lruEntry = lruHash[key]; @@ -4635,6 +4947,14 @@ function $CacheFactoryProvider() { }, + /** + * @ngdoc method + * @name $cacheFactory.Cache#removeAll + * @kind function + * + * @description + * Clears the cache object of any entries. + */ removeAll: function() { data = {}; size = 0; @@ -4643,6 +4963,15 @@ function $CacheFactoryProvider() { }, + /** + * @ngdoc method + * @name $cacheFactory.Cache#destroy + * @kind function + * + * @description + * Destroys the {@link $cacheFactory.Cache Cache} object entirely, + * removing it from the {@link $cacheFactory $cacheFactory} set. + */ destroy: function() { data = null; stats = null; @@ -4651,6 +4980,22 @@ function $CacheFactoryProvider() { }, + /** + * @ngdoc method + * @name $cacheFactory.Cache#info + * @kind function + * + * @description + * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. + * + * @returns {object} an object with the following properties: + *
    + *
  • **id**: the id of the cache instance
  • + *
  • **size**: the number of entries kept in the cache instance
  • + *
  • **...**: any additional properties from the options object when creating the + * cache.
  • + *
+ */ info: function() { return extend({}, stats, {size: size}); } @@ -4693,7 +5038,7 @@ function $CacheFactoryProvider() { * @name $cacheFactory#info * * @description - * Get information about all the of the caches that have been created + * Get information about all the caches that have been created * * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` */ @@ -4735,15 +5080,11 @@ function $CacheFactoryProvider() { * `$templateCache` service directly. * * Adding via the `script` tag: + * * ```html - * - * - * - * - * ... - * + * * ``` * * **Note:** the `script` tag containing the template does not need to be included in the `head` of @@ -4798,7 +5139,7 @@ function $TemplateCacheProvider() { /** * @ngdoc service * @name $compile - * @function + * @kind function * * @description * Compiles an HTML string or DOM into a template and produces a template function, which @@ -4836,11 +5177,11 @@ function $TemplateCacheProvider() { * template: '
', // or // function(tElement, tAttrs) { ... }, * // or * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, - * replace: false, * transclude: false, * restrict: 'A', * scope: false, * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, + * controllerAs: 'stringAlias', * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], * compile: function compile(tElement, tAttrs, transclude) { * return { @@ -4939,7 +5280,7 @@ function $TemplateCacheProvider() { * local name. Given `` and widget definition of * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to * a function wrapper for the `count = count + value` expression. Often it's desirable to - * pass data from the isolated scope via an expression and to the parent scope, this can be + * pass data from the isolated scope via an expression to the parent scope, this can be * done by passing a map of local variable names and values into the expression wrapper fn. * For example, if the expression is `increment(amount)` then we can specify the amount value * by calling the `localFn` as `localFn({amount: 22})`. @@ -4990,14 +5331,16 @@ function $TemplateCacheProvider() { * * * #### `template` - * replace the current element with the contents of the HTML. The replacement process - * migrates all of the attributes / classes from the old element to the new one. See the - * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive - * Directives Guide} for an example. + * HTML markup that may: + * * Replace the contents of the directive's element (default). + * * Replace the directive's element itself (if `replace` is true - DEPRECATED). + * * Wrap the contents of the directive's element (if `transclude` is true). * - * You can specify `template` as a string representing the template or as a function which takes - * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and - * returns a string value representing the template. + * Value may be: + * + * * A string. For example `
{{delete_str}}
`. + * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` + * function api below) and returns a string value. * * * #### `templateUrl` @@ -5011,12 +5354,15 @@ function $TemplateCacheProvider() { * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. * * - * #### `replace` - * specify where the template should be inserted. Defaults to `false`. + * #### `replace` ([*DEPRECATED*!], will be removed in next major release) + * specify what the template should replace. Defaults to `false`. * - * * `true` - the template will replace the current element. - * * `false` - the template will replace the contents of the current element. + * * `true` - the template will replace the directive's element. + * * `false` - the template will replace the contents of the directive's element. * + * The replacement process migrates all of the attributes / classes from the old element to the new + * one. See the {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive + * Directives Guide} for an example. * * #### `transclude` * compile the content of the element and make it available to the directive. @@ -5030,6 +5376,11 @@ function $TemplateCacheProvider() { * * `true` - transclude the content of the directive. * * `'element'` - transclude the whole element including any directives defined at lower priority. * + *
+ * **Note:** When testing an element transclude directive you must not place the directive at the root of the + * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives + * Testing Transclusion Directives}. + *
* * #### `compile` * @@ -5038,11 +5389,7 @@ function $TemplateCacheProvider() { * ``` * * The compile function deals with transforming the template DOM. Since most directives do not do - * template transformation, it is not used often. Examples that require compile functions are - * directives that transform template DOM, such as {@link - * api/ng.directive:ngRepeat ngRepeat}, or load the contents - * asynchronously, such as {@link ngRoute.directive:ngView ngView}. The - * compile function takes the following arguments. + * template transformation, it is not used often. The compile function takes the following arguments: * * * `tElement` - template element - The element where the directive has been declared. It is * safe to do template transformation on the element and child elements only. @@ -5058,6 +5405,16 @@ function $TemplateCacheProvider() { * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration * should be done in a linking function rather than in a compile function. *
+ + *
+ * **Note:** The compile function cannot handle directives that recursively use themselves in their + * own templates or compile functions. Compiling these directives results in an infinite loop and a + * stack overflow errors. + * + * This can be avoided by manually using $compile in the postLink function to imperatively compile + * a directive's template instead of relying on automatic template compilation via `template` or + * `templateUrl` declaration or manual compilation inside the compile function. + *
* *
* **Note:** The `transclude` function that is passed to the compile function is deprecated, as it @@ -5160,10 +5517,10 @@ function $TemplateCacheProvider() { * to illustrate how `$compile` works. *
* - + -
+


@@ -5270,7 +5626,7 @@ var $compileMinErr = minErr('$compile'); /** * @ngdoc provider * @name $compileProvider - * @function + * @kind function * * @description */ @@ -5278,9 +5634,8 @@ $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; function $CompileProvider($provide, $$sanitizeUriProvider) { var hasDirectives = {}, Suffix = 'Directive', - COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, - CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, - TABLE_CONTENT_REGEXP = /^<\s*(tr|th|td|tbody)(\s+[^>]*)?>/i; + COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, + CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/; // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes // The assumption is that future DOM event attribute names will begin with @@ -5290,7 +5645,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { /** * @ngdoc method * @name $compileProvider#directive - * @function + * @kind function * * @description * Register a new directive with the compiler. @@ -5343,7 +5698,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { /** * @ngdoc method * @name $compileProvider#aHrefSanitizationWhitelist - * @function + * @kind function * * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe @@ -5373,7 +5728,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { /** * @ngdoc method * @name $compileProvider#imgSrcSanitizationWhitelist - * @function + * @kind function * * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe @@ -5417,7 +5772,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { /** * @ngdoc method * @name $compile.directive.Attributes#$addClass - * @function + * @kind function * * @description * Adds the CSS class value specified by the classVal parameter to the element. If animations @@ -5434,7 +5789,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { /** * @ngdoc method * @name $compile.directive.Attributes#$removeClass - * @function + * @kind function * * @description * Removes the CSS class value specified by the classVal parameter from the element. If @@ -5451,7 +5806,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { /** * @ngdoc method * @name $compile.directive.Attributes#$updateClass - * @function + * @kind function * * @description * Adds and removes the appropriate CSS class values to the element based on the difference @@ -5539,7 +5894,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { /** * @ngdoc method * @name $compile.directive.Attributes#$observe - * @function + * @kind function * * @description * Observes an interpolated attribute. @@ -5602,7 +5957,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext); safeAddClass($compileNodes, 'ng-scope'); - return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){ + return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){ assertArg(scope, 'scope'); // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart // and sometimes changes the structure of the DOM. @@ -5624,7 +5979,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } if (cloneConnectFn) cloneConnectFn($linkNode, scope); - if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode); + if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); return $linkNode; }; } @@ -5671,7 +6026,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { : null; if (nodeLinkFn && nodeLinkFn.scope) { - safeAddClass(jqLite(nodeList[i]), 'ng-scope'); + safeAddClass(attrs.$$element, 'ng-scope'); } childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || @@ -5679,7 +6034,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { !childNodes.length) ? null : compileNodes(childNodes, - nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); + nodeLinkFn ? ( + (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) + && nodeLinkFn.transclude) : transcludeFn); linkFns.push(nodeLinkFn, childLinkFn); linkFnFound = linkFnFound || nodeLinkFn || childLinkFn; @@ -5690,8 +6047,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // return a linking function if we have found anything, null otherwise return linkFnFound ? compositeLinkFn : null; - function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { - var nodeLinkFn, childLinkFn, node, $node, childScope, childTranscludeFn, i, ii, n; + function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { + var nodeLinkFn, childLinkFn, node, childScope, i, ii, n, childBoundTranscludeFn; // copy nodeList so that linking doesn't break due to live list updates. var nodeListLength = nodeList.length, @@ -5704,32 +6061,40 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { node = stableNodeList[n]; nodeLinkFn = linkFns[i++]; childLinkFn = linkFns[i++]; - $node = jqLite(node); if (nodeLinkFn) { if (nodeLinkFn.scope) { childScope = scope.$new(); - $node.data('$scope', childScope); + jqLite.data(node, '$scope', childScope); } else { childScope = scope; } - childTranscludeFn = nodeLinkFn.transclude; - if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) { - nodeLinkFn(childLinkFn, childScope, node, $rootElement, - createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn) - ); + + if ( nodeLinkFn.transcludeOnThisElement ) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn); + + } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { + childBoundTranscludeFn = parentBoundTranscludeFn; + + } else if (!parentBoundTranscludeFn && transcludeFn) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); + } else { - nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn); + childBoundTranscludeFn = null; } + + nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); + } else if (childLinkFn) { - childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn); + childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); } } } } - function createBoundTranscludeFn(scope, transcludeFn) { - return function boundTranscludeFn(transcludedScope, cloneFn, controllers) { + function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { + + var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) { var scopeCreated = false; if (!transcludedScope) { @@ -5738,12 +6103,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { scopeCreated = true; } - var clone = transcludeFn(transcludedScope, cloneFn, controllers); + var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn); if (scopeCreated) { - clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy)); + clone.on('$destroy', function() { transcludedScope.$destroy(); }); } return clone; }; + + return boundTranscludeFn; } /** @@ -5769,7 +6136,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); // iterate over the attributes - for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, + for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { var attrStartName = false; var attrEndName = false; @@ -5777,9 +6144,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { attr = nAttrs[j]; if (!msie || msie >= 8 || attr.specified) { name = attr.name; + value = trim(attr.value); + // support ngAttr attribute binding ngAttrName = directiveNormalize(name); - if (NG_ATTR_BINDING.test(ngAttrName)) { + if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { name = snake_case(ngAttrName.substr(6), '-'); } @@ -5792,9 +6161,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { nName = directiveNormalize(name.toLowerCase()); attrsMap[nName] = name; - attrs[nName] = value = trim(attr.value); - if (getBooleanAttrName(node, nName)) { - attrs[nName] = true; // presence means true + if (isNgAttr || !attrs.hasOwnProperty(nName)) { + attrs[nName] = value; + if (getBooleanAttrName(node, nName)) { + attrs[nName] = true; // presence means true + } } addAttrInterpolateDirective(node, directives, value, nName); addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, @@ -5921,6 +6292,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { templateDirective = previousCompileContext.templateDirective, nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, hasTranscludeDirective = false, + hasTemplate = false, hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, $compileNode = templateAttrs.$$element = jqLite(compileNode), directive, @@ -5985,12 +6357,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (directiveValue == 'element') { hasElementTranscludeDirective = true; terminalPriority = directive.priority; - $template = groupScan(compileNode, attrStart, attrEnd); + $template = $compileNode; $compileNode = templateAttrs.$$element = jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); compileNode = $compileNode[0]; - replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); + replaceWith(jqCollection, sliceArgs($template), compileNode); childTranscludeFn = compile($template, transcludeFn, terminalPriority, replaceDirective && replaceDirective.name, { @@ -6011,6 +6383,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } if (directive.template) { + hasTemplate = true; assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; @@ -6022,7 +6395,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (directive.replace) { replaceDirective = directive; - $template = directiveTemplateContents(directiveValue); + if (jqLiteIsTextNode(directiveValue)) { + $template = []; + } else { + $template = jqLite(trim(directiveValue)); + } compileNode = $template[0]; if ($template.length != 1 || compileNode.nodeType !== 1) { @@ -6056,6 +6433,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } if (directive.templateUrl) { + hasTemplate = true; assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; @@ -6064,7 +6442,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, - templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, { + templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { controllerDirectives: controllerDirectives, newIsolateScopeDirective: newIsolateScopeDirective, templateDirective: templateDirective, @@ -6092,7 +6470,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; - nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn; + nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; + nodeLinkFn.templateOnThisElement = hasTemplate; + nodeLinkFn.transclude = childTranscludeFn; + previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; // might be normal or delayed nodeLinkFn depending on if templateUrl is present @@ -6104,6 +6485,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (pre) { if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); pre.require = directive.require; + pre.directiveName = directiveName; if (newIsolateScopeDirective === directive || directive.$$isolateScope) { pre = cloneAndAnnotateFn(pre, {isolateScope: true}); } @@ -6112,6 +6494,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (post) { if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); post.require = directive.require; + post.directiveName = directiveName; if (newIsolateScopeDirective === directive || directive.$$isolateScope) { post = cloneAndAnnotateFn(post, {isolateScope: true}); } @@ -6120,7 +6503,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } - function getControllers(require, $element, elementControllers) { + function getControllers(directiveName, require, $element, elementControllers) { var value, retrievalMethod = 'data', optional = false; if (isString(require)) { while((value = require.charAt(0)) == '^' || value == '?') { @@ -6146,7 +6529,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } else if (isArray(require)) { value = []; forEach(require, function(require) { - value.push(getControllers(require, $element, elementControllers)); + value.push(getControllers(directiveName, require, $element, elementControllers)); }); } return value; @@ -6156,28 +6539,26 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; - if (compileNode === linkNode) { - attrs = templateAttrs; - } else { - attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); - } + attrs = (compileNode === linkNode) + ? templateAttrs + : shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); $element = attrs.$$element; if (newIsolateScopeDirective) { var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; - var $linkNode = jqLite(linkNode); isolateScope = scope.$new(true); - if (templateDirective && (templateDirective === newIsolateScopeDirective.$$originalDirective)) { - $linkNode.data('$isolateScope', isolateScope) ; + if (templateDirective && (templateDirective === newIsolateScopeDirective || + templateDirective === newIsolateScopeDirective.$$originalDirective)) { + $element.data('$isolateScope', isolateScope); } else { - $linkNode.data('$isolateScopeNoTemplate', isolateScope); + $element.data('$isolateScopeNoTemplate', isolateScope); } - safeAddClass($linkNode, 'ng-isolate-scope'); + safeAddClass($element, 'ng-isolate-scope'); forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { var match = definition.match(LOCAL_REGEXP) || [], @@ -6211,7 +6592,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (parentGet.literal) { compare = equals; } else { - compare = function(a,b) { return a === b; }; + compare = function(a,b) { return a === b || (a !== a && b !== b); }; } parentSet = parentGet.assign || function() { // reset the change, or we will throw this exception on every $digest @@ -6289,7 +6670,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { try { linkFn = preLinkFns[i]; linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, - linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); + linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); } catch (e) { $exceptionHandler(e, startingTag($element)); } @@ -6309,7 +6690,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { try { linkFn = postLinkFns[i]; linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, - linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); + linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); } catch (e) { $exceptionHandler(e, startingTag($element)); } @@ -6395,7 +6776,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // reapply the old attributes to the new element forEach(dst, function(value, key) { if (key.charAt(0) != '$') { - if (src[key]) { + if (src[key] && src[key] !== value) { value += (key === 'style' ? ';' : ' ') + src[key]; } dst.$set(key, value, true, srcAttr[key]); @@ -6421,28 +6802,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } - function directiveTemplateContents(template) { - var type; - template = trim(template); - if ((type = TABLE_CONTENT_REGEXP.exec(template))) { - type = type[1].toLowerCase(); - var table = jqLite('' + template + '
'), - tbody = table.children('tbody'), - leaf = /(td|th)/.test(type) && table.find('tr'); - if (tbody.length && type !== 'tbody') { - table = tbody; - } - if (leaf && leaf.length) { - table = leaf; - } - return table.contents(); - } - return jqLite('
' + - template + - '
').contents(); - } - - function compileTemplateUrl(directives, $compileNode, tAttrs, $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { var linkQueue = [], @@ -6467,7 +6826,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { content = denormalizeTemplate(content); if (origAsyncDirective.replace) { - $template = directiveTemplateContents(content); + if (jqLiteIsTextNode(content)) { + $template = []; + } else { + $template = jqLite(trim(content)); + } compileNode = $template[0]; if ($template.length != 1 || compileNode.nodeType !== 1) { @@ -6502,7 +6865,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }); afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); - while(linkQueue.length) { var scope = linkQueue.shift(), beforeTemplateLinkNode = linkQueue.shift(), @@ -6524,8 +6886,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // Copy in CSS classes from original node safeAddClass(jqLite(linkNode), oldClasses); } - if (afterTemplateNodeLinkFn.transclude) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude); + if (afterTemplateNodeLinkFn.transcludeOnThisElement) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); } else { childBoundTranscludeFn = boundTranscludeFn; } @@ -6539,13 +6901,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }); return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { + var childBoundTranscludeFn = boundTranscludeFn; if (linkQueue) { linkQueue.push(scope); linkQueue.push(node); linkQueue.push(rootElement); - linkQueue.push(boundTranscludeFn); + linkQueue.push(childBoundTranscludeFn); } else { - afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn); + if (afterTemplateNodeLinkFn.transcludeOnThisElement) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); + } + afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); } }; } @@ -6570,23 +6936,31 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } - function addTextInterpolateDirective(directives, text) { - var interpolateFn = $interpolate(text, true); - if (interpolateFn) { - directives.push({ - priority: 0, - compile: valueFn(function textInterpolateLinkFn(scope, node) { - var parent = node.parent(), - bindings = parent.data('$binding') || []; - bindings.push(interpolateFn); - safeAddClass(parent.data('$binding', bindings), 'ng-binding'); - scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { - node[0].nodeValue = value; - }); - }) - }); + function addTextInterpolateDirective(directives, text) { + var interpolateFn = $interpolate(text, true); + if (interpolateFn) { + directives.push({ + priority: 0, + compile: function textInterpolateCompileFn(templateNode) { + // when transcluding a template that has bindings in the root + // then we don't have a parent and should do this in the linkFn + var parent = templateNode.parent(), hasCompileParent = parent.length; + if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding'); + + return function textInterpolateLinkFn(scope, node) { + var parent = node.parent(), + bindings = parent.data('$binding') || []; + bindings.push(interpolateFn); + parent.data('$binding', bindings); + if (!hasCompileParent) safeAddClass(parent, 'ng-binding'); + scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { + node[0].nodeValue = value; + }); + }; + } + }); + } } - } function getTrustedContext(node, attrNormalizedName) { @@ -6747,7 +7121,9 @@ function directiveNormalize(name) { * element attributes. The values reflect current binding state `{{ }}`. The normalization is * needed since all of these are treated as equivalent in Angular: * + * ``` * + * ``` */ /** @@ -6761,7 +7137,7 @@ function directiveNormalize(name) { /** * @ngdoc method * @name $compile.directive.Attributes#$set - * @function + * @kind function * * @description * Set DOM element attribute value. @@ -6884,7 +7260,7 @@ function $ControllerProvider() { instance = $injector.instantiate(expression, locals); if (identifier) { - if (!(locals && typeof locals.$scope == 'object')) { + if (!(locals && typeof locals.$scope === 'object')) { throw minErr('$controller')('noscp', "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", constructor || expression.name, identifier); @@ -6905,6 +7281,23 @@ function $ControllerProvider() { * * @description * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. + * + * @example + + +
+

$document title:

+

window.document title:

+
+
+ + angular.module('documentExample', []) + .controller('ExampleController', ['$scope', '$document', function($scope, $document) { + $scope.title = $document[0].title; + $scope.windowTitle = angular.element(window.document)[0].title; + }]); + +
*/ function $DocumentProvider(){ this.$get = ['$window', function(window){ @@ -6969,11 +7362,7 @@ function parseHeaders(headers) { val = trim(line.substr(i + 1)); if (key) { - if (parsed[key]) { - parsed[key] += ', ' + val; - } else { - parsed[key] = val; - } + parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; } }); @@ -7035,12 +7424,39 @@ function isSuccess(status) { } +/** + * @ngdoc provider + * @name $httpProvider + * @description + * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. + * */ function $HttpProvider() { var JSON_START = /^\s*(\[|\{[^\{])/, JSON_END = /[\}\]]\s*$/, PROTECTION_PREFIX = /^\)\]\}',?\n/, CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; + /** + * @ngdoc property + * @name $httpProvider#defaults + * @description + * + * Object containing default values for all {@link ng.$http $http} requests. + * + * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. + * Defaults value is `'XSRF-TOKEN'`. + * + * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the + * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. + * + * - **`defaults.headers`** - {Object} - Default headers for all $http requests. + * Refer to {@link ng.$http#setting-http-headers $http} for documentation on + * setting default headers. + * - **`defaults.headers.common`** + * - **`defaults.headers.post`** + * - **`defaults.headers.put`** + * - **`defaults.headers.patch`** + **/ var defaults = this.defaults = { // transform incoming response data transformResponse: [function(data) { @@ -7055,7 +7471,7 @@ function $HttpProvider() { // transform outgoing request data transformRequest: [function(d) { - return isObject(d) && !isFile(d) ? toJson(d) : d; + return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; }], // default headers @@ -7063,9 +7479,9 @@ function $HttpProvider() { common: { 'Accept': 'application/json, text/plain, */*' }, - post: copy(CONTENT_TYPE_APPLICATION_JSON), - put: copy(CONTENT_TYPE_APPLICATION_JSON), - patch: copy(CONTENT_TYPE_APPLICATION_JSON) + post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), + put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), + patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) }, xsrfCookieName: 'XSRF-TOKEN', @@ -7188,9 +7604,8 @@ function $HttpProvider() { * * # Shortcut methods * - * Since all invocations of the $http service require passing in an HTTP method and URL, and - * POST/PUT requests require request data to be provided as well, shortcut methods - * were created: + * Shortcut methods are also available. All shortcut methods require passing in the URL, and + * request data must be passed in for POST/PUT requests. * * ```js * $http.get('/someUrl').success(successCallback); @@ -7205,6 +7620,7 @@ function $HttpProvider() { * - {@link ng.$http#put $http.put} * - {@link ng.$http#delete $http.delete} * - {@link ng.$http#jsonp $http.jsonp} + * - {@link ng.$http#patch $http.patch} * * * # Setting HTTP Headers @@ -7230,7 +7646,7 @@ function $HttpProvider() { * * ``` * module.run(function($http) { - * $http.defaults.headers.common.Authentication = 'Basic YmVlcDpib29w' + * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' * }); * ``` * @@ -7308,14 +7724,14 @@ function $HttpProvider() { * * There are two kinds of interceptors (and two kinds of rejection interceptors): * - * * `request`: interceptors get called with http `config` object. The function is free to - * modify the `config` or create a new one. The function needs to return the `config` - * directly or as a promise. + * * `request`: interceptors get called with a http `config` object. The function is free to + * modify the `config` object or create a new one. The function needs to return the `config` + * object directly, or a promise containing the `config` or a new `config` object. * * `requestError`: interceptor gets called when a previous interceptor threw an error or * resolved with a rejection. * * `response`: interceptors get called with http `response` object. The function is free to - * modify the `response` or create a new one. The function needs to return the `response` - * directly or as a promise. + * modify the `response` object or create a new one. The function needs to return the `response` + * object directly, or as a promise containing the `response` or a new `response` object. * * `responseError`: interceptor gets called when a previous interceptor threw an error or * resolved with a rejection. * @@ -7327,7 +7743,7 @@ function $HttpProvider() { * // optional method * 'request': function(config) { * // do something on success - * return config || $q.when(config); + * return config; * }, * * // optional method @@ -7344,7 +7760,7 @@ function $HttpProvider() { * // optional method * 'response': function(response) { * // do something on success - * return response || $q.when(response); + * return response; * }, * * // optional method @@ -7505,8 +7921,8 @@ function $HttpProvider() { * caching. * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} * that should abort the request when resolved. - * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the - * XHR object. See [requests with credentials]https://developer.mozilla.org/en/http_access_control#section_5 + * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the + * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) * for more information. * - **responseType** - `{string}` - see * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). @@ -7524,15 +7940,16 @@ function $HttpProvider() { * - **status** – `{number}` – HTTP status code of the response. * - **headers** – `{function([headerName])}` – Header getter function. * - **config** – `{Object}` – The configuration object that was used to generate the request. + * - **statusText** – `{string}` – HTTP status text of the response. * - * @property {Array.<Object>} pendingRequests Array of config objects for currently pending + * @property {Array.} pendingRequests Array of config objects for currently pending * requests. This is primarily meant to be used for debugging purposes. * * * @example - + -
+

- * Current time is: - *
- * Blood 1 : {{blood_1}} - * Blood 2 : {{blood_2}} - * - * - * - *
+ *
+ *
+ * Date format:
+ * Current time is: + *
+ * Blood 1 : {{blood_1}} + * Blood 2 : {{blood_2}} + * + * + * *
+ *
* - * + * * */ function interval(fn, delay, count, invokeApply) { @@ -8578,13 +9040,13 @@ function $IntervalProvider() { * @description * Cancels a task associated with the `promise`. * - * @param {number} promise Promise returned by the `$interval` function. + * @param {promise} promise returned by the `$interval` function. * @returns {boolean} Returns `true` if the task was successfully canceled. */ interval.cancel = function(promise) { if (promise && promise.$$intervalId in intervals) { intervals[promise.$$intervalId].reject('canceled'); - clearInterval(promise.$$intervalId); + $window.clearInterval(promise.$$intervalId); delete intervals[promise.$$intervalId]; return true; } @@ -8867,7 +9329,7 @@ function LocationHashbangUrl(appBase, hashPrefix) { Matches paths for file protocol on windows, such as /C:/foo/bar, and captures only /foo/bar. */ - var windowsFilePathExp = /^\/?.*?:(\/.*)/; + var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; var firstPathSegmentMatch; @@ -8876,10 +9338,7 @@ function LocationHashbangUrl(appBase, hashPrefix) { url = url.replace(base, ''); } - /* - * The input URL intentionally contains a - * first path segment that ends with a colon. - */ + // The input URL intentionally contains a first path segment that ends with a colon. if (windowsFilePathExp.exec(url)) { return path; } @@ -8935,6 +9394,16 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) { return appBaseNoFile; } }; + + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#' + this.$$absUrl = appBase + hashPrefix + this.$$url; + }; + } @@ -9066,15 +9535,40 @@ LocationHashbangInHtml5Url.prototype = * * Change search part when called with parameter and return `$location`. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var searchObject = $location.search(); + * // => {foo: 'bar', baz: 'xoxo'} + * + * + * // set foo to 'yipee' + * $location.search('foo', 'yipee'); + * // => $location + * ``` + * * @param {string|Object.|Object.>} search New search params - string or - * hash object. Hash object may contain an array of values, which will be decoded as duplicates in - * the url. + * hash object. * - * @param {(string|Array)=} paramValue If `search` is a string, then `paramValue` will override only a - * single search parameter. If `paramValue` is an array, it will set the parameter as a - * comma-separated value. If `paramValue` is `null`, the parameter will be deleted. + * When called with a single argument the method acts as a setter, setting the `search` component + * of `$location` to the specified value. * - * @return {string} search + * If the argument is a hash object containing an array of values, these values will be encoded + * as duplicate search parameters in the url. + * + * @param {(string|Array|boolean)=} paramValue If `search` is a string, then `paramValue` + * will override only a single search property. + * + * If `paramValue` is an array, it will override the property of the `search` component of + * `$location` specified via the first argument. + * + * If `paramValue` is `null`, the property specified via the first argument will be deleted. + * + * If `paramValue` is `true`, the property specified via the first argument will be added with no + * value nor trailing equal sign. + * + * @return {Object} If called with no arguments returns the parsed `search` object. If called with + * one or more arguments returns `$location` object itself. */ search: function(search, paramValue) { switch (arguments.length) { @@ -9084,6 +9578,11 @@ LocationHashbangInHtml5Url.prototype = if (isString(search)) { this.$$search = parseKeyValue(search); } else if (isObject(search)) { + // remove object undefined or null properties + forEach(search, function(value, key) { + if (value == null) delete search[key]; + }); + this.$$search = search; } else { throw $locationMinErr('isrcharg', @@ -9175,8 +9674,7 @@ function locationGetterSetter(property, preprocess) { * - Clicks on a link. * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). * - * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular - * Services: Using $location} + * For more information see {@link guide/$location Developer Guide: Using $location} */ /** @@ -9190,7 +9688,7 @@ function $LocationProvider(){ html5Mode = false; /** - * @ngdoc property + * @ngdoc method * @name $locationProvider#hashPrefix * @description * @param {string=} prefix Prefix for hash part (containing path and search) @@ -9206,7 +9704,7 @@ function $LocationProvider(){ }; /** - * @ngdoc property + * @ngdoc method * @name $locationProvider#html5Mode * @description * @param {boolean=} mode Use HTML5 strategy if available. @@ -9266,6 +9764,8 @@ function $LocationProvider(){ $location = new LocationMode(appBase, '#' + hashPrefix); $location.$$parse($location.$$rewrite(initialUrl)); + var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; + $rootElement.on('click', function(event) { // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) // currently we open nice url link and redirect then @@ -9288,6 +9788,42 @@ function $LocationProvider(){ absHref = urlResolve(absHref.animVal).href; } + // Ignore when url is started with javascript: or mailto: + if (IGNORE_URI_REGEXP.test(absHref)) return; + + // Make relative links work in HTML5 mode for legacy browsers (or at least IE8 & 9) + // The href should be a regular url e.g. /link/somewhere or link/somewhere or ../somewhere or + // somewhere#anchor or http://example.com/somewhere + if (LocationMode === LocationHashbangInHtml5Url) { + // get the actual href attribute - see + // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx + var href = elm.attr('href') || elm.attr('xlink:href'); + + if (href.indexOf('://') < 0) { // Ignore absolute URLs + var prefix = '#' + hashPrefix; + if (href[0] == '/') { + // absolute path - replace old path + absHref = appBase + prefix + href; + } else if (href[0] == '#') { + // local anchor + absHref = appBase + prefix + ($location.path() || '/') + href; + } else { + // relative path - join with current path + var stack = $location.path().split("/"), + parts = href.split("/"); + for (var i=0; i + - function LogCtrl($scope, $log) { - $scope.$log = $log; - $scope.message = 'Hello World!'; - } + angular.module('logExample', []) + .controller('LogController', ['$scope', '$log', function($scope, $log) { + $scope.$log = $log; + $scope.message = 'Hello World!'; + }]); -
+

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

Message: @@ -9405,7 +9942,7 @@ function $LogProvider(){ self = this; /** - * @ngdoc property + * @ngdoc method * @name $logProvider#debugEnabled * @description * @param {boolean=} flag enable or disable debug level messages @@ -9531,14 +10068,7 @@ var promiseWarning; // // As an example, consider the following Angular expression: // -// {}.toString.constructor(alert("evil JS code")) -// -// We want to prevent this type of access. For the sake of performance, during the lexing phase we -// disallow any "dotted" access to any member named "constructor". -// -// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor -// while evaluating the expression, which is a stronger but more expensive test. Since reflective -// calls are expensive anyway, this is not such a big deal compared to static dereferencing. +// {}.toString.constructor('alert("evil JS code")') // // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits // against the expression language, but not to prevent exploits that were enabled by exposing @@ -9546,17 +10076,19 @@ var promiseWarning; // practice and therefore we are not even trying to protect against interaction with an object // explicitly exposed in this way. // -// A developer could foil the name check by aliasing the Function constructor under a different -// name on the scope. -// // In general, it is not possible to access a Window object from an angular expression unless a // window or some DOM object that has a reference to window is published onto a Scope. +// Similarly we prevent invocations of function known to be dangerous, as well as assignments to +// native objects. + function ensureSafeMemberName(name, fullExpression) { - if (name === "constructor") { + if (name === "__defineGetter__" || name === "__defineSetter__" + || name === "__lookupGetter__" || name === "__lookupSetter__" + || name === "__proto__") { throw $parseMinErr('isecfld', - 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', - fullExpression); + 'Attempting to access a disallowed field in Angular expressions! ' + +'Expression: {0}', fullExpression); } return name; } @@ -9578,11 +10110,34 @@ function ensureSafeObject(obj, fullExpression) { throw $parseMinErr('isecdom', 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', fullExpression); + } else if (// block Object so that we can't get hold of dangerous Object.* methods + obj === Object) { + throw $parseMinErr('isecobj', + 'Referencing Object in Angular expressions is disallowed! Expression: {0}', + fullExpression); } } return obj; } +var CALL = Function.prototype.call; +var APPLY = Function.prototype.apply; +var BIND = Function.prototype.bind; + +function ensureSafeFunction(obj, fullExpression) { + if (obj) { + if (obj.constructor === obj) { + throw $parseMinErr('isecfn', + 'Referencing Function in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (obj === CALL || obj === APPLY || (BIND && obj === BIND)) { + throw $parseMinErr('isecff', + 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } + } +} + var OPERATORS = { /* jshint bitwise : false */ 'null':function(){return null;}, @@ -9648,9 +10203,6 @@ Lexer.prototype = { this.tokens = []; - var token; - var json = []; - while (this.index < this.text.length) { this.ch = this.text.charAt(this.index); if (this.is('"\'')) { @@ -9659,19 +10211,11 @@ Lexer.prototype = { this.readNumber(); } else if (this.isIdent(this.ch)) { this.readIdent(); - // identifiers can only be if the preceding char was a { or , - if (this.was('{,') && json[0] === '{' && - (token = this.tokens[this.tokens.length - 1])) { - token.json = token.text.indexOf('.') === -1; - } } else if (this.is('(){}[].,;:?')) { this.tokens.push({ index: this.index, - text: this.ch, - json: (this.was(':[,') && this.is('{[')) || this.is('}]:,') + text: this.ch }); - if (this.is('{[')) json.unshift(this.ch); - if (this.is('}]')) json.shift(); this.index++; } else if (this.isWhitespace(this.ch)) { this.index++; @@ -9692,8 +10236,7 @@ Lexer.prototype = { this.tokens.push({ index: this.index, text: this.ch, - fn: fn, - json: (this.was('[,:') && this.is('+-')) + fn: fn }); this.index += 1; } else { @@ -9776,7 +10319,8 @@ Lexer.prototype = { this.tokens.push({ index: start, text: number, - json: true, + literal: true, + constant: true, fn: function() { return number; } }); }, @@ -9828,7 +10372,8 @@ Lexer.prototype = { // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn if (OPERATORS.hasOwnProperty(ident)) { token.fn = OPERATORS[ident]; - token.json = OPERATORS[ident]; + token.literal = true; + token.constant = true; } else { var getter = getterFn(ident, this.options, this.text); token.fn = extend(function(self, locals) { @@ -9845,13 +10390,11 @@ Lexer.prototype = { if (methodName) { this.tokens.push({ index:lastDot, - text: '.', - json: false + text: '.' }); this.tokens.push({ index: lastDot + 1, - text: methodName, - json: false + text: methodName }); } }, @@ -9874,11 +10417,7 @@ Lexer.prototype = { string += String.fromCharCode(parseInt(hex, 16)); } else { var rep = ESCAPE[ch]; - if (rep) { - string += rep; - } else { - string += ch; - } + string = string + (rep || ch); } escape = false; } else if (ch === '\\') { @@ -9889,7 +10428,8 @@ Lexer.prototype = { index: start, text: rawString, string: string, - json: true, + literal: true, + constant: true, fn: function() { return string; } }); return; @@ -9912,33 +10452,21 @@ var Parser = function (lexer, $filter, options) { this.options = options; }; -Parser.ZERO = function () { return 0; }; +Parser.ZERO = extend(function () { + return 0; +}, { + constant: true +}); Parser.prototype = { constructor: Parser, - parse: function (text, json) { + parse: function (text) { this.text = text; - //TODO(i): strip all the obsolte json stuff from this file - this.json = json; - this.tokens = this.lexer.lex(text); - 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. - this.assignment = this.logicalOR; - - this.functionCall = - this.fieldAccess = - this.objectIndex = - this.filterChain = function() { - this.throwError('is not valid json', {text: text, index: 0}); - }; - } - - var value = json ? this.primary() : this.statements(); + var value = this.statements(); if (this.tokens.length !== 0) { this.throwError('is an unexpected token', this.tokens[0]); @@ -9965,10 +10493,8 @@ Parser.prototype = { if (!primary) { this.throwError('not a primary expression', token); } - if (token.json) { - primary.constant = true; - primary.literal = true; - } + primary.literal = !!token.literal; + primary.constant = !!token.constant; } var next, context; @@ -10016,9 +10542,6 @@ Parser.prototype = { expect: function(e1, e2, e3, e4){ var token = this.peek(e1, e2, e3, e4); if (token) { - if (this.json && !token.json) { - this.throwError('is not valid json', token); - } this.tokens.shift(); return token; } @@ -10139,9 +10662,9 @@ Parser.prototype = { var middle; var token; if ((token = this.expect('?'))) { - middle = this.ternary(); + middle = this.assignment(); if ((token = this.expect(':'))) { - return this.ternaryFn(left, middle, this.ternary()); + return this.ternaryFn(left, middle, this.assignment()); } else { this.throwError('expected :', token); } @@ -10229,7 +10752,9 @@ Parser.prototype = { return getter(self || object(scope, locals)); }, { assign: function(scope, value, locals) { - return setter(object(scope, locals), field, value, parser.text, parser.options); + var o = object(scope, locals); + if (!o) object.assign(scope, o = {}); + return setter(o, field, value, parser.text, parser.options); } }); }, @@ -10245,6 +10770,7 @@ Parser.prototype = { i = indexFn(self, locals), v, p; + ensureSafeMemberName(i, parser.text); if (!o) return undefined; v = ensureSafeObject(o[i], parser.text); if (v && v.then && parser.options.unwrapPromises) { @@ -10258,10 +10784,11 @@ Parser.prototype = { return v; }, { assign: function(self, value, locals) { - var key = indexFn(self, locals); + var key = ensureSafeMemberName(indexFn(self, locals), parser.text); // prevent overwriting of Function.constructor which would break ensureSafeObject check - var safe = ensureSafeObject(obj(self, locals), parser.text); - return safe[key] = value; + var o = ensureSafeObject(obj(self, locals), parser.text); + if (!o) obj.assign(self, o = {}); + return o[key] = value; } }); }, @@ -10287,7 +10814,7 @@ Parser.prototype = { var fnPtr = fn(scope, locals, context) || noop; ensureSafeObject(context, parser.text); - ensureSafeObject(fnPtr, parser.text); + ensureSafeFunction(fnPtr, parser.text); // IE stupidity! (IE doesn't have apply for some native functions) var v = fnPtr.apply @@ -10396,6 +10923,8 @@ function setter(obj, path, setValue, fullExp, options) { } } key = ensureSafeMemberName(element.shift(), fullExp); + ensureSafeObject(obj, fullExp); + ensureSafeObject(obj[key], fullExp); obj[key] = setValue; return setValue; } @@ -10511,26 +11040,6 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { }; } -function simpleGetterFn1(key0, fullExp) { - ensureSafeMemberName(key0, fullExp); - - return function simpleGetterFn1(scope, locals) { - if (scope == null) return undefined; - return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; - }; -} - -function simpleGetterFn2(key0, key1, fullExp) { - ensureSafeMemberName(key0, fullExp); - ensureSafeMemberName(key1, fullExp); - - return function simpleGetterFn2(scope, locals) { - if (scope == null) return undefined; - scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; - return scope == null ? undefined : scope[key1]; - }; -} - function getterFn(path, options, fullExp) { // Check whether the cache has this getter already. // We can use hasOwnProperty directly on the cache because we ensure, @@ -10543,13 +11052,8 @@ function getterFn(path, options, fullExp) { pathKeysLength = pathKeys.length, fn; - // When we have only 1 or 2 tokens, use optimized special case closures. // http://jsperf.com/angularjs-parse-getter/6 - if (!options.unwrapPromises && pathKeysLength === 1) { - fn = simpleGetterFn1(pathKeys[0], fullExp); - } else if (!options.unwrapPromises && pathKeysLength === 2) { - fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp); - } else if (options.csp) { + if (options.csp) { if (pathKeysLength < 6) { fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, options); @@ -10653,7 +11157,7 @@ function getterFn(path, options, fullExp) { /** * @ngdoc provider * @name $parseProvider - * @function + * @kind function * * @description * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} @@ -10772,7 +11276,7 @@ function $ParseProvider() { var lexer = new Lexer($parseOptions); var parser = new Parser(lexer, $filter, $parseOptions); - parsedExpression = parser.parse(exp, false); + parsedExpression = parser.parse(exp); if (exp !== 'hasOwnProperty') { // Only cache the value if it's not going to mess up the cache object @@ -10815,17 +11319,13 @@ function $ParseProvider() { * var deferred = $q.defer(); * * setTimeout(function() { - * // since this fn executes async in a future turn of the event loop, we need to wrap - * // our code into an $apply call so that the model changes are properly observed. - * scope.$apply(function() { - * deferred.notify('About to greet ' + name + '.'); + * deferred.notify('About to greet ' + name + '.'); * - * if (okToGreet(name)) { - * deferred.resolve('Hello, ' + name + '!'); - * } else { - * deferred.reject('Greeting ' + name + ' is not allowed.'); - * } - * }); + * if (okToGreet(name)) { + * deferred.resolve('Hello, ' + name + '!'); + * } else { + * deferred.reject('Greeting ' + name + ' is not allowed.'); + * } * }, 1000); * * return deferred.promise; @@ -10904,7 +11404,7 @@ function $ParseProvider() { * * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to - * make your code IE8 compatible. + * make your code IE8 and Android 2.x compatible. * * # Chaining promises * @@ -10983,7 +11483,7 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc method * @name $q#defer - * @function + * @kind function * * @description * Creates a `Deferred` object which represents a task which will finish in the future. @@ -11099,7 +11599,7 @@ function qFactory(nextTick, exceptionHandler) { } catch(e) { return makePromise(e, false); } - if (callbackOutput && isFunction(callbackOutput.then)) { + if (isPromiseLike(callbackOutput)) { return callbackOutput.then(function() { return makePromise(value, isResolved); }, function(error) { @@ -11124,7 +11624,7 @@ function qFactory(nextTick, exceptionHandler) { var ref = function(value) { - if (value && isFunction(value.then)) return value; + if (isPromiseLike(value)) return value; return { then: function(callback) { var result = defer(); @@ -11140,7 +11640,7 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc method * @name $q#reject - * @function + * @kind function * * @description * Creates a promise that is resolved as rejected with the specified `reason`. This api should be @@ -11200,7 +11700,7 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc method * @name $q#when - * @function + * @kind function * * @description * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. @@ -11272,7 +11772,7 @@ function qFactory(nextTick, exceptionHandler) { /** * @ngdoc method * @name $q#all - * @function + * @kind function * * @description * Combines multiple promises into a single promise that is resolved when all of the input @@ -11317,21 +11817,32 @@ function qFactory(nextTick, exceptionHandler) { } function $$RAFProvider(){ //rAF - this.$get = ['$window', function($window) { + this.$get = ['$window', '$timeout', function($window, $timeout) { var requestAnimationFrame = $window.requestAnimationFrame || - $window.webkitRequestAnimationFrame; + $window.webkitRequestAnimationFrame || + $window.mozRequestAnimationFrame; var cancelAnimationFrame = $window.cancelAnimationFrame || - $window.webkitCancelAnimationFrame; + $window.webkitCancelAnimationFrame || + $window.mozCancelAnimationFrame || + $window.webkitCancelRequestAnimationFrame; - var raf = function(fn) { - var id = requestAnimationFrame(fn); - return function() { - cancelAnimationFrame(id); - }; - }; + var rafSupported = !!requestAnimationFrame; + var raf = rafSupported + ? function(fn) { + var id = requestAnimationFrame(fn); + return function() { + cancelAnimationFrame(id); + }; + } + : function(fn) { + var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 + return function() { + $timeout.cancel(timer); + }; + }; - raf.supported = !!requestAnimationFrame; + raf.supported = rafSupported; return raf; }]; @@ -11352,7 +11863,7 @@ function $$RAFProvider(){ //rAF * * Loop operations are optimized by using while(count--) { ... } * - this means that in order to keep the same order of execution as addition we have to add - * items to the array at the beginning (shift) instead of at the end (push) + * items to the array at the beginning (unshift) instead of at the end (push) * * Child scopes are created and removed often * - Using an array would be slow since inserts in middle are expensive so we use linked list @@ -11476,7 +11987,6 @@ function $RootScopeProvider(){ /** * @ngdoc property * @name $rootScope.Scope#$id - * @propertyOf ng.$rootScope.Scope * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for * debugging. */ @@ -11487,7 +11997,7 @@ function $RootScopeProvider(){ /** * @ngdoc method * @name $rootScope.Scope#$new - * @function + * @kind function * * @description * Creates a new child {@link ng.$rootScope.Scope scope}. @@ -11519,18 +12029,23 @@ function $RootScopeProvider(){ child.$$asyncQueue = this.$$asyncQueue; child.$$postDigestQueue = this.$$postDigestQueue; } else { - ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges - // the name it does not become random set of chars. This will then show up as class - // name in the web inspector. - ChildScope.prototype = this; - child = new ChildScope(); - child.$id = nextUid(); + // Only create a child scope class if somebody asks for one, + // but cache it to allow the VM to optimize lookups. + if (!this.$$childScopeClass) { + this.$$childScopeClass = function() { + this.$$watchers = this.$$nextSibling = + this.$$childHead = this.$$childTail = null; + this.$$listeners = {}; + this.$$listenerCount = {}; + this.$id = nextUid(); + this.$$childScopeClass = null; + }; + this.$$childScopeClass.prototype = this; + } + child = new this.$$childScopeClass(); } child['this'] = child; - child.$$listeners = {}; - child.$$listenerCount = {}; child.$parent = this; - child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null; child.$$prevSibling = this.$$childTail; if (this.$$childHead) { this.$$childTail.$$nextSibling = child; @@ -11544,7 +12059,7 @@ function $RootScopeProvider(){ /** * @ngdoc method * @name $rootScope.Scope#$watch - * @function + * @kind function * * @description * Registers a `listener` callback to be executed whenever the `watchExpression` changes. @@ -11556,10 +12071,14 @@ function $RootScopeProvider(){ * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) * - The `listener` is called only when the value from the current `watchExpression` and the * previous call to `watchExpression` are not equal (with the exception of the initial run, - * see below). The inequality is determined according to - * {@link angular.equals} function. To save the value of the object for later comparison, - * the {@link angular.copy} function is used. It also means that watching complex options - * will have adverse memory and performance implications. + * see below). Inequality is determined according to reference inequality, + * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) + * via the `!==` Javascript operator, unless `objectEquality == true` + * (see next point) + * - When `objectEquality == true`, inequality of the `watchExpression` is determined + * according to the {@link angular.equals} function. To save the value of the object for + * later comparison, the {@link angular.copy} function is used. This therefore means that + * watching complex objects will have adverse memory and performance implications. * - The watch `listener` may change the model, which may trigger other `listener`s to fire. * This is achieved by rerunning the watchers until no changes are detected. The rerun * iteration limit is 10 to prevent an infinite loop deadlock. @@ -11594,12 +12113,16 @@ function $RootScopeProvider(){ expect(scope.counter).toEqual(0); scope.$digest(); - // no variable change - expect(scope.counter).toEqual(0); + // the listener is always called during the first $digest loop after it was registered + expect(scope.counter).toEqual(1); + + scope.$digest(); + // but now it will not be called unless the value changes + expect(scope.counter).toEqual(1); scope.name = 'adam'; scope.$digest(); - expect(scope.counter).toEqual(1); + expect(scope.counter).toEqual(2); @@ -11647,7 +12170,8 @@ function $RootScopeProvider(){ * - `function(newValue, oldValue, scope)`: called with current and previous values as * parameters. * - * @param {boolean=} objectEquality Compare object for equality rather than for reference. + * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of + * comparing for reference equality. * @returns {function()} Returns a deregistration function for this listener. */ $watch: function(watchExp, listener, objectEquality) { @@ -11685,7 +12209,7 @@ function $RootScopeProvider(){ // the while loop reads in reverse order. array.unshift(watcher); - return function() { + return function deregisterWatch() { arrayRemove(array, watcher); lastDirtyWatch = null; }; @@ -11695,7 +12219,7 @@ function $RootScopeProvider(){ /** * @ngdoc method * @name $rootScope.Scope#$watchCollection - * @function + * @kind function * * @description * Shallow watches the properties of an object and fires whenever any of the properties change @@ -11736,30 +12260,40 @@ function $RootScopeProvider(){ * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the * collection will trigger a call to the `listener`. * - * @param {function(newCollection, oldCollection, scope)} listener a callback function that is - * fired with both the `newCollection` and `oldCollection` as parameters. - * The `newCollection` object is the newly modified data obtained from the `obj` expression - * and the `oldCollection` object is a copy of the former collection data. - * The `scope` refers to the current scope. + * @param {function(newCollection, oldCollection, scope)} listener a callback function called + * when a change is detected. + * - The `newCollection` object is the newly modified data obtained from the `obj` expression + * - The `oldCollection` object is a copy of the former collection data. + * Due to performance considerations, the`oldCollection` value is computed only if the + * `listener` function declares two or more arguments. + * - The `scope` argument refers to the current scope. * * @returns {function()} Returns a de-registration function for this listener. When the * de-registration function is executed, the internal watch operation is terminated. */ $watchCollection: function(obj, listener) { var self = this; - var oldValue; + // the current value, updated on each dirty-check run var newValue; + // a shallow copy of the newValue from the last dirty-check run, + // updated to match newValue during dirty-check run + var oldValue; + // a shallow copy of the newValue from when the last change happened + var veryOldValue; + // only track veryOldValue if the listener is asking for it + var trackVeryOldValue = (listener.length > 1); var changeDetected = 0; var objGetter = $parse(obj); var internalArray = []; var internalObject = {}; + var initRun = true; var oldLength = 0; function $watchCollectionWatch() { newValue = objGetter(self); - var newLength, key; + var newLength, key, bothNaN; - if (!isObject(newValue)) { + if (!isObject(newValue)) { // if primitive if (oldValue !== newValue) { oldValue = newValue; changeDetected++; @@ -11781,7 +12315,9 @@ function $RootScopeProvider(){ } // copy the items to oldValue and look for changes. for (var i = 0; i < newLength; i++) { - if (oldValue[i] !== newValue[i]) { + bothNaN = (oldValue[i] !== oldValue[i]) && + (newValue[i] !== newValue[i]); + if (!bothNaN && (oldValue[i] !== newValue[i])) { changeDetected++; oldValue[i] = newValue[i]; } @@ -11799,7 +12335,9 @@ function $RootScopeProvider(){ if (newValue.hasOwnProperty(key)) { newLength++; if (oldValue.hasOwnProperty(key)) { - if (oldValue[key] !== newValue[key]) { + bothNaN = (oldValue[key] !== oldValue[key]) && + (newValue[key] !== newValue[key]); + if (!bothNaN && (oldValue[key] !== newValue[key])) { changeDetected++; oldValue[key] = newValue[key]; } @@ -11825,7 +12363,32 @@ function $RootScopeProvider(){ } function $watchCollectionAction() { - listener(newValue, oldValue, self); + if (initRun) { + initRun = false; + listener(newValue, newValue, self); + } else { + listener(newValue, veryOldValue, self); + } + + // make a copy for the next time a collection is changed + if (trackVeryOldValue) { + if (!isObject(newValue)) { + //primitive + veryOldValue = newValue; + } else if (isArrayLike(newValue)) { + veryOldValue = new Array(newValue.length); + for (var i = 0; i < newValue.length; i++) { + veryOldValue[i] = newValue[i]; + } + } else { // if object + veryOldValue = {}; + for (var key in newValue) { + if (hasOwnProperty.call(newValue, key)) { + veryOldValue[key] = newValue[key]; + } + } + } + } } return this.$watch($watchCollectionWatch, $watchCollectionAction); @@ -11834,7 +12397,7 @@ function $RootScopeProvider(){ /** * @ngdoc method * @name $rootScope.Scope#$digest - * @function + * @kind function * * @description * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and @@ -11869,12 +12432,16 @@ function $RootScopeProvider(){ expect(scope.counter).toEqual(0); scope.$digest(); - // no variable change - expect(scope.counter).toEqual(0); + // the listener is always called during the first $digest loop after it was registered + expect(scope.counter).toEqual(1); + + scope.$digest(); + // but now it will not be called unless the value changes + expect(scope.counter).toEqual(1); scope.name = 'adam'; scope.$digest(); - expect(scope.counter).toEqual(1); + expect(scope.counter).toEqual(2); * ``` * */ @@ -11922,11 +12489,11 @@ function $RootScopeProvider(){ if ((value = watch.get(current)) !== (last = watch.last) && !(watch.eq ? equals(value, last) - : (typeof value == 'number' && typeof last == 'number' + : (typeof value === 'number' && typeof last === 'number' && isNaN(value) && isNaN(last)))) { dirty = true; lastDirtyWatch = watch; - watch.last = watch.eq ? copy(value) : value; + watch.last = watch.eq ? copy(value, null) : value; watch.fn(value, ((last === initWatchVal) ? value : last), current); if (ttl < 5) { logIdx = 4 - ttl; @@ -11989,7 +12556,6 @@ function $RootScopeProvider(){ /** * @ngdoc event * @name $rootScope.Scope#$destroy - * @eventOf ng.$rootScope.Scope * @eventType broadcast on scope being destroyed * * @description @@ -12002,7 +12568,7 @@ function $RootScopeProvider(){ /** * @ngdoc method * @name $rootScope.Scope#$destroy - * @function + * @kind function * * @description * Removes the current scope (and all of its children) from the parent scope. Removal implies @@ -12032,21 +12598,38 @@ function $RootScopeProvider(){ forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); + // sever all the references to parent scopes (after this cleanup, the current scope should + // not be retained by any of our references and should be eligible for garbage collection) if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; - // This is bogus code that works around Chrome's GC leak - // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 + + // All of the code below is bogus code that works around V8's memory leak via optimized code + // and inline caches. + // + // see: + // - https://code.google.com/p/v8/issues/detail?id=2073#c26 + // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 + // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 + this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = - this.$$childTail = null; + this.$$childTail = this.$root = null; + + // don't reset these to null in case some async task tries to register a listener/watch/task + this.$$listeners = {}; + this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = []; + + // prevent NPEs since these methods have references to properties we nulled out + this.$destroy = this.$digest = this.$apply = noop; + this.$on = this.$watch = function() { return noop; }; }, /** * @ngdoc method * @name $rootScope.Scope#$eval - * @function + * @kind function * * @description * Executes the `expression` on the current scope and returns the result. Any exceptions in @@ -12078,7 +12661,7 @@ function $RootScopeProvider(){ /** * @ngdoc method * @name $rootScope.Scope#$evalAsync - * @function + * @kind function * * @description * Executes the expression on the current scope at a later point in time. @@ -12125,7 +12708,7 @@ function $RootScopeProvider(){ /** * @ngdoc method * @name $rootScope.Scope#$apply - * @function + * @kind function * * @description * `$apply()` is used to execute an expression in angular from outside of the angular @@ -12187,7 +12770,7 @@ function $RootScopeProvider(){ /** * @ngdoc method * @name $rootScope.Scope#$on - * @function + * @kind function * * @description * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for @@ -12207,7 +12790,7 @@ function $RootScopeProvider(){ * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. * * @param {string} name Event name to listen on. - * @param {function(event, args...)} listener Function to call when the event is emitted. + * @param {function(event, ...args)} listener Function to call when the event is emitted. * @returns {function()} Returns a deregistration function for this listener. */ $on: function(name, listener) { @@ -12236,7 +12819,7 @@ function $RootScopeProvider(){ /** * @ngdoc method * @name $rootScope.Scope#$emit - * @function + * @kind function * * @description * Dispatches an event `name` upwards through the scope hierarchy notifying the @@ -12304,7 +12887,7 @@ function $RootScopeProvider(){ /** * @ngdoc method * @name $rootScope.Scope#$broadcast - * @function + * @kind function * * @description * Dispatches an event `name` downwards to all child scopes (and their children) notifying the @@ -12552,7 +13135,7 @@ function adjustMatchers(matchers) { /** * @ngdoc service * @name $sceDelegate - * @function + * @kind function * * @description * @@ -12599,19 +13182,21 @@ function adjustMatchers(matchers) { * * Here is what a secure configuration for this scenario might look like: * - *
- *    angular.module('myApp', []).config(function($sceDelegateProvider) {
- *      $sceDelegateProvider.resourceUrlWhitelist([
- *        // Allow same origin resource loads.
- *        'self',
- *        // Allow loading from our assets domain.  Notice the difference between * and **.
- *        'http://srv*.assets.example.com/**']);
+ * ```
+ *  angular.module('myApp', []).config(function($sceDelegateProvider) {
+ *    $sceDelegateProvider.resourceUrlWhitelist([
+ *      // Allow same origin resource loads.
+ *      'self',
+ *      // Allow loading from our assets domain.  Notice the difference between * and **.
+ *      'http://srv*.assets.example.com/**'
+ *    ]);
  *
- *      // The blacklist overrides the whitelist so the open redirect here is blocked.
- *      $sceDelegateProvider.resourceUrlBlacklist([
- *        'http://myapp.example.com/clickThru**']);
- *      });
- * 
+ * // The blacklist overrides the whitelist so the open redirect here is blocked. + * $sceDelegateProvider.resourceUrlBlacklist([ + * 'http://myapp.example.com/clickThru**' + * ]); + * }); + * ``` */ function $SceDelegateProvider() { @@ -12624,7 +13209,7 @@ function $SceDelegateProvider() { /** * @ngdoc method * @name $sceDelegateProvider#resourceUrlWhitelist - * @function + * @kind function * * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value * provided. This must be an array or null. A snapshot of this array is used so further @@ -12653,7 +13238,7 @@ function $SceDelegateProvider() { /** * @ngdoc method * @name $sceDelegateProvider#resourceUrlBlacklist - * @function + * @kind function * * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value * provided. This must be an array or null. A snapshot of this array is used so further @@ -12880,7 +13465,7 @@ function $SceDelegateProvider() { /** * @ngdoc service * @name $sce - * @function + * @kind function * * @description * @@ -12906,10 +13491,10 @@ function $SceDelegateProvider() { * * Here's an example of a binding in a privileged context: * - *
- *     
- *     
- *
+ * ``` + * + *
+ * ``` * * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE * disabled, this application allows the user to render arbitrary HTML into the DIV. @@ -12949,15 +13534,15 @@ function $SceDelegateProvider() { * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly * simplified): * - *
- *   var ngBindHtmlDirective = ['$sce', function($sce) {
- *     return function(scope, element, attr) {
- *       scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
- *         element.html(value || '');
- *       });
- *     };
- *   }];
- * 
+ * ``` + * var ngBindHtmlDirective = ['$sce', function($sce) { + * return function(scope, element, attr) { + * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { + * element.html(value || ''); + * }); + * }; + * }]; + * ``` * * ## Impact on loading templates * @@ -13006,10 +13591,10 @@ function $SceDelegateProvider() { * * | Context | Notes | * |---------------------|----------------| - * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. | + * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | - * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | + * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`

Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | * * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist}
@@ -13030,7 +13615,7 @@ function $SceDelegateProvider() { * - `**`: matches zero or more occurrences of *any* character. As such, it's not * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might - * not have been the intention.) It's usage at the very end of the path is ok. (e.g. + * not have been the intention.) Its usage at the very end of the path is ok. (e.g. * http://foo.example.com/templates/**). * - **RegExp** (*see caveat below*) * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax @@ -13061,66 +13646,65 @@ function $SceDelegateProvider() { * * ## Show me an example using SCE. * - * @example - - -
-

- User comments
- By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when - $sanitize is available. If $sanitize isn't available, this results in an error instead of an - exploit. -
-
- {{userComment.name}}: - -
-
-
-
-
- - - var mySceApp = angular.module('mySceApp', ['ngSanitize']); - - mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { - var self = this; - $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { - self.userComments = userComments; - }); - self.explicitlyTrustedHtml = $sce.trustAsHtml( - 'Hover over this text.'); - }); - - - -[ - { "name": "Alice", - "htmlComment": - "Is anyone reading this?" - }, - { "name": "Bob", - "htmlComment": "Yes! Am I the only other one?" - } -] - - - - describe('SCE doc demo', function() { - it('should sanitize untrusted values', function() { - expect(element(by.css('.htmlComment')).getInnerHtml()) - .toBe('Is anyone reading this?'); - }); - - it('should NOT sanitize explicitly trusted values', function() { - expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( - 'Hover over this text.'); - }); - }); - -
+ * + * + *
+ *

+ * User comments
+ * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when + * $sanitize is available. If $sanitize isn't available, this results in an error instead of an + * exploit. + *
+ *
+ * {{userComment.name}}: + * + *
+ *
+ *
+ *
+ *
+ * + * + * var mySceApp = angular.module('mySceApp', ['ngSanitize']); + * + * mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { + * var self = this; + * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { + * self.userComments = userComments; + * }); + * self.explicitlyTrustedHtml = $sce.trustAsHtml( + * 'Hover over this text.'); + * }); + * + * + * + * [ + * { "name": "Alice", + * "htmlComment": + * "Is anyone reading this?" + * }, + * { "name": "Bob", + * "htmlComment": "Yes! Am I the only other one?" + * } + * ] + * + * + * + * describe('SCE doc demo', function() { + * it('should sanitize untrusted values', function() { + * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) + * .toBe('Is anyone reading this?'); + * }); + * + * it('should NOT sanitize explicitly trusted values', function() { + * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( + * 'Hover over this text.'); + * }); + * }); + * + *
* * * @@ -13134,13 +13718,13 @@ function $SceDelegateProvider() { * * That said, here's how you can completely disable SCE: * - *
- *   angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
- *     // Completely disable SCE.  For demonstration purposes only!
- *     // Do not use in new projects.
- *     $sceProvider.enabled(false);
- *   });
- * 
+ * ``` + * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { + * // Completely disable SCE. For demonstration purposes only! + * // Do not use in new projects. + * $sceProvider.enabled(false); + * }); + * ``` * */ /* jshint maxlen: 100 */ @@ -13151,7 +13735,7 @@ function $SceProvider() { /** * @ngdoc method * @name $sceProvider#enabled - * @function + * @kind function * * @param {boolean=} value If provided, then enables/disables SCE. * @return {boolean} true if SCE is enabled, false otherwise. @@ -13224,12 +13808,12 @@ function $SceProvider() { 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); } - var sce = copy(SCE_CONTEXTS); + var sce = shallowCopy(SCE_CONTEXTS); /** * @ngdoc method * @name $sce#isEnabled - * @function + * @kind function * * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. @@ -13251,7 +13835,7 @@ function $SceProvider() { /** * @ngdoc method - * @name $sce#parse + * @name $sce#parseAs * * @description * Converts Angular {@link guide/expression expression} into a function. This is like {@link @@ -13764,7 +14348,7 @@ var originUrl = urlResolve(window.location.href, true); * https://github.com/angular/angular.js/pull/2902 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ * - * @function + * @kind function * @param {string} url The URL to be parsed. * @description Normalizes and parses a URL. * @returns {object} Returns the normalized URL as a dictionary. @@ -13837,17 +14421,18 @@ function urlIsSameOrigin(requestUrl) { * expression. * * @example - + -
+
@@ -13865,6 +14450,17 @@ function $WindowProvider(){ this.$get = valueFn(window); } +/* global currencyFilter: true, + dateFilter: true, + filterFilter: true, + jsonFilter: true, + limitToFilter: true, + lowercaseFilter: true, + numberFilter: true, + orderByFilter: true, + uppercaseFilter: true, + */ + /** * @ngdoc provider * @name $filterProvider @@ -13928,7 +14524,7 @@ function $WindowProvider(){ /** * @ngdoc service * @name $filter - * @function + * @kind function * @description * Filters are used for formatting data displayed to the user. * @@ -13938,7 +14534,24 @@ function $WindowProvider(){ * * @param {String} name Name of the filter function to retrieve * @return {Function} the filter function - */ + * @example + + +
+

{{ originalText }}

+

{{ filteredText }}

+
+
+ + + angular.module('filterExample', []) + .controller('MainCtrl', function($scope, $filter) { + $scope.originalText = 'hello'; + $scope.filteredText = $filter('uppercase')($scope.originalText); + }); + +
+ */ $FilterProvider.$inject = ['$provide']; function $FilterProvider($provide) { var suffix = 'Filter'; @@ -13998,7 +14611,7 @@ function $FilterProvider($provide) { /** * @ngdoc filter * @name filter - * @function + * @kind function * * @description * Selects a subset of items from `array` and returns it as a new array. @@ -14030,15 +14643,15 @@ function $FilterProvider($provide) { * * Can be one of: * - * - `function(actual, expected)`: - * The function will be given the object value and the predicate value to compare and - * should return true if the item should be included in filtered result. + * - `function(actual, expected)`: + * The function will be given the object value and the predicate value to compare and + * should return true if the item should be included in filtered result. * - * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. - * this is essentially strict comparison of expected and actual. + * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. + * this is essentially strict comparison of expected and actual. * - * - `false|undefined`: A short hand for a function which will look for a substring match in case - * insensitive way. + * - `false|undefined`: A short hand for a function which will look for a substring match in case + * insensitive way. * * @example @@ -14190,7 +14803,7 @@ function filterFilter() { // jshint +W086 for (var key in expression) { (function(path) { - if (typeof expression[path] == 'undefined') return; + if (typeof expression[path] === 'undefined') return; predicates.push(function(value) { return search(path == '$' ? value : (value && value[path]), expression[path]); }); @@ -14217,7 +14830,7 @@ function filterFilter() { /** * @ngdoc filter * @name currency - * @function + * @kind function * * @description * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default @@ -14229,14 +14842,15 @@ function filterFilter() { * * * @example - + -
+

default currency symbol ($): {{amount | currency}}
custom currency identifier (USD$): {{amount | currency:"USD$"}} @@ -14274,7 +14888,7 @@ function currencyFilter($locale) { /** * @ngdoc filter * @name number - * @function + * @kind function * * @description * Formats a number as text. @@ -14288,14 +14902,15 @@ function currencyFilter($locale) { * @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}}
@@ -14345,6 +14960,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); if (match && match[2] == '-' && match[3] > fractionSize + 1) { numStr = '0'; + number = 0; } else { formatedText = numStr; hasExponent = true; @@ -14359,8 +14975,11 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); } - var pow = Math.pow(10, fractionSize); - number = Math.round(number * pow) / pow; + // safely round numbers in JS without hitting imprecisions of floating-point arithmetics + // inspired by: + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round + number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); + var fraction = ('' + number).split(DECIMAL_SEP); var whole = fraction[0]; fraction = fraction[1] || ''; @@ -14486,7 +15105,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+ /** * @ngdoc filter * @name date - * @function + * @kind function * * @description * Formats `date` to a string based on the requested `format`. @@ -14535,7 +15154,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+ * (e.g. `"h 'o''clock'"`). * * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or - * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its + * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is * specified in the string input, the time is considered to be in the local timezone. * @param {string=} format Formatting rules (see Description). If not specified, @@ -14603,11 +15222,7 @@ function dateFilter($locale) { format = format || 'mediumDate'; format = $locale.DATETIME_FORMATS[format] || format; if (isString(date)) { - if (NUMBER_STRING.test(date)) { - date = int(date); - } else { - date = jsonStringToDate(date); - } + date = NUMBER_STRING.test(date) ? int(date) : jsonStringToDate(date); } if (isNumber(date)) { @@ -14643,7 +15258,7 @@ function dateFilter($locale) { /** * @ngdoc filter * @name json - * @function + * @kind function * * @description * Allows you to convert a JavaScript object into JSON string. @@ -14655,7 +15270,7 @@ function dateFilter($locale) { * @returns {string} JSON string. * * - * @example: + * @example
{{ {'name':'value'} | json }}
@@ -14678,7 +15293,7 @@ function jsonFilter() { /** * @ngdoc filter * @name lowercase - * @function + * @kind function * @description * Converts string to lowercase. * @see angular.lowercase @@ -14689,7 +15304,7 @@ var lowercaseFilter = valueFn(lowercase); /** * @ngdoc filter * @name uppercase - * @function + * @kind function * @description * Converts string to uppercase. * @see angular.uppercase @@ -14699,7 +15314,7 @@ var uppercaseFilter = valueFn(uppercase); /** * @ngdoc filter * @name limitTo - * @function + * @kind function * * @description * Creates a new array or string containing only a specified number of elements. The elements @@ -14715,17 +15330,18 @@ var uppercaseFilter = valueFn(uppercase); * had less than `limit` elements. * * @example - + -
+
Limit {{numbers}} to:

Output numbers: {{ numbers | limitTo:numLimit }}

Limit {{letters}} to: @@ -14769,7 +15385,11 @@ function limitToFilter(){ return function(input, limit) { if (!isArray(input) && !isString(input)) return input; - limit = int(limit); + if (Math.abs(Number(limit)) === Infinity) { + limit = Number(limit); + } else { + limit = int(limit); + } if (isString(input)) { //NaN check on limit @@ -14808,10 +15428,12 @@ function limitToFilter(){ /** * @ngdoc filter * @name orderBy - * @function + * @kind function * * @description - * Orders a specified `array` by the `expression` predicate. + * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically + * for strings and numerically for numbers. Note: if you notice numbers are not being sorted + * correctly, make sure they are actually being saved as numbers and not strings. * * @param {Array} array The array to sort. * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be @@ -14827,24 +15449,25 @@ function limitToFilter(){ * - `Array`: An array of function or string predicates. The first predicate in the array * is used for sorting, but when two items are equivalent, the next predicate is used. * - * @param {boolean=} reverse Reverse the order the array. + * @param {boolean=} reverse Reverse the order of the array. * @returns {Array} Sorted copy of the source array. * * @example - + -
+
Sorting predicate = {{predicate}}; reverse = {{reverse}}

[ unsorted ] @@ -14864,6 +15487,51 @@ function limitToFilter(){
+ * + * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the + * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the + * desired parameters. + * + * Example: + * + * @example + + +
+ + + + + + + + + + + +
Name + (^)Phone NumberAge
{{friend.name}}{{friend.phone}}{{friend.age}}
+
+
+ + + angular.module('orderByExample', []) + .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { + var orderBy = $filter('orderBy'); + $scope.friends = [ + { name: 'John', phone: '555-1212', age: 10 }, + { name: 'Mary', phone: '555-9876', age: 19 }, + { name: 'Mike', phone: '555-4321', age: 21 }, + { name: 'Adam', phone: '555-5678', age: 35 }, + { name: 'Julie', phone: '555-8765', age: 29 } + ]; + $scope.order = function(predicate, reverse) { + $scope.friends = orderBy($scope.friends, predicate, reverse); + }; + $scope.order('-age',false); + }]); + +
*/ orderByFilter.$inject = ['$parse']; function orderByFilter($parse){ @@ -14879,6 +15547,12 @@ function orderByFilter($parse){ predicate = predicate.substring(1); } get = $parse(predicate); + if (get.constant) { + var key = get(); + return reverseComparator(function(a,b) { + return compare(a[key], b[key]); + }, descending); + } } return reverseComparator(function(a,b){ return compare(get(a),get(b)); @@ -14904,6 +15578,10 @@ function orderByFilter($parse){ var t1 = typeof v1; var t2 = typeof v2; if (t1 == t2) { + if (isDate(v1) && isDate(v2)) { + v1 = v1.valueOf(); + v2 = v2.valueOf(); + } if (t1 == "string") { v1 = v1.toLowerCase(); v2 = v2.toLowerCase(); @@ -15041,7 +15719,7 @@ var htmlAnchorDirective = valueFn({ return browser.driver.getCurrentUrl().then(function(url) { return url.match(/\/123$/); }); - }, 1000, 'page should navigate to /123'); + }, 5000, 'page should navigate to /123'); }); xit('should execute ng-click but not reload when href empty string and name specified', function() { @@ -15069,7 +15747,7 @@ var htmlAnchorDirective = valueFn({ return browser.driver.getCurrentUrl().then(function(url) { return url.match(/\/6$/); }); - }, 1000, 'page should navigate to /6'); + }, 5000, 'page should navigate to /6'); }); @@ -15252,7 +15930,7 @@ var htmlAnchorDirective = valueFn({ * such as selected. (Their presence means true and their absence means false.) * If we put an Angular interpolation expression into such an attribute then the * binding information would be lost when the browser removes the attribute. - * The `ngSelected` directive solves this problem for the `selected` atttribute. + * The `ngSelected` directive solves this problem for the `selected` attribute. * This complementary directive is not removed by the browser and so provides * a permanent reliable place to store the binding information. * @@ -15409,7 +16087,7 @@ var nullFormCtrl = { * - `url` * * @description - * `FormController` keeps track of all its controls and nested forms as well as state of them, + * `FormController` keeps track of all its controls and nested forms as well as the state of them, * such as being valid/invalid or dirty/pristine. * * Each {@link ng.directive:form form} directive creates an instance @@ -15586,6 +16264,10 @@ function FormController(element, attrs, $scope, $animate) { * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a * sub-group of controls needs to be determined. * + * Note: the purpose of `ngForm` is to group controls, + * but not to be a replacement for the `` tag with all of its capabilities + * (e.g. posting to the server, ...). + * * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into * related scope, under this name. * @@ -15681,12 +16363,13 @@ function FormController(element, attrs, $scope, $animate) { * * * @example - + - + userType: Required!
userType = {{userType}}
@@ -15731,8 +16414,6 @@ function FormController(element, attrs, $scope, $animate) {
* - * @param {string=} name Name of the form. If specified, the form controller will be published into - * related scope, under this name. */ var formDirectiveFactory = function(isNgForm) { return ['$timeout', function($timeout) { @@ -15794,16 +16475,14 @@ var formDirectiveFactory = function(isNgForm) { var formDirective = formDirectiveFactory(); var ngFormDirective = formDirectiveFactory(true); -/* global - - -VALID_CLASS, - -INVALID_CLASS, - -PRISTINE_CLASS, - -DIRTY_CLASS +/* global VALID_CLASS: true, + INVALID_CLASS: true, + PRISTINE_CLASS: true, + DIRTY_CLASS: true */ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; -var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i; +var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; var inputType = { @@ -15833,15 +16512,16 @@ var inputType = { * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. * * @example - + - + Single word: @@ -15913,14 +16593,15 @@ var inputType = { * interaction with the input element. * * @example - + - + Number: @@ -15988,14 +16669,15 @@ var inputType = { * interaction with the input element. * * @example - + - + URL: Required! @@ -16064,14 +16746,15 @@ var inputType = { * interaction with the input element. * * @example - + - + Email: Required! @@ -16130,18 +16813,19 @@ var inputType = { * be set when selected. * * @example - + - + Red
Green
Blue
@@ -16180,15 +16864,16 @@ var inputType = { * interaction with the input element. * * @example - + - + Value1:
Value2:
@@ -16229,27 +16914,43 @@ function validate(ctrl, validatorName, validity, value){ return validity ? value : undefined; } +function testFlags(validity, flags) { + var i, flag; + if (flags) { + for (i=0; i @@ -16277,11 +16987,11 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { value = trim(value); } - if (ctrl.$viewValue !== value || - // If the value is still empty/falsy, and there is no `required` error, run validators - // again. This enables HTML5 constraint validation errors to affect Angular validation - // even when the first character entered causes an error. - (validity && value === '' && !validity.valueMissing)) { + // If a control is suffering from bad input, browsers discard its value, so it may be + // necessary to revalidate even if the control's value is the same empty value twice in + // a row. + var revalidate = validity && ctrl.$$hasNativeValidators; + if (ctrl.$viewValue !== value || (value === '' && revalidate)) { if (scope.$$phase) { ctrl.$setViewValue(value); } else { @@ -16387,6 +17097,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { } } +var numberBadFlags = ['badInput']; + function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); @@ -16401,7 +17113,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { } }); - addNativeHtml5Validators(ctrl, 'number', element); + addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState); ctrl.$formatters.push(function(value) { return ctrl.$isEmpty(value) ? '' : '' + value; @@ -16533,6 +17245,7 @@ function checkboxInputType(scope, element, attr, ctrl) { * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. + * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. */ @@ -16560,14 +17273,15 @@ function checkboxInputType(scope, element, attr, ctrl) { * interaction with the input element. * * @example - + -
+
User name: @@ -16682,14 +17396,14 @@ var VALID_CLASS = 'ng-valid', * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever the model value changes. Each function is called, in turn, passing the value through to the next. Used to format / convert values for display in the control and validation. - * ```js - * function formatter(value) { - * if (value) { - * return value.toUpperCase(); - * } - * } - * ngModel.$formatters.push(formatter); - * ``` + * ```js + * function formatter(value) { + * if (value) { + * return value.toUpperCase(); + * } + * } + * ngModel.$formatters.push(formatter); + * ``` * * @property {Array.} $viewChangeListeners Array of functions to execute whenever the * view value has changed. It is called with no arguments, and its return value is ignored. @@ -16718,7 +17432,12 @@ var VALID_CLASS = 'ng-valid', * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element * contents be edited in place by the user. This will not work on older browsers. * - * + * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} + * module to automatically remove "bad" content like inline event listener (e.g. ``). + * However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks + * that content using the `$sce` service. + * + * [contenteditable] { border: 1px solid black; @@ -16732,8 +17451,8 @@ var VALID_CLASS = 'ng-valid', - angular.module('customControl', []). - directive('contenteditable', function() { + angular.module('customControl', ['ngSanitize']). + directive('contenteditable', ['$sce', function($sce) { return { restrict: 'A', // only activate on element attribute require: '?ngModel', // get a hold of NgModelController @@ -16742,7 +17461,7 @@ var VALID_CLASS = 'ng-valid', // Specify how UI should be updated ngModel.$render = function() { - element.html(ngModel.$viewValue || ''); + element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); }; // Listen for change events to enable binding @@ -16763,7 +17482,7 @@ var VALID_CLASS = 'ng-valid', } } }; - }); + }]); @@ -16877,7 +17596,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * This method should be called by validators - i.e. the parser or formatter functions. * * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign - * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. + * to `$error[validationErrorKey]=!isValid` so that it is available for data-binding. * The `validationErrorKey` should be in camelCase and will get converted into dash-case * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` * class and can be bound to as `{{someForm.someControl.$error.myError}}` . @@ -17080,12 +17799,13 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * * * @example - * + * Update input to see transitions when valid/invalid. Integer is a valid value. - + @@ -17144,17 +17864,18 @@ var ngModelDirective = function() { * in input value. * * @example - * + * * * - *
+ *
* * *
@@ -17235,14 +17956,15 @@ var requiredDirective = function() { * specified in form `/something/` then the value will be converted into a regular expression. * * @example - + -
+ List: Required! @@ -17334,15 +18056,16 @@ var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; * of the `input` element * * @example - + - +

Which is your favorite?