Advanced Form Modal with JavaScript

Subscribe to my newsletter!

Idea

Today we will make something hard to explain: A Modal with a (somewhat) custom form that a programmer or we could use to ask the user something quickly and get the inputted data. Look at the demo to see what I mean. It’s going to be a function that will take in some config parameters for the form and the looks of the modal, and it will return a Promise that will resolve once the user has successfully submitted the form. This way, the function will be a little like prompt() but on steroids, if you get where I’m going.

To keep this Tutorial a little bit shorter, I won’t go over the CSS used for this Modal as it is purely cosmetic.

So let’s get into it.

JavaScript of the Advanced Form Modal

So the whole program will be housed inside a function that we can call multiple times to generate new modals with a form. This function will take in a parameters argument that we will destructure inside the function.

function advancedPrompt(parameters) {
    ...
}

Destructuring the Arguments

When we destructure the object to single constant variables, we essentially define the option the programmer can supply to the modal. Most importantly, we need the field data, which will tell the modal what fields/inputs there are and some other things about them. You will see where the other options are used shortly.

// Destructure Arguments
const {
    fieldData: _fieldData = [],
    title: _title = 'Modal',
    submitNodeText: _submitNodeText = 'Submit',
    panelHeight: _panelHeight = 500,
    canBeCancelled: _canBeCancelled = true,
    text: _text = ''
} = parameters

Creating the Elements

We then continue by creating the necessary elements for this modal. We create a wrapper that will make it so we can darken the background with CSS, and we add a form to it, which will be the panel of the modal itself. Here we use the _panelHeight option from before. After that, we add a node representing the title of the modal where use _title. We also add a node representing the describing text of the modal, and lastly, we have a wrapper/container for the fields that will be generated next.

// Create Elements
// Modal Wrapper which will take up the entire Screen
const modalWrapper = document.createElement('div');
modalWrapper.className = 'modalWrapper';

// The Modal itself which is also a form
const modalPanel = document.createElement('form');
modalPanel.className = 'modalPanel'
modalPanel.style.height = _panelHeight + 'px'
modalWrapper.appendChild(modalPanel);

// The Title showing what this modal is for
const titleNode = document.createElement('div');
titleNode.className = 'modalTitle';
titleNode.innerHTML = _title;
modalPanel.appendChild(titleNode);

// The Text which further describes the modal.
const textNode = document.createElement('div');
textNode.innerHTML = _text;
modalPanel.appendChild(textNode);

// Fields
// we add a wrapper / container that will hold all the fields
const fieldsWrapper = document.createElement('div');
fieldsWrapper.className = 'fieldsWrapper'
modalPanel.appendChild(fieldsWrapper);

We then loop over each object of the _fieldData option. Firstly we get the attributes property, or we default to an empty array. This enables the user to add attributes to the inputs in any way they like to.m Then we make a div that will hold a label and an input. We set the input value according to the field value, if there is one, and the type to the corresponding property of the field object. Then we also add the custom attributes via a for ... of loop.

// For each given field we add a Row Element with a label and an Input
for (const field of _fieldData) {
    const attributes = field.attributes || []

    const inputRow = document.createElement('div');
    inputRow.className = 'inputRow';

    const label = document.createElement('label')
    label.innerHTML = field.title

    const input = document.createElement('input')
    input.value = field.value || ''
    input.type = field.type

    for (const attribute of attributes) {
        input.setAttribute(attribute.name, attribute.value)
    }

    field.element = input;

    inputRow.appendChild(label)
    inputRow.appendChild(input)

    fieldsWrapper.appendChild(inputRow)
}

Then we also need to create the submit and cancel buttons. The user can omit the cancel button if they wish to.

// Actions Panel with submit and cancel button
const actionsWrapper = document.createElement('div');
actionsWrapper.className = 'actionBar'
modalPanel.appendChild(actionsWrapper);

const cancelNode = document.createElement('button');
cancelNode.className = 'cancelButton'
cancelNode.innerHTML = 'Cancel'

if (_canBeCancelled) actionsWrapper.appendChild(cancelNode);

const submitNode = document.createElement('input');
submitNode.type = 'submit'
submitNode.className = 'submitButton'
submitNode.value = _submitNodeText;
actionsWrapper.appendChild(submitNode);

document.body.appendChild(modalWrapper);

Promise and Return Value

Now that we have all the elements out of the way, we can get to the Promise that is returned from the function. Promises need a callback function that accepts a resolve and reject function. The resolve function can be called with any arguments we want to be available in the then function. In this Program, we call this function once the submit event of the form is dispatched. But before that, we also get all fields and their values, and we save them in returnObject variable. We also set up some event listeners in case the user has pressed escape or pressed the cancel button, and we reject the promise.

// Return Promise
return new Promise((resolve, reject) => {

    modalPanel.addEventListener('submit', (event) => {
        event.preventDefault();


        // Gather all values from the Form and store with the 
        // Specified name
        let returnObject = {}

        for (const field of _fieldData) {
            returnObject[field.name] = field.element.files || field.element.value || field.element.checked
        }

        // Resolve the promise with the Data
        resolve(returnObject)
        modalWrapper.remove()
    })

    cancelNode.addEventListener('pointerdown', rejectPromise)

    window.addEventListener('keydown', (event) => {
        if (event.key == 'Escape' && _canBeCancelled) rejectPromise()
    })

    function rejectPromise() {
        modalWrapper.remove()
        reject('User Cancelled')
    }
})

Cancel Reaction

Usage Example

Ok, so how do we use this thing? We first need to import the script and if we have styles we also get those too.

<link  rel="stylesheet"  href="advancedPrompt/advancedPrompt.css">
<script  src="advancedPrompt/advancedPrompt.js"></script>

Then in another script that comes after the import, we call the advancedPrompt() function with some parameters. Below we set a title and one field/input of type text, and we also add a then callback, so we can react to the user hitting the submit button.

advancedPrompt({
    title: 'My Cool Modal',
    fieldData: [
        {
            title: 'Username',
            name: 'username',
            type: 'text',
            attributes: [
                {name: 'required', value: 'true'},
            ]
    ]
}).then(result => {
    ...
});

Showcase

Below you see the showcase that you also can visit yourself. I used one of my previous programs to display the returned data.

Advanced Form Modal Showcase Gif

Visit the Demonstration
Look at the Code

Conclusion

That’s it, an Advanced Form Modal ready to be used in JavaScript. You could now add a cancel Button or make it a little bit better looking with your own CSS code.

Keep in mind that I am also just a beginner, so it could be that my way of solving these problems is not the best. Always ask questions and try to solve problems your way!

Leave a Reply