vendor/assets/javascripts/angular.js in rails-angularjs-1.4.0 vs vendor/assets/javascripts/angular.js in rails-angularjs-1.4.1

- old
+ new

@@ -1,7 +1,7 @@ /** - * @license AngularJS v1.4.0 + * @license AngularJS v1.4.1 * (c) 2010-2015 Google, Inc. http://angularjs.org * License: MIT */ (function(window, document, undefined) {'use strict'; @@ -55,11 +55,11 @@ } return match; }); - message += '\nhttp://errors.angularjs.org/1.4.0/' + + message += '\nhttp://errors.angularjs.org/1.4.1/' + (module ? module + '/' : '') + code; for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + encodeURIComponent(toDebugString(templateArgs[i])); @@ -862,50 +862,56 @@ "Can't copy! TypedArray destination cannot be mutated."); } if (!destination) { destination = source; - if (source) { + if (isObject(source)) { + var index; + if (stackSource && (index = stackSource.indexOf(source)) !== -1) { + return stackDest[index]; + } + + // TypedArray, Date and RegExp have specific copy functionality and must be + // pushed onto the stack before returning. + // Array and other objects create the base object and recurse to copy child + // objects. The array/object will be pushed onto the stack when recursed. if (isArray(source)) { - destination = copy(source, [], stackSource, stackDest); + return copy(source, [], stackSource, stackDest); } else if (isTypedArray(source)) { destination = new source.constructor(source); } else if (isDate(source)) { destination = new Date(source.getTime()); } else if (isRegExp(source)) { destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); destination.lastIndex = source.lastIndex; - } else if (isObject(source)) { + } else { var emptyObject = Object.create(getPrototypeOf(source)); - destination = copy(source, emptyObject, stackSource, stackDest); + return copy(source, emptyObject, stackSource, stackDest); } + + if (stackDest) { + stackSource.push(source); + stackDest.push(destination); + } } } else { if (source === destination) throw ngMinErr('cpi', "Can't copy! Source and destination are identical."); stackSource = stackSource || []; stackDest = stackDest || []; if (isObject(source)) { - var index = stackSource.indexOf(source); - if (index !== -1) return stackDest[index]; - stackSource.push(source); stackDest.push(destination); } var result, key; if (isArray(source)) { destination.length = 0; for (var i = 0; i < source.length; i++) { - result = copy(source[i], null, stackSource, stackDest); - if (isObject(source[i])) { - stackSource.push(source[i]); - stackDest.push(result); - } - destination.push(result); + destination.push(copy(source[i], null, stackSource, stackDest)); } } else { var h = destination.$$hashKey; if (isArray(destination)) { destination.length = 0; @@ -915,41 +921,31 @@ }); } if (isBlankObject(source)) { // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty for (key in source) { - putValue(key, source[key], destination, stackSource, stackDest); + destination[key] = copy(source[key], null, stackSource, stackDest); } } else if (source && typeof source.hasOwnProperty === 'function') { // Slow path, which must rely on hasOwnProperty for (key in source) { if (source.hasOwnProperty(key)) { - putValue(key, source[key], destination, stackSource, stackDest); + destination[key] = copy(source[key], null, stackSource, stackDest); } } } else { // Slowest path --- hasOwnProperty can't be called as a method for (key in source) { if (hasOwnProperty.call(source, key)) { - putValue(key, source[key], destination, stackSource, stackDest); + destination[key] = copy(source[key], null, stackSource, stackDest); } } } setHashKey(destination,h); } } return destination; - - function putValue(key, val, destination, stackSource, stackDest) { - // No context allocation, trivial outer scope, easily inlined - var result = copy(val, null, stackSource, stackDest); - if (isObject(val)) { - stackSource.push(val); - stackDest.push(result); - } - destination[key] = result; - } } /** * Creates a shallow copy of an object, an array or a primitive. * @@ -2003,33 +1999,33 @@ * @param {Function} providerType Construction function for creating new instance of the * service. * @description * See {@link auto.$provide#provider $provide.provider()}. */ - provider: invokeLater('$provide', 'provider'), + provider: invokeLaterAndSetModuleName('$provide', 'provider'), /** * @ngdoc method * @name angular.Module#factory * @module ng * @param {string} name service name * @param {Function} providerFunction Function for creating new instance of the service. * @description * See {@link auto.$provide#factory $provide.factory()}. */ - factory: invokeLater('$provide', 'factory'), + factory: invokeLaterAndSetModuleName('$provide', 'factory'), /** * @ngdoc method * @name angular.Module#service * @module ng * @param {string} name service name * @param {Function} constructor A constructor function that will be instantiated. * @description * See {@link auto.$provide#service $provide.service()}. */ - service: invokeLater('$provide', 'service'), + service: invokeLaterAndSetModuleName('$provide', 'service'), /** * @ngdoc method * @name angular.Module#value * @module ng @@ -2060,11 +2056,11 @@ * @param {Function} This function will be invoked when the service needs to be * instantiated and should return the decorated service instance. * @description * See {@link auto.$provide#decorator $provide.decorator()}. */ - decorator: invokeLater('$provide', 'decorator'), + decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), /** * @ngdoc method * @name angular.Module#animation * @module ng @@ -2094,11 +2090,11 @@ * ``` * * See {@link ng.$animateProvider#register $animateProvider.register()} and * {@link ngAnimate ngAnimate module} for more information. */ - animation: invokeLater('$animateProvider', 'register'), + animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), /** * @ngdoc method * @name angular.Module#filter * @module ng @@ -2112,11 +2108,11 @@ * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores * (`myapp_subsection_filterx`). * </div> */ - filter: invokeLater('$filterProvider', 'register'), + filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), /** * @ngdoc method * @name angular.Module#controller * @module ng @@ -2124,11 +2120,11 @@ * keys are the names and the values are the constructors. * @param {Function} constructor Controller constructor function. * @description * See {@link ng.$controllerProvider#register $controllerProvider.register()}. */ - controller: invokeLater('$controllerProvider', 'register'), + controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), /** * @ngdoc method * @name angular.Module#directive * @module ng @@ -2137,11 +2133,11 @@ * @param {Function} directiveFactory Factory function for creating new instance of * directives. * @description * See {@link ng.$compileProvider#directive $compileProvider.directive()}. */ - directive: invokeLater('$compileProvider', 'directive'), + directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), /** * @ngdoc method * @name angular.Module#config * @module ng @@ -2187,10 +2183,23 @@ return function() { queue[insertMethod || 'push']([provider, method, arguments]); return moduleInstance; }; } + + /** + * @param {string} provider + * @param {string} method + * @returns {angular.Module} + */ + function invokeLaterAndSetModuleName(provider, method) { + return function(recipeName, factoryFunction) { + if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; + invokeQueue.push([provider, method, arguments]); + return moduleInstance; + }; + } }); }; }); } @@ -2330,15 +2339,15 @@ * - `minor` – `{number}` – Minor version number, such as "9". * - `dot` – `{number}` – Dot version number, such as "18". * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.4.0', // all of these placeholder strings will be replaced by grunt's + full: '1.4.1', // all of these placeholder strings will be replaced by grunt's major: 1, // package task minor: 4, - dot: 0, - codeName: 'jaracimrman-existence' + dot: 1, + codeName: 'hyperionic-illumination' }; function publishExternalAPI(angular) { extend(angular, { @@ -2660,10 +2669,17 @@ // Otherwise we are only interested in elements (1) and documents (9) var nodeType = node.nodeType; return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; } +function jqLiteHasData(node) { + for (var key in jqCache[node.ng339]) { + return true; + } + return false; +} + function jqLiteBuildFragment(html, context) { var tmp, tag, wrap, fragment = context.createDocumentFragment(), nodes = [], i; @@ -3034,11 +3050,12 @@ return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name]; } forEach({ data: jqLiteData, - removeData: jqLiteRemoveData + removeData: jqLiteRemoveData, + hasData: jqLiteHasData }, function(fn, name) { JQLite[name] = fn; }); forEach({ @@ -4243,11 +4260,11 @@ var provider = providerInjector.get(serviceName + providerSuffix, caller); return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); })); - forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); + forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); }); return instanceInjector; //////////////////////////////////// // $provider @@ -5466,11 +5483,11 @@ history[replace ? 'replaceState' : 'pushState'](state, '', url); cacheState(); // Do the assignment again so that those two variables are referentially identical. lastHistoryState = cachedState; } else { - if (!sameBase) { + if (!sameBase || reloadLocation) { reloadLocation = url; } if (replace) { location.replace(url); } else if (!sameBase) { @@ -6472,17 +6489,20 @@ * between all directive linking functions. * * * `controller` - the directive's required controller instance(s) - Instances are shared * among all directives, which allows the directives to use the controllers as a communication * channel. The exact value depends on the directive's `require` property: + * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one * * `string`: the controller instance * * `array`: array of controller instances - * * no controller(s) required: `undefined` * * If a required controller cannot be found, and it is optional, the instance is `null`, * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. * + * Note that you can also require the directive's own controller - it will be made available like + * like any other controller. + * * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. * This is the same as the `$transclude` * parameter of directive controllers, see there for details. * `function([scope], cloneLinkingFn, futureParentElement)`. * @@ -6925,10 +6945,11 @@ var bindings = directive.$$bindings = parseDirectiveBindings(directive, directive.name); if (isObject(bindings.isolateScope)) { directive.$$isolateBindings = bindings.isolateScope; } + directive.$$moduleName = directiveFactory.$$moduleName; directives.push(directive); } catch (e) { $exceptionHandler(e); } }); @@ -7496,12 +7517,11 @@ childScope = scope; } if (nodeLinkFn.transcludeOnThisElement) { childBoundTranscludeFn = createBoundTranscludeFn( - scope, nodeLinkFn.transclude, parentBoundTranscludeFn, - nodeLinkFn.elementTranscludeOnThisElement); + scope, nodeLinkFn.transclude, parentBoundTranscludeFn); } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { childBoundTranscludeFn = parentBoundTranscludeFn; } else if (!parentBoundTranscludeFn && transcludeFn) { @@ -7519,11 +7539,11 @@ } } } } - function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn, elementTransclusion) { + function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { if (!transcludedScope) { transcludedScope = scope.$new(false, containingScope); @@ -7618,10 +7638,17 @@ className = className.substr(match.index + match[0].length); } } break; case NODE_TYPE_TEXT: /* Text Node */ + if (msie === 11) { + // Workaround for #11781 + while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { + node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; + node.parentNode.removeChild(node.nextSibling); + } + } addTextInterpolateDirective(directives, node.nodeValue); break; case NODE_TYPE_COMMENT: /* Comment */ try { match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); @@ -7910,11 +7937,10 @@ } nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; - nodeLinkFn.elementTranscludeOnThisElement = hasElementTranscludeDirective; nodeLinkFn.templateOnThisElement = hasTemplate; nodeLinkFn.transclude = childTranscludeFn; previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; @@ -8071,13 +8097,16 @@ } } for (i in elementControllers) { controller = elementControllers[i]; var controllerResult = controller(); + if (controllerResult !== controller.instance) { + // If the controller constructor has a return value, overwrite the instance + // from setupControllers and update the element data controller.instance = controllerResult; - $element.data('$' + directive.name + 'Controller', controllerResult); + $element.data('$' + i + 'Controller', controllerResult); if (controller === controllerForBindings) { // Remove and re-install bindToController bindings thisLinkFn.$$destroyBindings(); thisLinkFn.$$destroyBindings = initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective); @@ -8373,15 +8402,22 @@ if (diff !== 0) return diff; if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; return a.index - b.index; } - function assertNoDuplicate(what, previousDirective, directive, element) { + + function wrapModuleNameIfDefined(moduleName) { + return moduleName ? + (' (module: ' + moduleName + ')') : + ''; + } + if (previousDirective) { - throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', - previousDirective.name, directive.name, what, startingTag(element)); + throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', + previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), + directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); } } function addTextInterpolateDirective(directives, text) { @@ -8558,30 +8594,32 @@ // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it? var fragment = document.createDocumentFragment(); fragment.appendChild(firstElementToRemove); - // Copy over user data (that includes Angular's $scope etc.). Don't copy private - // data here because there's no public interface in jQuery to do that and copying over - // event listeners (which is the main use of private data) wouldn't work anyway. - jqLite(newNode).data(jqLite(firstElementToRemove).data()); + if (jqLite.hasData(firstElementToRemove)) { + // Copy over user data (that includes Angular's $scope etc.). Don't copy private + // data here because there's no public interface in jQuery to do that and copying over + // event listeners (which is the main use of private data) wouldn't work anyway. + jqLite(newNode).data(jqLite(firstElementToRemove).data()); - // Remove data of the replaced element. We cannot just call .remove() - // on the element it since that would deallocate scope that is needed - // for the new node. Instead, remove the data "manually". - if (!jQuery) { - delete jqLite.cache[firstElementToRemove[jqLite.expando]]; - } else { - // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after - // the replaced element. The cleanData version monkey-patched by Angular would cause - // the scope to be trashed and we do need the very same scope to work with the new - // element. However, we cannot just cache the non-patched version and use it here as - // that would break if another library patches the method after Angular does (one - // example is jQuery UI). Instead, set a flag indicating scope destroying should be - // skipped this one time. - skipDestroyOnNextJQueryCleanData = true; - jQuery.cleanData([firstElementToRemove]); + // Remove data of the replaced element. We cannot just call .remove() + // on the element it since that would deallocate scope that is needed + // for the new node. Instead, remove the data "manually". + if (!jQuery) { + delete jqLite.cache[firstElementToRemove[jqLite.expando]]; + } else { + // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after + // the replaced element. The cleanData version monkey-patched by Angular would cause + // the scope to be trashed and we do need the very same scope to work with the new + // element. However, we cannot just cache the non-patched version and use it here as + // that would break if another library patches the method after Angular does (one + // example is jQuery UI). Instead, set a flag indicating scope destroying should be + // skipped this one time. + skipDestroyOnNextJQueryCleanData = true; + jQuery.cleanData([firstElementToRemove]); + } } for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { var element = elementsToRemove[k]; jqLite(element).remove(); // must do this way to clean up expando @@ -8618,13 +8656,23 @@ optional = definition.optional, mode = definition.mode, // @, =, or & lastValue, parentGet, parentSet, compare; + if (!hasOwnProperty.call(attrs, attrName)) { + // In the case of user defined a binding with the same name as a method in Object.prototype but didn't set + // the corresponding attribute. We need to make sure subsequent code won't access to the prototype function + attrs[attrName] = undefined; + } + switch (mode) { case '@': + if (!attrs[attrName] && !optional) { + destination[scopeName] = undefined; + } + attrs.$observe(attrName, function(value) { destination[scopeName] = value; }); attrs.$$observers[attrName].$$scope = scope; if (attrs[attrName]) { @@ -8637,10 +8685,11 @@ case '=': if (optional && !attrs[attrName]) { return; } parentGet = $parse(attrs[attrName]); + if (parentGet.literal) { compare = equals; } else { compare = function(a, b) { return a === b || (a !== a && b !== b); }; } @@ -8675,13 +8724,10 @@ onNewScopeDestroyed = (onNewScopeDestroyed || []); onNewScopeDestroyed.push(unwatch); break; case '&': - // Don't assign Object.prototype method to scope - if (!attrs.hasOwnProperty(attrName) && optional) break; - parentGet = $parse(attrs[attrName]); // Don't assign noop to destination if expression is not valid if (parentGet === noop && optional) break; @@ -9078,17 +9124,21 @@ /** * @ngdoc service * @name $httpParamSerializer * @description * - * Default $http params serializer that converts objects to a part of a request URL + * Default {@link $http `$http`} params serializer that converts objects to strings * according to the following rules: + * * * `{'foo': 'bar'}` results in `foo=bar` * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object) + * + * Note that serializer will sort the request parameters alphabetically. * */ + this.$get = function() { return function ngParamSerializer(params) { if (!params) return ''; var parts = []; forEachSorted(params, function(value, key) { @@ -9111,11 +9161,47 @@ /** * @ngdoc service * @name $httpParamSerializerJQLike * @description * - * Alternative $http params serializer that follows jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. + * Alternative {@link $http `$http`} params serializer that follows + * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. + * The serializer will also sort the params alphabetically. + * + * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: + * + * ```js + * $http({ + * url: myUrl, + * method: 'GET', + * params: myParams, + * paramSerializer: '$httpParamSerializerJQLike' + * }); + * ``` + * + * It is also possible to set it as the default `paramSerializer` in the + * {@link $httpProvider#defaults `$httpProvider`}. + * + * Additionally, you can inject the serializer and use it explicitly, for example to serialize + * form data for submission: + * + * ```js + * .controller(function($http, $httpParamSerializerJQLike) { + * //... + * + * $http({ + * url: myUrl, + * method: 'POST', + * data: $httpParamSerializerJQLike(myData), + * headers: { + * 'Content-Type': 'application/x-www-form-urlencoded' + * } + * }); + * + * }); + * ``` + * * */ this.$get = function() { return function jQueryLikeParamSerializer(params) { if (!params) return ''; var parts = []; @@ -9285,15 +9371,16 @@ * - **`defaults.headers.common`** * - **`defaults.headers.post`** * - **`defaults.headers.put`** * - **`defaults.headers.patch`** * - * - **`defaults.paramSerializer`** - {string|function(Object<string,string>):string} - A function used to prepare string representation - * of request parameters (specified as an object). - * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. - * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. * + * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function + * used to the prepare string representation of request parameters (specified as an object). + * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. + * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. + * **/ var defaults = this.defaults = { // transform incoming response data transformResponse: [defaultHttpResponseTransform], @@ -9754,19 +9841,21 @@ * * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, * or the per-request config object. * + * In order to prevent collisions in environments where multiple Angular apps share the + * same domain or subdomain, we recommend that each application uses unique cookie name. * + * * @param {object} config Object describing the request to be made and how it should be * processed. The object has following properties: * * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. - * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be turned - * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be - * JSONified. + * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized + * with the `paramSerializer` and appended as GET parameters. * - **data** – `{string|Object}` – Data to be sent as the request message data. * - **headers** – `{Object}` – Map of strings or functions which return strings representing * HTTP headers to send to the server. If the return value of a function is null, the * header will not be sent. Functions accept a config object as an argument. * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. @@ -9780,14 +9869,18 @@ * - **transformResponse** – * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` – * transform function or an array of such functions. The transform function takes the http * response body, headers and status and returns its transformed (typically deserialized) version. * See {@link ng.$http#overriding-the-default-transformations-per-request - * Overriding the Default Transformations} - * - **paramSerializer** - {string|function(Object<string,string>):string} - A function used to prepare string representation - * of request parameters (specified as an object). - * Is specified as string, it is interpreted as function registered in with the {$injector}. + * Overriding the Default TransformationjqLiks} + * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to + * prepare the string representation of request parameters (specified as an object). + * If specified as string, it is interpreted as function registered with the + * {@link $injector $injector}, which means you can create your own serializer + * by registering it as a {@link auto.$provide#service service}. + * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; + * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the * GET request, otherwise if a cache instance built with * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for * caching. * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} @@ -13211,12 +13304,14 @@ if (nameId) { nameId.computed = false; nameId.name = ast.property.name; } } - recursionFn(intoId); + }, function() { + self.assign(intoId, 'undefined'); }); + recursionFn(intoId); }, !!create); break; case AST.CallExpression: intoId = intoId || this.nextId(); if (ast.filter) { @@ -13250,12 +13345,14 @@ } else { expression = right + '(' + args.join(',') + ')'; } expression = self.ensureSafeObject(expression); self.assign(intoId, expression); - recursionFn(intoId); + }, function() { + self.assign(intoId, 'undefined'); }); + recursionFn(intoId); }); } break; case AST.AssignmentExpression: right = this.nextId(); @@ -14634,10 +14731,23 @@ return result.promise.then(callback, errback, progressBack); }; /** * @ngdoc method + * @name $q#resolve + * @kind function + * + * @description + * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. + * + * @param {*} value Value or a promise + * @returns {Promise} Returns a promise of the passed value or promise + */ + var resolve = when; + + /** + * @ngdoc method * @name $q#all * @kind function * * @description * Combines multiple promises into a single promise that is resolved when all of the input @@ -14700,10 +14810,11 @@ }; $Q.defer = defer; $Q.reject = reject; $Q.when = when; + $Q.resolve = resolve; $Q.all = all; return $Q; } @@ -18009,14 +18120,16 @@ * Note that a named property will match properties on the same level only, while the special * `$` property will match properties on the same level or deeper. E.g. an array item like * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but * **will** be matched by `{$: 'John'}`. * - * - `function(value, index)`: A predicate function can be used to write arbitrary filters. The - * function is called for each element of `array`. The final result is an array of those - * elements that the predicate returned true for. + * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. + * The function is called for each element of the array, with the element, its index, and + * the entire array itself as arguments. * + * The final result is an array of those elements that the predicate returned true for. + * * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in * determining if the expected value (from the filter expression) and actual value (from * the object in the array) should be considered a match. * * Can be one of: @@ -18312,13 +18425,14 @@ * @kind function * * @description * Formats a number as text. * + * If the input is null or undefined, it will just be returned. + * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned. * If the input is not a number an empty string is returned. * - * If the input is an infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned. * * @param {number|string} number Number to format. * @param {(number|string)=} fractionSize Number of decimal places to round the number to. * If this is not provided then the fraction size is computed from the current locale's number * formatting pattern. In the case of the default locale, it will be 3. @@ -18943,11 +19057,11 @@ * @kind function * * @description * 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. + * as expected, 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 * used by the comparator to determine the order of elements. * @@ -19018,23 +19132,44 @@ [{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.predicate = '-age'; + $scope.predicate = 'age'; + $scope.reverse = true; + $scope.order = function(predicate) { + $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; + $scope.predicate = predicate; + }; }]); </script> + <style type="text/css"> + .sortorder:after { + content: '\25b2'; + } + .sortorder.reverse:after { + content: '\25bc'; + } + </style> <div ng-controller="ExampleController"> <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> <hr/> [ <a href="" ng-click="predicate=''">unsorted</a> ] <table class="friend"> <tr> - <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> - (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th> - <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> - <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> + <th> + <a href="" ng-click="order('name')">Name</a> + <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> + </th> + <th> + <a href="" ng-click="order('phone')">Phone Number</a> + <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> + </th> + <th> + <a href="" ng-click="order('age')">Age</a> + <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> + </th> </tr> <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> <td>{{friend.name}}</td> <td>{{friend.phone}}</td> <td>{{friend.age}}</td> @@ -20193,11 +20328,11 @@ // Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/; 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-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; -var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; +var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; @@ -20792,10 +20927,20 @@ * The model must always be of type `number` otherwise Angular will throw an error. * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} * error docs for more information and an example of how to convert your model if necessary. * </div> * + * ## Issues with HTML5 constraint validation + * + * In browsers that follow the + * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), + * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. + * If a non-number is entered in the input, the browser will report the value as an empty string, + * which means the view / model values in `ngModel` and subsequently the scope value + * will also be an empty string. + * + * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * @param {string=} required Sets `required` validation error key if the value is not entered. @@ -22328,22 +22473,22 @@ * element. * * @example Example that demonstrates basic bindings via ngClass directive. <example> <file name="index.html"> - <p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p> + <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> <label> <input type="checkbox" ng-model="deleted"> deleted (apply "strike" class) </label><br> <label> <input type="checkbox" ng-model="important"> important (apply "bold" class) </label><br> <label> <input type="checkbox" ng-model="error"> - error (apply "red" class) + error (apply "has-error" class) </label> <hr> <p ng-class="style">Using String Syntax</p> <input type="text" ng-model="style" placeholder="Type: bold strike red" aria-label="Type: bold strike red"> @@ -22368,27 +22513,31 @@ font-weight: bold; } .red { color: red; } + .has-error { + color: red; + background-color: yellow; + } .orange { color: orange; } </file> <file name="protractor.js" type="protractor"> var ps = element.all(by.css('p')); it('should let you toggle the class', function() { expect(ps.first().getAttribute('class')).not.toMatch(/bold/); - expect(ps.first().getAttribute('class')).not.toMatch(/red/); + expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); element(by.model('important')).click(); expect(ps.first().getAttribute('class')).toMatch(/bold/); element(by.model('error')).click(); - expect(ps.first().getAttribute('class')).toMatch(/red/); + expect(ps.first().getAttribute('class')).toMatch(/has-error/); }); it('should let you toggle string example', function() { expect(ps.get(1).getAttribute('class')).toBe(''); element(by.model('style')).clear(); @@ -25218,11 +25367,11 @@ * events using an space delimited list. There is a special event called `default` that * matches the default events belonging of the control. * - `debounce`: integer value which contains the debounce model update value in milliseconds. A * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a * custom value for each event. For example: - * `ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"` + * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` * - `allowInvalid`: boolean value which indicates that the model can be set with values that did * not validate correctly instead of the default behavior of setting the model to undefined. * - `getterSetter`: boolean value which determines whether or not to treat functions bound to `ngModel` as getters/setters. * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for @@ -25468,11 +25617,13 @@ } function isObjectEmpty(obj) { if (obj) { for (var prop in obj) { - return false; + if (obj.hasOwnProperty(prop)) { + return false; + } } } return true; } @@ -25811,10 +25962,11 @@ // that only runs the handler once if anything changes var watchedArray = []; values = values || []; Object.keys(values).forEach(function getWatchable(key) { + if (key.charAt(0) === '$') return; var locals = getLocals(values[key], key); var selectValue = getTrackByValueFn(values[key], locals); watchedArray.push(selectValue); // Only need to watch the displayFn if there is a specific label expression @@ -26214,12 +26366,11 @@ ngModelCtrl.$render(); // Check to see if the value has changed due to the update to the options if (!ngModelCtrl.$isEmpty(previousValue)) { var nextValue = selectCtrl.readValue(); - if (ngOptions.trackBy && !equals(previousValue, nextValue) || - previousValue !== nextValue) { + if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) { ngModelCtrl.$setViewValue(nextValue); ngModelCtrl.$render(); } } @@ -26563,10 +26714,19 @@ * <div ng-repeat="obj in collection track by $id(obj)"> * {{obj.prop}} * </div> * ``` * + * <div class="alert alert-warning"> + * **Note:** `track by` must always be the last expression: + * </div> + * ``` + * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id"> + * {{model.name}} + * </div> + * ``` + * * # Special repeat start and end points * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) * up to and including the ending HTML tag where **ng-repeat-end** is placed. @@ -26634,11 +26794,12 @@ * * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression * is specified, ng-repeat associates elements by identity. It is an error to have * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are - * mapped to the same DOM element, which is not possible.) If filters are used in the expression, they should be - * applied before the tracking expression. + * mapped to the same DOM element, which is not possible.) + * + * Note that the tracking expression must come last, after any filters, and the alias expression. * * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements * will be associated by item identity in the array. * * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique \ No newline at end of file