Sorry I know this is supposed to be a bug report but I have some questions about this library because it is extremely confusing and the documentation isn't clear.
I've added a custom element fieldPhone that I reference with type phone. Inside this custom element, I have a method onInput({ number, isValid, country }) and I want to pass the isValid from the custom abstract field component up to VueFormGenerator so I can validate using it.
How can I do this without having to add vuex on top or add custom event buses?
Thanks.
Same here @rublev . I am not sure but have you tried to use the custom events to pass the isValid to the parent. It would be great if you post a jsfiddle here
What I ended up doing was just using the same validatino library that vue-tel-input was using and then checking the value of the field in my custom validator function.
import { isValidNumber } from 'libphonenumber-js'
// field in schema
{
type: 'phone',
label: 'Phone',
model: 'phone',
required: true,
validator: (value) => {
if (isValidNumber(value)) {
return []
} else {
return ['Enter your primary phone number']
}
},
},
It works and it's not too hacky, makes me less uncomfortable than using vuex to pass up a single piece of state for one field and mucking everything up.
I won't post a fiddle because it will take too much time, but here's the code. Here's my PhoneInput component:
<script>
import 'vue-tel-input/dist/vue-tel-input.css'
import { abstractField } from 'vue-form-generator'
import VueTelInput from 'vue-tel-input'
export default {
mixins: [ abstractField ],
components: {
VueTelInput
},
}
</script>
<template>
<vue-tel-input
v-model='value'
:preferredCountries="['ca', 'us', 'gb', 'hk', 'tw', 'sg', 'ch', 'ru']"
placeholder='Primary Phone Number'
>
</vue-tel-input>
</template>
Now I can't get nested fields to work in 2.3.0 :P
You can override the validate method of a custom field, which looks something like this:
// custom validate function for a checkbox component
validate(calledParent) {
// disabled inputs should always be assumed
// to be "valid" as they can not be changed
if(this.disabled) return true;
let isValid = false;
// clear previous errors
this.clearValidationErrors();
// BE SURE TO IMPLEMENT THE "required" validation rules
if(this.schema.required && !this.value) {
isValid = false;
this.errors.push(this.schema.errorText || 'Input is required');
}
// CUSTOM VALIDATION LOGIC HERE
if (isValidNumber(value)) {
return []
} else {
return ['Enter your primary phone number']
}
// internal VFG logic for how validation is processed
// be sure to implement any core VFG logic in this method
if (isFunction(this.schema.onValidated)) {
this.schema.onValidated.call(this, this.model, this.errors, this.schema);
}
if (!calledParent)
this.$emit("validated", isValid, this.errors, this);
return this.errors;
}
So if your custom component is for telephone numbers, you can embed any logic for validating the number into the components validate method so you do not need to provide validation logic for it in the schema.
@zoul0813 if I understood you correctly, the above logic should be placed in validator in parent component?
Here's a complete 'custom checkbox' component I built, which builds the label for the checkbox into the component (this is pretty basic) ...
<script>
import { get as objGet, isFunction } from 'lodash';
import { abstractField } from 'vue-form-generator';
export default {
mixins: [ abstractField ],
// mounted() {
// console.log('field-checkboxEx', 'schema', this.schema, 'model', this.model, objGet(this.model, this.schema.model, false));
// },
render(createElement) {
// console.log('checkbox', 'render', createElement);
if(!this.disabled) {
return createElement('div',
{
'class': ['wrapper']
},
[
createElement('label', [
createElement('input', {
'class': this.schema.fieldClasses,
'attrs': {
'id': this.getFieldID(this.schema),
'type': 'checkbox',
'autocomplete': false,
'disabled': this.disabled,
'checked': objGet(this.model, this.schema.model, false),
},
on: {
change: this.onChange
}
}),
' ',
this.schema.labelText
])
]
);
}
return null;
},
methods: {
onChange($event) {
// console.log('checkbox', 'onInput', $event);
this.value = $event.target.checked;
},
validate(calledParent) {
if(this.disabled) return true;
let isValid = false;
this.clearValidationErrors();
if(this.schema.required && !this.value) {
isValid = false;
this.errors.push(this.schema.errorText || 'Input is required');
}
if (isFunction(this.schema.onValidated)) {
this.schema.onValidated.call(this, this.model, this.errors, this.schema);
}
if (!calledParent)
this.$emit("validated", isValid, this.errors, this);
return this.errors;
}
}
};
</script>
<style>
.vue-form-generator .field-checkbox input {
margin-left: 12px;
}
</style>
It is added to VFG with the following:
import FieldCheckboxExComponent from './vue/fields/checkbox.vue';
Vue.component('field-checkboxEx', FieldCheckboxExComponent);
The schema for this component would look like this:
{
"type": "checkboxEx",
"labelText": "I have read and agree to the Terms Of Service.",
"errorText": "You must accept the Terms of Service to continue",
"styleClasses": "col-sm-12",
"model": "terms_agree",
"required": true
}
This field is considered "invalid" when it is not checked, which invalidates the VFG form ... when the user checks the box, the field is considered valid.
The same logic can be applied to any custom component.
For example, something like this for the vue-tel-input wrapper ...
<template>
<vue-tel-input v-model="value"
@onInput="onInput"
<!-- optional -->
:preferredCountries="preferredCountries">
</vue-tel-input>
<template>
<script>
import { abstractField } from 'vue-form-generator';
export default {
mixins: [abstractField],
computed: {
preferredCountries() {
// use the 'preferredCountries' property of the field schema, and default to [us,gb,ua]
return this.schema.preferredCountries || ['us', 'gb', 'ua'];
}
},
data() {
return {
isValid: false,
};
},
methods: {
onInput({ number, isValid, country }) {
console.log(number, isValid, country);
this.isValid = isValid;
this.value = number
},
validate(calledParent) {
if(this.disabled) return true;
this.clearValidationErrors();
if(this.schema.required && !this.value) {
isValid = false;
this.errors.push(this.schema.errorText || 'Input is required');
}
if(!this.isValid) {
this.errors.push(this.schema.errorText || 'Input is invalid');
}
if (isFunction(this.schema.onValidated)) {
this.schema.onValidated.call(this, this.model, this.errors, this.schema);
}
if (!calledParent)
this.$emit("validated", isValid, this.errors, this);
return this.errors;
}
}
}
</script>
You can also override the formatValueToModel and formatValueToField methods of abstractField, so that ... for example "+ (123) 456-7890" can be stored in the model as "1234567890" and sent to vue-tel-input as "+ (123) 456-7890".
Take a look at the code used in the core and optional fields, and you'll see various use cases for this.
** NOTE: the vue-tel-input sample is untested, and provided as an example only
thanks for the solution @zoul0813 .Does it make sense to close the issue @rublev
@rublev I'm closing this issue as I believe the issue has been resolved by the provided examples
@adi3230 my bad yeah, the solution worked greated and i moved validation into my custom components instead which declutters a lot of my schema. thanks @zoul0813!
and someone know how to do that with google recaptcha?? please help
I tried in same way but for recaptcha this doesn't work :( can you help?
@OkaSroka Can you please elaborate your issue for eg providing a jsfiddle link because it's really difficult to understand what you encountered/ why something doesn't work for you
Most helpful comment
@adi3230 my bad yeah, the solution worked greated and i moved validation into my custom components instead which declutters a lot of my schema. thanks @zoul0813!