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>

Specifying the element to focus when the modal is closed

Available since Hyvä 1.2.9 and 1.3.5

To keep the store accessible, it is important to move the focus to the correct element when the modal is closed.
When opening a modal with an event, this element can be specified with a focusAfterHide property:

{dialog: 'my-modal', focusAfterHide: '#some-selector'}

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>