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

- old
+ new

@@ -9188,11 +9188,11 @@ return jQuery; })); /** - * @license AngularJS v1.4.0 + * @license AngularJS v1.4.2 * (c) 2010-2015 Google, Inc. http://angularjs.org * License: MIT */ (function(window, document){ var _jQuery = window.jQuery.noConflict(true); @@ -9247,11 +9247,11 @@ } return match; }); - message += '\nhttp://errors.angularjs.org/1.4.0/' + + message += '\nhttp://errors.angularjs.org/1.4.2/' + (module ? module + '/' : '') + code; for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + encodeURIComponent(toDebugString(templateArgs[i])); @@ -9611,12 +9611,16 @@ for (var j = 0, jj = keys.length; j < jj; j++) { var key = keys[j]; var src = obj[key]; if (deep && isObject(src)) { - if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; - baseExtend(dst[key], [src], true); + if (isDate(src)) { + dst[key] = new Date(src.valueOf()); + } else { + if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; + baseExtend(dst[key], [src], true); + } } else { dst[key] = src; } } } @@ -9723,10 +9727,15 @@ identity.$inject = []; function valueFn(value) {return function() {return value;};} +function hasCustomToString(obj) { + return isFunction(obj.toString) && obj.toString !== Object.prototype.toString; +} + + /** * @ngdoc function * @name angular.isUndefined * @module ng * @kind function @@ -10054,50 +10063,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; @@ -10107,41 +10122,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. * @@ -11195,33 +11200,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 @@ -11252,11 +11257,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 @@ -11286,11 +11291,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 @@ -11304,11 +11309,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 @@ -11316,11 +11321,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 @@ -11329,11 +11334,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 @@ -11379,10 +11384,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; + }; + } }); }; }); } @@ -11522,15 +11540,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.2', // all of these placeholder strings will be replaced by grunt's major: 1, // package task minor: 4, - dot: 0, - codeName: 'jaracimrman-existence' + dot: 2, + codeName: 'nebular-readjustment' }; function publishExternalAPI(angular) { extend(angular, { @@ -11852,10 +11870,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; @@ -12226,11 +12251,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({ @@ -13435,11 +13461,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 @@ -14574,11 +14600,11 @@ } } function getHash(url) { var index = url.indexOf('#'); - return index === -1 ? '' : url.substr(index + 1); + return index === -1 ? '' : url.substr(index); } /** * @private * Note: this method is used only by scenario runner @@ -14658,11 +14684,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) { @@ -15664,17 +15690,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)`. * @@ -16117,10 +16146,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); } }); @@ -16688,12 +16718,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) { @@ -16711,11 +16740,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); @@ -16810,10 +16839,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); @@ -16909,11 +16945,11 @@ jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, previousCompileContext) { previousCompileContext = previousCompileContext || {}; var terminalPriority = -Number.MAX_VALUE, - newScopeDirective, + newScopeDirective = previousCompileContext.newScopeDirective, controllerDirectives = previousCompileContext.controllerDirectives, newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, templateDirective = previousCompileContext.templateDirective, nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, hasTranscludeDirective = false, @@ -17075,10 +17111,11 @@ } nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { controllerDirectives: controllerDirectives, + newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, newIsolateScopeDirective: newIsolateScopeDirective, templateDirective: templateDirective, nonTlbTranscludeDirective: nonTlbTranscludeDirective }); ii = directives.length; @@ -17102,11 +17139,10 @@ } nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; - nodeLinkFn.elementTranscludeOnThisElement = hasElementTranscludeDirective; nodeLinkFn.templateOnThisElement = hasTemplate; nodeLinkFn.transclude = childTranscludeFn; previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; @@ -17263,13 +17299,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); @@ -17457,11 +17496,11 @@ : origAsyncDirective.templateUrl, templateNamespace = origAsyncDirective.templateNamespace; $compileNode.empty(); - $templateRequest($sce.getTrustedResourceUrl(templateUrl)) + $templateRequest(templateUrl) .then(function(content) { var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; content = denormalizeTemplate(content); @@ -17565,15 +17604,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) { @@ -17750,30 +17796,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 @@ -17810,13 +17858,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]) { @@ -17829,10 +17887,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); }; } @@ -17867,13 +17926,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; @@ -18270,17 +18326,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) { @@ -18303,11 +18363,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 = []; @@ -18460,11 +18556,11 @@ * * Object containing default values for all {@link ng.$http $http} requests. * * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} * that will provide the cache for all requests who set their `cache` property to `true`. - * If you set the `default.cache = false` then only requests that specify their own custom + * If you set the `defaults.cache = false` then only requests that specify their own custom * cache object will be cached. See {@link $http#caching $http Caching} for more information. * * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. * Defaults value is `'XSRF-TOKEN'`. * @@ -18477,15 +18573,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], @@ -18946,19 +19043,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. @@ -18972,25 +19071,29 @@ * - **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} * that should abort the request when resolved. * - **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). + * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). * * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the * standard `then` method and two http specific methods: `success` and `error`. The `then` * method takes two arguments a success and an error callback which will be called with a * response object. The `success` and `error` methods take a single argument - a function that @@ -20469,11 +20572,11 @@ */ this.$$parse = function(url) { var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); var withoutHashUrl; - if (withoutBaseUrl.charAt(0) === '#') { + if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { // The rest of the url starts with a hash so we have // got either a hashbang path or a plain hash fragment withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); if (isUndefined(withoutHashUrl)) { @@ -20483,11 +20586,19 @@ } else { // There was no hashbang path nor hash fragment: // If we are in HTML5 mode we use what is left as the path; // Otherwise we ignore what is left - withoutHashUrl = this.$$html5 ? withoutBaseUrl : ''; + if (this.$$html5) { + withoutHashUrl = withoutBaseUrl; + } else { + withoutHashUrl = ''; + if (isUndefined(withoutBaseUrl)) { + appBase = url; + this.replace(); + } + } } parseAppUrl(withoutHashUrl, this); this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); @@ -22403,12 +22514,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) { @@ -22442,12 +22555,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(); @@ -23826,10 +23941,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 @@ -23892,10 +24020,11 @@ }; $Q.defer = defer; $Q.reject = reject; $Q.when = when; + $Q.resolve = resolve; $Q.all = all; return $Q; } @@ -26536,27 +26665,38 @@ /** * @ngdoc service * @name $templateRequest * * @description - * The `$templateRequest` service downloads the provided template using `$http` and, upon success, - * stores the contents inside of `$templateCache`. If the HTTP request fails or the response data - * of the HTTP request is empty, a `$compile` error will be thrown (the exception can be thwarted - * by setting the 2nd parameter of the function to true). + * The `$templateRequest` service runs security checks then downloads the provided template using + * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request + * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the + * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the + * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted + * when `tpl` is of type string and `$templateCache` has the matching entry. * - * @param {string} tpl The HTTP request template URL + * @param {string|TrustedResourceUrl} tpl The HTTP request template URL * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty * * @return {Promise} a promise for the HTTP response data of the given URL. * * @property {number} totalPendingRequests total amount of pending template requests being downloaded. */ function $TemplateRequestProvider() { - this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) { + this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { function handleRequestFn(tpl, ignoreRequestError) { handleRequestFn.totalPendingRequests++; + // We consider the template cache holds only trusted templates, so + // there's no need to go through whitelisting again for keys that already + // are included in there. This also makes Angular accept any script + // directive, no matter its name. However, we still need to unwrap trusted + // types. + if (!isString(tpl) || !$templateCache.get(tpl)) { + tpl = $sce.getTrustedResourceUrl(tpl); + } + var transformResponse = $http.defaults && $http.defaults.transformResponse; if (isArray(transformResponse)) { transformResponse = transformResponse.filter(function(transformer) { return transformer !== defaultHttpResponseTransform; @@ -27201,14 +27341,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: @@ -27328,14 +27470,10 @@ return Array.prototype.filter.call(array, predicateFn); }; } -function hasCustomToString(obj) { - return isFunction(obj.toString) && obj.toString !== Object.prototype.toString; -} - // Helper functions for `filterFilter` function createPredicateFn(expression, comparator, matchAgainstAnyProp) { var shouldMatchPrimitives = isObject(expression) && ('$' in expression); var predicateFn; @@ -27504,13 +27642,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. @@ -28135,11 +28274,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. * @@ -28210,23 +28349,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> @@ -28282,94 +28442,120 @@ </example> */ orderByFilter.$inject = ['$parse']; function orderByFilter($parse) { return function(array, sortPredicate, reverseOrder) { + if (!(isArrayLike(array))) return array; - sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate]; + + if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } if (sortPredicate.length === 0) { sortPredicate = ['+']; } - sortPredicate = sortPredicate.map(function(predicate) { - var descending = false, get = predicate || identity; - if (isString(predicate)) { + + var predicates = processPredicates(sortPredicate, reverseOrder); + + // The next three lines are a version of a Swartzian Transform idiom from Perl + // (sometimes called the Decorate-Sort-Undecorate idiom) + // See https://en.wikipedia.org/wiki/Schwartzian_transform + var compareValues = Array.prototype.map.call(array, getComparisonObject); + compareValues.sort(doComparison); + array = compareValues.map(function(item) { return item.value; }); + + return array; + + function getComparisonObject(value, index) { + return { + value: value, + predicateValues: predicates.map(function(predicate) { + return getPredicateValue(predicate.get(value), index); + }) + }; + } + + function doComparison(v1, v2) { + var result = 0; + for (var index=0, length = predicates.length; index < length; ++index) { + result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending; + if (result) break; + } + return result; + } + }; + + function processPredicates(sortPredicate, reverseOrder) { + reverseOrder = reverseOrder ? -1 : 1; + return sortPredicate.map(function(predicate) { + var descending = 1, get = identity; + + if (isFunction(predicate)) { + get = predicate; + } else if (isString(predicate)) { if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { - descending = predicate.charAt(0) == '-'; + descending = predicate.charAt(0) == '-' ? -1 : 1; predicate = predicate.substring(1); } - if (predicate === '') { - // Effectively no predicate was passed so we compare identity - return reverseComparator(compare, descending); + if (predicate !== '') { + get = $parse(predicate); + if (get.constant) { + var key = get(); + get = function(value) { return value[key]; }; + } } - 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)); - }, descending); + return { get: get, descending: descending * reverseOrder }; }); - return slice.call(array).sort(reverseComparator(comparator, reverseOrder)); + } - function comparator(o1, o2) { - for (var i = 0; i < sortPredicate.length; i++) { - var comp = sortPredicate[i](o1, o2); - if (comp !== 0) return comp; - } - return 0; + function isPrimitive(value) { + switch (typeof value) { + case 'number': /* falls through */ + case 'boolean': /* falls through */ + case 'string': + return true; + default: + return false; } - function reverseComparator(comp, descending) { - return descending - ? function(a, b) {return comp(b,a);} - : comp; - } + } - function isPrimitive(value) { - switch (typeof value) { - case 'number': /* falls through */ - case 'boolean': /* falls through */ - case 'string': - return true; - default: - return false; - } + function objectValue(value, index) { + // If `valueOf` is a valid function use that + if (typeof value.valueOf === 'function') { + value = value.valueOf(); + if (isPrimitive(value)) return value; } + // If `toString` is a valid function and not the one from `Object.prototype` use that + if (hasCustomToString(value)) { + value = value.toString(); + if (isPrimitive(value)) return value; + } + // We have a basic object so we use the position of the object in the collection + return index; + } - function objectToString(value) { - if (value === null) return 'null'; - if (typeof value.valueOf === 'function') { - value = value.valueOf(); - if (isPrimitive(value)) return value; - } - if (typeof value.toString === 'function') { - value = value.toString(); - if (isPrimitive(value)) return value; - } - return ''; + function getPredicateValue(value, index) { + var type = typeof value; + if (value === null) { + type = 'string'; + value = 'null'; + } else if (type === 'string') { + value = value.toLowerCase(); + } else if (type === 'object') { + value = objectValue(value, index); } + return { value: value, type: type }; + } - function compare(v1, v2) { - var t1 = typeof v1; - var t2 = typeof v2; - if (t1 === t2 && t1 === "object") { - v1 = objectToString(v1); - v2 = objectToString(v2); + function compare(v1, v2) { + var result = 0; + if (v1.type === v2.type) { + if (v1.value !== v2.value) { + result = v1.value < v2.value ? -1 : 1; } - if (t1 === t2) { - if (t1 === "string") { - v1 = v1.toLowerCase(); - v2 = v2.toLowerCase(); - } - if (v1 === v2) return 0; - return v1 < v2 ? -1 : 1; - } else { - return t1 < t2 ? -1 : 1; - } + } else { + result = v1.type < v2.type ? -1 : 1; } - }; + return result; + } } function ngDirective(directive) { if (isFunction(directive)) { directive = { @@ -28619,10 +28805,17 @@ * @name ngChecked * @restrict A * @priority 100 * * @description + * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. + * + * Note that this directive should not be used together with {@link ngModel `ngModel`}, + * as this can lead to unexpected behavior. + * + * ### Why do we need `ngChecked`? + * * The HTML specification does not require browsers to preserve the values of boolean attributes * such as checked. (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 `ngChecked` directive solves this problem for the `checked` attribute. @@ -28643,11 +28836,11 @@ </file> </example> * * @element INPUT * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, - * then special attribute "checked" will be set on the element + * then the `checked` attribute will be set on the element */ /** * @ngdoc directive @@ -29385,11 +29578,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})?)?$/; @@ -29984,10 +30177,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. @@ -30275,16 +30478,19 @@ * * @description * HTML radio button. * * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string} value The value to which the expression should be set when selected. + * @param {string} value The value to which the `ngModel` expression should be set when selected. + * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, + * too. Use `ngValue` if you need complex models (`number`, `object`, ...). * @param {string=} name Property name of the form under which the control is published. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. - * @param {string} ngValue Angular expression which sets the value to which the expression should - * be set when selected. + * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio + * is selected. Should be used instead of the `value` attribute if you need + * a non-string `ngModel` (`boolean`, `array`, ...). * * @example <example name="radio-input-directive" module="radioExample"> <file name="index.html"> <script> @@ -31520,22 +31726,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"> @@ -31560,27 +31766,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(); @@ -32704,10 +32914,11 @@ /** * @ngdoc directive * @name ngIf * @restrict A + * @multiElement * * @description * The `ngIf` directive removes or recreates a portion of the DOM tree based on an * {expression}. If the expression assigned to `ngIf` evaluates to a false * value then the element is removed from the DOM, otherwise a clone of the @@ -33002,12 +33213,12 @@ * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) * * @param {Object} angularEvent Synthetic event object. * @param {String} src URL of content to load. */ -var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', '$sce', - function($templateRequest, $anchorScroll, $animate, $sce) { +var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', + function($templateRequest, $anchorScroll, $animate) { return { restrict: 'ECA', priority: 400, terminal: true, transclude: 'element', @@ -33039,11 +33250,11 @@ previousElement = currentElement; currentElement = null; } }; - scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { + scope.$watch(srcExp, function ngIncludeWatchAction(src) { var afterAnimation = function() { if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { $anchorScroll(); } }; @@ -34410,11 +34621,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 @@ -34660,11 +34871,13 @@ } function isObjectEmpty(obj) { if (obj) { for (var prop in obj) { - return false; + if (obj.hasOwnProperty(prop)) { + return false; + } } } return true; } @@ -34992,23 +35205,45 @@ this.label = label; this.group = group; this.disabled = disabled; } + function getOptionValuesKeys(optionValues) { + var optionValuesKeys; + + if (!keyName && isArrayLike(optionValues)) { + optionValuesKeys = optionValues; + } else { + // if object, extract keys, in enumeration order, unsorted + optionValuesKeys = []; + for (var itemKey in optionValues) { + if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { + optionValuesKeys.push(itemKey); + } + } + } + return optionValuesKeys; + } + return { trackBy: trackBy, getTrackByValue: getTrackByValue, - getWatchables: $parse(valuesFn, function(values) { + getWatchables: $parse(valuesFn, function(optionValues) { // Create a collection of things that we would like to watch (watchedArray) // so that they can all be watched using a single $watchCollection // that only runs the handler once if anything changes var watchedArray = []; - values = values || []; + optionValues = optionValues || []; - Object.keys(values).forEach(function getWatchable(key) { - var locals = getLocals(values[key], key); - var selectValue = getTrackByValueFn(values[key], locals); + var optionValuesKeys = getOptionValuesKeys(optionValues); + var optionValuesLength = optionValuesKeys.length; + for (var index = 0; index < optionValuesLength; index++) { + var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; + var value = optionValues[key]; + + var locals = getLocals(optionValues[key], key); + var selectValue = getTrackByValueFn(optionValues[key], locals); watchedArray.push(selectValue); // Only need to watch the displayFn if there is a specific label expression if (match[2] || match[1]) { var label = displayFn(scope, locals); @@ -35018,11 +35253,11 @@ // Only need to watch the disableWhenFn if there is a specific disable expression if (match[4]) { var disableWhen = disableWhenFn(scope, locals); watchedArray.push(disableWhen); } - }); + } return watchedArray; }), getOptions: function() { @@ -35030,25 +35265,11 @@ var selectValueMap = {}; // The option values were already computed in the `getWatchables` fn, // which must have been called to trigger `getOptions` var optionValues = valuesFn(scope) || []; - var optionValuesKeys; - - - if (!keyName && isArrayLike(optionValues)) { - optionValuesKeys = optionValues; - } else { - // if object, extract keys, in enumeration order, unsorted - optionValuesKeys = []; - for (var itemKey in optionValues) { - if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { - optionValuesKeys.push(itemKey); - } - } - } - + var optionValuesKeys = getOptionValuesKeys(optionValues); var optionValuesLength = optionValuesKeys.length; for (var index = 0; index < optionValuesLength; index++) { var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; var value = optionValues[key]; @@ -35406,12 +35627,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(); } } @@ -35661,10 +35881,11 @@ }]; /** * @ngdoc directive * @name ngRepeat + * @multiElement * * @description * The `ngRepeat` directive instantiates a template once per item from a collection. Each template * instance gets its own scope, where the given loop variable is set to the current collection item, * and `$index` is set to the item index or key. @@ -35755,10 +35976,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. @@ -35826,13 +36056,14 @@ * * * `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 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements @@ -36180,10 +36411,11 @@ var NG_HIDE_CLASS = 'ng-hide'; var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; /** * @ngdoc directive * @name ngShow + * @multiElement * * @description * The `ngShow` directive shows or hides the given HTML element based on the expression * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined @@ -36355,9 +36587,10 @@ /** * @ngdoc directive * @name ngHide + * @multiElement * * @description * The `ngHide` directive shows or hides the given HTML element based on the expression * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined \ No newline at end of file