summaryrefslogtreecommitdiffstats
path: root/js/vendor/angular/angular.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/vendor/angular/angular.js')
-rw-r--r--js/vendor/angular/angular.js474
1 files changed, 341 insertions, 133 deletions
diff --git a/js/vendor/angular/angular.js b/js/vendor/angular/angular.js
index ff96d85ea..f91161493 100644
--- a/js/vendor/angular/angular.js
+++ b/js/vendor/angular/angular.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.0-rc.3
+ * @license AngularJS v1.3.0-rc.4
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -71,7 +71,7 @@ function minErr(module, ErrorConstructor) {
return match;
});
- message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.3/' +
+ message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.4/' +
(module ? module + '/' : '') + code;
for (i = 2; i < arguments.length; i++) {
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
@@ -2112,11 +2112,11 @@ function setupModuleLoader(window) {
* - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
*/
var version = {
- full: '1.3.0-rc.3', // all of these placeholder strings will be replaced by grunt's
+ full: '1.3.0-rc.4', // all of these placeholder strings will be replaced by grunt's
major: 1, // package task
minor: 3,
dot: 0,
- codeName: 'aggressive-pacifism'
+ codeName: 'unicorn-hydrafication'
};
@@ -4653,8 +4653,6 @@ function Browser(window, document, $log, $sniffer) {
if (replace) history.replaceState(null, '', url);
else {
history.pushState(null, '', url);
- // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
- baseElement.attr('href', baseElement.attr('href'));
}
} else {
newLocation = url;
@@ -5495,8 +5493,11 @@ function $TemplateCacheProvider() {
* * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
* * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
* * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
+ * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
* * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
* `null` to the `link` fn if not found.
+ * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
+ * `null` to the `link` fn if not found.
*
*
* #### `controllerAs`
@@ -5577,13 +5578,20 @@ function $TemplateCacheProvider() {
* compile the content of the element and make it available to the directive.
* Typically used with {@link ng.directive:ngTransclude
* ngTransclude}. The advantage of transclusion is that the linking function receives a
- * transclusion function which is pre-bound to the correct scope. In a typical setup the widget
- * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate`
- * scope. This makes it possible for the widget to have private state, and the transclusion to
- * be bound to the parent (pre-`isolate`) scope.
+ * transclusion function which is pre-bound to the scope of the position in the DOM from where
+ * it was taken.
+ *
+ * In a typical setup the widget creates an `isolate` scope, but the transcluded
+ * content has its own **transclusion scope**. While the **transclusion scope** is owned as a child,
+ * by the **isolate scope**, it prototypically inherits from the original scope from where the
+ * transcluded content was taken.
*
- * * `true` - transclude the content of the directive.
- * * `'element'` - transclude the whole element including any directives defined at lower priority.
+ * This makes it possible for the widget to have private state, and the transclusion to
+ * be bound to the original (pre-`isolate`) scope.
+ *
+ * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
+ * * `'element'` - transclude the whole of the directive's element including any directives on this
+ * element that defined at a lower priority than this directive.
*
* <div class="alert alert-warning">
* **Note:** When testing an element transclude directive you must not place the directive at the root of the
@@ -5687,7 +5695,6 @@ function $TemplateCacheProvider() {
* It is safe to do DOM transformation in the post-linking function on elements that are not waiting
* for their async templates to be resolved.
*
- * <a name="Attributes"></a>
* ### Attributes
*
* The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
@@ -5725,7 +5732,7 @@ function $TemplateCacheProvider() {
* }
* ```
*
- * Below is an example using `$compileProvider`.
+ * ## Example
*
* <div class="alert alert-warning">
* **Note**: Typically directives are registered with `module.directive`. The example below is
@@ -5850,7 +5857,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/,
- ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset');
+ ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
+ REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
// The assumption is that future DOM event attribute names will begin with
@@ -6155,10 +6163,44 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
nodeName = nodeName_(this.$$element);
- // sanitize a[href] and img[src] values
if ((nodeName === 'a' && key === 'href') ||
(nodeName === 'img' && key === 'src')) {
+ // sanitize a[href] and img[src] values
this[key] = value = $$sanitizeUri(value, key === 'src');
+ } else if (nodeName === 'img' && key === 'srcset') {
+ // sanitize img[srcset] values
+ var result = "";
+
+ // first check if there are spaces because it's not the same pattern
+ var trimmedSrcset = trim(value);
+ // ( 999x ,| 999w ,| ,|, )
+ var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
+ var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
+
+ // split srcset into tuple of uri and descriptor except for the last item
+ var rawUris = trimmedSrcset.split(pattern);
+
+ // for each tuples
+ var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
+ for (var i=0; i<nbrUrisWith2parts; i++) {
+ var innerIdx = i*2;
+ // sanitize the uri
+ result += $$sanitizeUri(trim( rawUris[innerIdx]), true);
+ // add the descriptor
+ result += ( " " + trim(rawUris[innerIdx+1]));
+ }
+
+ // split the last item into uri and descriptor
+ var lastTuple = trim(rawUris[i*2]).split(/\s/);
+
+ // sanitize the last uri
+ result += $$sanitizeUri(trim(lastTuple[0]), true);
+
+ // and add the last descriptor if any
+ if( lastTuple.length === 2) {
+ result += (" " + trim(lastTuple[1]));
+ }
+ this[key] = value = result;
}
if (writeAttr !== false) {
@@ -6196,12 +6238,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* @param {string} key Normalized key. (ie ngAttribute) .
* @param {function(interpolatedValue)} fn Function that will be called whenever
the interpolated value of the attribute changes.
- * See the {@link guide/directive#Attributes Directives} guide for more info.
+ * See {@link ng.$compile#attributes $compile} for more info.
* @returns {function()} Returns a deregistration function for this observer.
*/
$observe: function(key, fn) {
var attrs = this,
- $$observers = (attrs.$$observers || (attrs.$$observers = {})),
+ $$observers = (attrs.$$observers || (attrs.$$observers = Object.create(null))),
listeners = ($$observers[key] || ($$observers[key] = []));
listeners.push(fn);
@@ -6449,20 +6491,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn, elementTransclusion) {
- var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement) {
- var scopeCreated = false;
+ var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
if (!transcludedScope) {
- transcludedScope = scope.$new();
+ transcludedScope = scope.$new(false, containingScope);
transcludedScope.$$transcluded = true;
- scopeCreated = true;
}
- var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
- if (scopeCreated && !elementTransclusion) {
- clone.on('$destroy', function() { transcludedScope.$destroy(); });
- }
- return clone;
+ return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
};
return boundTranscludeFn;
@@ -6872,14 +6908,26 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function getControllers(directiveName, require, $element, elementControllers) {
var value, retrievalMethod = 'data', optional = false;
+ var $searchElement = $element;
+ var match;
if (isString(require)) {
- while((value = require.charAt(0)) == '^' || value == '?') {
- require = require.substr(1);
- if (value == '^') {
- retrievalMethod = 'inheritedData';
- }
- optional = optional || value == '?';
+ match = require.match(REQUIRE_PREFIX_REGEXP);
+ require = require.substring(match[0].length);
+
+ if (match[3]) {
+ if (match[1]) match[3] = null;
+ else match[1] = match[3];
}
+ if (match[1] === '^') {
+ retrievalMethod = 'inheritedData';
+ } else if (match[1] === '^^') {
+ retrievalMethod = 'inheritedData';
+ $searchElement = $element.parent();
+ }
+ if (match[2] === '?') {
+ optional = true;
+ }
+
value = null;
if (elementControllers && retrievalMethod === 'data') {
@@ -6887,7 +6935,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
value = value.instance;
}
}
- value = value || $element[retrievalMethod]('$' + require + 'Controller');
+ value = value || $searchElement[retrievalMethod]('$' + require + 'Controller');
if (!value && !optional) {
throw $compileMinErr('ctreq',
@@ -7093,7 +7141,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (!futureParentElement) {
futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
}
- return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement);
+ return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
}
}
}
@@ -7277,6 +7325,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
boundTranscludeFn = linkQueue.shift(),
linkNode = $compileNode[0];
+ if (scope.$$destroyed) continue;
+
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
var oldClasses = beforeTemplateLinkNode.className;
@@ -7303,6 +7353,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
var childBoundTranscludeFn = boundTranscludeFn;
+ if (scope.$$destroyed) return;
if (linkQueue) {
linkQueue.push(scope);
linkQueue.push(node);
@@ -10785,6 +10836,11 @@ forEach({
CONSTANTS[name] = constantGetter;
});
+//Not quite a constant, but can be lex/parsed the same
+CONSTANTS['this'] = function(self) { return self; };
+CONSTANTS['this'].sharedGetter = true;
+
+
//Operators - will be wrapped by binaryFn/unaryFn/assignment/filter
var OPERATORS = extend(createMap(), {
/* jshint bitwise : false */
@@ -12648,14 +12704,11 @@ function $RootScopeProvider(){
this.$$phase = this.$parent = this.$$watchers =
this.$$nextSibling = this.$$prevSibling =
this.$$childHead = this.$$childTail = null;
- this['this'] = this.$root = this;
+ this.$root = this;
this.$$destroyed = false;
- this.$$asyncQueue = [];
- this.$$postDigestQueue = [];
this.$$listeners = {};
this.$$listenerCount = {};
this.$$isolateBindings = null;
- this.$$applyAsyncQueue = [];
}
/**
@@ -12704,18 +12757,23 @@ function $RootScopeProvider(){
* When creating widgets, it is useful for the widget to not accidentally read parent
* state.
*
+ * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
+ * of the newly created scope. Defaults to `this` scope if not provided.
+ * This is used when creating a transclude scope to correctly place it
+ * in the scope hierarchy while maintaining the correct prototypical
+ * inheritance.
+ *
* @returns {Object} The newly created child scope.
*
*/
- $new: function(isolate) {
+ $new: function(isolate, parent) {
var child;
+ parent = parent || this;
+
if (isolate) {
child = new Scope();
child.$root = this.$root;
- // ensure that there is just one async queue per $rootScope and its children
- child.$$asyncQueue = this.$$asyncQueue;
- child.$$postDigestQueue = this.$$postDigestQueue;
} else {
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
@@ -12732,16 +12790,27 @@ function $RootScopeProvider(){
}
child = new this.$$ChildScope();
}
- child['this'] = child;
- child.$parent = this;
- child.$$prevSibling = this.$$childTail;
- if (this.$$childHead) {
- this.$$childTail.$$nextSibling = child;
- this.$$childTail = child;
+ child.$parent = parent;
+ child.$$prevSibling = parent.$$childTail;
+ if (parent.$$childHead) {
+ parent.$$childTail.$$nextSibling = child;
+ parent.$$childTail = child;
} else {
- this.$$childHead = this.$$childTail = child;
+ parent.$$childHead = parent.$$childTail = child;
}
+
+ // When the new scope is not isolated or we inherit from `this`, and
+ // the parent scope is destroyed, the property `$$destroyed` is inherited
+ // prototypically. In all other cases, this property needs to be set
+ // when the parent scope is destroyed.
+ // The listener needs to be added after the parent is set
+ if (isolate || parent != this) child.$on('$destroy', destroyChild);
+
return child;
+
+ function destroyChild() {
+ child.$$destroyed = true;
+ }
},
/**
@@ -13217,8 +13286,6 @@ function $RootScopeProvider(){
$digest: function() {
var watch, value, last,
watchers,
- asyncQueue = this.$$asyncQueue,
- postDigestQueue = this.$$postDigestQueue,
length,
dirty, ttl = TTL,
next, current, target = this,
@@ -13383,6 +13450,10 @@ function $RootScopeProvider(){
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
+ // 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.
@@ -13393,15 +13464,7 @@ function $RootScopeProvider(){
// - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
- this.$$childTail = this.$root = null;
-
- // don't reset these to null in case some async task tries to register a listener/watch/task
- this.$$listeners = {};
- this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = [];
-
- // prevent NPEs since these methods have references to properties we nulled out
- this.$destroy = this.$digest = this.$apply = noop;
- this.$on = this.$watch = this.$watchGroup = function() { return noop; };
+ this.$$childTail = this.$root = this.$$watchers = null;
},
/**
@@ -13468,19 +13531,19 @@ function $RootScopeProvider(){
$evalAsync: function(expr) {
// if we are outside of an $digest loop and this is the first time we are scheduling async
// task also schedule async auto-flush
- if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
+ if (!$rootScope.$$phase && !asyncQueue.length) {
$browser.defer(function() {
- if ($rootScope.$$asyncQueue.length) {
+ if (asyncQueue.length) {
$rootScope.$digest();
}
});
}
- this.$$asyncQueue.push({scope: this, expression: expr});
+ asyncQueue.push({scope: this, expression: expr});
},
$$postDigest : function(fn) {
- this.$$postDigestQueue.push(fn);
+ postDigestQueue.push(fn);
},
/**
@@ -13564,7 +13627,7 @@ function $RootScopeProvider(){
*/
$applyAsync: function(expr) {
var scope = this;
- expr && $rootScope.$$applyAsyncQueue.push($applyAsyncExpression);
+ expr && applyAsyncQueue.push($applyAsyncExpression);
scheduleApplyAsync();
function $applyAsyncExpression() {
@@ -13773,6 +13836,11 @@ function $RootScopeProvider(){
var $rootScope = new Scope();
+ //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
+ var asyncQueue = $rootScope.$$asyncQueue = [];
+ var postDigestQueue = $rootScope.$$postDigestQueue = [];
+ var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
+
return $rootScope;
@@ -13806,10 +13874,9 @@ function $RootScopeProvider(){
function initWatchVal() {}
function flushApplyAsync() {
- var queue = $rootScope.$$applyAsyncQueue;
- while (queue.length) {
+ while (applyAsyncQueue.length) {
try {
- queue.shift()();
+ applyAsyncQueue.shift()();
} catch(e) {
$exceptionHandler(e);
}
@@ -17215,9 +17282,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
parentForm.$addControl(form);
- // Setup initial state of the control
- element.addClass(PRISTINE_CLASS);
-
/**
* @ngdoc method
* @name form.FormController#$rollbackViewValue
@@ -17590,9 +17654,12 @@ var formDirectiveFactory = function(isNgForm) {
name: 'form',
restrict: isNgForm ? 'EAC' : 'E',
controller: FormController,
- compile: function() {
+ compile: function ngFormCompile(formElement) {
+ // Setup initial state of the control
+ formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
+
return {
- pre: function(scope, formElement, attr, controller) {
+ pre: function ngFormPreLink(scope, formElement, attr, controller) {
if (!attr.action) {
// we can't use jq events because if a form is destroyed during submission the default
// action is not prevented. see #1238
@@ -18747,16 +18814,15 @@ function createDateInputType(type, regexp, parseDate, format) {
badInputChecker(scope, element, attr, ctrl);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
+ var previousDate;
ctrl.$$parserName = type;
ctrl.$parsers.push(function(value) {
if (ctrl.$isEmpty(value)) return null;
if (regexp.test(value)) {
- var previousDate = ctrl.$modelValue;
- if (previousDate && timezone === 'UTC') {
- var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
- previousDate = new Date(previousDate.getTime() + timezoneOffset);
- }
+ // Note: We cannot read ctrl.$modelValue, as there might be a different
+ // parser/formatter in the processing chain so that the model
+ // contains some different data format!
var parsedDate = parseDate(value, previousDate);
if (timezone === 'UTC') {
parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
@@ -18767,8 +18833,18 @@ function createDateInputType(type, regexp, parseDate, format) {
});
ctrl.$formatters.push(function(value) {
- if (isDate(value)) {
+ if (!ctrl.$isEmpty(value)) {
+ if (!isDate(value)) {
+ throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
+ }
+ previousDate = value;
+ if (previousDate && timezone === 'UTC') {
+ var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
+ previousDate = new Date(previousDate.getTime() + timezoneOffset);
+ }
return $filter('date')(value, format, timezone);
+ } else {
+ previousDate = null;
}
return '';
});
@@ -18794,6 +18870,11 @@ function createDateInputType(type, regexp, parseDate, format) {
ctrl.$validate();
});
}
+ // Override the standard $isEmpty to detect invalid dates as well
+ ctrl.$isEmpty = function(value) {
+ // Invalid Date: getTime() returns NaN
+ return !value || (value.getTime && value.getTime() !== value.getTime());
+ };
function parseObservedDateValue(val) {
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
@@ -19108,10 +19189,12 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
return {
restrict: 'E',
require: ['?ngModel'],
- link: function(scope, element, attr, ctrls) {
- if (ctrls[0]) {
- (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
- $browser, $filter, $parse);
+ link: {
+ pre: function(scope, element, attr, ctrls) {
+ if (ctrls[0]) {
+ (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
+ $browser, $filter, $parse);
+ }
}
}
};
@@ -19412,11 +19495,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
currentValidationRunId = 0;
- // Setup initial state of the control
- $element
- .addClass(PRISTINE_CLASS)
- .addClass(UNTOUCHED_CLASS);
-
/**
* @ngdoc method
* @name ngModel.NgModelController#$setValidity
@@ -19709,14 +19787,17 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
};
this.$$parseAndValidate = function() {
- var parserValid = true,
- viewValue = ctrl.$$lastCommittedViewValue,
- modelValue = viewValue;
- for(var i = 0; i < ctrl.$parsers.length; i++) {
- modelValue = ctrl.$parsers[i](modelValue);
- if (isUndefined(modelValue)) {
- parserValid = false;
- break;
+ var viewValue = ctrl.$$lastCommittedViewValue;
+ var modelValue = viewValue;
+ var parserValid = isUndefined(modelValue) ? undefined : true;
+
+ if (parserValid) {
+ for(var i = 0; i < ctrl.$parsers.length; i++) {
+ modelValue = ctrl.$parsers[i](modelValue);
+ if (isUndefined(modelValue)) {
+ parserValid = false;
+ break;
+ }
}
}
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
@@ -20033,42 +20114,51 @@ var ngModelDirective = function() {
restrict: 'A',
require: ['ngModel', '^?form', '^?ngModelOptions'],
controller: NgModelController,
- link: {
- pre: function(scope, element, attr, ctrls) {
- var modelCtrl = ctrls[0],
- formCtrl = ctrls[1] || nullFormCtrl;
+ // Prelink needs to run before any input directive
+ // so that we can set the NgModelOptions in NgModelController
+ // before anyone else uses it.
+ priority: 1,
+ compile: function ngModelCompile(element) {
+ // Setup initial state of the control
+ element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
- modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
+ return {
+ pre: function ngModelPreLink(scope, element, attr, ctrls) {
+ var modelCtrl = ctrls[0],
+ formCtrl = ctrls[1] || nullFormCtrl;
- // notify others, especially parent forms
- formCtrl.$addControl(modelCtrl);
+ modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
- attr.$observe('name', function(newValue) {
- if (modelCtrl.$name !== newValue) {
- formCtrl.$$renameControl(modelCtrl, newValue);
- }
- });
+ // notify others, especially parent forms
+ formCtrl.$addControl(modelCtrl);
- scope.$on('$destroy', function() {
- formCtrl.$removeControl(modelCtrl);
- });
- },
- post: function(scope, element, attr, ctrls) {
- var modelCtrl = ctrls[0];
- if (modelCtrl.$options && modelCtrl.$options.updateOn) {
- element.on(modelCtrl.$options.updateOn, function(ev) {
- modelCtrl.$$debounceViewValueCommit(ev && ev.type);
+ attr.$observe('name', function(newValue) {
+ if (modelCtrl.$name !== newValue) {
+ formCtrl.$$renameControl(modelCtrl, newValue);
+ }
});
- }
- element.on('blur', function(ev) {
- if (modelCtrl.$touched) return;
+ scope.$on('$destroy', function() {
+ formCtrl.$removeControl(modelCtrl);
+ });
+ },
+ post: function ngModelPostLink(scope, element, attr, ctrls) {
+ var modelCtrl = ctrls[0];
+ if (modelCtrl.$options && modelCtrl.$options.updateOn) {
+ element.on(modelCtrl.$options.updateOn, function(ev) {
+ modelCtrl.$$debounceViewValueCommit(ev && ev.type);
+ });
+ }
- scope.$apply(function() {
- modelCtrl.$setTouched();
+ element.on('blur', function(ev) {
+ if (modelCtrl.$touched) return;
+
+ scope.$apply(function() {
+ modelCtrl.$setTouched();
+ });
});
- });
- }
+ }
+ };
}
};
};
@@ -20623,8 +20713,9 @@ function addSetValidityMethod(context) {
parentForm = context.parentForm,
$animate = context.$animate;
+ classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
+
ctrl.$setValidity = setValidity;
- toggleValidationCss('', true);
function setValidity(validationErrorKey, state, options) {
if (state === undefined) {
@@ -21624,7 +21715,125 @@ var ngControllerDirective = [function() {
...
</html>
```
- */
+ * @example
+ // Note: the suffix `.csp` in the example name triggers
+ // csp mode in our http server!
+ <example name="example.csp" module="cspExample" ng-csp="true">
+ <file name="index.html">
+ <div ng-controller="MainController as ctrl">
+ <div>
+ <button ng-click="ctrl.inc()" id="inc">Increment</button>
+ <span id="counter">
+ {{ctrl.counter}}
+ </span>
+ </div>
+
+ <div>
+ <button ng-click="ctrl.evil()" id="evil">Evil</button>
+ <span id="evilError">
+ {{ctrl.evilError}}
+ </span>
+ </div>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('cspExample', [])
+ .controller('MainController', function() {
+ this.counter = 0;
+ this.inc = function() {
+ this.counter++;
+ };
+ this.evil = function() {
+ // jshint evil:true
+ try {
+ eval('1+2');
+ } catch (e) {
+ this.evilError = e.message;
+ }
+ };
+ });
+ </file>
+ <file name="protractor.js" type="protractor">
+ var util, webdriver;
+
+ var incBtn = element(by.id('inc'));
+ var counter = element(by.id('counter'));
+ var evilBtn = element(by.id('evil'));
+ var evilError = element(by.id('evilError'));
+
+ function getAndClearSevereErrors() {
+ return browser.manage().logs().get('browser').then(function(browserLog) {
+ return browserLog.filter(function(logEntry) {
+ return logEntry.level.value > webdriver.logging.Level.WARNING.value;
+ });
+ });
+ }
+
+ function clearErrors() {
+ getAndClearSevereErrors();
+ }
+
+ function expectNoErrors() {
+ getAndClearSevereErrors().then(function(filteredLog) {
+ expect(filteredLog.length).toEqual(0);
+ if (filteredLog.length) {
+ console.log('browser console errors: ' + util.inspect(filteredLog));
+ }
+ });
+ }
+
+ function expectError(regex) {
+ getAndClearSevereErrors().then(function(filteredLog) {
+ var found = false;
+ filteredLog.forEach(function(log) {
+ if (log.message.match(regex)) {
+ found = true;
+ }
+ });
+ if (!found) {
+ throw new Error('expected an error that matches ' + regex);
+ }
+ });
+ }
+
+ beforeEach(function() {
+ util = require('util');
+ webdriver = require('protractor/node_modules/selenium-webdriver');
+ });
+
+ // For now, we only test on Chrome,
+ // as Safari does not load the page with Protractor's injected scripts,
+ // and Firefox webdriver always disables content security policy (#6358)
+ if (browser.params.browser !== 'chrome') {
+ return;
+ }
+
+ it('should not report errors when the page is loaded', function() {
+ // clear errors so we are not dependent on previous tests
+ clearErrors();
+ // Need to reload the page as the page is already loaded when
+ // we come here
+ browser.driver.getCurrentUrl().then(function(url) {
+ browser.get(url);
+ });
+ expectNoErrors();
+ });
+
+ it('should evaluate expressions', function() {
+ expect(counter.getText()).toEqual('0');
+ incBtn.click();
+ expect(counter.getText()).toEqual('1');
+ expectNoErrors();
+ });
+
+ it('should throw and report an error when using "eval"', function() {
+ evilBtn.click();
+ expect(evilError.getText()).toMatch(/Content Security Policy/);
+ expectError(/Content Security Policy/);
+ });
+ </file>
+ </example>
+ */
// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
// bootstrap the system (before $parse is instantiated), for this reason we just have
@@ -24600,6 +24809,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
id: option.id,
selected: option.selected
});
+ selectCtrl.addOption(option.label, element);
if (lastElement) {
lastElement.after(element);
} else {
@@ -24611,7 +24821,9 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
// remove any excessive OPTIONs in a group
index++; // increment since the existingOptions[0] is parent element not OPTION
while(existingOptions.length > index) {
- existingOptions.pop().element.remove();
+ option = existingOptions.pop();
+ selectCtrl.removeOption(option.label);
+ option.element.remove();
}
}
// remove any excessive OPTGROUPs from select
@@ -24647,11 +24859,7 @@ var optionDirective = ['$interpolate', function($interpolate) {
selectCtrl = parent.data(selectCtrlName) ||
parent.parent().data(selectCtrlName); // in case we are in optgroup
- if (selectCtrl && selectCtrl.databound) {
- // For some reason Opera defaults to true and if not overridden this messes up the repeater.
- // We don't want the view to drive the initialization of the model anyway.
- element.prop('selected', false);
- } else {
+ if (!selectCtrl || !selectCtrl.databound) {
selectCtrl = nullSelectCtrl;
}