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

formla

v1.5.4

Published

A form object that makes sending ajax forms a breeze

Downloads

163

Readme

Formla

An easy to use, highly configurable, object oriented ajax form package.

Installation

Using npm:

npm install formla --save

Using yarn:

yarn add formla --save

Usage

To use the form, first import it into your project and instantiate a new instance with the form fields and initial values.

import Form from 'formla';

const form = new Form({
  username: '',
  password: '',
});

Alternately you can pass a callback that will get resolved and will be used when resetting the form.

import Form from 'formla';

const form = new Form(() => ({
    username: '',
    password: '',
}));

Append data

You can add fields to the form after instantiation using the append method.

You can pass the field name and initial value as arguments or you can pass an object of key value pairs.

form.append('email', '');

form.append({
  firstName: '',
  lastName: '',
});

The values supplied to the form can be accessed and updated directly on the form object.

let name = form.firstName + ' ' + form.lastName;

form.username = 'cheesy123';

Constant data

Sometimes you may want to use data that shouldn't be changed, for example an api key or a CSRF token.

For these fields you can use the constantData method.

As with the append method you can either send a single key and value or an object.

const form = new Form({
  username: '',
  password: '',
}).constantData('_token', csrfToken);

Any fields added using the constantData method cannot be accessed on the form object but will be sent in the body of the request when the form is submitted.

Dealing with files

You can add a file as a value to any key in the form data but it can be quite annoying to do so.

Instead you can use the addFileFromEvent method which accepts an input event from a file <input> element and adds the file to the form.

const form = new Form({
    file: null,
});

document.querySelector('input[name="file"]')
    .addEventListener('input', (event) => form.addFileFromEvent(event));

By default the file will use the name of the input field to find the key, however you can override this by passing the key as the second argument.

Submitting the form

The submit method accepts 3 arguments. The request method, the url, and some optional configuration. Alternatively you could just pass the options as the first argument, so long as the options includes the method and the url.

form.submit('post', 'https://api.com');

The form object also exposes a function for each request method.

form.get('https://api.com');
form.post('https://api.com');
form.put('https://api.com');
form.patch('https://api.com');
form.delete('https://api.com');

By default the form uses a basic implementation to submit the data however it is recommended to use a different library like jQuery or axios to send the request.

You can do this by passing a callback function to the sendWith options which accepts the method, url, and data as parameters.

import axios from 'axios';

Form.setOptions({
    sendWith(method, url, data) {
        axios({
            method,
            url,
            data,
        });
    },
});

The data argument will be a FormData object constructed from the data provided to the form. You can set this to a json object by setting the useJson option to true.

Note, you cannot have a File or Blob object in JSON so if a file is detected in the form data the form will be sent as a FormData object, even if useJson is true. Unless the strictMode option is true in which case an error will be thrown.

All options can either be set globally using the static setOptions method, on a per form instance basis by passing them as the second argument to the constructor, or on a per request basis by passing them as the last parameter of the request method.

GraphQL

The form class has a graphql method that allows you to submit the data as a GraphQL request.

The method accepts a query string and an optional second argument of options.

The data held by the form will be submitted in the variables key of the request data.

// Here is an example of how to submit the mutation described in the GraphQL
// docs: https://graphql.org/learn/queries/#mutation
const form = new Form({
    ep: 'JEDI',
    review: {
        stars: 5,
        commentary: 'This is a great movie!',
    },
});

form.graphql(`
    mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
        createReview(episode: $ep, review: $review) {
            stars
            commentary
        }
    }
`);

You can use the formatData option to manipulate the data before it is sent. This is great for dealing with input types and some of the variables are constant.

const form = new Form({
    stars: 5,
    commentary: 'This is a great movie!',
}, {
    formatData(data) {
        return {
            ep: 'JEDI',
            review: data,
        }
    }
});

form.graphql(`
    mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
        createReview(episode: $ep, review: $review) {
            stars
            commentary
        }
    }
`);

File uploads

Files are extracted from GraphQL requests according to the multipart request specs: https://github.com/jaydenseric/graphql-multipart-request-spec#multipart-form-field-structure

Handling validation errors

An important feature of the form is handling validation errors.

If the form is submitted with invalid data, the server typically sends back error messages with information on why the data was invalid.

The form object will automatically store these errors and allow you to display them however you like.

By default the form will check for a 422 status code and a json response of the format:

{
  "errors": {
    "username": [
      "The username was already taken",
      "The username was too long"
    ],
    "password": [
      "The password is required"
    ]
  }
}

You can change the logic for discerning validation errors by overriding the isValidationError callback in the config.

If you're server doesn't return validation errors in this format, you can pass a callback to the formatErrorResponse option which should return an object of the form data keys with their corresponding error messages as a string or an array of strings:

Form.setOptions({
    formatErrorResponse(response) {
        return response.error.details;
    }
})

Once an error response has been caught by the form you can access the errors class with the errors property on the form.

if (form.errors.any()) {
    alert('There was an issue');
}

The has method accepts a string or a regular expression and tells you if the first field matching that string or expression has an error.

The get method will return all the errors for the field provided as the argument.

The getFirst method will return the first error if there is an array of errors, otherwise it will just return the error.

The clear method will remove the error messages for the specified field, or all messages if no field is specified.

If you want all the errors to clear after a certain interval you can use the timeout option on the form with the number of milliseconds to wait before clearing the errors.

When the value for a field with errors is updated the error messages for that field are automatically cleared.

This behaviour can be prevented by setting the autoRemoveError option to false.

Typically you will want to display an error message by the input element with the invalid data.

However sometimes this element could be off the page, especially when the user is on a mobile device.

The form object can automatically scroll to any elements that have errors if you tell it where to go:

const form = new Form({
    username: '',
    password: '',
});

const usernameInput = document.querySelector('input[name="username"]');
const passwordInput = document.querySelector('input[name="password"]');

form.addElement('username', usernameInput);
form.addElement('password', passwordInput);

Make sure you add the elements in the order they appear in the page as the form will use the order they are added to determine which is the first element with an error and scroll to that one.

If you have a group of inputs inside a container and you just want to scroll to that element for all of the fields within it, you can pass an array of fields as the first argument, or a regular expression, or even just a string with * wildcards.

Configuration

The form class has many options for customising the plugin to your needs.

You can set the options globally using the setOptions static method:

import Form from 'formla';

Form.setOptions({
    baseUrl: 'https://api.com',
});

You can set the options for a specific form instance by passing them as a second argument to the constructor or using the setOptions instance method. These options will override any global options and any options set before them.

const form = new Form({
    username: '',
}, {
    autoRemoveError: false,
});

form.setOptions({
    quiet: true,
});

Finally you can set additional options when sending a request. These options are only used for the request and not saved once the request is finished.

form.post('https://api.com/comments', {
    timeout: 5000,
});

All available options and their defaults

Form.setOptions({
    // The default method type used by the submit method
    method: 'post',

    // If set any relative urls will be appended to the baseUrl
    baseUrl: '',

    // The url to submit the form
    url: '',
    
    // The url of the GraphQL endpoint which will be used by all GraphQL
    // requests.
    graphql: 'graphql',

    // A callback to implement custom HTTP logic.
    // It is recommended to use this option so the form can utilise your HTTP
    // library.
    // The callback should return a promise that the form can use to handle the
    // response.
    sendWith: null,

    // Set to true if you want the form to submit the data as a json object.
    // This will pass the data as a JavaScript object to the sendWith callback
    // so it is up to you to stringify it for your HTTP library.
    // If the data contains a File or Blob object the data will be a FormData
    // object regardless of this option (unless strictMode is true).
    useJson: false,

    // If set to true the form will use follow the `useJson` option even if the
    // data contains non JSONable values (including files).
    strictMode: false,

    // The status code for which the form should handle validation errors.
    isValidationError: ({ status }) => status === 422,
    
    // A callback to format the data before sending it.
    formatData: (data) => data,

    // A callback that should turn the error response into an object of field
    // names and their validation errors.
    formatErrorResponse: ({ errors }) => errors,

    // The number of milliseconds to wait before clearing the error messages.
    // When timeout is false the error messages will stay indefinitely.
    timeout: false,

    // When set to true the errors for a field will be cleared when that field's
    // value is updated.
    autoRemoveError: true,

    // When set to true, the data will be reverted to it's original values after
    // a successful request.
    clear: true,

    // When set to true, no errors will be recorded.
    quiet: false,
    
    // When instantiating the form with a callback any fields appended to the
    // form after construction are included in the form after reset. Set this
    // field to false to reset the form only using the callback supplied.
    addAppendToDataCallback: true,
});