koumoul-dev / vuetify-jsonschema-form

Create beautiful and low-effort forms that output valid data. Published on npm as @koumoul/vjsf.
https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/
MIT License
538 stars 154 forks source link

Using vuetify-tel-input #333

Closed IOIIOOIO closed 2 years ago

IOIIOOIO commented 2 years ago

Hi, I am unsure how to pass a validation event up to vjsf-form? In my example I am using vuetify-tel-input. It emits a valid event. The problem is that vjs-form doesn't have any awareness of it, so even if the input is invalid it will still submit the form.

custom component:

<template>
    <!-- using vuetify's generic component v-input is handy for homogeneous labels, validation, etc. -->
    <v-input
            :value="value"
            :name="fullKey"
            :label="label"
            :disabled="disabled"
            :rules="rules"
            :required="required"
            class="c-tel-input"
    >
        <vue-tel-input-vuetify :valid-characters-only="true"
                               @input="onInput"
                               :error-messages="phoneError"
                               persistent-placeholder></vue-tel-input-vuetify>
    </v-input>
</template>

<script>
    export default {
        name: "LTelInput",
        // available props are the contextual elements passed by v-jsf to its slots
        props: {
            value: { type: String, default: '' },
            options: { type: Object, required: true },
            schema: { type: Object, required: true },
            fullSchema: { type: Object, required: true },
            fullKey: { type: String, required: true },
            label: { type: String, default: '' },
            htmlDescription: { type: String, default: '' },
            disabled: { type: Boolean, default: false },
            required: { type: Boolean, default: false },
            rules: { type: Array, required: true },
            on: { type: Object, required: true },
        },
        data: () => ({
            phone: {
                number: "",
                valid: false,
                country: undefined
            },
            phoneError: [],
        }),
        computed: {},
        methods: {
            onInput(formattedNumber, { number, valid, country }) {
                this.phone.number = number.international;
                this.phone.valid = valid;
                this.phone.country = country && country.name;
                if(this.phone.valid ) {
                    this.phoneError = [];
                    this.on.input(this.phone.number)
                } else {
                    this.phoneError = [
                        'please enter a valid phone number'
                    ];
                }
            },
        }
    }
</script>

v-jsf form:

<v-jsf class="c-json-form" v-model="model" :schema="form.schema" :options="form.options">
    <template slot="custom-tel-input" slot-scope="context">
        <l-tel-input v-bind="context"/>
    </template>
</v-jsf>
IOIIOOIO commented 2 years ago

I have also tried using this method: https://github.com/koumoul-dev/vuetify-jsonschema-form/issues/263

But this.validationState remains undefined. And if I change the code on the vue-tel-input-vuetify so that it uses :value and v-on then it crashes with the error:

Invalid prop: type check failed for prop "value". Expected Number with value 1, got String with value "1"

Example:

<template>
    <!-- using vuetify's generic component v-input is handy for homogeneous labels, validation, etc. -->
    <vue-tel-input-vuetify :valid-characters-only="true"
                           :value="value"
                           v-on="{ ...on, onInput }"
                           :error-messages="isValidationError ? ['please enter valid number'] : []"
                           persistent-placeholder></vue-tel-input-vuetify>
</template>

<script>
    import VInput from 'vuetify/es5/components/VInput'
    export default {
        extends: VInput,
        name: "LTelInput",
        // available props are the contextual elements passed by v-jsf to its slots
        props: {
            value: { type: Number },
            options: { type: Object, required: true },
            schema: { type: Object, required: true },
            fullSchema: { type: Object, required: true },
            fullKey: { type: String, required: true },
            label: { type: String, default: '' },
            htmlDescription: { type: String, default: '' },
            disabled: { type: Boolean, default: false },
            required: { type: Boolean, default: false },
            rules: { type: Array, required: true },
            on: { type: Object, required: true },
        },
        data: () => ({
            phone: {
                number: "",
                valid: false,
                country: undefined
            },
        }),
        computed: {
            isValidationError() {
                return this.validationState === 'error'
            },
        },
        methods: {
            onInput(formattedNumber, { number, valid, country }) {
                console.log('this.validationState', this.validationState); // logs out as undefined
                this.phone.number = number.international;
                this.phone.valid = valid;
                this.phone.country = country && country.name;
                if(this.phone.valid ) {
                    this.on.input(this.phone.number)
                }
            },
        }
    }
</script>
albanm commented 2 years ago

I think you can put the v-input in error state using its "error" prop, or you can give a "rules" array completed by your own rule based on this.phone.valid.

IOIIOOIO commented 2 years ago

Thanks. This is my working code for anyone else who might have issues. I alos found that I needed to force an error if the input was required.

<template>
    <!-- using vuetify's generic component v-input is handy for homogeneous labels, validation, etc. -->
    <v-input
            :value="value"
            :name="fullKey"
            :label="label"
            :disabled="disabled"
            :rules="phoneError"
            :error="isRequired"
            :required="required"
            class="c-tel-input"
    >
        <vue-tel-input-vuetify :valid-characters-only="true"
                               @input="validate"
                               :value="value"></vue-tel-input-vuetify>
    </v-input>
</template>

<script>
    export default {
        name: "LTelInput",
        // available props are the contextual elements passed by v-jsf to its slots
        props: {
            value: { type: String, default: '' },
            options: { type: Object, required: true },
            schema: { type: Object, required: true },
            fullSchema: { type: Object, required: true },
            fullKey: { type: String, required: true },
            label: { type: String, default: '' },
            htmlDescription: { type: String, default: '' },
            disabled: { type: Boolean, default: false },
            required: { type: Boolean, default: false },
            rules: { type: Array, required: true },
            on: { type: Object, required: true },
        },
        data: () => ({
            phone: {
                number: "",
                valid: false,
                country: undefined
            },
            phoneError: [],
        }),
        computed: {
            isRequired() {
                let valid = true;
                 if(this.required && !this.value) {
                    valid = false;
                     this.phoneError = [
                         'This field is required'
                     ];
                 }

                return valid;
            }
        },
        methods: {
            validate(formattedNumber, { number, valid, country }) {

                this.phone.number = number.international;
                this.phone.valid = valid;
                this.phone.country = country && country.name;

                if(this.phone.valid ) {
                    this.phoneError = [];
                    this.on.input(this.phone.number)
                } else {
                    this.phoneError = [
                        'please enter a valid phone number'
                    ];
                }
            },
        },
    }
</script>

<style lang="scss" scoped>
.c-tel-input {
    &::v-deep {
        .v-messages__message {
            top: -22px;
            position: relative;
        }
    }
}
</style>

Furthermore, I found that it is necessary to add it to the "required" property of theschema object. For example, adding required: true inside properties.mobile will not work here:

schema: {
    "type": "object",
    "required": ["mobile"],
    "properties": {
        "mobile": {
            "type": "string",
            "title": "Mobile number",
            "x-display": "custom-tel-input",
        },
    }
},