aurelia / validation

A validation plugin for Aurelia.
MIT License
132 stars 128 forks source link

Support multiple validators #259

Closed y2k4life closed 8 years ago

y2k4life commented 8 years ago

I see that the plugin aurelia-validatejs registers a validator into the aurlia global container config.container.registerInstance(ValidatorInterface, new Validator());. Then it is injected into the ValidationController as this @inject(Validator) and I assume this is a singleton. But what if or how would I use multiple Validators? I know as of now I can't. If this could happen I already see that if a validator is called it does not do validation unless there are rules. So even if I have multple validators, if for a give viewmodel I did not setup up rules for a validator then it will not validate. I also understand that each validator will have different rules. My custom validator will not get overlapped with the validatejs rules. But with my idea I would not add a validator to the ValidateController unless I needed it.

I built a validator to handle a more complex situation, but I still want to use aurelia-validatejs to handle the more mundane validations. I have a form with 5 or 6 simple none complex questions that are required. Some are number fields. For those I use the aurelia-validatejs validtor and the other complex questions I use my custom validator.

I know how I might change the VlidationController, but I don't know the time to live on the ValidationController and if I add a validator to handle form A how do I know that it is not there when I want to handle form B which has a different complex validator along with the validatejs. I'm reading about the NewInstance.of(ValidationController) DI call. I think I would be safe base on this In this case it's telling the container to always retrieve a new instance of a ValidationController. that I could then have an array of validators and methods on the ValidationController to add validators. Then change the logic const newErrors = this.validator.validateProperty(object, property, rules); to loop through each validator and call validateProperty.

Here is the live example: http://direct.axa.minico.forebrain.net/app/index.html

If you scroll down and hit continue to the "location" information there are yes/no and multiple choice questions which present more questions that are only required based on the answer. If you answer "Apartment/Condo" to the Location Type question then Door Man, Unit Number, and Floor Number are required. But if you select Storage Unit then those question are not required, but Facility Name, Unit #, and Floor # are required, but if you select Single Home then none of those are required. I can write the logic for validation in a customer validator, but how do I use my custom validator along with ValidateJs to handle the Address, City, State, and Zip, or to make sure Year Built is required and is a number.

If you hit continue to "additional info" you now have a different form with again complex validation. How do I remove the location validator and add the additional info validator and yet keep the validatjs? On this one, if you answer yes to prior losses I need to test if you have added any additional data and if you did not that is an error with at least one required. The last question answer yes and if you hit yes but don't add then that is a validation error.

All this came about when I built my custom validator and my custom validations work, but the other validation for validatejs did not work.

y2k4life commented 8 years ago

Time has passed and by now I have implemented my ideas. All the changes take place in the validation-controller.js. With that said if you look at the links mentioned in my previous post the ideas and the design I want is there. I removed the injection of the Validator from ValidationController. I add an array validators = [];. I added a method to ValidationController to add a vlidator to the controller.

  addValidator(validator: Validator) {
    this.validators.push(validator)
  }

Then I changed the _validateBinding method to iterate through the validators until either an error occurs or all validation is completed.

  _validateBinding(binding) {
    const { target, rules, errors } = this.bindings.get(binding);
    const { object, property } = getPropertyInfo(binding.sourceExpression, binding.source);
    var newErrors;
    for(let i=0; i < this.validators.length; i++) {
      newErrors = this.validators[i].validateProperty(object, property, rules);
      if(newErrors.length > 0) {
        break;
      }
    }
    this._updateErrors(errors, newErrors, target);
    return errors;
  }

Then in my view model I add the validators shown below. The first one added is the aurelia-validatejs and the second one is a custom validator I built that calls a function to test for validation. There might be more to this than I lead on, but that is more of how I implement FunctionValidator and how I setup rules. The point here is that I can now use two different validators for one VM.

this.controller.addValidator(new Validator());
this.controller.addValidator(new FunctionValidator());
jdanyow commented 8 years ago

I think we can support multiple validator implementations pretty easily. We'll need to change the validation controller class's @inject(Validator) to @inject(All.of(Validator)) and update the validateBinding method as you've done.

y2k4life commented 8 years ago

But would inject all Validator(s) be a good design? What if on some cases I don't need all? Would injecting them be overkill? I like that idea because then one does not need to add them. These are the trade offs, lean and mean vs. I forgot to add my validator. I guess it is also the 80/20 rule how many times will there be more than one and does inject all only injects one for 80% of the time. I won't loss sleep over it either way :)

jdanyow commented 8 years ago

We can have both APIs (registration & add/remove validator). Then folks can decide for themselves.

jdanyow commented 8 years ago

closing- this was added in friday's release.