vendor/assets/javascripts/angular-scenario.js in angularjs-rails-1.4.7 vs vendor/assets/javascripts/angular-scenario.js in angularjs-rails-1.4.8
- old
+ new
@@ -9188,11 +9188,11 @@
return jQuery;
}));
/**
- * @license AngularJS v1.4.7
+ * @license AngularJS v1.4.8
* (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.7/' +
+ message += '\nhttp://errors.angularjs.org/1.4.8/' +
(module ? module + '/' : '') + code;
for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
encodeURIComponent(toDebugString(templateArgs[i]));
@@ -9457,24 +9457,28 @@
* @param {*} obj
* @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
* String ...)
*/
function isArrayLike(obj) {
- if (obj == null || isWindow(obj)) {
- return false;
- }
+ // `null`, `undefined` and `window` are not array-like
+ if (obj == null || isWindow(obj)) return false;
+
+ // arrays, strings and jQuery/jqLite objects are array like
+ // * jqLite is either the jQuery or jqLite constructor function
+ // * we have to check the existance of jqLite first as this method is called
+ // via the forEach method when constructing the jqLite object in the first place
+ if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
+
// 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;
- }
-
- return isString(obj) || isArray(obj) || length === 0 ||
- typeof length === 'number' && length > 0 && (length - 1) in obj;
+ // NodeList objects (with `item` method) and
+ // other objects with suitable length characteristics are array-like
+ return isNumber(length) &&
+ (length >= 0 && (length - 1) in obj || typeof obj.item == 'function');
}
/**
* @ngdoc function
* @name angular.forEach
@@ -9615,10 +9619,14 @@
if (deep && isObject(src)) {
if (isDate(src)) {
dst[key] = new Date(src.valueOf());
} else if (isRegExp(src)) {
dst[key] = new RegExp(src);
+ } else if (src.nodeName) {
+ dst[key] = src.cloneNode(true);
+ } else if (isElement(src)) {
+ dst[key] = src.clone();
} else {
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
baseExtend(dst[key], [src], true);
}
} else {
@@ -9730,11 +9738,11 @@
function valueFn(value) {return function() {return value;};}
function hasCustomToString(obj) {
- return isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
+ return isFunction(obj.toString) && obj.toString !== toString;
}
/**
* @ngdoc function
@@ -9929,13 +9937,13 @@
function isPromiseLike(obj) {
return obj && isFunction(obj.then);
}
-var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/;
+var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
function isTypedArray(value) {
- return TYPED_ARRAY_REGEXP.test(toString.call(value));
+ return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
}
var trim = function(value) {
return isString(value) ? value.trim() : value;
@@ -10053,104 +10061,115 @@
}]);
</script>
</file>
</example>
*/
-function copy(source, destination, stackSource, stackDest) {
- if (isWindow(source) || isScope(source)) {
- throw ngMinErr('cpws',
- "Can't copy! Making copies of Window or Scope instances is not supported.");
- }
- if (isTypedArray(destination)) {
- throw ngMinErr('cpta',
- "Can't copy! TypedArray destination cannot be mutated.");
- }
+function copy(source, destination) {
+ var stackSource = [];
+ var stackDest = [];
- if (!destination) {
- destination = source;
- if (isObject(source)) {
- var index;
- if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
- return stackDest[index];
- }
+ if (destination) {
+ if (isTypedArray(destination)) {
+ throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
+ }
+ if (source === destination) {
+ throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
+ }
- // 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)) {
- 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 (isFunction(source.cloneNode)) {
- destination = source.cloneNode(true);
- } else {
- var emptyObject = Object.create(getPrototypeOf(source));
- return copy(source, emptyObject, stackSource, stackDest);
- }
-
- if (stackDest) {
- stackSource.push(source);
- stackDest.push(destination);
- }
+ // Empty the destination object
+ if (isArray(destination)) {
+ destination.length = 0;
+ } else {
+ forEach(destination, function(value, key) {
+ if (key !== '$$hashKey') {
+ delete destination[key];
+ }
+ });
}
- } else {
- if (source === destination) throw ngMinErr('cpi',
- "Can't copy! Source and destination are identical.");
- stackSource = stackSource || [];
- stackDest = stackDest || [];
+ stackSource.push(source);
+ stackDest.push(destination);
+ return copyRecurse(source, destination);
+ }
- if (isObject(source)) {
- stackSource.push(source);
- stackDest.push(destination);
- }
+ return copyElement(source);
+ function copyRecurse(source, destination) {
+ var h = destination.$$hashKey;
var result, key;
if (isArray(source)) {
- destination.length = 0;
- for (var i = 0; i < source.length; i++) {
- destination.push(copy(source[i], null, stackSource, stackDest));
+ for (var i = 0, ii = source.length; i < ii; i++) {
+ destination.push(copyElement(source[i]));
}
- } else {
- var h = destination.$$hashKey;
- if (isArray(destination)) {
- destination.length = 0;
- } else {
- forEach(destination, function(value, key) {
- delete destination[key];
- });
+ } else if (isBlankObject(source)) {
+ // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
+ for (key in source) {
+ destination[key] = copyElement(source[key]);
}
- if (isBlankObject(source)) {
- // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
- for (key in source) {
- 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)) {
+ destination[key] = copyElement(source[key]);
}
- } else if (source && typeof source.hasOwnProperty === 'function') {
- // Slow path, which must rely on hasOwnProperty
- for (key in source) {
- if (source.hasOwnProperty(key)) {
- 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)) {
+ destination[key] = copyElement(source[key]);
}
- } else {
- // Slowest path --- hasOwnProperty can't be called as a method
- for (key in source) {
- if (hasOwnProperty.call(source, key)) {
- destination[key] = copy(source[key], null, stackSource, stackDest);
- }
- }
}
- setHashKey(destination,h);
}
+ setHashKey(destination, h);
+ return destination;
}
- return destination;
+
+ function copyElement(source) {
+ // Simple values
+ if (!isObject(source)) {
+ return source;
+ }
+
+ // Already copied values
+ var index = stackSource.indexOf(source);
+ if (index !== -1) {
+ return stackDest[index];
+ }
+
+ if (isWindow(source) || isScope(source)) {
+ throw ngMinErr('cpws',
+ "Can't copy! Making copies of Window or Scope instances is not supported.");
+ }
+
+ var needsRecurse = false;
+ var destination;
+
+ if (isArray(source)) {
+ destination = [];
+ needsRecurse = true;
+ } 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 (isFunction(source.cloneNode)) {
+ destination = source.cloneNode(true);
+ } else {
+ destination = Object.create(getPrototypeOf(source));
+ needsRecurse = true;
+ }
+
+ stackSource.push(source);
+ stackDest.push(destination);
+
+ return needsRecurse
+ ? copyRecurse(source, destination)
+ : destination;
+ }
}
/**
* Creates a shallow copy of an object, an array or a primitive.
*
@@ -11270,11 +11289,11 @@
* @name angular.Module#constant
* @module ng
* @param {string} name constant name
* @param {*} object Constant value.
* @description
- * Because the constant are fixed, they get applied before other provide methods.
+ * Because the constants are fixed, they get applied before other provide methods.
* See {@link auto.$provide#constant $provide.constant()}.
*/
constant: invokeLater('$provide', 'constant', 'unshift'),
/**
@@ -11569,15 +11588,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.7', // all of these placeholder strings will be replaced by grunt's
+ full: '1.4.8', // all of these placeholder strings will be replaced by grunt's
major: 1, // package task
minor: 4,
- dot: 7,
- codeName: 'dark-luminescence'
+ dot: 8,
+ codeName: 'ice-manipulation'
};
function publishExternalAPI(angular) {
extend(angular, {
@@ -11955,10 +11974,18 @@
}
return [];
}
+
+// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
+var jqLiteContains = Node.prototype.contains || function(arg) {
+ // jshint bitwise: false
+ return !!(this.compareDocumentPosition(arg) & 16);
+ // jshint bitwise: true
+};
+
/////////////////////////////////////////////
function JQLite(element) {
if (element instanceof JQLite) {
return element;
}
@@ -12013,21 +12040,27 @@
removeEventListenerFn(element, type, handle);
}
delete events[type];
}
} else {
- forEach(type.split(' '), function(type) {
+
+ var removeHandler = function(type) {
+ var listenerFns = events[type];
if (isDefined(fn)) {
- var listenerFns = events[type];
arrayRemove(listenerFns || [], fn);
- if (listenerFns && listenerFns.length > 0) {
- return;
- }
}
+ if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
+ removeEventListenerFn(element, type, handle);
+ delete events[type];
+ }
+ };
- removeEventListenerFn(element, type, handle);
- delete events[type];
+ forEach(type.split(' '), function(type) {
+ removeHandler(type);
+ if (MOUSE_EVENT_MAP[type]) {
+ removeHandler(MOUSE_EVENT_MAP[type]);
+ }
});
}
}
function jqLiteRemoveData(element, name) {
@@ -12478,28 +12511,47 @@
event.isImmediatePropagationStopped = function() {
return event.immediatePropagationStopped === true;
};
+ // Some events have special handlers that wrap the real handler
+ var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
+
// Copy event handlers in case event handlers array is modified during execution.
if ((eventFnsLength > 1)) {
eventFns = shallowCopy(eventFns);
}
for (var i = 0; i < eventFnsLength; i++) {
if (!event.isImmediatePropagationStopped()) {
- eventFns[i].call(element, event);
+ handlerWrapper(element, event, eventFns[i]);
}
}
};
// TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
// events on `element`
eventHandler.elem = element;
return eventHandler;
}
+function defaultHandlerWrapper(element, event, handler) {
+ handler.call(element, event);
+}
+
+function specialMouseHandlerWrapper(target, event, handler) {
+ // Refer to jQuery's implementation of mouseenter & mouseleave
+ // Read about mouseenter and mouseleave:
+ // http://www.quirksmode.org/js/events_mouse.html#link8
+ var related = event.relatedTarget;
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if (!related || (related !== target && !jqLiteContains.call(target, related))) {
+ handler.call(target, event);
+ }
+}
+
//////////////////////////////////////////
// Functions iterating traversal.
// These functions chain results into a single
// selector.
//////////////////////////////////////////
@@ -12524,39 +12576,32 @@
// http://jsperf.com/string-indexof-vs-split
var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
var i = types.length;
- while (i--) {
- type = types[i];
+ var addHandler = function(type, specialHandlerWrapper, noEventListener) {
var eventFns = events[type];
if (!eventFns) {
- events[type] = [];
-
- if (type === 'mouseenter' || type === 'mouseleave') {
- // Refer to jQuery's implementation of mouseenter & mouseleave
- // Read about mouseenter and mouseleave:
- // http://www.quirksmode.org/js/events_mouse.html#link8
-
- jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) {
- var target = this, related = event.relatedTarget;
- // For mousenter/leave call the handler if related is outside the target.
- // NB: No relatedTarget if the mouse left/entered the browser window
- if (!related || (related !== target && !target.contains(related))) {
- handle(event, type);
- }
- });
-
- } else {
- if (type !== '$destroy') {
- addEventListenerFn(element, type, handle);
- }
+ eventFns = events[type] = [];
+ eventFns.specialHandlerWrapper = specialHandlerWrapper;
+ if (type !== '$destroy' && !noEventListener) {
+ addEventListenerFn(element, type, handle);
}
- eventFns = events[type];
}
+
eventFns.push(fn);
+ };
+
+ while (i--) {
+ type = types[i];
+ if (MOUSE_EVENT_MAP[type]) {
+ addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
+ addHandler(type, undefined, true);
+ } else {
+ addHandler(type);
+ }
}
},
off: jqLiteOff,
@@ -13733,11 +13778,11 @@
*
* @description
* When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
* current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
* in the
- * [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
+ * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
*
* It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
* match any anchor whenever it changes. This can be disabled by calling
* {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
*
@@ -14248,11 +14293,11 @@
* @description The $animate service exposes a series of DOM utility methods that provide support
* for animation hooks. The default behavior is the application of DOM operations, however,
* when an animation is detected (and animations are enabled), $animate will do the heavy lifting
* to ensure that animation runs with the triggered DOM operation.
*
- * By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't
+ * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
* included and only when it is active then the animation hooks that `$animate` triggers will be
* functional. Once active then all structural `ng-` directives will trigger animations as they perform
* their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
* `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
*
@@ -15100,13 +15145,13 @@
throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId);
}
var size = 0,
stats = extend({}, options, {id: cacheId}),
- data = {},
+ data = createMap(),
capacity = (options && options.capacity) || Number.MAX_VALUE,
- lruHash = {},
+ lruHash = createMap(),
freshEnd = null,
staleEnd = null;
/**
* @ngdoc type
@@ -15230,10 +15275,12 @@
link(lruEntry.n,lruEntry.p);
delete lruHash[key];
}
+ if (!(key in data)) return;
+
delete data[key];
size--;
},
@@ -15244,13 +15291,13 @@
*
* @description
* Clears the cache object of any entries.
*/
removeAll: function() {
- data = {};
+ data = createMap();
size = 0;
- lruHash = {};
+ lruHash = createMap();
freshEnd = staleEnd = null;
},
/**
@@ -16647,10 +16694,11 @@
? identity
: function denormalizeTemplate(template) {
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
},
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
+ var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
var bindings = $element.data('$binding') || [];
if (isArray(binding)) {
@@ -16699,10 +16747,18 @@
compile.$$addScopeClass($compileNodes);
var namespace = null;
return function publicLinkFn(scope, cloneConnectFn, options) {
assertArg(scope, 'scope');
+ if (previousCompileContext && previousCompileContext.needsNewScope) {
+ // A parent directive did a replace and a directive on this element asked
+ // for transclusion, which caused us to lose a layer of element on which
+ // we could hold the new transclusion scope, so we will create it manually
+ // here.
+ scope = scope.$parent.$new();
+ }
+
options = options || {};
var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
transcludeControllers = options.transcludeControllers,
futureParentElement = options.futureParentElement;
@@ -16844,15 +16900,10 @@
if (nodeLinkFn) {
if (nodeLinkFn.scope) {
childScope = scope.$new();
compile.$$addScopeInfo(jqLite(node), childScope);
- var destroyBindings = nodeLinkFn.$$destroyBindings;
- if (destroyBindings) {
- nodeLinkFn.$$destroyBindings = null;
- childScope.$on('$destroyed', destroyBindings);
- }
} else {
childScope = scope;
}
if (nodeLinkFn.transcludeOnThisElement) {
@@ -16867,12 +16918,11 @@
} else {
childBoundTranscludeFn = null;
}
- nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn,
- nodeLinkFn);
+ nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
} else if (childLinkFn) {
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
}
}
@@ -16937,17 +16987,15 @@
.substr(8).replace(/_(.)/g, function(match, letter) {
return letter.toUpperCase();
});
}
- var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
- if (directiveIsMultiElement(directiveNName)) {
- if (ngAttrName === directiveNName + 'Start') {
- attrStartName = name;
- attrEndName = name.substr(0, name.length - 5) + 'end';
- name = name.substr(0, name.length - 6);
- }
+ var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
+ if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
+ attrStartName = name;
+ attrEndName = name.substr(0, name.length - 5) + 'end';
+ name = name.substr(0, name.length - 6);
}
nName = directiveNormalize(name.toLowerCase());
attrsMap[nName] = name;
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
@@ -17182,11 +17230,12 @@
nonTlbTranscludeDirective: nonTlbTranscludeDirective
});
} else {
$template = jqLite(jqLiteClone(compileNode)).contents();
$compileNode.empty(); // clear contents
- childTranscludeFn = compile($template, transcludeFn);
+ childTranscludeFn = compile($template, transcludeFn, undefined,
+ undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
}
}
if (directive.template) {
hasTemplate = true;
@@ -17224,12 +17273,15 @@
// - collect directives from the template and sort them by priority
// - combine directives as: processed + template + unprocessed
var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
- if (newIsolateScopeDirective) {
- markDirectivesAsIsolate(templateDirectives);
+ if (newIsolateScopeDirective || newScopeDirective) {
+ // The original directive caused the current element to be replaced but this element
+ // also needs to have a new scope, so we need to tell the template directives
+ // that they would need to get their scope from further up, if they require transclusion
+ markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
}
directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
ii = directives.length;
@@ -17378,25 +17430,27 @@
}
}
return elementControllers;
}
- function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn,
- thisLinkFn) {
- var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element,
- attrs;
+ function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
+ var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
+ attrs, removeScopeBindingWatches, removeControllerBindingWatches;
if (compileNode === linkNode) {
attrs = templateAttrs;
$element = templateAttrs.$$element;
} else {
$element = jqLite(linkNode);
attrs = new Attributes($element, templateAttrs);
}
+ controllerScope = scope;
if (newIsolateScopeDirective) {
isolateScope = scope.$new(true);
+ } else if (newScopeDirective) {
+ controllerScope = scope.$parent;
}
if (boundTranscludeFn) {
// track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
// is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
@@ -17413,46 +17467,38 @@
compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
templateDirective === newIsolateScopeDirective.$$originalDirective)));
compile.$$addScopeClass($element, true);
isolateScope.$$isolateBindings =
newIsolateScopeDirective.$$isolateBindings;
- initializeDirectiveBindings(scope, attrs, isolateScope,
- isolateScope.$$isolateBindings,
- newIsolateScopeDirective, isolateScope);
+ removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
+ isolateScope.$$isolateBindings,
+ newIsolateScopeDirective);
+ if (removeScopeBindingWatches) {
+ isolateScope.$on('$destroy', removeScopeBindingWatches);
+ }
}
- if (elementControllers) {
- // Initialize bindToController bindings for new/isolate scopes
- var scopeDirective = newIsolateScopeDirective || newScopeDirective;
- var bindings;
- var controllerForBindings;
- if (scopeDirective && elementControllers[scopeDirective.name]) {
- bindings = scopeDirective.$$bindings.bindToController;
- controller = elementControllers[scopeDirective.name];
- if (controller && controller.identifier && bindings) {
- controllerForBindings = controller;
- thisLinkFn.$$destroyBindings =
- initializeDirectiveBindings(scope, attrs, controller.instance,
- bindings, scopeDirective);
- }
+ // Initialize bindToController bindings
+ for (var name in elementControllers) {
+ var controllerDirective = controllerDirectives[name];
+ var controller = elementControllers[name];
+ var bindings = controllerDirective.$$bindings.bindToController;
+
+ if (controller.identifier && bindings) {
+ removeControllerBindingWatches =
+ initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
}
- 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('$' + i + 'Controller', controllerResult);
- if (controller === controllerForBindings) {
- // Remove and re-install bindToController bindings
- thisLinkFn.$$destroyBindings();
- thisLinkFn.$$destroyBindings =
- initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective);
- }
- }
+ var controllerResult = controller();
+ if (controllerResult !== controller.instance) {
+ // If the controller constructor has a return value, overwrite the instance
+ // from setupControllers
+ controller.instance = controllerResult;
+ $element.data('$' + controllerDirective.name + 'Controller', controllerResult);
+ removeControllerBindingWatches && removeControllerBindingWatches();
+ removeControllerBindingWatches =
+ initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
}
}
// PRELINKING
for (i = 0, ii = preLinkFns.length; i < ii; i++) {
@@ -17508,14 +17554,19 @@
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
}
}
}
- function markDirectivesAsIsolate(directives) {
- // mark all directives as needing isolate scope.
+ // Depending upon the context in which a directive finds itself it might need to have a new isolated
+ // or child scope created. For instance:
+ // * if the directive has been pulled into a template because another directive with a higher priority
+ // asked for element transclusion
+ // * if the directive itself asks for transclusion but it is at the root of a template and the original
+ // element was replaced. See https://github.com/angular/angular.js/issues/12936
+ function markDirectiveScope(directives, isolateScope, newScope) {
for (var j = 0, jj = directives.length; j < jj; j++) {
- directives[j] = inherit(directives[j], {$$isolateScope: true});
+ directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
}
}
/**
* looks up the directive and decorates it with exception handling and proper parameters. We
@@ -17658,11 +17709,13 @@
tempTemplateAttrs = {$attr: {}};
replaceWith($rootElement, $compileNode, compileNode);
var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
if (isObject(origAsyncDirective.scope)) {
- markDirectivesAsIsolate(templateDirectives);
+ // the original directive that caused the template to be loaded async required
+ // an isolate scope
+ markDirectiveScope(templateDirectives, true);
}
directives = templateDirectives.concat(directives);
mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
} else {
compileNode = beforeTemplateCompileNode;
@@ -17707,11 +17760,11 @@
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
} else {
childBoundTranscludeFn = boundTranscludeFn;
}
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
- childBoundTranscludeFn, afterTemplateNodeLinkFn);
+ childBoundTranscludeFn);
}
linkQueue = null;
});
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
@@ -17724,12 +17777,11 @@
childBoundTranscludeFn);
} else {
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
}
- afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn,
- afterTemplateNodeLinkFn);
+ afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
}
};
}
@@ -17937,11 +17989,11 @@
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());
+ jqLite.data(newNode, jqLite.data(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) {
@@ -17985,13 +18037,12 @@
}
// Set up $watches for isolate scope and controller bindings. This process
// only occurs for isolate scopes and new scopes with controllerAs.
- function initializeDirectiveBindings(scope, attrs, destination, bindings,
- directive, newScope) {
- var onNewScopeDestroyed;
+ function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
+ var removeWatchCollection = [];
forEach(bindings, function(definition, scopeName) {
var attrName = definition.attrName,
optional = definition.optional,
mode = definition.mode, // @, =, or &
lastValue,
@@ -18049,18 +18100,17 @@
}
}
return lastValue = parentValue;
};
parentValueWatch.$stateful = true;
- var unwatch;
+ var removeWatch;
if (definition.collection) {
- unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
+ removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
} else {
- unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
+ removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
}
- onNewScopeDestroyed = (onNewScopeDestroyed || []);
- onNewScopeDestroyed.push(unwatch);
+ removeWatchCollection.push(removeWatch);
break;
case '&':
// Don't assign Object.prototype method to scope
parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
@@ -18072,20 +18122,16 @@
return parentGet(scope, locals);
};
break;
}
});
- var destroyBindings = onNewScopeDestroyed ? function destroyBindings() {
- for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) {
- onNewScopeDestroyed[i]();
+
+ return removeWatchCollection.length && function removeWatches() {
+ for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
+ removeWatchCollection[i]();
}
- } : noop;
- if (newScope && destroyBindings !== noop) {
- newScope.$on('$destroy', destroyBindings);
- return noop;
- }
- return destroyBindings;
+ };
}
}];
}
var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
@@ -18806,13 +18852,13 @@
* @description
*
* Configure `$http` service to return promises without the shorthand methods `success` and `error`.
* This should be used to make sure that applications work without these methods.
*
- * Defaults to false. If no value is specified, returns the current configured value.
+ * Defaults to true. If no value is specified, returns the current configured value.
*
- * @param {boolean=} value If true, `$http` will return a normal promise without the `success` and `error` methods.
+ * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods.
*
* @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
* otherwise, returns the current configured value.
**/
this.useLegacyPromiseExtensions = function(value) {
@@ -19460,15 +19506,12 @@
return promise;
function transformResponse(response) {
// make a copy since the response must be cacheable
var resp = extend({}, response);
- if (!response.data) {
- resp.data = response.data;
- } else {
- resp.data = transformData(response.data, response.headers, response.status, config.transformResponse);
- }
+ resp.data = transformData(response.data, response.headers, response.status,
+ config.transformResponse);
return (isSuccess(response.status))
? resp
: $q.reject(resp);
}
@@ -21098,13 +21141,13 @@
* @name $location#hash
*
* @description
* This method is getter / setter.
*
- * Return hash fragment when called without any parameter.
+ * Returns the hash fragment when called without any parameters.
*
- * Change hash fragment when called with parameter and return `$location`.
+ * Changes the hash fragment when called with a parameter and returns `$location`.
*
*
* ```js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
* var hash = $location.hash();
@@ -21121,12 +21164,12 @@
/**
* @ngdoc method
* @name $location#replace
*
* @description
- * If called, all changes to $location during current `$digest` will be replacing current history
- * record, instead of adding new one.
+ * If called, all changes to $location during the current `$digest` will replace the current history
+ * record, instead of adding a new one.
*/
replace: function() {
this.$$replace = true;
return this;
}
@@ -21442,11 +21485,11 @@
$rootScope.$evalAsync(function() {
var oldUrl = $location.absUrl();
var oldState = $location.$$state;
var defaultPrevented;
-
+ newUrl = trimEmptyHash(newUrl);
$location.$$parse(newUrl);
$location.$$state = newState;
defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
newState, oldState).defaultPrevented;
@@ -23583,17 +23626,18 @@
}
function addInterceptor(parsedExpression, interceptorFn) {
if (!interceptorFn) return parsedExpression;
var watchDelegate = parsedExpression.$$watchDelegate;
+ var useInputs = false;
var regularWatch =
watchDelegate !== oneTimeLiteralWatchDelegate &&
watchDelegate !== oneTimeWatchDelegate;
var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
- var value = parsedExpression(scope, locals, assign, inputs);
+ var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
return interceptorFn(value, scope, locals);
} : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
var value = parsedExpression(scope, locals, assign, inputs);
var result = interceptorFn(value, scope, locals);
// we only return the interceptor's result if the
@@ -23607,10 +23651,11 @@
fn.$$watchDelegate = parsedExpression.$$watchDelegate;
} else if (!interceptorFn.$stateful) {
// If there is an interceptor, but no watchDelegate then treat the interceptor like
// we treat filters - it is assumed to be a pure function unless flagged with $stateful
fn.$$watchDelegate = inputsWatchDelegate;
+ useInputs = !parsedExpression.inputs;
fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
}
return fn;
}
@@ -23668,10 +23713,12 @@
* });
* ```
*
* Note: progress/notify callbacks are not currently supported via the ES6-style interface.
*
+ * Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise.
+ *
* However, the more traditional CommonJS-style usage is still available, and documented below.
*
* [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
* interface for interacting with an object that represents the result of an action that is
* performed asynchronously, and may or may not be finished at any given point in time.
@@ -24252,19 +24299,19 @@
* - No closures, instead use prototypical inheritance for API
* - Internal state needs to be stored on scope directly, which means that private state is
* exposed as $$____ properties
*
* Loop operations are optimized by using while(count--) { ... }
- * - this means that in order to keep the same order of execution as addition we have to add
+ * - This means that in order to keep the same order of execution as addition we have to add
* items to the array at the beginning (unshift) instead of at the end (push)
*
* Child scopes are created and removed often
- * - Using an array would be slow since inserts in middle are expensive so we use linked list
+ * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
*
- * There are few watches then a lot of observers. This is why you don't want the observer to be
- * implemented in the same way as watch. Watch requires return of initialization function which
- * are expensive to construct.
+ * There are fewer watches than observers. This is why you don't want the observer to be implemented
+ * in the same way as watch. Watch requires return of the initialization function which is expensive
+ * to construct.
*/
/**
* @ngdoc provider
@@ -24302,11 +24349,11 @@
* @description
*
* Every application has a single root {@link ng.$rootScope.Scope scope}.
* All other scopes are descendant scopes of the root scope. Scopes provide separation
* between the model and the view, via a mechanism for watching the model for changes.
- * They also provide an event emission/broadcast and subscription facility. See the
+ * They also provide event emission/broadcast and subscription facility. See the
* {@link guide/scope developer guide on scopes}.
*/
function $RootScopeProvider() {
var TTL = 10;
var $rootScopeMinErr = minErr('$rootScope');
@@ -24339,10 +24386,33 @@
function destroyChildScope($event) {
$event.currentScope.$$destroyed = true;
}
+ function cleanUpScope($scope) {
+
+ if (msie === 9) {
+ // There is a memory leak in IE9 if all child scopes are not disconnected
+ // completely when a scope is destroyed. So this code will recurse up through
+ // all this scopes children
+ //
+ // See issue https://github.com/angular/angular.js/issues/10706
+ $scope.$$childHead && cleanUpScope($scope.$$childHead);
+ $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling);
+ }
+
+ // The code below works around IE9 and V8's memory leaks
+ //
+ // See:
+ // - https://code.google.com/p/v8/issues/detail?id=2073#c26
+ // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
+ // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
+
+ $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
+ $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
+ }
+
/**
* @ngdoc type
* @name $rootScope.Scope
*
* @description
@@ -25135,20 +25205,13 @@
// Disable listeners, watchers and apply/digest methods
this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
this.$on = this.$watch = this.$watchGroup = function() { return noop; };
this.$$listeners = {};
- // All of the code below is bogus code that works around V8's memory leak via optimized code
- // and inline caches.
- //
- // see:
- // - https://code.google.com/p/v8/issues/detail?id=2073#c26
- // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
- // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
-
- this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
- this.$$childTail = this.$root = this.$$watchers = null;
+ // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
+ this.$$nextSibling = null;
+ cleanUpScope(this);
},
/**
* @ngdoc method
* @name $rootScope.Scope#$eval
@@ -26140,11 +26203,11 @@
* `templateUrl`'s specified by {@link guide/directive directives}.
*
* By default, Angular only loads templates from the same domain and protocol as the application
* document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
* $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
- * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
+ * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
* them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
*
* *Please note*:
* The browser's
* [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
@@ -28391,11 +28454,11 @@
if (isNumber(input)) input = input.toString();
if (!isArray(input) && !isString(input)) return input;
begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
- begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin;
+ begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
if (limit >= 0) {
return input.slice(begin, begin + limit);
} else {
if (begin === 0) {
@@ -29751,11 +29814,12 @@
ngModelMinErr: false,
*/
// 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#!:.?+=&%@!\-\/]))?$/;
+// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
+var URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\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*)))([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)$/;
@@ -33317,11 +33381,17 @@
* @priority 400
*
* @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
* make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
* @param {string=} onload Expression to evaluate when a new partial is loaded.
- *
+ * <div class="alert alert-warning">
+ * **Note:** When using onload on SVG elements in IE11, the browser will try to call
+ * a function with the name on the window element, which will usually throw a
+ * "function is undefined" error. To fix this, you can instead use `data-onload` or a
+ * different form that {@link guide/directive#normalization matches} `onload`.
+ * </div>
+ *
* @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
* $anchorScroll} to scroll the viewport after the content is loaded.
*
* - If the attribute is not set, disable scrolling.
* - If the attribute is set without value, enable scrolling.
@@ -34910,16 +34980,17 @@
<label>Other data:
<input type="text" ng-model="user.data" />
</label><br />
</form>
<pre>user.name = <span ng-bind="user.name"></span></pre>
+ <pre>user.data = <span ng-bind="user.data"></span></pre>
</div>
</file>
<file name="app.js">
angular.module('optionsExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.user = { name: 'say', data: '' };
+ $scope.user = { name: 'John', data: '' };
$scope.cancel = function(e) {
if (e.keyCode == 27) {
$scope.userForm.userName.$rollbackViewValue();
}
@@ -34930,24 +35001,24 @@
var model = element(by.binding('user.name'));
var input = element(by.model('user.name'));
var other = element(by.model('user.data'));
it('should allow custom events', function() {
- input.sendKeys(' hello');
+ input.sendKeys(' Doe');
input.click();
- expect(model.getText()).toEqual('say');
+ expect(model.getText()).toEqual('John');
other.click();
- expect(model.getText()).toEqual('say hello');
+ expect(model.getText()).toEqual('John Doe');
});
it('should $rollbackViewValue when model changes', function() {
- input.sendKeys(' hello');
- expect(input.getAttribute('value')).toEqual('say hello');
+ input.sendKeys(' Doe');
+ expect(input.getAttribute('value')).toEqual('John Doe');
input.sendKeys(protractor.Key.ESCAPE);
- expect(input.getAttribute('value')).toEqual('say');
+ expect(input.getAttribute('value')).toEqual('John');
other.click();
- expect(model.getText()).toEqual('say');
+ expect(model.getText()).toEqual('John');
});
</file>
</example>
This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
@@ -34969,11 +35040,11 @@
</div>
</file>
<file name="app.js">
angular.module('optionsExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.user = { name: 'say' };
+ $scope.user = { name: 'Igor' };
}]);
</file>
</example>
This one shows how to bind to getter/setters:
@@ -35202,24 +35273,32 @@
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
* option. See example below for demonstration.
*
* ## 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.
+ * By default, `ngModel` watches the model by reference, not value. This is important to know when
+ * binding the select 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.
+ * One issue occurs if you want to preselect an option. For example, if you set
+ * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
+ * because the objects are not identical. So by default, you should always reference the item in your collection
+ * for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
*
- * 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.
+ * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
+ * of the item not by reference, but by the result of the `track by` expression. For example, if your
+ * collection items have an id property, you would `track by item.id`.
*
+ * A different issue with objects or collections is that ngModel won't detect if an object property or
+ * a collection item changes. For that reason, `ngOptions` additionally watches the model using
+ * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
+ * 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 than the first level inside the
+ * object/collection 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
@@ -35227,45 +35306,54 @@
*
*
* ### `select` **`as`** and **`track by`**
*
* <div class="alert alert-warning">
- * Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
+ * Be careful when using `select` **`as`** and **`track by`** in the same expression.
* </div>
*
- * Consider the following example:
+ * Given this array of items on the $scope:
*
- * ```html
- * <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"></select>
- * ```
- *
* ```js
- * $scope.values = [{
+ * $scope.items = [{
* id: 1,
* label: 'aLabel',
* subItem: { name: 'aSubItem' }
* }, {
* id: 2,
* label: 'bLabel',
* subItem: { name: 'bSubItem' }
* }];
+ * ```
*
- * $scope.selected = { name: 'aSubItem' };
+ * This will work:
+ *
+ * ```html
+ * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
* ```
+ * ```js
+ * $scope.selected = $scope.items[0];
+ * ```
*
- * With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
- * of the data source (to `item` in this example). To calculate whether an element is selected, we do the
- * following:
+ * but this will not work:
*
- * 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
- * 2. Apply **`track by`** to the already selected value in `ngModel`.
- * In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
- * value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
- * a wrong object, the selected element can't be found, `<select>` is always reset to the "not
- * selected" option.
+ * ```html
+ * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
+ * ```
+ * ```js
+ * $scope.selected = $scope.items[0].subItem;
+ * ```
*
+ * In both examples, the **`track by`** expression is applied successfully to each `item` in the
+ * `items` array. Because the selected option has been set programmatically in the controller, the
+ * **`track by`** expression is also applied to the `ngModel` value. In the first example, the
+ * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
+ * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
+ * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
+ * is not matched against any `<option>` and the `<select>` appears as having no selected value.
*
+ *
* @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=} required The control is considered valid only if value is entered.
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
@@ -35561,16 +35649,13 @@
// we can't just jqLite('<option>') since jqLite is not smart enough
// to create it in <select> and IE barfs otherwise.
var optionTemplate = document.createElement('option'),
optGroupTemplate = document.createElement('optgroup');
- return {
- restrict: 'A',
- terminal: true,
- require: ['select', '?ngModel'],
- link: function(scope, selectElement, attr, ctrls) {
+ function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
+
// if ngModel is not defined, we don't need to do anything
var ngModelCtrl = ctrls[1];
if (!ngModelCtrl) return;
var selectCtrl = ctrls[0];
@@ -35620,11 +35705,10 @@
var removeUnknownOption = function() {
unknownOption.remove();
};
-
// Update the controller methods for multiple selectable options
if (!multiple) {
selectCtrl.writeValue = function writeNgOptionsValue(value) {
var option = options.getOptionFromViewValue(value);
@@ -35795,17 +35879,19 @@
function skipEmptyAndUnknownOptions(current) {
var emptyOption_ = emptyOption && emptyOption[0];
var unknownOption_ = unknownOption && unknownOption[0];
+ // We cannot rely on the extracted empty option being the same as the compiled empty option,
+ // because the compiled empty option might have been replaced by a comment because
+ // it had an "element" transclusion directive on it (such as ngIf)
if (emptyOption_ || unknownOption_) {
while (current &&
(current === emptyOption_ ||
current === unknownOption_ ||
- emptyOption_ && emptyOption_.nodeType === NODE_TYPE_COMMENT)) {
- // Empty options might have directives that transclude
- // and insert comments (e.g. ngIf)
+ current.nodeType === NODE_TYPE_COMMENT ||
+ current.value === '')) {
current = current.nextSibling;
}
}
return current;
}
@@ -35898,11 +35984,24 @@
ngModelCtrl.$render();
}
}
}
+ }
+ return {
+ restrict: 'A',
+ terminal: true,
+ require: ['select', '?ngModel'],
+ link: {
+ pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
+ // Deactivate the SelectController.register method to prevent
+ // option directives from accidentally registering themselves
+ // (and unwanted $destroy handlers etc.)
+ ctrls[0].registerOption = noop;
+ },
+ post: ngOptionsPostLink
}
};
}];
/**
@@ -36185,30 +36284,36 @@
* used to sort the keys alphabetically.)
*
* Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
* when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
* keys in the order in which they were defined, although there are exceptions when keys are deleted
- * and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues
+ * and reinstated. See the [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes).
*
* If this is not desired, the recommended workaround is to convert your object into an array
* that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
* do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
* or implement a `$watch` on the object yourself.
*
*
* # Tracking and Duplicates
*
- * When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM:
+ * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
+ * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM:
*
* * When an item is added, a new instance of the template is added to the DOM.
* * When an item is removed, its template instance is removed from the DOM.
* * When items are reordered, their respective templates are reordered in the DOM.
*
- * By default, `ngRepeat` does not allow duplicate items in arrays. This is because when
- * there are duplicates, it is not possible to maintain a one-to-one mapping between collection
- * items and DOM elements.
+ * To minimize creation of DOM elements, `ngRepeat` uses a function
+ * to "keep track" of all items in the collection and their corresponding DOM elements.
+ * For example, if an item is added to the collection, ngRepeat will know that all other items
+ * already have DOM elements, and will not re-render them.
*
+ * The default tracking function (which tracks items by their identity) does not allow
+ * duplicate items in arrays. This is because when there are duplicates, it is not possible
+ * to maintain a one-to-one mapping between collection items and DOM elements.
+ *
* If you do need to repeat duplicate items, you can substitute the default tracking behavior
* with your own using the `track by` expression.
*
* For example, you may track items by the index of each item in the collection, using the
* special scope property `$index`:
@@ -36216,22 +36321,26 @@
* <div ng-repeat="n in [42, 42, 43, 43] track by $index">
* {{n}}
* </div>
* ```
*
- * You may use arbitrary expressions in `track by`, including references to custom functions
+ * You may also use arbitrary expressions in `track by`, including references to custom functions
* on the scope:
* ```html
* <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
* {{n}}
* </div>
* ```
*
- * If you are working with objects that have an identifier property, you can track
+ * <div class="alert alert-success">
+ * If you are working with objects that have an identifier property, you should track
* by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
* will not have to rebuild the DOM elements for items it has already rendered, even if the
- * JavaScript objects in the collection have been substituted for new ones:
+ * JavaScript objects in the collection have been substituted for new ones. For large collections,
+ * this signifincantly improves rendering performance. If you don't have a unique identifier,
+ * `track by $index` can also provide a performance boost.
+ * </div>
* ```html
* <div ng-repeat="model in collection track by model.id">
* {{model.name}}
* </div>
* ```
@@ -37385,10 +37494,19 @@
};
}];
var noopNgModelController = { $setViewValue: noop, $render: noop };
+function chromeHack(optionElement) {
+ // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
+ // Adding an <option selected="selected"> element to a <select required="required"> should
+ // automatically select the new element
+ if (optionElement[0].hasAttribute('selected')) {
+ optionElement[0].selected = true;
+ }
+}
+
/**
* @ngdoc type
* @name select.SelectController
* @description
* The controller for the `<select>` directive. This provides support for reading
@@ -37460,10 +37578,12 @@
if (value === '') {
self.emptyOption = element;
}
var count = optionsMap.get(value) || 0;
optionsMap.put(value, count + 1);
+ self.ngModelCtrl.$render();
+ chromeHack(element);
};
// Tell the select control that an option, with the given value, has been removed
self.removeOption = function(value) {
var count = optionsMap.get(value);
@@ -37481,10 +37601,43 @@
// Check whether the select control has an option matching the given value
self.hasOption = function(value) {
return !!optionsMap.get(value);
};
+
+
+ self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
+
+ if (interpolateValueFn) {
+ // The value attribute is interpolated
+ var oldVal;
+ optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
+ if (isDefined(oldVal)) {
+ self.removeOption(oldVal);
+ }
+ oldVal = newVal;
+ self.addOption(newVal, optionElement);
+ });
+ } else if (interpolateTextFn) {
+ // The text content is interpolated
+ optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
+ optionAttrs.$set('value', newVal);
+ if (oldVal !== newVal) {
+ self.removeOption(oldVal);
+ }
+ self.addOption(newVal, optionElement);
+ });
+ } else {
+ // The value attribute is static
+ self.addOption(optionAttrs.value, optionElement);
+ }
+
+ optionElement.on('$destroy', function() {
+ self.removeOption(optionAttrs.value);
+ self.ngModelCtrl.$render();
+ });
+ };
}];
/**
* @ngdoc directive
* @name select
@@ -37526,10 +37679,12 @@
* </div>
*
*
* @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=} multiple Allows multiple options to be selected. The selected values will be
+ * bound to the model as an array.
* @param {string=} required Sets `required` validation error key if the value is not entered.
* @param {string=} ngRequired Adds required attribute and required validation constraint to
* the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
* when you want to data-bind to the required attribute.
* @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
@@ -37691,12 +37846,18 @@
return {
restrict: 'E',
require: ['select', '?ngModel'],
controller: SelectController,
- link: function(scope, element, attr, ctrls) {
+ priority: 1,
+ link: {
+ pre: selectPreLink
+ }
+ };
+ function selectPreLink(scope, element, attr, ctrls) {
+
// if ngModel is not defined, we don't need to do anything
var ngModelCtrl = ctrls[1];
if (!ngModelCtrl) return;
var selectCtrl = ctrls[0];
@@ -37761,41 +37922,30 @@
return !value || value.length === 0;
};
}
}
- };
};
// The option directive is purely designed to communicate the existence (or lack of)
// of dynamically created (and destroyed) option elements to their containing select
// directive via its controller.
var optionDirective = ['$interpolate', function($interpolate) {
-
- function chromeHack(optionElement) {
- // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
- // Adding an <option selected="selected"> element to a <select required="required"> should
- // automatically select the new element
- if (optionElement[0].hasAttribute('selected')) {
- optionElement[0].selected = true;
- }
- }
-
return {
restrict: 'E',
priority: 100,
compile: function(element, attr) {
if (isDefined(attr.value)) {
// If the value attribute is defined, check if it contains an interpolation
- var valueInterpolated = $interpolate(attr.value, true);
+ var interpolateValueFn = $interpolate(attr.value, true);
} else {
// If the value attribute is not defined then we fall back to the
// text content of the option element, which may be interpolated
- var interpolateFn = $interpolate(element.text(), true);
- if (!interpolateFn) {
+ var interpolateTextFn = $interpolate(element.text(), true);
+ if (!interpolateTextFn) {
attr.$set('value', element.text());
}
}
return function(scope, element, attr) {
@@ -37805,49 +37955,13 @@
var selectCtrlName = '$selectController',
parent = element.parent(),
selectCtrl = parent.data(selectCtrlName) ||
parent.parent().data(selectCtrlName); // in case we are in optgroup
- function addOption(optionValue) {
- selectCtrl.addOption(optionValue, element);
- selectCtrl.ngModelCtrl.$render();
- chromeHack(element);
+ if (selectCtrl) {
+ selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
}
-
- // Only update trigger option updates if this is an option within a `select`
- // that also has `ngModel` attached
- if (selectCtrl && selectCtrl.ngModelCtrl) {
-
- if (valueInterpolated) {
- // The value attribute is interpolated
- var oldVal;
- attr.$observe('value', function valueAttributeObserveAction(newVal) {
- if (isDefined(oldVal)) {
- selectCtrl.removeOption(oldVal);
- }
- oldVal = newVal;
- addOption(newVal);
- });
- } else if (interpolateFn) {
- // The text content is interpolated
- scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
- attr.$set('value', newVal);
- if (oldVal !== newVal) {
- selectCtrl.removeOption(oldVal);
- }
- addOption(newVal);
- });
- } else {
- // The value attribute is static
- addOption(attr.value);
- }
-
- element.on('$destroy', function() {
- selectCtrl.removeOption(attr.value);
- selectCtrl.ngModelCtrl.$render();
- });
- }
};
}
};
}];
@@ -38425,10 +38539,11 @@
window.browserTrigger = function browserTrigger(element, eventType, eventData) {
if (element && !element.nodeName) element = element[0];
if (!element) return;
eventData = eventData || {};
+ var relatedTarget = eventData.relatedTarget || element;
var keys = eventData.keys;
var x = eventData.x;
var y = eventData.y;
var inputType = (element.type) ? element.type.toLowerCase() : null,
@@ -38494,10 +38609,10 @@
} else {
evnt = document.createEvent('MouseEvents');
x = x || 0;
y = y || 0;
evnt.initMouseEvent(eventType, true, true, window, 0, x, y, x, y, pressed('ctrl'),
- pressed('alt'), pressed('shift'), pressed('meta'), 0, element);
+ pressed('alt'), pressed('shift'), pressed('meta'), 0, relatedTarget);
}
/* we're unable to change the timeStamp value directly so this
* is only here to allow for testing where the timeStamp value is
* read */
\ No newline at end of file