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:
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
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:
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()
.