AJAX Form

AJAX Form Submissions

It is a common feature to have a form that submits via an AJAX request. So common in fact that we have added an Alpine JS component that facilitates the most common functionality, and allows for customisation.

Starter:

To get started, simply add the x-data="ajaxForm()" attribute on the form element.

You may optionally add some styled areas for successFeedback and errorFeedback.

Equally, you may make use of the submitting property on the submit button.

As we are using ajax submission techniques, we need to ensure the validation feedback element exists in the DOM. To do this, make sure:show-validation-container=true is set on any form elements.

<form
    x-data="ajaxForm()"
    action="{{ route('apex.demo.ajaxForm', ['respondAs' => $respondAs ?? 'redirect']) }}"
    method="POST"
>
    @csrf
    <x-apex::formElements.inputText
        label="Name"
        name="name"
        :show-validation-container=true
    ></x-apex::formElements.inputText>

    <x-apex::formElements.inputText
        label="Surname"
        name="surname"
        :show-validation-container=true
    ></x-apex::formElements.inputText>

    <x-apex::formElements.inputText
        label="Age"
        name="age"
        :show-validation-container=true
    ></x-apex::formElements.inputText>

    <x-apex::formElements.button
        size="md"
        theme="primary"
    >
        <span x-show="submitting">Submitting...</span>
        <span x-show="!submitting">Create</span>
    </x-apex::formElements.button>

    <p class="mt-5 text-sm text-gray-600">Respond as: {{ $respondAs ?? 'redirect' }}</p>

    <p
        class="my-1 text-sm text-red-600"
        x-show="errorFeedback"
        x-text="errorFeedback"
    ></p>

    <p
        class="my-1 text-sm text-emerald-600"
        x-show="successFeedback"
        x-text="successFeedback"
    ></p>
</form>
//

Successful Responses:

When a form submission is successful, ajaxForm() will attempt to process the response using one of the supported actions. Those actions are:

  • redirect
  • toast

To work correctly, the server's response must follow a known shape:

Redirect

This will redirect the user to the URL in options.redirectTo.

return response()->json([
    'success' => true,
    'action' => 'redirect',
    'options' => [
        'redirectTo' => route('...'),
    ],
]);

Toast

This will invoke the Apex-native makeToast function.

return response()->json([
    'success' => true,
    'action' => 'toast',
    'options' => [
        'title' => 'Action Success',
        'message' => 'Lorem ipsum dolor sit amet',
    ],
]);

The user also has the option to set a simple message key in the response which will be applied to the successFeedback property on the ajaxForm component:

return response()->json([
    'success' => true,
    'message' => 'This is a message that will be set to the "successFeedback" property',
]);
//

Custom Responses:

The user can also write their own custom response handler:

The onSuccess property accepts a callback function that will be given the response result as well as the form object.

This example essentially recreates native functionality.

Be aware: : "success" denotes that the HTTP request was successful, not necessarily that the underlying action was successful. You should test the value of result.

<form
    x-data="ajaxForm({
        onSuccess: function (result, form){
            window.makeToast(result.options.title, result.options.message, 'danger');
            form.reset()
        },
    })"
    action="{{ route('apex.demo.ajaxForm') }}"
    method="POST"
>

This can be a useful method of adding more complex functionality.

For example, if your form exists within a modal, you can make a toast and close the modal and reset the form fields.

<x-apex::modals.modal id="ajax-form-modal">
    <form
        x-data="ajaxForm({
            onSuccess: function (result, form){
                // Toast notification, optionally referring to the server's response.
                window.makeToast(result.options.title, '', 'info')

                // Close the modal.
                $dispatch('close-modal', 'ajax-form-modal') // Alpine's native event dispatcher.
                // EventBridge.emit('close-modal', 'ajax-form-modal') // Apex's agnostic event bridge.

                // Reset the form fields.
                form.reset()
            },
        })"
        action="{{ route('apex.demo.ajaxForm2') }}"
        method="POST"
    >
</x-apex::modals.modal>
//

Options:

method
The HTTP method with which we should submit the form. In order, the precedence is: the user's input, the "method" attribute on the <form> element, then defaulting to POST.

resetOnSuccess
Whether or not we should reset the form when the form is submitted successfully.

Default: false.
NB: If using onSuccess you will need to do this manually.

logErrorMessage
Whether we should log the server's warning message to the console in the event the form does not submit successfully.

Default: false.

validationFeedbackSelector
The query selector used to reset all validation feedback nodes.

onSuccess
A closure to be called on successful submit.
The function will receive the server's result, the form element and the component as arguments.

<form
    x-data="ajaxForm({
        method: 'PATCH',

        resetOnSuccess: true,

        logErrorMessage: true,

        validationFeedbackSelector: '.validation',

        onSuccess: function (result, form, ajaxSubmitComponent){
            if (result.success === true) {
                //
            } else {
                //
            }
        },
    })"
>
//

Examples:

Alongside these examples, it's worth exploring the APEX.src/routes/demo.php routes file for examples of server-side functionality.

Respond as: redirect

Respond as: toast

<form
    x-data="ajaxForm()"
    action="{{ route('apex.demo.ajaxForm', ['respondAs' => $respondAs ?? 'redirect']) }}"
    method="POST"
>
    @csrf
    <x-apex::formElements.inputText
        label="Name"
        name="name"
        :show-validation-container=true
    ></x-apex::formElements.inputText>

    <x-apex::formElements.inputText
        label="Surname"
        name="surname"
        :show-validation-container=true
    ></x-apex::formElements.inputText>

    <x-apex::formElements.inputText
        label="Age"
        name="age"
        :show-validation-container=true
    ></x-apex::formElements.inputText>

    <x-apex::formElements.button
        size="md"
        theme="primary"
    >
        <span x-show="submitting">Submitting...</span>
        <span x-show="!submitting">Create</span>
    </x-apex::formElements.button>

    <p class="mt-5 text-sm text-gray-600">Respond as: {{ $respondAs ?? 'redirect' }}</p>

    <p
        class="my-1 text-sm text-red-600"
        x-show="errorFeedback"
        x-text="errorFeedback"
    ></p>

    <p
        class="my-1 text-sm text-emerald-600"
        x-show="successFeedback"
        x-text="successFeedback"
    ></p>
</form>

This example uses a custom handler to make a toast, close the modal and reset the form.

<x-apex::modals.modal
    id="ajax-form-modal"
>
    <x-slot name="title">
        AJAX Form in a Modal
    </x-slot>

    <form
        x-data="ajaxForm({
            onSuccess: function (result, form, n){
                window.makeToast(
                    result.options.title,
                    'This is a custom handler',
                    result.options.theme || 'success',
                )
                $dispatch('close-modal', 'ajax-form-modal')
                form.reset()
            },
        })"
        action="{{ route('apex.demo.ajaxForm') }}"
        method="POST"
    >
        @csrf

        <x-apex::formElements.inputText
            label="Name"
            name="name"
            :show-validation-container=true
        ></x-apex::formElements.inputText>

        <x-apex::formElements.inputText
            label="Age"
            name="age"
            :show-validation-container=true
        ></x-apex::formElements.inputText>

        <x-apex::modals.actions-container alignment="block">
            <x-apex::formElements.button
                size="lg"
                theme="primary"
                style="default"
                :extra="[]"
            >
                <span x-show="submitting">Submitting...</span>
                <span x-show="!submitting">Create</span>
            </x-apex::formElements.button>
        </x-apex::modals.actions-container>

        <p
            class="my-1 text-sm text-red-600"
            x-show="errorFeedback"
            x-text="errorFeedback"
        ></p>

        <p
            class="my-1 text-sm text-emerald-600"
            x-show="successFeedback"
            x-text="successFeedback"
        ></p>
    </form>


</x-apex::modals.modal>

<x-apex::modals.trigger modal-id="ajax-form-modal" style="default">
    Ajax Form in Modal
</x-apex::modals.trigger>