npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

vue-forms-kit

v0.1.4

Published

A set of primitives to build your own forms and inputs with low effort.

Downloads

2

Readme

Vue Forms kit

A set of primitives to build your own forms and inputs with low effort.

Installation

yarn install vue-forms-kit
# or
npm i vue-forms-kit

Note: The package does not provide transpiled JavaScript files. Use it with module bundlers.

Architecture

Unlike many other libraries, this one aims to provide a low-level set of renderless components that you may compose to create a higher lever form inputs. The library doesn't enforce any styling deferring that decision to a developer. It also ships with a collection of ready-to-use form inputs that you can style and use in your project, or use them as a reference. Moreover, it is better to implement your form components in the project using these components.

The core building blocks are input, form group, and form.

  • "input" is the most simple form element that renders a native form element or Vue component.
  • "form group" is an optional decorator for the input component that enhances it with extra functionality like errors, help text, or labels.
  • "form" is an (optional) element, that operates with user input data, performs validation, and can handle the form submission. It also propagates validation errors to the child form groups.

Quick start

In this mini-tutorial, we are going to create a simple form with form groups and form inputs. This form will ask for user information, perform validation and submit the data. The tutorial assumes that you are familiar with scoped slots.

Form inputs

Let's start with the smallest entity - the form input:

<script>
    // TextInput.vue
    import { InputController } from 'vue-forms-kit';

    export default {
        components: { InputController },
    };
</script>
<template>
    <input-controller v-on="$listeners" v-slot="{onInput}">
        <input type="text" v-bind="$attrs" @input="onInput" />
    </input-controller>
</template>

We just created a simple text input. In real life, you would need handlers for "change", "focus", and "blur" events. At this stage, you can already start using the <text-input /> component in your forms.

Form groups

Okay, let's decorate it with a label, with help text and validation errors. First, we will create a "form group" component that will receive label text and help text via properties.

<script>
    // FormGroup.vue
    import { FormGroupController } from 'vue-forms-kit';

    export default {
        components: { FormGroupController },
        props: {
            label: String,
            help: String,
        },
    };
</script>
<template>
    <form-group-controller v-slot="{required, errors}">
        <div class="form-group">
            <label class="form-label">
                <span>{{ label }}</span>
                <span class="form-label-asterisk" v-if="required">*</span>
            </label>
            <slot></slot>
            <div v-if="help" class="form-help">{{ help }}</div>
            <ul v-if="errors" class="form-errors">
                <li v-for="(error, index) in errors" :key="error + index">
                    {{ error}}
                </li>
            </ul>
        </div>
    </form-group-controller>
</template>

We created a form group component that decorates TextInput. You can implement any other features like icons, floating labels, and so on. The usage may look like this:

<form-group label="Your email" help="The email address must include @ sign">
    <text-input name="email">
</form-group>

Form fields

When you have many forms it feels like you are writing a lot of boilerplate code. Let's reduce it by introducing a new concept: "form fields". The form field is a component that combines inputs and form groups into a new one.

<script>
    // TextField.vue
    import TextInput from './TextInput.vue';
    import FormGroup from './FormGroup.vue';

    export default {
        components: { TextInput, FormGroup },
        props: {
            label: String,
            help: String,
        },
    };
</script>
<template>
    <form-group :label="label" :help="help">
        <text-input v-bind="$attrs" v-on="$listeners" />
    </form-group>
</template>

Now our form becomes cleaner:

<text-field
    name="email"
    label="Your email"
    help="The email address must include @ sign"
></text-field>

Forms

Once we have building blocks, let's create our form component with these requirements:

  • it must validate email address length and report an error when invalid
  • it must submit the form via our function
  • it must render errors returned by the submit handler

We won't create a new Vue component for the form in this example. So let's use FormController to achieve our goal. The form controller requires the following properties:

  • data - the object containing all user input
  • handler - a function that handles form submission
  • validator - an optional function that performs validation. It must return a key-value mapping where keys are field names and values are errors for the field.
<script>
    // EditUserForm.vue
    import { FormController } from 'vue-forms-kit';
    import TextField from './TextField.vue';

    export default {
        components: { FormController, TextField },
        data() {
            return {
                formData: {
                    first_name: '',
                    last_name: '',
                    email: '',
                },
            };
        },
        methods: {
            async validate(formData) {
                let errors = {};
                if (!formData.email) {
                    errors.email = 'This field is required.';
                }
                return errors;
            },
            async submit(formData) {
                async function wait() {
                    return new Promise((resolve, reject) => {
                        setTimeout(() => {
                            if (formData.email.indexOf('error') !== -1) {
                                reject({
                                    message: 'Form submission error.',
                                    errors: {
                                        email: 'This value is invalid.',
                                    },
                                });
                            } else {
                                resolve();
                            }
                        }, 1000);
                    });
                }
                await wait();
            },
        },
    };
</script>
<template>
    <form-controller
        :data="formData"
        :handler="submit"
        :validator="validate"
        v-slot="{state, message}"
    >
        <div v-if="message">Error message: {{ message }}</div>
        <text-field
            name="first_name"
            label="First name"
            v-model="formData.first_name"
        />
        <text-field
            name="last_name"
            label="Last name"
            v-model="formData.last_name"
        />
        <text-field
            type="email"
            name="email"
            label="Email"
            required
            v-model="formData.email"
        />
        <button type="submit" :disabled="state === 'loading'">Submit</button>
    </form-controller>
</template>

The form is ready to use. I want to point you at some moments. First, the form exposes the state property that you can use to react to form state transition. For example, we disabled the submit button while the form was handling the submission. Second, if the API returns an error message, we can display it to use. Use the message key to access the message value.

If you want to test error response, include "error" string in the email input. For example, "[email protected]".

More information about validation, submission, and error handling you can find in other sections.

Form Validation

The form validation triggers right before the form submission. The validator is a pure JavaScript function that receives formData as the first argument and returns an object where keys are field names and values are error messages.

Here is an example of such function:

function validator(formData) {
    let errors = {};
    if (!formData.email) {
        errors.email = 'This field is required'.
    }
    return errors;
}

Submit handlers

Similar to validators, submit handlers are pure async JavaScript functions. They accept formData as the first argument. The FormController component will handle exceptions or promise rejections and try to guess the error message and field errors.

Handling response errors

If the submit handler throws an exception or returns promise rejection, the form controller will try to get message and field errors from this object. In other words, in case of error your submit handler has to raise an exception which has "message" and "errors" properties or return a promise rejection:

// exception based
class CustomError extends Error {
    constructor(message, errors) {
        this.message = message;
        this.errors = errors;
    }
}
function submitWithExceptionRaised() {
    throw new CustomError('Submission error', {
        email: 'This field is not a valid email address'.,
    });
}

// promise based
function submitWithPromiseRejection() {
    return Promise.reject({
        message: 'Submission error',
        errors: {
            email: 'This field is not a valid email address'.,
        }
    })
}

You can make your own error handler either by passing errorHandler property of the form controller or make it global by setting Config.responseErrorHandler parameter.

<script>
    // CustomErrorHandler.vue
    import { FormController } from 'vue-forms-kit';

    export default {
        components: { FormController },
        methods: {
            submit() {
                return Promise.reject({
                    data: {
                        message: 'Rejected',
                        errors: {},
                    },
                });
            },
            async errorHandler(response) {
                // response is the promise rejection value seen in submit() method.
                let { message, errors } = response.data;
                return { message, errors };
            },
        },
    };
</script>

<template>
    <form-controller
        :data="{}"
        :handler="submit"
        :error-handler="errorHandler"
        v-slot="{ message }"
    >
        <p>{{ message }}</p>
        <button>Submit</button>
    </form-controller>
</template>

Configuration

You can set global response error handler via exported Config object:

import { Config } from 'vue-forms-kit';

Config.responseErrorHandler = response => {
    let { message, errors } = response;
    return { message, errors };
};

API

InputController

Properties


| Name | Type | Default value | Comment | | -------------- | ------ | ------------- | --------------------------------- | | input-selector | string | - | A selector for the input element. |

Events

| Name | Data | Comment | | ------ | ---------- | ------------------------------------------------------------------------------------------------------ | | input | any | Triggered on user input. Carries the value of "input" event of the underlying form element. | | change | any | Triggered when input value changes. Carries the value of "input" event of the underlying form element. | | focus | FocusEvent | Same as HTML focus event. | | blur | FocusEvent | Same as HTML blue event. |

FormGroupController

Properties

| Name | Type | Default value | Comment | | ------ | ------------------ | ------------- | ----------------------- | | errors | string | string[] | [] | A list of field errors. |

Events

No public events emitted.

FormController

Properties

| Name | Type | Default value | Comment | | --------- | -------- | ------------- | ----------------------------------------- | | data | object | (required) | Key-value object containing user input. | | handler | function | (required) | A form submission handler. | | validator | function | () => ({}) | A form validator. | | errors | object | {} | Key-value object containing field errors. |

Events

| Name | Data | Comment | | -------- | ---- | ------------------------------------------------------------------- | | response | any | Triggered after submission handler has been successfully completed. | | error | any | Fired then submission handler finished with an error. |

Interfaces

type FormData = { [key: string]: any };
type ValidationErrors = { [key: string]: any };

type ValidatorInterface = (formData: FormData) => ValidationErrors;

interface SubmitResponse {
    message?: string;
    errors?: ValidationErrors;
}
type SubmitHandler = (formData: FormData) => SubmitResponse;