Table of Contents & Menu
Navigation

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.

  1. The visitor clicks > Open the modal > Show a loading animation > Show content when loaded
  2. 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
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="hyva_example" frontName="example">
            <module name="Hyva_Example" />
        </route>
    </router>
</config>

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>
Adjust the slider arguments to match your requirements.

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 loadContent method is merged into the Alpine component defined by hyva.modal().
  • The example content DOM ID dom-element-id is specified in three places:
    1. In the initial modal content, to indicate the target element to receive the content.
    2. In the layout XML, on the modal content wrapper container, so it is present in the response content.
    3. As the first argument of the hyva.replaceDomElement call in the loadContent method.
  • 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!