Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Correcting the Form Action & Preventing Default

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Submitting the form via Ajax just exploded! We can see the error down here: the POST request failed with a 405 error. Open that link in a new tab so we can see what happened.

Ah:

No route found for POST /admin/product: method not allowed, allowed get

This is not the URL that we expected: we expected this to submit to /admin/product/new

What happened? I was trying to be really responsible by reading the action and the method directly from the form. The problem is, if you inspect the form... there is no action attribute! That's normally fine: it just means that the form should submit back to the current URL.

When we render this form on the actual new product page - /admin/product/new - the empty action is ok because it will submit to this URL. But over here on the list page, the missing action attribute makes it look like it should submit right back to this URL. That's not what we want!

To fix this, instead of $form.prop('action'), use the formUrl value: this.formUrlValue. This assumes that the same URL that you use to fetch the form is also the URL that should be used to submit the form.

... lines 1 - 4
export default class extends Controller {
... lines 6 - 18
async submitForm() {
const $form = $(this.modalBodyTarget).find('form');
this.modalBodyTarget.innerHTML = await $.ajax({
url: this.formUrlValue,
... lines 23 - 24
});
}
}

Let's try that again. Refresh the page, hit add, and Save. Yes! Look at that! We instantly see validation errors! If I fill out one field and submit again, it goes away!

Preventing the Full Form Submit

But there is one tiny problem. If I focus on a field and hit enter... oh... actually let me fill in a price. Now hit enter. Woh! The form submitted! But not via Ajax.

No problem. In addition to executing the submitForm() method when we click the "Save" button, we also need to execute that method if the form is submitted.

But wait: does this mean that we need to add a Stimulus data-action attribute to the form element... inside this form_start() function? I thought we were trying to avoid needing to add stuff to this template!

Fortunately, we do not need to do that. In _modal.html.twig, break the modal-body element onto multiple lines for readability. Now add data-action= the name of the event - submit, ->, the name of our controller - modal-form - # sign and then submitForm.

<div
... lines 2 - 5
>
<div class="modal-dialog">
<div class="modal-content">
... lines 9 - 14
<div
class="modal-body"
data-modal-form-target="modalBody"
data-action="submit->modal-form#submitForm"
>
{{ modalContent|default('Loading...') }}
</div>
... lines 22 - 29
</div>
</div>
</div>

Remember: events bubble up. The submit event is first dispatched on this form element. But then it bubbles up to the modal-body div. That means that this element will also receive the submit event.

To prevent the form from actually submitting... so that we can make the Ajax call, add an event argument to submitForm() and say event.preventDefault().

... lines 1 - 4
export default class extends Controller {
... lines 6 - 18
async submitForm(event) {
event.preventDefault();
... lines 21 - 25
});
}
}

When we hit enter on the form, that will prevent the submit. When we click the save button, this will have no effect... because clicking a button outside of a form doesn't do anything by default. So there's nothing to prevent.

Try it out. Reload, open up the modal and hit enter. Bah! HTML 5 validation is keeping me honest. Fill in the two required fields... but put a negative price so we hit a validation error. Hit enter now. Gorgeous!

Next: this is working awesome when there's a validation error. But if the submit is successful, we need to do something different: close the modal.

To do that, we need to create a systematic way for our controller to communicate whether or not the form submit was successful. This new idea will serve us super well in the next tutorial when we Ajaxify more form submits with Turbo.

Leave a comment!

13
Login or Register to join the conversation

Hello!

I have the same problem that Macarena and Martín.

I try to use this modal example on my own project but error message are not displayed in the modal.
I have a 422 response from AJAX with the correct error message.

Can you help me please ?

I'm using Symfony 6.2 and Flowbite for the modal.

Reply

Hey @cctaborin!

Hmm. Let's see if we can figure this out! If you're using the jQuery ajax like we are in this tutorial (btw, you can also just use fetch() these days - it's built into the browser and works great) - then, irrc, if the server returns a 422 status code, then the Ajax call is seen as "failed". When I recorded, the Ajax call was returning a 200 status code when the validation errors happened.

Why the difference? Because starting in Symfony 6.2, when you use $this->render() in your controller and pass in a form value, Symfony now (correctly) changes the status code to 422 if the form has validation errors. This is GOOD, but it changes the behavior of the tutorial :). Sorry about that!

This what the fixed code should look like:

async submitForm() {
    const $form = $(this.modalBodyTarget).find('form');
    try {
        this.modalBodyTarget.innerHTML = await $.ajax({
            url: this.formUrlValue,
            method: $form.prop('method'),
            data: $form.serialize(),
        });
    } catch (error) {
        this.modalBodyTarget.innerHTML = error.message;
    }
}

That last error.message part I'm not 100% sure about. If that's wrong, console.log(error) to see what that is and where the HTML response might be. And please, if you get this working, let me know to help others :).

Cheers and thanks for bringing this up again!

1 Reply
Djamina Avatar
Djamina Avatar Djamina | weaverryan | posted 4 months ago | edited

Hey,
I just tried this and it work, you just need to use error.responseText.

1 Reply

Hey @weaverryan!

Thank you for your explanations!
It works very well with this.modalBodyTarget.innerHTML = error.message; (as @Djamina suggests).

Besides, your tutorials are great, keep it up!

Cheers!

Reply

Awesome! Thanks for the verifications! I'll get a note added to the tutorial to help others ❤️

Reply
 Avatar
 Avatar Abdul | posted 1 year ago | edited

I have put the code below inside _form.html.twig but when I click the button, it returns with the error:

Uncaught ReferenceError: test is not defined.

Is there a reason why scripts don't work?

<button onclick="test()">Try it</button>

<script>
    function test() {
        alert("Hello! I am an alert box!");
    }
</script>

I'd appreciate any help.

Reply
Macarena I. Avatar
Macarena I. Avatar Macarena I. | posted 2 years ago

Hi!!, sorry me again....
I try this modal example in my own proyect but it not appear the error message in the modal, just an error in the console.
But if I execute the form normaly it have no problem with the validation.
Pleaseeeee helpp

Reply
Default user avatar
Default user avatar Martín Maglianesi | Macarena I. | posted 1 year ago

Hello. The same thing happens to me too.

I have unsuccessfully tried to fire a checkValid() on the form before submitting via Ajax. I don't have much knowledge of Javascript so maybe it didn't work because I didn't know how to do it.

What I haven't finished understanding is that in some cases the invalid form is processed as expected, returning from the Ajax request with a 422 error, the HTML5 validation errors are shown in the form. In other cases, it returns with a 500 error. There, the form does not show HTML5 errors. Shows a symfony exception error. Ugly! That's why I've tried to validate the form before making the Ajax request. I think doing so would solve the problem.

Not a minor fact. If I submit the form from the submit button that contains the original Symfony form, everything works perfectly. Validations, automatic focus on required fields, etc.

The modal's submit button does not have the same behavior.

I appreciate any hint or solution.

Reply

Hey Martin,

Make sure your submit button is inside of the form, and submit that form via JS using that submit button. Also, take a look at some tips here: https://stackoverflow.com/a... - I believe they might be useful for you.

Otherwise, you can turn off HTML validation and base your validation on server side re-rendering the form with errors if there're any.

Cheers!

Reply

Hey Macarena I.

Which error is in console? Can you provide more information about issue =)

Cheers!

Reply
Steven J. Avatar
Steven J. Avatar Steven J. | posted 2 years ago

Hi team, great track! As there is no validation set in the source code, I guess you enabled auto mapping.

For those passing by, see https://symfonycasts.com/sc...

Reply

Hey Steven J.

Sorry for the confusion but if you open up the Product.php class you will see that we have some validation on the properties. If you have any other doubt, please let us know.

Cheers!

Reply
Steven J. Avatar

sorry, I was using an old code version, thank you for this course, really love it

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial works perfectly with Stimulus 3!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "1.11.99.1", // 1.11.99.1
        "doctrine/annotations": "^1.0", // 1.11.1
        "doctrine/doctrine-bundle": "^2.2", // 2.2.3
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
        "doctrine/orm": "^2.8", // 2.8.1
        "phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
        "sensio/framework-extra-bundle": "^5.6", // v5.6.1
        "symfony/asset": "5.2.*", // v5.2.3
        "symfony/console": "5.2.*", // v5.2.3
        "symfony/dotenv": "5.2.*", // v5.2.3
        "symfony/flex": "^1.3.1", // v1.18.5
        "symfony/form": "5.2.*", // v5.2.3
        "symfony/framework-bundle": "5.2.*", // v5.2.3
        "symfony/property-access": "5.2.*", // v5.2.3
        "symfony/property-info": "5.2.*", // v5.2.3
        "symfony/proxy-manager-bridge": "5.2.*", // v5.2.3
        "symfony/security-bundle": "5.2.*", // v5.2.3
        "symfony/serializer": "5.2.*", // v5.2.3
        "symfony/twig-bundle": "5.2.*", // v5.2.3
        "symfony/ux-chartjs": "^1.1", // v1.2.0
        "symfony/validator": "5.2.*", // v5.2.3
        "symfony/webpack-encore-bundle": "^1.9", // v1.11.1
        "symfony/yaml": "5.2.*", // v5.2.3
        "twig/extra-bundle": "^2.12|^3.0", // v3.2.1
        "twig/intl-extra": "^3.2", // v3.2.1
        "twig/twig": "^2.12|^3.0" // v3.2.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
        "symfony/debug-bundle": "^5.2", // v5.2.3
        "symfony/maker-bundle": "^1.27", // v1.30.0
        "symfony/monolog-bundle": "^3.0", // v3.6.0
        "symfony/stopwatch": "^5.2", // v5.2.3
        "symfony/var-dumper": "^5.2", // v5.2.3
        "symfony/web-profiler-bundle": "^5.2" // v5.2.3
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@babel/preset-react": "^7.0.0", // 7.12.13
        "@popperjs/core": "^2.9.1", // 2.9.1
        "@symfony/stimulus-bridge": "^2.0.0", // 2.1.0
        "@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
        "@symfony/webpack-encore": "^1.0.0", // 1.0.4
        "bootstrap": "^5.0.0-beta2", // 5.0.0-beta2
        "core-js": "^3.0.0", // 3.8.3
        "jquery": "^3.6.0", // 3.6.0
        "react": "^17.0.1", // 17.0.1
        "react-dom": "^17.0.1", // 17.0.1
        "regenerator-runtime": "^0.13.2", // 0.13.7
        "stimulus": "^2.0.0", // 2.0.0
        "stimulus-autocomplete": "^2.0.1-phylor-6095f2a9", // 2.0.1-phylor-6095f2a9
        "stimulus-use": "^0.24.0-1", // 0.24.0-1
        "sweetalert2": "^10.13.0", // 10.14.0
        "webpack-bundle-analyzer": "^4.4.0", // 4.4.0
        "webpack-notifier": "^1.6.0" // 1.13.0
    }
}
userVoice