GithubHelp home page GithubHelp logo

Comments (29)

prsth avatar prsth commented on June 26, 2024 9

Found a nice way for myself. Maybe it will help somebody. Works with parent-chidren references.

The code below is from a save() component method. Vee-Validate using globally.

let promises = []
for (let child in this.$refs) promises.push(this.$refs[child].$validator.validateAll());

Promise.all(promises)
      .then(this.$validator.validateAll())
      .then(
         () => {//OK CONTINUE SAVING},
         () => {//NOT OK})

@logaretm I am afraid that I absolutely not professional but I would add this snippet in a method sounds something like validateAllWithRefs() in my lib. If you OKay with it I think I can do my first Pull-request))

from vee-validate.

logaretm avatar logaretm commented on June 26, 2024 6

Well in Vue 2.0 the component can only communicate via the old API by $emit, both $broadcast and $dispatch were deprecated, so I don't think It will be included in the plugin since there is no distinction between a form component with multiple children acting as custom inputs and say a layout with some inputs in it.

But I think you can do it in a couple of ways, depending on Vue version:

Vue 1.0

In Vue 1.0 you can trigger the event on all child components, you just need to hook into the event and broadcast it again to the children.

ready() {
  this.$on('veeValidate', () => {
    this.$broadcast('veeValidate');
  });
}

// Triggering validateAll should now trigger validations for all child components.
this.$validator.validateAll();

Vue 2.0

You can use an empty Vue instance as an event bus:

// in bus.js
import Vue from 'vue';

const bus = new Vue();

export default bus;

// In your parent component
import bus from './bus';
mounted() {
  this.$on('veeValidate', () => {
    bus.$emit('veeValidate');
  });
}

// in your child components.
import bus from './bus';

mounted() {
  bus.$on('veeValidate', () => {
    this.$validator.validateAll());
  });
}

or you can go ahead and loop over the child components and trigger validateAll individually on each of them:

// in your parent component
// you might want to recursively loop over the instances.
this.$children.forEach(vm => {
  vm.$validator.validateAll();
});

I didn't test those suggestions, but I can build an example if you continue to have trouble solving this.

I would love to offer some better way built into the plugin, but until I figure a nice way to do it, I don't think it will be in time for full release tho.

from vee-validate.

Kocal avatar Kocal commented on June 26, 2024 5

Thanks @prsth, I think it's one of the cleanest solution I found here to make validation working with a dynamic and very nested component. 👍

This is what I am using right now:

const promises = [];
promises.push(this.$validator.validateAll());
// ref `widget-form` can be not rendered, so we resolve `false` by default
promises.push(this.$refs['widget-form'] ? this.$refs['widget-form'].$validator.validateAll() : Promise.resolve(false));

Promise.all(promises).then(validations => {
  // If one validation has failed, we stop here
  if (validations.some(validation => validation === false)) return;

  // Everything is valid
});

from vee-validate.

ahmed-essawy avatar ahmed-essawy commented on June 26, 2024 5

For me it works fine by adding inject: ['$validator'] in all nested components

export default {
  name: 'child-component',
  inject: ['$validator'],
}

from vee-validate.

juanmirod avatar juanmirod commented on June 26, 2024 4

Since this thread is not yet locked for further comments, I'd like to add a solution I built for v4 in case anyone else like me is scouting around trying to find out how to do this properly with Vue3 and Composition API.

It works by adding each validate method from useForm to an array and then on form submit it will loop through the array to ensure all validators pass asynchronously. The idea is borrowed from a reply from @logaretm in this issue: #2915

formAggregator.ts

import { InjectionKey, provide, ref } from "vue";

type ValidatorMethodType = () => Promise<{ valid: boolean }>;

export const AddValidatorKey: InjectionKey<
  (validator: ValidatorMethodType) => void
> = Symbol("AddValidatorKey");

export function useValidationAggregator() {
  const validators = ref<ValidatorMethodType[]>([]);

  // used by components to add validators to the array
  const register = (validator: ValidatorMethodType) => {
    validators.value.push(validator);
  };

  // provide this aggregator to components
  provide(AddValidatorKey, register);

  // run all validators
  const validateAll = async () => {
    return Promise.all(validators.value.map((v) => v()));
  };

  return {
    validateAll,
    register,
  };
}

ParentComponent.vue

<template>
<form @submit.prevent="onSubmit">
    <ChildComponent />
   <button type="submit" :disabled="inSubmit">Submit</button>
</form>
</template>
<script setup>
import useValidationAggregator from "./formAggregator.ts"
const { validateAll } = useValidationAggregator();
// note: you can use register() to also add validator from the parent component

const inSubmit = ref(false);
const onSubmit = async () => {
   try {
      inSubmit.value = true;

      // run all form validators
      const results = await validateAll();

      // ensure all are valid
      if (results.some((r) => !r.valid)) {
         return;
      }

      // todo: post form or whatever you need after validation passes
   } finally {
      inSubmit.value = false;
   }
}
</script>

ChildComponent.vue

<template>
   <!-- some validated fields -->
</template>
<script setup>
import { AddValidatorKey } from "./formAggregator.ts"

const { validate } = useForm({ ... setup validated fields ... });

// add this component's validator to the aggregator
const addValidator = inject(AddValidatorKey, () => {
  throw new Error("No aggregator provided for inject");
});
addValidator(validate);
</script>

@joakimriedel Thanks a lot for your example! I was looking for something like this!

@logaretm I think adding something like this useValidationAggregator to the library would be very useful, I think that it is quite common to have components that encapsulate a slice of the form that is dynamic or just repeats (asking for several children/employee info, several addresses, etc...) and to have a parent component that needs to check that all the children are valid...

from vee-validate.

sproogen avatar sproogen commented on June 26, 2024 3

@shakee93 I have just added an example Gist demonstrating the principle of how we passed the error bag bag to the parent element.

https://gist.github.com/sproogen/147d75db261505e8a558a7fd11a20551

@logaretm might have another suggestion on how this could be done though?

from vee-validate.

joakimriedel avatar joakimriedel commented on June 26, 2024 3

Since this thread is not yet locked for further comments, I'd like to add a solution I built for v4 in case anyone else like me is scouting around trying to find out how to do this properly with Vue3 and Composition API.

It works by adding each validate method from useForm to an array and then on form submit it will loop through the array to ensure all validators pass asynchronously. The idea is borrowed from a reply from @logaretm in this issue: #2915

formAggregator.ts

import { InjectionKey, provide, ref } from "vue";

type ValidatorMethodType = () => Promise<{ valid: boolean }>;

export const AddValidatorKey: InjectionKey<
  (validator: ValidatorMethodType) => void
> = Symbol("AddValidatorKey");

export function useValidationAggregator() {
  const validators = ref<ValidatorMethodType[]>([]);

  // used by components to add validators to the array
  const register = (validator: ValidatorMethodType) => {
    validators.value.push(validator);
  };

  // provide this aggregator to components
  provide(AddValidatorKey, register);

  // run all validators
  const validateAll = async () => {
    return Promise.all(validators.value.map((v) => v()));
  };

  return {
    validateAll,
    register,
  };
}

ParentComponent.vue

<template>
<form @submit.prevent="onSubmit">
    <ChildComponent />
   <button type="submit" :disabled="inSubmit">Submit</button>
</form>
</template>
<script setup>
import useValidationAggregator from "./formAggregator.ts"
const { validateAll } = useValidationAggregator();
// note: you can use register() to also add validator from the parent component

const inSubmit = ref(false);
const onSubmit = async () => {
   try {
      inSubmit.value = true;

      // run all form validators
      const results = await validateAll();

      // ensure all are valid
      if (results.some((r) => !r.valid)) {
         return;
      }

      // todo: post form or whatever you need after validation passes
   } finally {
      inSubmit.value = false;
   }
}
</script>

ChildComponent.vue

<template>
   <!-- some validated fields -->
</template>
<script setup>
import { AddValidatorKey } from "./formAggregator.ts"

const { validate } = useForm({ ... setup validated fields ... });

// add this component's validator to the aggregator
const addValidator = inject(AddValidatorKey, () => {
  throw new Error("No aggregator provided for inject");
});
addValidator(validate);
</script>

from vee-validate.

sproogen avatar sproogen commented on June 26, 2024 1

I had already done it, so I thought I would pull that out and stick it in the gist.

Feel free to modify it and use is as an example in the future.

from vee-validate.

daylightstudio avatar daylightstudio commented on June 26, 2024 1

Yes, App.event is just a new Vue() instance used as the event bus. In my example, I have a global App that it attaches too but it would be different depending on your setup and where you place the event bus.

from vee-validate.

Thrajnor avatar Thrajnor commented on June 26, 2024 1

@ahmed-essawy Why nobody is reacting/commenting on this solution it is really the cleanest and most obvious so far. Is there any drawbacks in using it, or i am missing something?

from vee-validate.

sproogen avatar sproogen commented on June 26, 2024

Thanks for the response,

We are using Vue2 so I thought that an event bus would be the best approach to this. But I was unsure if it was something that might be included in the plugin.

I think we should be good to implement this our selves but it might be good to include something about this in the docs, maybe as an advanced example.

Thanks

from vee-validate.

logaretm avatar logaretm commented on June 26, 2024

Yea sure, I might add an example regarding this, thanks!

from vee-validate.

shakee93 avatar shakee93 commented on June 26, 2024

Thanks for the great plugin. little doubt. how am i supposed to check if there are any errors in child component this.errors.any() returns false currently. any workaround for this ?

from vee-validate.

logaretm avatar logaretm commented on June 26, 2024

@sproogen that is pretty much how I would have handled it, yours are even cleaner. Sorry I couldn't create an example for this, thanks for the gist!

from vee-validate.

daylightstudio avatar daylightstudio commented on June 26, 2024

Another option could be to have child components register themselves with the parent to be validated at the same time:

In the code below, App.event is a Vue object used as the event bus.

Parent Component:

...
mounted: function(){
	var self = this;

	this.childValidators = [];

	App.event.$on('child-validator-added', function(component){
		self.childValidators.push(component);
	});

	App.event.$on('validate', function(){
		self.$validator.validateAll();
		self.childValidators.forEach(function(component){
			component.$validator.validateAll();
			component.$validator.getErrors().errors.forEach(function(error){
				self.errors.add(error.field, error.msg);
			})
		})
	});

	$('#resource-form').submit(function(e){
		e.preventDefault();
		App.event.$emit('validate');
		if (self.errors.count()) {
			console.log('ERRORS')
		} else {
			console.log('SUCCESS')
		}
	});
},

Child Component:

...
mounted: function(){
	var self = this;
	this.$nextTick(function(){
		App.event.$emit('child-validator-added', this);
	});
}

from vee-validate.

Vaerum avatar Vaerum commented on June 26, 2024

@sproogen I have used your example, but I keep getting the error "TypeError: _vm.errors.has is not a function". It is properly not your example, but I cannot solve the error.

from vee-validate.

daylightstudio avatar daylightstudio commented on June 26, 2024

The example above doesn't directly call the method "has()" on errors. Is that somewhere in your code by chance? If so, what does it look like?

from vee-validate.

Vaerum avatar Vaerum commented on June 26, 2024

@daylightstudio I did not use your example, but the example of @sproogen.

https://gist.github.com/sproogen/147d75db261505e8a558a7fd11a20551

But I have a question for you. From where do you get App.Event? Is this a new Vue Instance?

//Rasmus

from vee-validate.

pimboden avatar pimboden commented on June 26, 2024

This is not really usefull... validateAll() is now a promise. So none of these samples really solve the issue.

from vee-validate.

logaretm avatar logaretm commented on June 26, 2024

@pimboden There are already multiple solutions to issues like this depending on your usage or needs, you just have to be consistent in your code.

for example if your custom components serve as a custom input with special behavior then you should use component validation which is supported, no need to transfer errors via events or anything.

if your components serve as organizing or grouping of some inputs, then you should use the events.

I'm considering adding a centralized error object which is optional for such cases, but it will require complex scoping, so I hope I will be able to have a general idea about it after releasing the new version.

from vee-validate.

pimboden avatar pimboden commented on June 26, 2024

@logaretm Thank you. But when you say "there are already multiple solutions to issues like this" , could you tell me where... I have goggled, and I only found 2 or 3 solutions, all working with validateAll() without using it as promise...
My app, uses a component (form-component) that has some input fields,

<template>
    <div>
      <input type = text -..... ></text>
       <vss-inputs...>      </vss-inputs>
    </div>
<template>`

Inside it uses a child-component, that only loops through some array, and depending on the values of the array, this child-component renders its own child-components: So my vss-inputs is something like this

<template v-for="(vss, index) in vssToRender">
    <template v-if="showCheckbox(vss)||showCheckboxRequired(vss)">
          <vss-subcomponent-1...>      </vss-subcomponent-1>
    </template>
    <template v-if="showCheckbox(vss)||showCheckboxRequired(vss)">
      <vss-subcomponent-2>      </vss-subcomponent-2>
    </template>
 </template>

I didn't' find out, how my "form-component" gets informed that all components in the hierarchy have finished validating.
My form. component triggers an event for validating.
All my sub-components, go through their validations.. (they correctly render the error messages)
The problem... how does my "form-component" get informed that all other components finished validating? And how does it know if some sub components didn't pass the validation?

Thanks for your help

from vee-validate.

logaretm avatar logaretm commented on June 26, 2024

I understand that those "solutions" aren't documented, but its hard to document every single solution that may not fit all projects, ideally the plugin should be easy enough for users to implement their own solutions for such problems.

having said that, your issue seems a little bit more complex than the others mentioned, but you can create a dedicated event bus for the errors, whenever a component gets created, it should register itself as an "error provider" meaning the parent knows of such a component.

You can then trigger validation across all registered components, by looping over their container (array) and triggering validateAll then you add the errors in the catch callback to the parent errors, using Promise.all and collecting all promises can tell you when all validations are finished validating.

I have not implemented such a thing, but this comes to my mind as a possible solution for your case.

from vee-validate.

coderabsolute avatar coderabsolute commented on June 26, 2024

@prsth I have just tested this method at my side and it seems to work pretty well, I just need to add the ref property to my custom component. The ref value should be same as name I think?

from vee-validate.

prsth avatar prsth commented on June 26, 2024

@coderabsolute I guess you can name it in a way you would like to. It doesn't matter. It even could be an evaluated string like this

<template v-for="(item, n) in myarray">
   <child-component ref="`anyname${n}`"></child-component>
</template>

All the child components would be validated.

from vee-validate.

ahmed-essawy avatar ahmed-essawy commented on June 26, 2024

@ahmed-essawy Why nobody is reacting/commenting on this solution it is really the cleanest and most obvious so far. Is there any drawbacks in using it, or i am missing something?

Thanks @Thrajnor,
It's working perfectly since I wrote the comment without issues till now

from vee-validate.

robertoconceicao avatar robertoconceicao commented on June 26, 2024

@ahmed-essawy Why nobody is reacting/commenting on this solution it is really the cleanest and most obvious so far. Is there any drawbacks in using it, or i am missing something?

Thanks @Thrajnor,
It's working perfectly since I wrote the comment without issues till now

@ahmed-essawy Hello, I do this, but no working for me, this is my code:

Code parent:

...
<v-form
          novalidate
          @submit.prevent="validateBeforeSubmit">
....
<v-flex md3>
                  <v-text-field
                    v-validate="'required'"
                    v-model="form.sigla"
                    :error-messages="errors.collect('Sigla')"
                    label="Sigla"
                    type="text"
                    data-vv-name="Sigla"
                    required
                  />
                </v-flex>
          <!-- child component -->
          <core-regra-financeira
              v-model="regra"
           />
           
          <v-btn
                :loading="loadingBtn"
                type="submit"
                color="primary"
              >Submit</v-btn>
...
</form>
...
methods: {
    validateBeforeSubmit () {
      this.$validator.validate()
        .then(result => {
          if (result) {
             // OK
          } else {
            // NOK
          }
        })

Code Child:

      ...

<v-flex
	md2
	sm6
	xs12>
	<v-text-field
	  v-formata-moeda="value.limiteCreditoMesMaximo"
	  v-money="money"
	  v-validate="'required'"
	  v-model.lazy="value.limiteCreditoMesMaximo"
	  :disabled="disabled"
	  :error-messages="errors.collect('Limite Crédito Mês Máximo')"
	  data-vv-name="Limite Crédito Mês Máximo"
	  required
	  type="tel"
	  suffix="R$"
	  reverse
	  label="Limite Crédito Mês Máximo"
	/>
</v-flex>

export default {
  inject: ['$validator'],

...

This only works if the user changes the value of the form, otherwise the validation does not work.

Do you have an example?

from vee-validate.

ahmed-essawy avatar ahmed-essawy commented on June 26, 2024

Hello @robertoconceicao

try to use this.$validator.validateAll().then(); instead of this.$validator.validate().then();

if it still not working

try to use refs as below
<v-form @submit.prevent="validateBeforeSubmit" ref="exampleForm">.....</v-form>
then change validation method to be this.$refs.exampleForm.$validator.validateAll().then(); instead of this.$validator.validate().then();

from vee-validate.

robertoconceicao avatar robertoconceicao commented on June 26, 2024

Thanks @ahmed-essawy

I try the two solution, but not working still.

I resolved passing into child the method save of parent, it's working for me, in this case.

My solution:

Code Parent:

	<form>
		fields parent
		<!-- child component -->
		<core-regra-financeira
		  v-model="regra"
		  :disabled="disabled"
		  :salve="validateBeforeSubmit" <!-- Here, I'm passing into method validate and save of Parent for child -->
		  :go-back="goBack"
		  :cancel="cancel"
		/>
	</form>

Code Child:

  fields childs required
  ....
  
  export default {
  inject: ['$validator'],
  props: {
    value: {
      type: Object,
      required: true
    },
    save: {
      type: Function,
      required: true
    },
    go-back: {
      type: Function,
      required: true
    },
    cancel: {
      type: Function,
      required: true
    },
    // eslint-disable-next-line vue/require-default-prop
    disabled: false
  },
  methods: {
    validationChild () {
      this.$validator.validate()
        .then(result => {
          if (result) {
            this.save() // callback from Parent 
          } else {
            // NOK alert
          }
        })
    }
  }

from vee-validate.

psy21d avatar psy21d commented on June 26, 2024

Has a little better by-default?

from vee-validate.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.