Dispatching an Event from modal-form

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

Earlier, when we were working on the cart page, we learned that if you need two controllers to talk to each other, like the modal-form_controller and the reload-content_controller, a great way to do that is to dispatch an event. Specifically, reload-content_controller needs to know when it should refresh the content. To help it know that, we're going to dispatch a custom event from modal-form after the form submits successfully.

So since we did this before, let's jump straight in. First, at the top, import { useDispatch } from 'stimulus-use'. Then activate that with a new connect() method: useDispatch(this). Temporarily pass this debug: true so we can see the event being dispatched in the log.

... lines 1 - 3
import { useDispatch } from 'stimulus-use';
... line 5
export default class extends Controller {
... lines 7 - 12
connect() {
useDispatch(this, {debug: true});
}
... lines 16 - 44
}

Now, down after success, say this.dispatch('success').

... lines 1 - 5
export default class extends Controller {
... lines 7 - 24
async submitForm(event) {
... lines 26 - 28
try {
... lines 30 - 35
this.dispatch('success');
} catch (e) {
... line 38
}
}
... lines 41 - 44
}

Try it: reload... and try selling some rotted, I mean, reclaimed wood at our store. Submit and... awesome! That did just dispatch an event... and its name is modal-form:success.

Listening to the Custom Event

Copy that. Now that we've seen the event name, go back over and remove the debug option. Here's the last magic piece to make this work. In index.html.twig, up on the reload-content controller <div>, add an action for the new event: data-action="" the name of the event - modal-form:success - arrow, the name of the controller - reload-content - a pound sign and the name of the method: refreshContent.

... lines 1 - 4
{% block body %}
<div
... lines 7 - 10
data-action="modal-form:success->reload-content#refreshContent"
>
... lines 13 - 37
</div>
{% endblock %}

That's it! When the modal-form:success event is dispatched, it will bubble up to this element and we will call refreshContent(). Then... that'll take care of the rest!

Let's test it. Reload the page, open the modal and let's sell some avocado peels. Submit. Ah! It... kinda seems like it worked? Except that the Ajax endpoint apparently returned the entire page, not just the template partial.

Using a Query Param instead of isXmlHttpRequest()

Let's go look at the controller. Ah... I warned about this and then did it anyways! We're using fetch() to make the Ajax call... and fetch() does not send the header needed for isXmlHttpRequest() to work. And so, this always renders index.html.twig.

That's okay! Let's just add a query parameter to the end of the URL. I like that better anyways.

Replace this code with $request->query->get('ajax'). So, we'll be looking for a ?ajax=1 on the end of the URL.

... lines 1 - 15
class ProductAdminController extends AbstractController
{
... lines 18 - 20
public function index(ProductRepository $productRepository, Request $request): Response
{
$template = $request->query->get('ajax') ? '_list.html.twig' : 'index.html.twig';
... lines 24 - 27
}
... lines 29 - 104
}

In the template - index.html.twig - add that to the URL by passing extra params with ajax set to 1.

... lines 1 - 4
{% block body %}
<div
... line 7
{{ stimulus_controller('reload-content', {
url: path('product_admin_index', { ajax: 1 })
}) }}
... line 11
>
... lines 13 - 37
</div>
{% endblock %}

Try the form again. Refresh! We'll sell some salsa to go with those avocados... and this time... perfect! The section reloaded. We're done!

If we want to make this a bit fancier, we could even add some classes and use those to force CSS transitions. Or we can do an even simpler trick. When it first starts loading, let's say this.contentTarget.style.opacity = .5.

Copy that, and then, after it finishes loading, set the opacity back to 1.

... lines 1 - 2
export default class extends Controller {
... lines 4 - 8
async refreshContent(event) {
this.contentTarget.style.opacity = .5;
... line 11
this.contentTarget.innerHTML = await response.text();
this.contentTarget.style.opacity = 1;
}
}

Add one more product: this time, a mystery box of donuts. Watch the table closely when I hit save. Yes! It was quick, but the table had less transparency for just a moment while it reloaded.

Now that we've got this cool new re-usable reload-content_controller, let's use it to completely replace our custom cart-list controller. Yay for less code! That's next.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=7.4.0",
        "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.12.1
        "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
    }
}