1.0.15 • Published 2 years ago

formjs-framework v1.0.15

Weekly downloads
-
License
-
Repository
-
Last release
2 years ago

Introduction

HTML forms are laborious – often repeating the same cocktail of creating DOM elements, handling events, input validation, requests, responses and more, all before the backend receives the request.

All of this can mean working in several files and languages which takes time and if your application is large enough, you risk creating unmaintainable code filled with different methods of creating and handling front-end forms.

FormJS aims to fix the above by providing an efficient, neat and declarative framework to easily create forms, validate inputs, handle requests and responses all from within your JavaScript. This makes your code simpler, more concise, predictable and in one place.

Installation

You can install FormJS through NPM:

npm install formjs-framework

Usage

Then to initialise the framework simply import the script into your project through your desired method:

// As a module...
import { FormJS } from 'formjs-framework';

// Require...
const FormJS = require('formjs-framework').FormJS;

And then create a FormJS instance that we can use to access the API and framework:

const formjs = new FormJS;

From there we can make a new form instance by using the create method. This will take an options object and create a new instance.

// Creates a new form instance...
const options = {...};
const newForm = formjs.create(options);

This creates the instance but for easier reactivity, you have to separately tell FormJS to mount and render the instance to the DOM. You do this through the mount method on the newly created form instance.

// Creates a new form instance...
const options = {...};
const newForm = formjs.create(options);

// Mounts it!
newForm.mount();

You can also destroy an instance, removing it from the DOM and from FormJS's reference of instances. To do this, simply call the destroy method on the form instance.

// Destroys instance.
newForm.destroy();

Sometimes we might just want to remove the form from the DOM but keep the instance. For this, you can use the unmount method. This will remove the form from the DOM but the form instance is still tracked by FormJS so you can easily call mount again to re-render.

// Unmounts the instance.
newForm.unmount();

Example

Below is a brief boilerplate of the above concepts that creates an empty form in the DOM with some submissions instructions.

This code imports FormJS, initialises it, creates a new form instance and then renders it. After 5 seconds, we then destroy the instance.

import { FormJS } from 'formjs';

const formjs = new FormJS;

// Create a contact form...
const contactForm = formjs.create({
    ref: 'contactForm',
    el: 'contact-form-wrapper',

    form: {
        id: 'contact-form',
        elements: [... elements],
    },

    onsubmit: {
        method: 'POST',
        url: '/api/submit-form',
    },
});

contactForm.mount();

window.setTimeout(() => {
    contactForm.destroy();
}, 5000);

The resulting HTML before destroy:

<!-- This element already exists. -->
<div id="contact-form-wrapper">
    <!-- This `form` element is generated by FormJS. -->
    <form id="contact-form"></form>
</div>

Pretty basic stuff in terms of what's rendered. However, behind the scenes FormJS has sorted everything and the form is ready to be manipulated.

Options

FormJS comes pre-packaged with a number of options for creating forms, validating inputs, handling submissions and triggering events which can be accessed through adding to our options object passed to when creating a form instance.

Required Options

Every created form instance must include the following in it's options:

OptionDescription
ref: stringAn internal FormJS reference to the instance. Think of this like a unique ID.
form: string,objectThis is an object that describes our forms elements or an ID reference to a pre-existing form.
onsubmit: objectThis is an object that describes what happens when our form is submitted.

Lifecycle Hooks

When you create a form instance, a number of lifecycle stages occur. You can get a callback from these by including hooks in the options:

const form = formjs.create({
    ...options,

    created() {
        // Run this code when the form instance has been created.
    },

    beforeMount() {
        // Run this code *after* calling `mount` on the form but before any
        // elements are created or events bound.
    },

    mounted() {
        // Run this code when all elements are created and events are bound.
    }
});

The Form Option

The form option can be passed as either an object, or a string.

As an object, the form option is passed to the library to describe what elements need to be created with what attributes. Here is also where we describe what form input validation is required.

When defining the form object, we must include an ID which gets rendered to the DOM. This is so FormJS can identify the form.

const form = formjs.create({
    ...options,

    form: {
        id: 'new-form' // <- Required
    }
});

We must also pass an array of objects that describe the internal markup of the form. The objects inside the elements array use the following structure:

const form = formjs.create({
    ...options,

    form: {
        id: 'new-form',

        elements: [ // <- Required
            {
                el: 'input', // <- Required | HTML element type.
                text: 'innerHTML', // <- Optional | Set innerHTML of element.

                attributes: { // <- Required | HTML attributes to add.
                    id: 'element-id' // <- Required | set input ID.
                    type: 'text',
                    name: 'foo bar',
                    placeholder: 'Foo bar'
                    foo: 'bar',
                },

                elements: [ // <- Optional | Repeat the above structure to nest elements in the form.
                    ...nestedElements
                ]
            },
        ]
    },
});

The above renders the below HTML:

<div id="new-form-wrapper">
    <form id="new-form">
        <input type="text" name="foo bar" placeholder="Foo bar" foo="bar">
            innerHTML
            <!-- Nesting would occur here -->
        </input>
    </form>
</div>

Alternatively, you can create a form in your markup and then pass it's ID to the framework. FormJS can then take care of the rest. See the example below.

<form id="new-form">
    <input id="foo" type="text" name="bar"></input>
    <input type="submit" value="Search"></input>
</form>

<script>
    const form = formjs.create({
        ...options,

        form: 'new-form', // <- This references the above form by ID that already exists.

        onsubmit: { // <- Required | FormJS binds to your form.
            method: 'POST',
            url: '/api',
        }
    });
</script>

The onsubmit Option

The onsubmit option is passed to the library to describe what the form should do upon being submitted. It includes information about the type of request to make, where to make the request to and what should happen upon success or failure of the request.

When defining the onsubmit option we must include a request method and URL. However, also included are some methods that will be called and ran upon success or failure as well as a number of optional parameters. See the below example:

const form = formjs.create({
    ...options,

    onsubmit: {
        method: 'POST', // <- Required | `GET` or `POST`
        url: '/url-to-send-request-to', // <- Required.
        includeFormData: true, // <- Optional | `true` or `false`. Defaults to `true`.

        ...otherFetchAPIParams, // <- Optional | You can optionally add any Fetch API accepted key=>value pairs here and FormJS will add it to the list of parameters sent with the request as long as it doesn't conflict with another FormJS option.

        before() {
            // Optional | Run this code before any request or validations are made.
        },

        /**
         * @param  {object} response - response from submission.
         */
        success(response) {
            // Optional | Run this code after the request is sent and a response indicating success is received.
        },

        /**
         * @param  {object} error - error message.
         */
        error(error) {
            // Optional | Run this code before or after the request is sent because of something going wrong. This could be a server or front-end error.
        }
    }
});

You'll notice the available callback methods but also includeFormData boolean which dictates whether or not the request is sent with the form data attached or not. It defaults to true if you don't include it.

Additionally, you'll notice a blanket ...otherFetchAPIParams, you can replace this with any number of parameters that the Fetch API accepts. See this link on Using the Fetch API to see available parameters.

Note: FormJS will only check that the passed parameters are not conflicting with internal variables and not whether the passed parameter is one the Fetch API recognises as a suppliable option. This could or could not cause an error.

Form Validation

Built into the library is also the ability to validate form inputs through pre-defined tests. If you want a form input to be validated you can do this by passing validate as a key inside the form objects elements array or by calling the validate method on the FormJS instance. Validations are passed as as a single string separated by a "|" for the library to parse. See the below examples:

As an option:

const form = formjs.create({
    ...options,

    form: {
        id: 'new-form',

        elements: [
            {
                el: 'input',

                validate: 'minLength:5|hasNumber|hasSymbol',

                attributes: {
                    id: 'new-input',
                    type: 'text'
                }
            },
        ]
    },
});

As you can see, the above adds three validations that will be ran once the form is submitted and if any of them fail, the form won't submit and the onsubmit object error method will be called if it exists.

Alternatively you can call the validate method as part of the FormJS instance and make ad-hoc validations as you go without being tied to a specific form. This means you can work with pre-existing forms and "sprinkle" in validations as you go. See the below example.

<input id="foo" type="text"></input>

<script>
    const formjs = new FormJS;

    /**
     * Validate an input...
     * @param {string} el - ID of the element you wish to validate.
     * @param {string} rules - Validation rules to test against.
     * @return {Promise<object>} - Promise with a validation status object.
     */
    formjs.validate('foo', 'hasCapital|hasSymbol').then(result => {
        console.log(result) // Success.
    }).catch(result => {
        console.log(result) // Error.
    });
</script>

This way of validating will only return an output object as a promise for you as the user to handle.

A list of validations that can be used are below:

ValidationDescription
minLength:xTests the length of the input value is at least x.
maxLength:xTests the length of the input value is no more than x.
isEmailTests if the value of the input is an email.
isNotDisposableEmailFormJS has an inbuilt list of up to date disposable email domains. Add this validation to test if the value of the input is not equal to one of them.
hasNumberTests if the value of the input has at least one number.
hasSymbolTests if the value of the input has at least one symbol.
hasCapitalTests if the value of the input has at least one capital letter.
requiredTests if the length of the input value is not 0.

API

Asides from the ones already mentioned, FormJS provides a library of API methods that you can call upon to help with front-end form handling. Below is a list.

Global FormJS Instance

MethodArgumentsDescription
.create()options: objectCreate a new form instance, returns a form instance.
.version()NoneGet the version of FormJS returned as a string.
.validate()el: string, rules: stringRun an ad-hoc validation of a passed element (as an ID) against passed rules. Returns the result as a promise.
.getInstances()NoneReturns an array of form instances.

Individual Form Instances

MethodArgumentsDescription
.mount()NoneMount the form instance, rendering it to the DOM.
.destroy()NoneDestroy the form instance, removing it from the DOM and FormJS altogether.
.unmount()NoneRemove the form from the DOM but keep the instance.
.getInputValue()elementId: stringGet the value of a passed element by ID. Returns a string.
.getAllElements()NoneReturns an array an array of all the form elements.

Roadmap 🛣

I built FormJS because of a need for it within my companies tech stack, a way to standardise forms to keep our codebases neat, predictable and written properly. However, we're always on a mission to ship products faster yet still keep them reliable and FormJS helps with that providing a lightweight framework to do all of the above.

Nevertheless, although I love dev and working on projects like this, for the moment, it can only be in my spare time. Be that as it may, I do have a list of features I would love to work as and when I can:

  • onsubmit can be a method with completely optional code and this function is called if so.
  • Front-end website if enough interest.
  • More validations: This might be a good reference: https://laravel.com/docs/8.x/validation#available-validation-rules
  • More testing coverage.
  • Support for JSX-esque code for forms so either you can pass the form as a JSON object, or as HTML.
  • Framework wrappers for Vue, React, Angular etc.

Is This Framework Production Ready?

I would say use at your own discretion. The bottom line is that it works. However, I'm still increasing testing coverage and trialing the framework within my other projects and until that's done, I can't promise that FormJS will be entirely stable. I will keep this updated though.

Contributing

In the spirit of open source, contributions are welcome! Please feel free to add to this repo through pull requests and issues.

Here are a couple of dev details:

  • FormJS is written in TypeScript under the hood.
  • Distribution is bundled with Rollup.
  • Testing is done with Jest at the moment.
1.0.15

2 years ago

1.0.14

2 years ago

1.0.13

2 years ago

1.0.12

2 years ago

1.0.11

2 years ago

1.0.10

2 years ago

1.0.9

2 years ago

1.0.8

2 years ago

1.0.7

2 years ago

1.0.6

2 years ago

1.0.5

2 years ago

1.0.4

2 years ago

1.0.3

2 years ago

1.0.2

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago