vendor/assets/javascripts/angular.js in rails-angularjs-1.4.0.pre.rc.1 vs vendor/assets/javascripts/angular.js in rails-angularjs-1.4.0.pre.rc.2
- old
+ new
@@ -1,7 +1,7 @@
/**
- * @license AngularJS v1.4.0-rc.1
+ * @license AngularJS v1.4.0-rc.2
* (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-rc.1/' +
+ message += '\nhttp://errors.angularjs.org/1.4.0-rc.2/' +
(module ? module + '/' : '') + code;
for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
encodeURIComponent(toDebugString(templateArgs[i]));
@@ -103,10 +103,11 @@
identity: true,
valueFn: true,
isUndefined: true,
isDefined: true,
isObject: true,
+ isBlankObject: true,
isString: true,
isNumber: true,
isDate: true,
isArray: true,
isFunction: true,
@@ -242,10 +243,11 @@
jQuery, // delay binding
slice = [].slice,
splice = [].splice,
push = [].push,
toString = Object.prototype.toString,
+ getPrototypeOf = Object.getPrototypeOf,
ngMinErr = minErr('ng'),
/** @name angular */
angular = window.angular || (window.angular = {}),
angularModule,
@@ -267,11 +269,13 @@
function isArrayLike(obj) {
if (obj == null || isWindow(obj)) {
return false;
}
- var length = obj.length;
+ // Support: iOS 8.2 (not reproducible in simulator)
+ // "length" in obj used to prevent JIT error (gh-11508)
+ var length = "length" in Object(obj) && obj.length;
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
return true;
}
@@ -332,16 +336,29 @@
iterator.call(context, obj[key], key, obj);
}
}
} else if (obj.forEach && obj.forEach !== forEach) {
obj.forEach(iterator, context, obj);
- } else {
+ } else if (isBlankObject(obj)) {
+ // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
for (key in obj) {
+ iterator.call(context, obj[key], key, obj);
+ }
+ } else if (typeof obj.hasOwnProperty === 'function') {
+ // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
+ for (key in obj) {
if (obj.hasOwnProperty(key)) {
iterator.call(context, obj[key], key, obj);
}
}
+ } else {
+ // Slow path for objects which do not have a method `hasOwnProperty`
+ for (key in obj) {
+ if (hasOwnProperty.call(obj, key)) {
+ iterator.call(context, obj[key], key, obj);
+ }
+ }
}
}
return obj;
}
@@ -564,10 +581,20 @@
return value !== null && typeof value === 'object';
}
/**
+ * Determine if a value is an object with a null prototype
+ *
+ * @returns {boolean} True if `value` is an `Object` with a null prototype
+ */
+function isBlankObject(value) {
+ return value !== null && typeof value === 'object' && !getPrototypeOf(value);
+}
+
+
+/**
* @ngdoc function
* @name angular.isString
* @module ng
* @kind function
*
@@ -846,11 +873,11 @@
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)) {
- var emptyObject = Object.create(Object.getPrototypeOf(source));
+ var emptyObject = Object.create(getPrototypeOf(source));
destination = copy(source, emptyObject, stackSource, stackDest);
}
}
} else {
if (source === destination) throw ngMinErr('cpi',
@@ -865,11 +892,11 @@
stackSource.push(source);
stackDest.push(destination);
}
- var result;
+ 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])) {
@@ -885,25 +912,44 @@
} else {
forEach(destination, function(value, key) {
delete destination[key];
});
}
- for (var key in source) {
- if (source.hasOwnProperty(key)) {
- result = copy(source[key], null, stackSource, stackDest);
- if (isObject(source[key])) {
- stackSource.push(source[key]);
- stackDest.push(result);
+ 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);
+ }
+ } 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] = result;
}
+ } 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);
+ }
+ }
}
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.
*
@@ -980,18 +1026,18 @@
} else if (isRegExp(o1)) {
return isRegExp(o2) ? o1.toString() == o2.toString() : false;
} else {
if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
- keySet = {};
+ keySet = createMap();
for (key in o1) {
if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
if (!equals(o1[key], o2[key])) return false;
keySet[key] = true;
}
for (key in o2) {
- if (!keySet.hasOwnProperty(key) &&
+ if (!(key in keySet) &&
key.charAt(0) !== '$' &&
o2[key] !== undefined &&
!isFunction(o2[key])) return false;
}
return true;
@@ -1024,21 +1070,21 @@
* @ngdoc directive
* @module ng
* @name ngJq
*
* @element ANY
- * @param {string=} the name of the library available under `window`
+ * @param {string=} ngJq the name of the library available under `window`
* to be used for angular.element
* @description
* Use this directive to force the angular.element library. This should be
* used to force either jqLite by leaving ng-jq blank or setting the name of
* the jquery variable under window (eg. jQuery).
*
- * Since this directive is global for the angular library, it is recommended
- * that it's added to the same element as ng-app or the HTML element, but it is not mandatory.
- * It needs to be noted that only the first instance of `ng-jq` will be used and all others
- * ignored.
+ * Since angular looks for this directive when it is loaded (doesn't wait for the
+ * DOMContentLoaded event), it must be placed on an element that comes before the script
+ * which loads angular. Also, only the first instance of `ng-jq` will be used and all
+ * others ignored.
*
* @example
* This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
```html
<!doctype html>
@@ -2284,15 +2330,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-rc.1', // all of these placeholder strings will be replaced by grunt's
+ full: '1.4.0-rc.2', // all of these placeholder strings will be replaced by grunt's
major: 1, // package task
minor: 4,
dot: 0,
- codeName: 'sartorial-chronography'
+ codeName: 'rocket-zambonimation'
};
function publishExternalAPI(angular) {
extend(angular, {
@@ -2471,11 +2517,11 @@
*
* <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
* Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
* commonly needed functionality with the goal of having a very small footprint.</div>
*
- * To use jQuery, simply load it before `DOMContentLoaded` event fired.
+ * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
*
* <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
* jqLite; they are never raw DOM references.</div>
*
* ## Angular's jqLite
@@ -2487,11 +2533,11 @@
* - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
* - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
* - [`clone()`](http://api.jquery.com/clone/)
* - [`contents()`](http://api.jquery.com/contents/)
- * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`
+ * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'.
* - [`data()`](http://api.jquery.com/data/)
* - [`detach()`](http://api.jquery.com/detach/)
* - [`empty()`](http://api.jquery.com/empty/)
* - [`eq()`](http://api.jquery.com/eq/)
* - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
@@ -3731,23 +3777,23 @@
*
* @description
* Return an instance of the service.
*
* @param {string} name The name of the instance to retrieve.
- * @param {string} caller An optional string to provide the origin of the function call for error messages.
+ * @param {string=} caller An optional string to provide the origin of the function call for error messages.
* @return {*} The instance.
*/
/**
* @ngdoc method
* @name $injector#invoke
*
* @description
* Invoke the method and supply the method arguments from the `$injector`.
*
- * @param {!Function} fn The function to invoke. Function parameters are injected according to the
- * {@link guide/di $inject Annotation} rules.
+ * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
+ * injected according to the {@link guide/di $inject Annotation} rules.
* @param {Object=} self The `this` for the invoked method.
* @param {Object=} locals Optional object. If preset then any argument names are read from this
* object first, before the `$injector` is consulted.
* @returns {*} the value returned by the invoked `fn` function.
*/
@@ -4010,12 +4056,12 @@
* which is the given service factory function.
* You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
* configure your service in a provider.
*
* @param {string} name The name of the instance.
- * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand
- * for `$provide.provider(name, {$get: $getFn})`.
+ * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
+ * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
* @returns {Object} registered provider instance
*
* @example
* Here is an example of registering a service
* ```js
@@ -4046,11 +4092,12 @@
*
* You should use {@link auto.$provide#service $provide.service(class)} if you define your service
* as a type/class.
*
* @param {string} name The name of the instance.
- * @param {Function} constructor A class (constructor function) that will be instantiated.
+ * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
+ * that will be instantiated.
* @returns {Object} registered provider instance
*
* @example
* Here is an example of registering a service using
* {@link auto.$provide#service $provide.service(class)}.
@@ -4145,11 +4192,11 @@
* intercepts the creation of a service, allowing it to override or modify the behaviour of the
* service. The object returned by the decorator may be the original service, or a new service
* object which replaces or wraps and delegates to the original service.
*
* @param {string} name The name of the service to decorate.
- * @param {function()} decorator This function will be invoked when the service needs to be
+ * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
* instantiated and should return the decorated service instance. The function is called using
* the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
* Local injection arguments:
*
* * `$delegate` - The original service instance, which can be monkey patched, configured,
@@ -4674,10 +4721,11 @@
}];
}
var $animateMinErr = minErr('$animate');
var ELEMENT_NODE = 1;
+var NG_ANIMATE_CLASSNAME = 'ng-animate';
function mergeClasses(a,b) {
if (!a && !b) return '';
if (!a) return b;
if (!b) return a;
@@ -4698,11 +4746,13 @@
function splitClasses(classes) {
if (isString(classes)) {
classes = classes.split(' ');
}
- var obj = {};
+ // Use createMap() to prevent class assumptions involving property names in
+ // Object.prototype
+ var obj = createMap();
forEach(classes, function(klass) {
// sometimes the split leaves empty string values
// incase extra spaces were applied to the options
if (klass.length) {
obj[klass] = true;
@@ -4903,10 +4953,17 @@
* @return {RegExp} The current CSS className expression value. If null then there is no expression value
*/
this.classNameFilter = function(expression) {
if (arguments.length === 1) {
this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
+ if (this.$$classNameFilter) {
+ var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)");
+ if (reservedRegex.test(this.$$classNameFilter.toString())) {
+ throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
+
+ }
+ }
}
return this.$$classNameFilter;
};
this.$get = ['$$animateQueue', function($$animateQueue) {
@@ -6801,10 +6858,15 @@
function assertValidDirectiveName(name) {
var letter = name.charAt(0);
if (!letter || letter !== lowercase(letter)) {
throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
}
+ if (name !== name.trim()) {
+ throw $compileMinErr('baddir',
+ "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
+ name);
+ }
}
/**
* @ngdoc method
* @name $compileProvider#directive
@@ -8985,38 +9047,18 @@
'[': /]$/,
'{': /}$/
};
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
-function paramSerializerFactory(jQueryMode) {
-
- function serializeValue(v) {
- if (isObject(v)) {
- return isDate(v) ? v.toISOString() : toJson(v);
- }
- return v;
+function serializeValue(v) {
+ if (isObject(v)) {
+ return isDate(v) ? v.toISOString() : toJson(v);
}
-
- return function paramSerializer(params) {
- if (!params) return '';
- var parts = [];
- forEachSorted(params, function(value, key) {
- if (value === null || isUndefined(value)) return;
- if (isArray(value) || isObject(value) && jQueryMode) {
- forEach(value, function(v, k) {
- var keySuffix = jQueryMode ? '[' + (!isArray(value) ? k : '') + ']' : '';
- parts.push(encodeUriQuery(key + keySuffix) + '=' + encodeUriQuery(serializeValue(v)));
- });
- } else {
- parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
- }
- });
-
- return parts.length > 0 ? parts.join('&') : '';
- };
+ return v;
}
+
function $HttpParamSerializerProvider() {
/**
* @ngdoc service
* @name $httpParamSerializer
* @description
@@ -9027,11 +9069,26 @@
* * `{'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)
* */
this.$get = function() {
- return paramSerializerFactory(false);
+ return function ngParamSerializer(params) {
+ if (!params) return '';
+ var parts = [];
+ forEachSorted(params, function(value, key) {
+ if (value === null || isUndefined(value)) return;
+ if (isArray(value)) {
+ forEach(value, function(v, k) {
+ parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
+ });
+ } else {
+ parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
+ }
+ });
+
+ return parts.join('&');
+ };
};
}
function $HttpParamSerializerJQLikeProvider() {
/**
@@ -9040,11 +9097,34 @@
* @description
*
* Alternative $http params serializer that follows jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
* */
this.$get = function() {
- return paramSerializerFactory(true);
+ return function jQueryLikeParamSerializer(params) {
+ if (!params) return '';
+ var parts = [];
+ serialize(params, '', true);
+ return parts.join('&');
+
+ function serialize(toSerialize, prefix, topLevel) {
+ if (toSerialize === null || isUndefined(toSerialize)) return;
+ if (isArray(toSerialize)) {
+ forEach(toSerialize, function(value) {
+ serialize(value, prefix + '[]');
+ });
+ } else if (isObject(toSerialize) && !isDate(toSerialize)) {
+ forEachSorted(toSerialize, function(value, key) {
+ serialize(value, prefix +
+ (topLevel ? '' : '[') +
+ key +
+ (topLevel ? '' : ']'));
+ });
+ } else {
+ parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
+ }
+ }
+ };
};
}
function defaultHttpResponseTransform(data, headers) {
if (isString(data)) {
@@ -11409,15 +11489,23 @@
* @description
* This method is getter only.
*
* Return host of current url.
*
+ * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
*
+ *
* ```js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
* var host = $location.host();
* // => "example.com"
+ *
+ * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
+ * host = $location.host();
+ * // => "example.com"
+ * host = location.host;
+ * // => "example.com:8080"
* ```
*
* @return {string} host of current url.
*/
host: locationGetter('$$host'),
@@ -14170,13 +14258,15 @@
* as soon as the result is available. The callbacks are called with a single argument: the result
* or rejection reason. Additionally, the notify callback may be called zero or more times to
* provide a progress indication, before the promise is resolved or rejected.
*
* This method *returns a new promise* which is resolved or rejected via the return value of the
- * `successCallback`, `errorCallback`. It also notifies via the return value of the
- * `notifyCallback` method. The promise cannot be resolved or rejected from the notifyCallback
- * method.
+ * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
+ * with the value which is resolved in that promise using
+ * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
+ * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
+ * resolved or rejected from the notifyCallback method.
*
* - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
*
* - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
* but to do so without modifying the final value. This is useful to release resources or do some
@@ -14608,11 +14698,11 @@
var cancelAnimationFrame = $window.cancelAnimationFrame ||
$window.webkitCancelAnimationFrame ||
$window.webkitCancelRequestAnimationFrame;
var rafSupported = !!requestAnimationFrame;
- var raf = rafSupported
+ var rafFn = rafSupported
? function(fn) {
var id = requestAnimationFrame(fn);
return function() {
cancelAnimationFrame(id);
};
@@ -14622,13 +14712,51 @@
return function() {
$timeout.cancel(timer);
};
};
- raf.supported = rafSupported;
+ queueFn.supported = rafSupported;
- return raf;
+ var cancelLastRAF;
+ var taskCount = 0;
+ var taskQueue = [];
+ return queueFn;
+
+ function flush() {
+ for (var i = 0; i < taskQueue.length; i++) {
+ var task = taskQueue[i];
+ if (task) {
+ taskQueue[i] = null;
+ task();
+ }
+ }
+ taskCount = taskQueue.length = 0;
+ }
+
+ function queueFn(asyncFn) {
+ var index = taskQueue.length;
+
+ taskCount++;
+ taskQueue.push(asyncFn);
+
+ if (index === 0) {
+ cancelLastRAF = rafFn(flush);
+ }
+
+ return function cancelQueueFn() {
+ if (index >= 0) {
+ taskQueue[index] = null;
+ index = null;
+
+ if (--taskCount === 0 && cancelLastRAF) {
+ cancelLastRAF();
+ cancelLastRAF = null;
+ taskQueue.length = 0;
+ }
+ }
+ };
+ }
}];
}
/**
* DESIGN NOTES
@@ -19930,15 +20058,15 @@
}
</style>
<form name="myForm" ng-controller="FormController" class="my-form">
userType: <input name="input" ng-model="userType" required>
<span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
- <tt>userType = {{userType}}</tt><br>
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br>
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br>
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
+ <code>userType = {{userType}}</code><br>
+ <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
+ <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
+ <code>myForm.$valid = {{myForm.$valid}}</code><br>
+ <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
</form>
</file>
<file name="protractor.js" type="protractor">
it('should initialize to model', function() {
var userType = element(by.binding('userType'));
@@ -22032,11 +22160,13 @@
var newClasses = digestClassCounts(classes, -1);
attr.$removeClass(newClasses);
}
function digestClassCounts(classes, count) {
- var classCounts = element.data('$classCounts') || {};
+ // Use createMap() to prevent class assumptions involving property
+ // names in Object.prototype
+ var classCounts = element.data('$classCounts') || createMap();
var classesToUpdate = [];
forEach(classes, function(className) {
if (count > 0 || classCounts[className]) {
classCounts[className] = (classCounts[className] || 0) + count;
if (classCounts[className] === +(count > 0)) {
@@ -22415,21 +22545,17 @@
*
* For the best result, the `angular.js` script must be loaded in the head section of the html
* document; alternatively, the css rule above must be included in the external stylesheet of the
* application.
*
- * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
- * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
- * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below.
- *
* @element ANY
*
* @example
<example>
<file name="index.html">
<div id="template1" ng-cloak>{{ 'hello' }}</div>
- <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
+ <div id="template2" class="ng-cloak">{{ 'world' }}</div>
</file>
<file name="protractor.js" type="protractor">
it('should remove the template directive and css class', function() {
expect($('#template1').getAttribute('ng-cloak')).
toBeNull();
@@ -24454,11 +24580,11 @@
* Runs each of the registered validators (first synchronous validators and then
* asynchronous validators).
* If the validity changes to invalid, the model will be set to `undefined`,
* unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
* If the validity changes to valid, it will set the model to the last available valid
- * modelValue, i.e. either the last parsed value or the last value set from the scope.
+ * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
*/
this.$validate = function() {
// ignore $validate before model is initialized
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
return;
@@ -24946,14 +25072,15 @@
angular.module('getterSetterExample', [])
.controller('ExampleController', ['$scope', function($scope) {
var _name = 'Brian';
$scope.user = {
name: function(newName) {
- if (angular.isDefined(newName)) {
- _name = newName;
- }
- return _name;
+ // Note that newName can be undefined for two reasons:
+ // 1. Because it is called as a getter and thus called with no arguments
+ // 2. Because the property should actually be set to undefined. This happens e.g. if the
+ // input is invalid
+ return arguments.length ? (_name = newName) : _name;
}
};
}]);
</file>
* </example>
@@ -25163,11 +25290,15 @@
angular.module('getterSetterExample', [])
.controller('ExampleController', ['$scope', function($scope) {
var _name = 'Brian';
$scope.user = {
name: function(newName) {
- return angular.isDefined(newName) ? (_name = newName) : _name;
+ // Note that newName can be undefined for two reasons:
+ // 1. Because it is called as a getter and thus called with no arguments
+ // 2. Because the property should actually be set to undefined. This happens e.g. if the
+ // input is invalid
+ return arguments.length ? (_name = newName) : _name;
}
};
}]);
</file>
</example>
@@ -25363,16 +25494,26 @@
*
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
* option. See example below for demonstration.
*
- * <div class="alert alert-warning">
- * **Note:** By default, `ngModel` compares by reference, not value. This is important when binding to an
- * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). When using `track by`
- * in an `ngOptions` expression, however, deep equality checks will be performed.
- * </div>
+ * ## Complex Models (objects or collections)
*
+ * **Note:** By default, `ngModel` watches the model by reference, not value. This is important when
+ * binding any input directive to a model that is an object or a collection.
+ *
+ * Since this is a common situation for `ngOptions` the directive additionally watches the model using
+ * `$watchCollection` when the select has the `multiple` attribute or when there is a `track by` clause in
+ * the options expression. This allows ngOptions to trigger a re-rendering of the options even if the actual
+ * object/collection has not changed identity but only a property on the object or an item in the collection
+ * changes.
+ *
+ * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
+ * if the model is an array). This means that changing a property deeper inside the object/collection that the
+ * first level will not trigger a re-rendering.
+ *
+ *
* ## `select` **`as`**
*
* Using `select` **`as`** will bind the result of the `select` expression to the model, but
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
* or property name (for object data sources) of the value within the collection. If a **`track by`** expression
@@ -25583,13 +25724,17 @@
var trackByFn = trackBy && $parse(trackBy);
// Get the value by which we are going to track the option
// if we have a trackFn then use that (passing scope and locals)
// otherwise just hash the given viewValue
- var getTrackByValue = trackBy ?
- function(viewValue, locals) { return trackByFn(scope, locals); } :
- function getHashOfValue(viewValue) { return hashKey(viewValue); };
+ var getTrackByValueFn = trackBy ?
+ function(value, locals) { return trackByFn(scope, locals); } :
+ function getHashOfValue(value) { return hashKey(value); };
+ var getTrackByValue = function(value, key) {
+ return getTrackByValueFn(value, getLocals(value, key));
+ };
+
var displayFn = $parse(match[2] || match[1]);
var groupByFn = $parse(match[3] || '');
var disableWhenFn = $parse(match[4] || '');
var valuesFn = $parse(match[8]);
@@ -25612,24 +25757,25 @@
this.disabled = disabled;
}
return {
trackBy: trackBy,
+ getTrackByValue: getTrackByValue,
getWatchables: $parse(valuesFn, function(values) {
// 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 || [];
Object.keys(values).forEach(function getWatchable(key) {
var locals = getLocals(values[key], key);
- var selectValue = getTrackByValue(values[key], locals);
+ var selectValue = getTrackByValueFn(values[key], locals);
watchedArray.push(selectValue);
// Only need to watch the displayFn if there is a specific label expression
- if (match[2]) {
+ if (match[2] || match[1]) {
var label = displayFn(scope, locals);
watchedArray.push(label);
}
// Only need to watch the disableWhenFn if there is a specific disable expression
@@ -25647,35 +25793,47 @@
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;
- var keys = Object.keys(optionValues);
- keys.forEach(function getOption(key) {
- // Ignore "angular" properties that start with $ or $$
- if (key.charAt(0) === '$') return;
+ 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 optionValuesLength = optionValuesKeys.length;
+
+ for (var index = 0; index < optionValuesLength; index++) {
+ var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
var value = optionValues[key];
var locals = getLocals(value, key);
var viewValue = viewValueFn(scope, locals);
- var selectValue = getTrackByValue(viewValue, locals);
+ var selectValue = getTrackByValueFn(viewValue, locals);
var label = displayFn(scope, locals);
var group = groupByFn(scope, locals);
var disabled = disableWhenFn(scope, locals);
var optionItem = new Option(selectValue, viewValue, label, group, disabled);
optionItems.push(optionItem);
selectValueMap[selectValue] = optionItem;
- });
+ }
return {
items: optionItems,
selectValueMap: selectValueMap,
getOptionFromViewValue: function(value) {
- return selectValueMap[getTrackByValue(value, getLocals(value))];
+ return selectValueMap[getTrackByValue(value)];
},
getViewValueFromOption: function(option) {
// If the viewValue could be an object that may be mutated by the application,
// we need to make a copy and not return the reference to the value on the option.
return trackBy ? angular.copy(option.viewValue) : option.viewValue;
@@ -25749,49 +25907,59 @@
var removeUnknownOption = function() {
unknownOption.remove();
};
- selectCtrl.writeValue = function writeNgOptionsValue(value) {
- var option = options.getOptionFromViewValue(value);
+ // Update the controller methods for multiple selectable options
+ if (!multiple) {
- if (option && !option.disabled) {
- if (selectElement[0].value !== option.selectValue) {
- removeUnknownOption();
- removeEmptyOption();
+ selectCtrl.writeValue = function writeNgOptionsValue(value) {
+ var option = options.getOptionFromViewValue(value);
- selectElement[0].value = option.selectValue;
- option.element.selected = true;
- option.element.setAttribute('selected', 'selected');
- }
- } else {
- if (value === null || providedEmptyOption) {
- removeUnknownOption();
- renderEmptyOption();
+ if (option && !option.disabled) {
+ if (selectElement[0].value !== option.selectValue) {
+ removeUnknownOption();
+ removeEmptyOption();
+
+ selectElement[0].value = option.selectValue;
+ option.element.selected = true;
+ option.element.setAttribute('selected', 'selected');
+ }
} else {
- removeEmptyOption();
- renderUnknownOption();
+ if (value === null || providedEmptyOption) {
+ removeUnknownOption();
+ renderEmptyOption();
+ } else {
+ removeEmptyOption();
+ renderUnknownOption();
+ }
}
- }
- };
+ };
- selectCtrl.readValue = function readNgOptionsValue() {
+ selectCtrl.readValue = function readNgOptionsValue() {
- var selectedOption = options.selectValueMap[selectElement.val()];
+ var selectedOption = options.selectValueMap[selectElement.val()];
- if (selectedOption && !selectedOption.disabled) {
- removeEmptyOption();
- removeUnknownOption();
- return options.getViewValueFromOption(selectedOption);
+ if (selectedOption && !selectedOption.disabled) {
+ removeEmptyOption();
+ removeUnknownOption();
+ return options.getViewValueFromOption(selectedOption);
+ }
+ return null;
+ };
+
+ // If we are using `track by` then we must watch the tracked value on the model
+ // since ngModel only watches for object identity change
+ if (ngOptions.trackBy) {
+ scope.$watch(
+ function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
+ function() { ngModelCtrl.$render(); }
+ );
}
- return null;
- };
+ } else {
- // Update the controller methods for multiple selectable options
- if (multiple) {
-
ngModelCtrl.$isEmpty = function(value) {
return !value || value.length === 0;
};
@@ -25818,10 +25986,26 @@
if (!option.disabled) selections.push(options.getViewValueFromOption(option));
});
return selections;
};
+
+ // If we are using `track by` then we must watch these tracked values on the model
+ // since ngModel only watches for object identity change
+ if (ngOptions.trackBy) {
+
+ scope.$watchCollection(function() {
+ if (isArray(ngModelCtrl.$viewValue)) {
+ return ngModelCtrl.$viewValue.map(function(value) {
+ return ngOptions.getTrackByValue(value);
+ });
+ }
+ }, function() {
+ ngModelCtrl.$render();
+ });
+
+ }
}
if (providedEmptyOption) {
@@ -25844,15 +26028,10 @@
updateOptions();
// We will re-render the option elements if the option values or labels change
scope.$watchCollection(ngOptions.getWatchables, updateOptions);
- // We also need to watch to see if the internals of the model changes, since
- // ngModel only watches for object identity change
- if (ngOptions.trackBy) {
- scope.$watch(attr.ngModel, function() { ngModelCtrl.$render(); }, true);
- }
// ------------------------------------------------------------------ //
function updateOptionElement(option, element) {
option.element = element;
@@ -27192,11 +27371,11 @@
* ```
*
*
* @scope
* @priority 1200
- * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
+ * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
* On child elements add:
*
* * `ngSwitchWhen`: the case statement to match against. If match then this
* case will be displayed. If the same match appears multiple times, all the
* elements will be displayed.
@@ -27209,11 +27388,11 @@
<example module="switchExample" deps="angular-animate.js" animations="true">
<file name="index.html">
<div ng-controller="ExampleController">
<select ng-model="selection" ng-options="item for item in items">
</select>
- <tt>selection={{selection}}</tt>
+ <code>selection={{selection}}</code>
<hr/>
<div class="animate-switch-container"
ng-switch on="selection">
<div class="animate-switch" ng-switch-when="settings">Settings Div</div>
<div class="animate-switch" ng-switch-when="home">Home Span</div>
@@ -27600,16 +27779,10 @@
*
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
* option. See example below for demonstration.
*
- * <div class="alert alert-warning">
- * **Note:** By default, `ngModel` compares by reference, not value. This is important when binding to an
- * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). When using `track by`
- * in an `ngOptions` expression, however, deep equality checks will be performed.
- * </div>
- *
*/
var selectDirective = function() {
return {
restrict: 'E',
@@ -27867,6 +28040,6 @@
angularInit(document, bootstrap);
});
})(window, document);
-!window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-animate-anchor{position:absolute;}</style>');
+!window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');
\ No newline at end of file