Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: cawabunga/angular.js
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: g3_v1_5
Choose a base ref
...
head repository: cawabunga/angular.js
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: feature-directive-scope-validation
Choose a head ref
Able to merge. These branches can be automatically merged.
  • 4 commits
  • 2 files changed
  • 1 contributor

Commits on May 31, 2016

  1. Copy the full SHA
    3eb8d99 View commit details
  2. Fix errors; add tests

    cawabunga committed May 31, 2016
    Copy the full SHA
    2bcfe64 View commit details
  3. Copy the full SHA
    5c64e3e View commit details
  4. Bugfixes

    cawabunga committed May 31, 2016
    Copy the full SHA
    fd01a26 View commit details
Showing with 349 additions and 7 deletions.
  1. +66 −7 src/ng/compile.js
  2. +283 −0 test/ng/compileSpec.js
73 changes: 66 additions & 7 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
@@ -871,7 +871,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
var bindingCache = createMap();

function parseIsolateBindings(scope, directiveName, isController) {
function parseIsolateBindings(scope, directiveName, isController, validatation) {
var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/;

var bindings = createMap();
@@ -898,6 +898,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
optional: match[3] === '?',
attrName: match[4] || scopeName
};
if (hasOwnProperty.call(validatation, scopeName)) {
bindings[scopeName].validator = validatation[scopeName];
}
if (match[4]) {
bindingCache[definition] = bindings[scopeName];
}
@@ -914,16 +917,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (isObject(directive.scope)) {
if (directive.bindToController === true) {
bindings.bindToController = parseIsolateBindings(directive.scope,
directiveName, true);
directiveName, true, directive.validation);
bindings.isolateScope = {};
} else {
bindings.isolateScope = parseIsolateBindings(directive.scope,
directiveName, false);
directiveName, false, directive.validation);
}
}
if (isObject(directive.bindToController)) {
bindings.bindToController =
parseIsolateBindings(directive.bindToController, directiveName, true);
parseIsolateBindings(directive.bindToController, directiveName, true, directive.validation);
}
if (isObject(bindings.bindToController)) {
var controller = directive.controller;
@@ -969,6 +972,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return require;
}

function processDirectiveValidation($injector, validation) {

if (isArray(validation) || isFunction(validation)) {
validation = $injector.invoke(validation);
} else if (!isObject(validation)) {
validation = createMap(); // or should throw?
}

return validation;
}

/**
* @ngdoc method
* @name $compileProvider#directive
@@ -1007,6 +1021,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
directive.name = directive.name || name;
directive.require = getDirectiveRequire(directive);
directive.restrict = directive.restrict || 'EA';
directive.validation = processDirectiveValidation($injector, directive.validation);
directive.$$moduleName = directiveFactory.$$moduleName;
directives.push(directive);
} catch (e) {
@@ -1133,7 +1148,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
scope: {},
bindToController: options.bindings || {},
restrict: 'E',
require: options.require
require: options.require,
validation: processDirectiveValidation($injector, options.validation)
};

// Copy annotations (starting with $) over to the DDO
@@ -1287,9 +1303,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {

this.$get = [
'$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
'$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri',
'$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri', '$log',
function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
$controller, $rootScope, $sce, $animate, $$sanitizeUri) {
$controller, $rootScope, $sce, $animate, $$sanitizeUri, $log) {

var SIMPLE_ATTR_NAME = /^\w/;
var specialAttrHolder = window.document.createElement('div');
@@ -3134,6 +3150,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
}

function checkDirectiveBindingSpec(directive, destination, validator, scopeName) {
// todo: check for debug mode

var error;

if (!isFunction(validator)) {
return;
}

/**
* Validator can throw, so application should not crash
*/
try {
error = validator(destination, scopeName, directive.name);
} catch (cacthedError) {
error = cacthedError;
}

if (error instanceof Error) {
$log.warn('Failed scope validation:', error.message);
}
}

// Set up $watches for isolate scope and controller bindings. This process
// only occurs for isolate scopes and new scopes with controllerAs.
@@ -3145,6 +3183,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var attrName = definition.attrName,
optional = definition.optional,
mode = definition.mode, // @, =, or &
validator = definition.validator,
lastValue,
parentGet, parentSet, compare, removeWatch;

@@ -3153,12 +3192,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
case '@':
if (!optional && !hasOwnProperty.call(attrs, attrName)) {
destination[scopeName] = attrs[attrName] = void 0;
checkDirectiveBindingSpec(directive, destination, validator, scopeName);
}
attrs.$observe(attrName, function(value) {
if (isString(value) || isBoolean(value)) {
var oldValue = destination[scopeName];
recordChanges(scopeName, value, oldValue);
destination[scopeName] = value;

if (oldValue !== value) {
checkDirectiveBindingSpec(directive, destination, validator, scopeName);
}
}
});
attrs.$$observers[attrName].$$scope = scope;
@@ -3172,6 +3216,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// the value to boolean rather than a string, so we special case this situation
destination[scopeName] = lastValue;
}

// Should it be called before SimpleChange?
if (!optional || (optional && hasOwnProperty.call(attrs, attrName))) {
checkDirectiveBindingSpec(directive, destination, validator, scopeName);
}

initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
break;

@@ -3196,6 +3246,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
attrs[attrName], attrName, directive.name);
};
lastValue = destination[scopeName] = parentGet(scope);
checkDirectiveBindingSpec(directive, destination, validator, scopeName);

var parentValueWatch = function parentValueWatch(parentValue) {
if (!compare(parentValue, destination[scopeName])) {
// we are out of sync and need to copy
@@ -3206,6 +3258,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// if the parent can be assigned then do so
parentSet(scope, parentValue = destination[scopeName]);
}
checkDirectiveBindingSpec(directive, destination, validator, scopeName);
}
return lastValue = parentValue;
};
@@ -3228,6 +3281,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
parentGet = $parse(attrs[attrName]);

var initialValue = destination[scopeName] = parentGet(scope);
checkDirectiveBindingSpec(directive, destination, validator, scopeName); // Should it be called before SimpleChange?
initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);

removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) {
@@ -3237,6 +3291,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
recordChanges(scopeName, newValue, oldValue);
destination[scopeName] = newValue;
checkDirectiveBindingSpec(directive, destination, validator, scopeName);
}, parentGet.literal);

removeWatchCollection.push(removeWatch);
@@ -3249,6 +3304,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// Don't assign noop to destination if expression is not valid
if (parentGet === noop && optional) break;

// What should be checked here?
// If can't check result of the expression, because we can't be sure that the expression is idempotent.
// By now let just leave `&` binding

destination[scopeName] = function(locals) {
return parentGet(scope, locals);
};
Loading