json-schema-form / angular-schema-form

Generate forms from a JSON schema, with AngularJS!
https://json-schema-form.github.io/angular-schema-form
MIT License
2.47k stars 653 forks source link

Using other fields values in a custom validator #847

Open donalmurtagh opened 7 years ago

donalmurtagh commented 7 years ago

The docs include this example that shows how to write a custom validator for a field:

[
  {
    key: 'name',
    validationMessage: {
      'noBob': 'Bob is not OK! You here me?'
    },
    $validators: {
      noBob: function(value) {
        if (angular.isString(value) && value.indexOf('Bob') !== -1) {
          return false;
        }
        return true
      }
    }
  }
]

Is it somehow possible to use other field values in a custom validator? For example, if we want to apply the validation above only if model.gender == 'MALE'?

Anthropic commented 7 years ago

I would suggest not using $validators from the ui-schema, instead if you have a controller or a directive to manage it, then you can make scope available to it when you define it.

donalmurtagh commented 7 years ago

I would suggest not using $validators from the ui-schema

Based on the example above, it isn't possible to do this with $validators, because the validation function only provides access to the value of the field itself, or am I missing something?

instead if you have a controller or a directive to manage it, then you can make scope available to it when you define it.

Are you suggesting that I should implement this with an addon?

One solution I'd considered is to define an addon which validates the field by calling back into a function defined in the form/schema with this signature

function (fieldValue, model) { }

In the case of my example above, the function would be defined like so:

function (fieldValue, model) { 
  if (model.gender === 'MALE') {
    return angular.isString(fieldValue) && fieldValue.indexOf('Bob')
  }
  return true;
}
Anthropic commented 7 years ago

@donalmurtagh if you look at setting up defaults you can see the use of the provider to apply form attribute selection, you could apply your validator to f as the f value in the example is the form definition and I would expect that adding the validator here where it can access your scope would work and can be done within your controller or an add on.

Disclaimer: I have not tried it so cannot guarantee it works.

Lorless commented 7 years ago

Im also having trouble here. I need to make sure several values are not the same but I have no access to the model in my validator.

@Anthropic, your suggestion above seems it would affect every instance of that type of field. That doesn't seem viable, or the right way to do things. Even if we managed a way to turn that specific validator on or off we still have the issue of integrating with schema form / tv4. How do we set validity of the field? How do we set validation messages? It seems like a lot of work creating bespoke directives for every validation scenario when all we need is a second argument with a copy of the model (and probably the form at the level the change was made so we can tell where it was in our form we made that change)

Anthropic commented 7 years ago

@donalmurtagh @Lorless the issue is $validator is not an ASF feature and it is passed directly to Angular as is, Angular is the one that only passes a single model value to the function.

I suspect you could try using a wrapper around the code that adds the validator, change

      [ '$validators', '$asyncValidators' ].forEach(function(attr) {
        // Check if our version of angular has validators, i.e. 1.3+
        if (form[attr] && ngModel[attr]) {
          angular.forEach(form[attr], function(fn, name) {
            ngModel[attr][name] = fn;
          });
        }
      });

Into this

      [ '$validators', '$asyncValidators' ].forEach(function(attr) {
        // Check if our version of angular has validators, i.e. 1.3+
        if (form[attr] && ngModel[attr]) {
          angular.forEach(form[attr], function(fn, name) {
            ngModel[attr][name] = function(modelValue, viewValue) {
              return fn(modelValue, viewValue, scope.model, form);
            };
          });
        }
      });

If one of you want to try that and see if you could get it working I'd love confirmation it works or a PR or even just details on modifications to get it working :)

Anthropic commented 7 years ago

Didn't mean to close, if someone can confirm the solution I suggested either works or offer suggested changes it would be a great help toward the issue :)

Lorless commented 7 years ago

@Anthropic Ill test this out tonight :)

Anthropic commented 7 years ago

@donalmurtagh @Lorless any feedback?

donalmurtagh commented 7 years ago

@Anthropic not yet, but it's still very much on my radar. I expect to get to this within the next week or two. Sorry for the delay.

tivie commented 6 years ago

You can access other field values in the validator function (third parameter):

(tested on Angular 1.6)

$validators: {
  foo: function(newValue, oldValue, form) {
    if (form.bar === 'Male') {
      // do domething because it's male (that's sexist)
    }
  }
}
donalmurtagh commented 6 years ago

@tivie thanks, that's very useful to know. This should be added to the docs