Skip to content

Opening a modal from outside the Alpine component.

Sometimes you might want to open a dialog from anywhere on the page, from outside the Alpine dialog component.

Using an event

Since release 1.1.18 modal dialogs can be opened by dispatching the event hyva-modal-show.

From within an Alpine.js component:

<button type="button" aria-haspopup="dialog"
        @click="$dispatch('hyva-modal-show', {dialog: 'my-modal'})"
>Open</button>

From anywhere with vanilla JS:

window.dispatchEvent(
  new CustomEvent('hyva-modal-show', {detail: {dialog: 'my-modal'}})
)

Hyvä >= 1.1.18

The hyva-modal-show event listener is present since version 1.1.18 of the hyva-themes/magento2-theme-module package.
If you use rely on this event in a module, be sure to add the appropriate dependency to the modules composer.json file: "hyva-themes/magento2-theme-module": "^1.1.18"

Using a custom event for Hyvä <= 1.1.17

The most straight forward way to allow opening a modal from anywhere is by using a custom event.

First, declare the view model as usual:

$modal = $viewModels->require(\Hyva\Theme\ViewModel\Modal::class)
                    ->createModal()
                    ->withDialogRefName('my-dialog')
                    ->withContent(<<<EOCONTENT
<div>
    <h3>Example dialog that can be opened with an event.</h3>
    <pre class="my-4">
// To open from an alpine component use

$dispatch('open-my-dialog')

// or globaly

window.dispatchEvent(new CustomEvent('open-my-dialog'))
    </pre>
    <button @click="hide" type="button" class="btn">Cancel</button>
</div>
EOCONTENT
      );

Then, declare the modal component with an event listener for open-my-dialog

<div x-data="hyva.modal()" @open-my-dialog.window="<?= $modal->getShowJs() ?>">
    <?= $modal ?>
</div>

Using a global function

It is also possible to declare a global function that can be called from anywhere.
To do this, the Alpine object scope needs to be captured in a global function, so the dialog reference is available.

This sounds complicated, as almost always is the case with JavaScript scoping rules, but it actually is only a one line function that is declared inside the component initialization callback.

The following example will clarify.

First, declare the view model as usual:

$modal = $viewModels->require(\Hyva\Theme\ViewModel\Modal::class)
                    ->createModal()
                    ->withDialogRefName('my-dialog')
                    ->withContent(<<<EOCONTENT
<div>
    <h3>Example dialog that can be opened from anywhere using a function.</h3>
    <pre class="my-4">
// To open this dialog call the function

window.openMyDialog();
    </pre>
    <button @click="hide" type="button" class="btn">Cancel</button>
</div>
EOCONTENT
      );

Second, when rendering the modal inside the Alpine.js component, add an init() function that captures the component scope in a global function.

<div x-data="{
    ...hyva.modal(),
    init() {
        window.openMyDialog = function (event) {
            this.show('my-dialog', event);
        }.bind(this);
    }
}" x-init="init()">
    <?= $modal ?>
</div>

Here the init function is declared directly inside the x-data attribute and merged onto the view model returned by hyva.modal() using object destructuring but it could be declared just as well in a view model function.

Then, the init function is called with x-init="init()" (in Alpine v3 that is taken care of automatically).

Now, the dialog can be opened from anywhere on the page by calling window.openMyDialog().

<div>
    <a onclick="openMyDialog()" class="btn">This trigger can be anywhere</a>
</div>