Creating a modal popup in Magento 2 using Hyvä themes is an efficient way to enhance user interaction and provide additional content without navigating away from the current page. In this tutorial, we’ll guide you through the process of adding a simple modal popup to your Magento 2 store using the Hyvä theme.
- Firstly create a child theme from Hyva Parent theme. If you want to go through the steps to create a child theme into Hyva, you can follow the steps from here : https://hardikmalkani.com/hello-world/
- Mini cart in hyva theme comes from cart-drawer.phtml. So create a folder stucture like below –
app/design/frontend/MageMango/hyvachild/Magento_Theme/templates/html/cart/cart-drawer.phtml - Replace cart-drawer.phtml with below code :
<?php
/**
* Hyvä Themes – https://hyva.io
* Copyright © Hyvä Themes 2020-present. All rights reserved.
* This product is licensed per Magento install
* See https://hyva.io/license
*/
declare(strict_types=1);
use Hyva\Theme\Model\ViewModelRegistry;
use Hyva\Theme\ViewModel\StoreConfig;
use Hyva\Theme\ViewModel\HeroiconsOutline;
use Magento\Framework\Escaper;
use Magento\Framework\View\Element\Template;
use Hyva\Theme\ViewModel\Modal;
/** @var Escaper $escaper */
/** @var Template $block */
/** @var ViewModelRegistry $viewModels */
/** @var Modal $modalViewModel */
$modalViewModel = $viewModels->require(Modal::class);
$storeConfig = $viewModels->require(StoreConfig::class);
$maxItemsToDisplay = $storeConfig->getStoreConfig(‘checkout/sidebar/max_items_display_count’);
/** @var HeroiconsOutline $heroicons */
$heroicons = $viewModels->require(HeroiconsOutline::class);
?>
<script>
function initCartDrawer() {
return {
open: false,
isLoading: false,
cart: {},
maxItemsToDisplay: <?= (int) $maxItemsToDisplay ?>,
itemsCount: 0,
getData(data) {
if (data.cart) {
this.cart = data.cart;
this.itemsCount = data.cart.items && data.cart.items.length || 0;
this.setCartItems();
}
this.isLoading = false;
},
cartItems: [],
getItemCountTitle() {
return hyva.strf(‘<?= $escaper->escapeJs(__(‘%0 of %1 products in cart displayed’)) ?>’, this.maxItemsToDisplay, this.itemsCount)
},
setCartItems() {
this.cartItems = this.cart.items && this.cart.items.sort((a, b) => b.item_id – a.item_id) || [];
if (this.maxItemsToDisplay > 0) {
this.cartItems = this.cartItems.slice(0, parseInt(this.maxItemsToDisplay, 10));
}
},
deleteItemFromCart(itemId) {
this.isLoading = true;
const formKey = hyva.getFormKey();
const postUrl = BASE_URL + ‘checkout/sidebar/removeItem/’;
fetch(postUrl, {
“headers”: {
“content-type”: “application/x-www-form-urlencoded; charset=UTF-8”,
},
“body”: “form_key=” + formKey + “&item_id=” + itemId,
“method”: “POST”,
“mode”: “cors”,
“credentials”: “include”
}).then(response => {
if (response.redirected) {
window.location.href = response.url;
} else if (response.ok) {
return response.json();
} else {
window.dispatchMessages && window.dispatchMessages([{
type: ‘warning’,
text: ‘<?= $escaper->escapeJs(__(‘Could not remove item from quote.’)) ?>’
}]);
this.isLoading = false;
}
}).then(result => {
window.dispatchMessages && window.dispatchMessages([{
type: result.success ? ‘success’ : ‘error’,
text: result.success
? ‘<?= $escaper->escapeJs(__(‘You removed the item.’)) ?>’
: result.error_message
}], result.success ? 5000 : 0)
window.dispatchEvent(new CustomEvent(‘reload-customer-section-data’));
});
},
scrollLock(use = true) {
document.body.style.overflow = use ? “hidden” : “”;
},
toggleCartDrawer(event) {
if (event.detail && event.detail.isOpen !== undefined) {
if (event.detail.isOpen) {
this.openCartDrawer();
} else {
this.open = false;
this.scrollLock(false);
this.$refs && this.$refs.cartDialogContent && hyva.releaseFocus(this.$refs.cartDialogContent);
}
} else {
<?php
/*
* The toggle-cart event was previously dispatched without parameter to open the drawer (not toggle).
* Keeping this in here for backwards compatibility.
*/
?>
this.openCartDrawer()
}
},
openCartDrawer() {
this.open = true;
this.scrollLock(true);
this.$nextTick(() => {
this.$refs && this.$refs.cartDialogContent && hyva.trapFocus(this.$refs.cartDialogContent)
})
},
closeCartDrawer() {
this.$dispatch(‘toggle-cart’, { isOpen: false })
},
getSectionDataExtraActions() {
if (!this.cart.extra_actions) {
return ”;
}
const contentNode = document.createElement(‘div’);
contentNode.innerHTML = this.cart.extra_actions;
hyva.activateScripts(contentNode);
return contentNode.innerHTML;
}
}
}
</script>
<section x-cloak
x-show=”cart”
id=”cart-drawer”
x-data=”initCartDrawer()”
@private-content-loaded.window=”getData($event.detail.data)”
@toggle-cart.window=”toggleCartDrawer($event)”
@keydown.escape=”closeCartDrawer”
>
<div role=”dialog”
aria-labelledby=”cart-drawer-title”
aria-modal=”true”
:aria-hidden=”!open”
class=”fixed inset-y-0 right-0 z-30 flex max-w-full”>
<div class=”backdrop”
x-show=”open”
x-transition:enter=”ease-in-out duration-500″
x-transition:enter-start=”opacity-0″
x-transition:enter-end=”opacity-100″
x-transition:leave=”ease-in-out duration-500″
x-transition:leave-start=”opacity-100″
x-transition:leave-end=”opacity-0″
role=”button”
@click=”closeCartDrawer”
aria-label=”<?= $escaper->escapeHtmlAttr(__(‘Close minicart’)) ?>”></div>
<div class=”relative w-screen max-w-md shadow-2xl”
x-show=”open”
x-transition:enter=”transform transition ease-in-out duration-500 sm:duration-700″
x-transition:enter-start=”translate-x-full”
x-transition:enter-end=”translate-x-0″
x-transition:leave=”transform transition ease-in-out duration-500 sm:duration-700″
x-transition:leave-start=”translate-x-0″
x-transition:leave-end=”translate-x-full”
x-ref=”cartDialogContent”
role=”region”
:tabindex=”open ? 0 : -1″
aria-label=”<?= $escaper->escapeHtmlAttr(__(‘My Cart’)) ?>”
>
<div class=”flex flex-col h-full max-h-screen bg-white shadow-xl”>
<?= $block->getChildHtml(‘cart-drawer.top’); ?>
<header class=”relative px-4 py-6 sm:px-6″>
<p id=”cart-drawer-title” class=”text-lg font-medium leading-7 text-gray-900″>
<strong><?= $escaper->escapeHtml(__(‘My Cart’)) ?></strong>
<span class=”items-total text-xs”
x-show=”maxItemsToDisplay && maxItemsToDisplay < itemsCount”
x-text=”getItemCountTitle()”>
</span>
</p>
</header>
<?= $block->getChildHtml(‘cart-drawer.items.before’); ?>
<template x-if=”!itemsCount”>
<div class=”relative px-4 py-6 bg-white border-bs sm:px-6 border-container”>
<?= $escaper->escapeHtml(__(‘Cart is empty’)) ?>
</div>
</template>
<template x-if=”itemsCount”>
<div class=”relative grid gap-6 sm:gap-8 px-1 py-3 sm:px-3 bg-white border-b border-container overflow-y-auto overscroll-y-contain”>
<template x-for=”item in cartItems”>
<div class=”flex items-start p-3 space-x-4 transition duration-150 ease-in-out rounded-lg hover:bg-gray-100″>
<a :href=”item.product_url”
class=”w-1/4″
:aria-label=”hyva.strf(‘<?= $escaper->escapeJs(__(‘Product “%0″‘)) ?>’, item.product_name)”
>
<img
:src=”item.product_image.src”
:width=”item.product_image.width”
:height=”item.product_image.height”
loading=”lazy”
alt=””
/>
</a>
<div class=”w-3/4 space-y-2″>
<div>
<p class=”text-xl”>
<span x-html=”item.qty”></span> x <span x-html=”item.product_name”></span>
</p>
<p class=”text-sm”><span x-html=”item.product_sku”></span></p>
</div>
<template x-for=”option in item.options”>
<div class=”pt-2″>
<p class=”font-semibold” x-text=”option.label + ‘:'”></p>
<p class=”text-secondary” x-html=”option.value”></p>
</div>
</template>
<p><span x-html=”item.product_price”></span></p>
<div class=”pt-4″>
<a :href=”item.configure_url”
x-show=”item.product_type !== ‘grouped’ && item.is_visible_in_site_visibility”
class=”inline-flex p-2 mr-2 btn btn-primary”
:aria-label=”hyva.strf(‘<?= $escaper->escapeJs(__(‘Edit product “%0″‘)) ?>’, item.product_name)”
>
<?= $heroicons->pencilHtml(”, 20, 20, [‘aria-hidden’ => ‘true’]); ?>
</a>
<div x-data=”hyva.modal()”>
<button type=”button”
class=”inline-flex p-2 btn btn-primary”
@click=”show(‘modelname’, $event)”
>
<?= $heroicons->trashHtml(”, 20, 20, [‘aria-hidden’ => ‘true’]); ?>
</button>
<?= $modalViewModel->createModal()->withContent(<<<END_OF_CONTENT
<div id=”the-label”>{$escaper->escapeHtml(__(‘Are you sure you want to remove this item?’))}</div>
<div class=”mt-20 flex justify-between gap-2″>
<button @click=”hide” type=”button” class=”btn”>
{$escaper->escapeHtml(__(‘Cancel’))}
</button>
<button x-focus-first @click=”hide, deleteItemFromCart(item.item_id)” type=”button” class=”btn btn-primary”>
{$escaper->escapeHtml(__(‘OK’))}
</button>
</div>
END_OF_CONTENT
)->positionCenter()
->overlayEnabled()
->withAriaLabelledby(‘the-label’)
->withDialogRefName(“modelname”)
->addDialogClass(‘border’, ‘border-10’, ‘border-blue-800’)
?>
</div>
</div>
</div>
</div>
</template>
</div>
</template>
<template x-if=”itemsCount”>
<div>
<?= $block->getChildHtml(‘cart-drawer.totals.before’); ?>
<div class=”relative grid gap-6 sm:gap-8 py-3 px-1 sm:px-3 bg-white”>
<div class=”w-full p-3 space-x-4 transition duration-150 ease-in-out rounded-lg hover:bg-gray-100″>
<p><?= $escaper->escapeHtml(__(‘Subtotal’)) ?>: <span x-html=”cart.subtotal”></span></p>
</div>
<div class=”w-full p-3 space-x-4 transition duration-150 ease-in-out rounded-lg hover:bg-gray-100″>
<a @click.prevent.stop=”closeCartDrawer; $dispatch(‘toggle-authentication’,
{url: ‘<?= $escaper->escapeUrl($block->getUrl(‘checkout’)) ?>’});”
href=”<?= $escaper->escapeUrl($block->getUrl(‘checkout’)) ?>”
class=”inline-flex btn btn-primary”
>
<?= $escaper->escapeHtml(__(‘Checkout’)) ?>
</a>
<span><?= $escaper->escapeHtml(__(‘or’)) ?></span>
<a href=”<?= $escaper->escapeUrl($block->getUrl(‘checkout/cart’)) ?>”
class=”underline”
>
<?= $escaper->escapeHtml(__(‘View and Edit Cart’)) ?>
</a>
</div>
<div x-html=”getSectionDataExtraActions()”></div>
<?= $block->getChildHtml(‘extra_actions’); ?>
</div>
</div>
</template>
<?= $block->getChildHtml(‘cart-drawer.bottom’); ?>
</div>
<button
type=”button”
@click=”closeCartDrawer”
aria-label=”<?= $escaper->escapeHtmlAttr(__(‘Close minicart’)) ?>”
class=”absolute top-0 right-2 p-4 mt-2 text-gray-300 transition-colors hover:text-black”
>
<?= $heroicons->xHtml(”, 24, 24, [‘aria-hidden’ => ‘true’]); ?>
</button>
</div>
<?= $block->getChildHtml(‘loading’) ?>
</div>
</section>
- To initialize hyva modal, we have to call the hyva_modal handle to the page where we need the modal popup to appear. As we need on mini cart, we will add in default.xml. So create the below file in your theme – app/design/frontend/MageMango/hyvachild/layout/default.xml and add the below code –
<?xml version=”1.0″?>
<!–
/**
* Hyvä Themes – https://hyva.io
* Copyright © Hyvä Themes 2020-present. All rights reserved.
* This product is licensed per Magento install
* See https://hyva.io/license
*/
–>
<page layout=”3columns” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xsi:noNamespaceSchemaLocation=”urn:magento:framework:View/Layout/etc/page_configuration.xsd”>
<update handle=”hyva_modal”/>
</page>
- After uploading the file into your child theme. Run the below commands :
php bin/magento s:up
php bin/magento s:d:c
php bin/magento s:s:d -f
php bin/magento ind:rein
php bin/magento c:f
- Once you are done with all the commands, the output will be like the below image –
