Whishlist Power
Retrouvez la documentation de Whishlist Power ici (opens in a new tab)
Setup Boutique
Téléchargez et activez l’app ($15,99 / mois).
Activez l’app dans les settings de votre branche du theme.
Créez le template Wishlist dans les pages.
Setup VsCode
Ajoutez les appels aux fichiers css + js dans le fichier theme.liquid
{% Wishlist %}
{{ 'mm-wishlist.css' | asset_url | stylesheet_tag }}
<script src="{{ 'mm-wishlist.js' | asset_url }}" defer="defer"></script>Créez le fichier mm-wishlist.js dans le dossier assets
class WishlistManager {
constructor() {
this.boundHandleClick = this.handleButtonClick.bind(this);
this.wishlistContainer = document.querySelector('[data-wishlist-button-header]');
this.countBubble = this.wishlistContainer?.querySelector('.mm-wishlist-count-bubble');
this.initializeWishlist();
this.setupEventListeners();
}
setupEventListeners() {
document.addEventListener('ooo-wishlist:product-added', (event) => {
const count = event.detail.wishlist_product_list.length;
this.updateUI(count);
});
document.addEventListener('ooo-wishlist:product-removed', (event) => {
const count = event.detail.wishlist_product_list.length;
this.updateUI(count);
});
}
async handleButtonClick(e) {
e.preventDefault();
e.stopPropagation();
const button = e.currentTarget;
const handle = button.dataset.productHandle;
if (!handle) return;
try {
const isCurrentlyActive = button.classList.contains('active');
// Action API
await (isCurrentlyActive
? window.Maestrooo.wishlist.removeProduct(handle)
: window.Maestrooo.wishlist.addProduct(handle));
// Mise à jour visuelle du bouton
this.updateButtonState(button, !isCurrentlyActive);
// Mise à jour de la page wishlist si nécessaire
const wishlistSection = document.querySelector('wishlist-products-section');
if (wishlistSection && isCurrentlyActive) {
await wishlistSection.loadWishlistProducts();
}
} catch (error) {
console.error('Erreur wishlist:', error);
}
}
updateUI(count) {
if (!this.countBubble || !this.wishlistContainer) return;
this.countBubble.textContent = count;
this.countBubble.classList.toggle('hidden', count === 0);
this.wishlistContainer.classList.toggle('active', count > 0);
}
updateButtonState(button, isActive) {
button.classList.toggle('active', isActive);
button.dataset.added = isActive ? 'true' : null;
}
async initButtons() {
const wishlistButtons = document.querySelectorAll('[data-wishlist-button]');
if (!wishlistButtons.length) return;
const promises = Array.from(wishlistButtons).map(async button => {
const handle = button.dataset.productHandle;
if (!handle) return;
button.removeEventListener('click', this.boundHandleClick);
button.addEventListener('click', this.boundHandleClick);
try {
const isInWishlist = await window.Maestrooo.wishlist.containsProduct(handle);
this.updateButtonState(button, isInWishlist);
} catch (error) {
console.error('Erreur état initial:', error);
}
});
await Promise.all(promises);
}
async initializeWishlist() {
try {
const content = await window.Maestrooo.wishlist.getContent();
this.updateUI(content?.products?.length || 0);
await this.initButtons();
} catch (error) {
console.error('Erreur initialisation wishlist:', error);
}
}
}
// Initialisation unique
const initWishlistManager = () => {
if (!window.wishlistManager) {
window.wishlistManager = new WishlistManager();
}
};
document.addEventListener('DOMContentLoaded', initWishlistManager);Créez le fichier mm-wishlist.css dans le dossier assets
/* ----------------- Wishlist content */
.mm-wishlist {
padding: 2px;
cursor: pointer;
position: relative;
}
.mm-wishlist .mm-icon-wishlist path {
fill: transparent;
transition: var(--duration-fast) var(--cubic-bezier-fast);
}
.mm-wishlist.active .mm-icon-wishlist path {
fill: var(--black-700);
}
.mm-header .mm-wishlist.active svg path {
fill: transparent !important;
}
/* Wishlist -- Product card */
.mm-card-product-wishlist {
position: absolute;
top: 0;
right: 0;
margin: 12px;
}
@media (max-width: 768px) {
.mm-card-product-wishlist {
margin: 6px;
}
}
/* Wishlist -- Product page */
.mm-main-product-wishlist {
margin-top: 4px;
}
/* Wishlist -- Header */
.mm-wishlist-count-bubble {
position: absolute;
top: 50%;
right: -4px;
transform: translateY(-50%);
}
/* ----------------- Page wishlist */
.mm-wishlist-products {
padding: var(--spacing-7, 64px) 20px 72px 20px;
grid-gap: 40px;
background: var(--black-50, #F4F4F4);
}
@media (max-width: 768px) {
.mm-wishlist-products {
padding: 40px 10px 20px 10px;
grid-gap: 40px;
}
.mm-wishlist-products-title {
font-size: var(--font-size-x-large, 24px);
line-height: 130%;
letter-spacing: -0.96px;
text-align: center;
align-items: center;
}
}
/* Wishlist -- Container */
.mm-wishlist-products-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 2px;
}
@media (max-width: 1024px) {
.mm-wishlist-products-container {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.mm-wishlist-products-container {
grid-template-columns: repeat(1, 1fr);
grid-gap: 22px 2px;
}
}
/* Wishlist -- Empty */
.mm-text-empty-wishlist {
padding-bottom: 10px;
}
.mm-wishlist-empty[hidden] {
display: none;
}
.mm-wishlist-empty {
align-items: center;
justify-content: center;
display: flex;
}
.mm-wishlist-products .mm-product-card-link .mm-product-infos-top {
margin-left: inherit;
text-align: center;
align-items: center;
}Créer le fichier MM-Wishlist-Products.liquid dans le dossier sections
{% comment %} Settings info section {% endcomment %}
{% liquid
assign section_title = section.settings.section_title
assign section_title_type = section.settings.section_title_type
assign wishlist_empty_text = section.settings.wishlist_empty_text
assign wishlist_empty_cta_text = section.settings.wishlist_empty_cta_text
assign wishlist_empty_cta_url = section.settings.wishlist_empty_cta_url
%}
<wishlist-products-section>
<div class="mm-wishlist-products mm-flex mm-column mm-align-center">
{% render 'mm-title-selector', title: section_title, type: section_title_type, class: 'mm-wishlist-products-title mm-text-3xl' %}
<div class="mm-wishlist-products-grid">
<div class="mm-wishlist-products-container" data-wishlist-products-container>
{% comment %} Les produits seront injectés ici via JavaScript {% endcomment %}
</div>
<div class="mm-wishlist-empty mm-column" data-wishlist-empty hidden>
{% render 'mm-title-selector', title: wishlist_empty_text, type: 'p', class: 'mm-text-md mm-text-empty-wishlist' %}
{% render 'mm-btn', url: wishlist_empty_cta_url, text: wishlist_empty_cta_text, fallback_text: 'MM_custom.cta.discover', class: 'mm-btn mm-btn-secondary mm-w-fit', icon: true, icon_name: 'arrow-right', icon_class: 'mm-arrow-right', icon_style: 'height: 16px;', icon_color: 'var(--white)' %}
</div>
</div>
</div>
</wishlist-products-section>
{% render "mm-wishlist-card-js" %}
{% schema %}
{
"name": "MM Wishlist Products",
"tag": "section",
"class": "section-wishlist-products",
"settings": [
{
"type": "header",
"content": "Section info"
},
{
"type": "text",
"id": "section_title",
"label": "Titre de la section"
},
{
"type": "select",
"id": "section_title_type",
"label": "Type titre de la section",
"options": [
{
"value": "h1",
"label": "Titre 1 (h1)"
},
{
"value": "h2",
"label": "Titre 2 (h2)"
},
{
"value": "h3",
"label": "Titre 3 (h3)"
},
{
"value": "p",
"label": "Texte (p)"
}
],
"default": "p"
},
{
"type": "header",
"content": "Wishlist Settings"
},
{
"type": "text",
"id": "wishlist_empty_text",
"label": "Texte quand la wishlist est vide",
"default": "Votre liste de souhaits est vide"
},
{
"type": "text",
"id": "wishlist_empty_cta_text",
"label": "Texte du bouton",
"default": "Découvrir nos produits"
},
{
"type": "url",
"id": "wishlist_empty_cta_url",
"label": "Lien du bouton"
}
],
"presets": [
{
"name": "MM Wishlist Products"
}
]
}
{% endschema %}Créez le fichier mm-wishlist-card-js.liquid utilisé dans la section MM-Wishlist-Products
<script>
class WishlistProductsSection extends HTMLElement {
constructor() {
super();
this.container = this.querySelector('[data-wishlist-products-container]');
this.emptyMessage = this.querySelector('[data-wishlist-empty]');
this.boundLoadProducts = this.loadWishlistProducts.bind(this);
this.setupEventListeners();
this.initializeWishlist();
}
setupEventListeners() {
document.addEventListener('ooo-wishlist:product-removed', (event) => {
const count = event.detail.wishlist_product_list.length;
this.updateUI(count);
});
}
initializeWishlist() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', this.boundLoadProducts);
} else {
this.boundLoadProducts();
}
}
async fetchProductCard(handle) {
try {
// Détecter la langue depuis l'URL
const path = window.location.pathname;
const languageMatch = path.match(/^\/([a-z]{2})(\/|$)/); // Capture un code langue comme "fr" ou "en" au début du chemin
const languagePrefix = languageMatch ? `/${languageMatch[1]}` : ''; // Ajoute le préfixe langue si trouvé
// Construire l'URL avec la langue
const response = await fetch(`${languagePrefix}/products/${handle}?view=card`);
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const productCard = doc.querySelector('.mm-product-card');
return productCard ? `<div class="mm-wishlist-product-item">${productCard.outerHTML}</div>` : '';
} catch (error) {
console.error(`Erreur lors du chargement du produit ${handle}:`, error);
return '';
}
}
updateUI(count) {
const wishlistContainer = document.querySelector('[data-wishlist-button-header]');
const countBubble = wishlistContainer?.querySelector('.mm-wishlist-count-bubble');
if (countBubble && wishlistContainer) {
countBubble.textContent = count;
countBubble.classList.toggle('hidden', count === 0);
wishlistContainer.classList.toggle('active', count > 0);
}
}
handleProductRemoval(button, handle) {
return async (e) => {
e.preventDefault();
e.stopPropagation();
if (!handle) return;
try {
await window.Maestrooo.wishlist.removeProduct(handle);
const item = button.closest('.mm-wishlist-product-item');
item?.remove();
if (!this.container.querySelector('.mm-wishlist-product-item')) {
this.container.hidden = true;
this.emptyMessage.hidden = false;
}
} catch (error) {
console.error('Erreur lors de la suppression:', error);
}
};
}
async loadWishlistProducts() {
if (!this.container) return;
try {
const wishlistContent = await window.Maestrooo.wishlist.getContent();
if (wishlistContent?.products?.length) {
this.container.hidden = false;
this.emptyMessage.hidden = true;
const productsHTML = await Promise.all(
wishlistContent.products.map(p => this.fetchProductCard(p.handle))
);
this.container.innerHTML = productsHTML.join('');
this.container.querySelectorAll('[data-wishlist-button]').forEach(button => {
const handle = button.dataset.productHandle;
button.removeEventListener('click', this.handleProductRemoval(button, handle));
button.addEventListener('click', this.handleProductRemoval(button, handle));
});
} else {
this.container.hidden = true;
this.emptyMessage.hidden = false;
}
} catch (error) {
console.error('Erreur lors du chargement de la wishlist:', error);
this.container.innerHTML = '<p class="mm-text-center">Une erreur est survenue</p>';
}
}
disconnectedCallback() {
document.removeEventListener('DOMContentLoaded', this.boundLoadProducts);
document.removeEventListener('ooo-wishlist:product-removed', this.updateUI);
}
}
customElements.define('wishlist-products-section', WishlistProductsSection);
</script>Créez le fichier product.card.liquid dans le dossier templates
{% render 'mm-card-product'
, product: product
, class: 'mm-page-wishlist',
, product_handle: product.handle
, show_wishlist: true
, is_wishlist_page: true
, data_added: true
%}Créez le fichier mm-wishlist-content.liquid (Bouton d'ajout à la wishlist ou de suppression) dans le dossier snippets
{% comment %}
Renders a wishlist button with optional functionality based on the context (header or product).
Accepts:
- header: {Boolean} If true, renders the wishlist button in the header.
- class: {String} Additional CSS class for the wishlist button.
- product_handle: {String} The product handle for the product-specific wishlist button (optional, used only when `header` is false).
- is_wishlist_page: {Boolean} If true, renders a "close" icon for the wishlist page.
Usage:
{% render 'mm-wishlist', header: true, class: 'wishlist-header-class', product_handle: 'product.handle', is_wishlist_page: true %}
{% endcomment %}
{% if header %}
<a href="{% if routes.root_url != '/' %}{{ routes.root_url }}{% endif %}/pages/wishlist" class="mm-wishlist {{ class }}" id="wishlist-icon-bubble" data-wishlist-button-header>
{% render 'mm-icon', icon_name: 'wishlist', icon_class: 'mm-icon-wishlist', icon_style: '', color: 'var(--black-900)' %}
<span class="visually-hidden">{{ 'MM_custom.wishlist.wishlist' | t }}</span>
{% render 'mm-wishlist-count-bubble' %}
</a>
{% else %}
<div class="mm-wishlist {{ class }}" data-product-handle="{{ product_handle }}" data-wishlist-button>
{% if is_wishlist_page %}
{% render 'mm-icon', icon_name: 'wishlist-close', icon_class: 'mm-icon-close', icon_style: '', color: 'var(--black-700)' %}
{% else %}
{% render 'mm-icon', icon_name: 'wishlist', icon_class: 'mm-icon-wishlist', icon_style: '', color: 'var(--black-900)' %}
{% endif %}
</div>
{% endif %}Exemples d'utilisation
Section Main-Product (page produit):
{% render 'mm-wishlist-content', header: false, class: 'mm-main-product-wishlist', is_wishlist_page: false, product_handle: product.handle %}Snippet Product Card :
{% render 'mm-wishlist-content', header: false, class: 'mm-card-product-wishlist', is_wishlist_page: false, product_handle: product.handle %}Section Header :
{% render 'mm-wishlist-content', header: true %}Créez le fichier mm-icon-wishlist-close.liquid dans le dossier snippets
<svg {{ class_attribute }} xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" {{ style_attribute }} {{ function }}>
<g opacity="0.7">
<path d="M6.20117 6.20117L13.7988 13.7988M13.7988 6.20117L6.20117 13.7988" stroke="{{ color }}" stroke-linecap="square" stroke-linejoin="bevel"/>
</g>
</svg>Créez le fichier mm-wishlist.liquid dans le dossier snippets
<svg {{ class_attribute }} xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none" {{ style_attribute }} {{ function }}>
<path d="M12 14.3333V1.66667H4V14.3333L8 11.6667L12 14.3333Z" stroke="{{ color }}" stroke-linecap="round"/>
</svg>Ajoutez les nouvelles icones au snippet qui gère les icones mm-icon.liquid (si présent)
when 'wishlist-close'
render 'mm-wishlist-close', class_attribute: class_attribute, style_attribute: style_attribute, color: color, function: function
when 'wishlist'
render 'mm-wishlist', class_attribute: class_attribute, style_attribute: style_attribute, color: color, function: functionSi vous n'avez pas le snippet mm-icon.liquid, le code liquid utilisé dans les fichiers d'icones SVG n'est pas utile.
Créez le fichier mm-wishlist-count-bubble.liquid dans le dossier snippets
<div class="mm-wishlist-count-bubble mm-count-cart hidden">
0
</div>Ajoutez les textes de traduction dans le fichier local en.default.json situé dans le dossier locales
"wishlist": {
"wishlist": "Wishlist",
"empty": "Your wishlist is empty"
},Et dans le fichier de correspondance fr.json situé dans le dossier locales
"wishlist": {
"wishlist": "Wishlist",
"empty": "Votre liste de souhaits est vide"
},