Loading modal contents with JavaScript
Load modal content dynamically using JavaScript (Fetch/Ajax). Dynamic modal content loading often involves considerations like loading animations and timing.
- The visitor clicks > Open the modal > Show a loading animation > Show content when loaded
- The visitor clicks > Show a loading animation > Open the modal with content when loaded
Tutorial Scenario
This tutorial demonstrates loading a product slider into a modal. Adapt this example to your specific needs.
1. Create a Magento Module
A Magento module is required for this functionality, as it extends beyond theme-specific frontend code. We will use the example module name Hyva_Example.
The steps to create a Magento module are out of scope of this tutorial.
2. Create endpoint to serve the modal content
An endpoint is needed to serve the modal content. This example uses a standard Magento controller action.
Custom API or standard?
Consider using existing Magento GraphQL or REST APIs if suitable. If so, you'll only need to adjust the JavaScript for content loading.
We need three things in our module:
A frontend route definition
Create the file etc/frontend/routes.xml
The action class
Create the file Controller/Modal/Content.php
<?php
declare(strict_types=1);
namespace Hyva\Example\Controller\Modal;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\View\Result\PageFactory;
class Content implements HttpGetActionInterface
{
private PageFactory $pageFactory;
public function __construct(PageFactory $pageFactory)
{
$this->pageFactory = $pageFactory;
}
public function execute()
{
return $this->pageFactory->create();
}
}
The layout XML file
Create the file view/frontend/layout/hyva_example_modal_content.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<update handle="hyva_product_slider" />
<body>
<referenceContainer name="content">
<!-- Important! The wrapper ID is specified in the htmlId attribute: -->
<container name="slider-wrapper" htmlTag="div" htmlId="dom-element-id">
<block name="slider" template="Magento_Catalog::product/slider/product-slider.phtml">
<arguments>
<argument name="class" xsi:type="string" translate="true">class-slider</argument>
<argument name="category_ids" xsi:type="string">22</argument>
<argument name="include_child_category_products" xsi:type="boolean">true</argument>
<argument name="page_size" xsi:type="string">100</argument>
<argument name="hide_rating_summary" xsi:type="boolean">true</argument>
<argument name="hide_details" xsi:type="boolean">true</argument>
</arguments>
</block>
</container>
</referenceContainer>
</body>
</page>
Remember to enable the new module, and flush the Magento cache after making any changes.
3. Create the modal
Here's the high-level approach:
- We render the modal content with a "Loading" placeholder. We give the placeholder a wrapper element with
id="dom-element-id". - When visitor opens the modal, we also fetch the content.
- When the content is available, we replace the temporary "Loading" placeholder content with the real stuff.
This implementation resides within a .phtml template in your module or theme.
<?php
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Hyva\Theme\Model\ViewModelRegistry $viewModels */
?>
<script>
function fetchModalContent() {
let isContentLoaded = false
return {
loadContent() {
if (isContentLoaded) return
window.fetch(`${BASE_URL}example/modal/content`)
.then(response => response.text())
.then(content => {
isContentLoaded = true
hyva.replaceDomElement('#dom-element-id', content)
})
.catch(console.error)
}
}
}
</script>
<div x-data="{...hyva.modal(), ...fetchModalContent()}">
<button type="button" class="text-center p-2 px-6 border border-secondary rounded"
aria-haspopup="dialog"
@click="show('the-modal', $event); loadContent()"
>
<?= $escaper->escapeHtml(__('Open')) ?>
</button>
<?= $modalViewModel->createModal()
->withDialogRefName("the-modal")
->withContent("<div id='dom-element-id' class='mt-2'>{$escaper->escapeHtml(__('Loading products...'))}</div>")
->withAriaLabelledby('the-label')
->addDialogClass('border', 'border-1');
?>
</div>
Things of note
- The
loadContentmethod is merged into the Alpine component defined byhyva.modal(). - The example content DOM ID
dom-element-idis specified in three places:- In the initial modal content, to indicate the target element to receive the content.
- In the layout XML, on the modal content wrapper container, so it is present in the response content.
- As the first argument of the
hyva.replaceDomElementcall in theloadContentmethod.
- The contents of the slider used for this example are declared in layout XML. See the slider documentation for more details.
- Instead of declaring the modal content using
withContent()as done in the example above, a separate .phtml template could be used, too.
Thanks to Adam from DrinkSupermarket.com for contributing this tutorial!