Liquid, Javascript, thèmes
Bonjour à toutes et à tous,
Je fais face à un problème JavaScript persistant sur mon thème Shopify et j'ai besoin de votre expertise. Mon objectif est d'implémenter une section FAQ interactive et cliquable sur mes articles de blog. Cette FAQ est personnalisable grâce à l'utilisation de champs méta (metafields) Shopify pour chaque question/réponse. Elle doit permettre aux questions de s'ouvrir et de se fermer (effet accordéon) pour révéler des réponses formatées avec du texte riche. J'ai développé le HTML, le CSS et le JavaScript nécessaire pour cette fonctionnalité.
Le problème principal : Malgré l'intégration de mon code dans le fichier theme.liquid, la fonctionnalité ne s'active pas et je vois des erreurs JavaScript bloquantes dans la console de mon navigateur.
Voici les erreurs récurrentes que je rencontre :
Ma configuration actuelle et les tests effectués :
Objectif du code FAQ : J'utilise du JavaScript pur (sans jQuery) pour gérer l'ouverture/fermeture des réponses FAQ et le rendu du contenu riche des champs méta. Ce script est actuellement encapsulé dans un setTimeout de 100ms pour s'assurer qu'il s'exécute après le chargement du DOM. Voici un extrait de mon code FAQ dans theme.liquid:
<script>
setTimeout(function() {
(function() {
const faqQuestions = document.querySelectorAll('.faq-question');
const richTextContents = document.querySelectorAll('.faq-answer-richtext-content');
const faqSectionExists = document.querySelector('.faq-astellow-section');
if (!faqSectionExists) {
return;
}
// ... (code de rendu du texte riche et gestion des clics) ...
})();
}, 100);
</script>
Inclusion de jQuery : Pour tenter de résoudre l'erreur jQuery is not defined provenant de jquery-ui.js, j'ai inclus la bibliothèque jQuery via la balise script recommandée par Shopify:
{{ '//ajax.googleapis.com/ajax/libs/jquery/2.2.3/jquery.min.js' | script_tag }}
Cette ligne est placée juste avant la balise de fermeture </body> dans mon fichier theme.liquid.
Test de la communauté (suggéré par le support Shopify) : Sur les conseils du support Shopify, j'ai ajouté un script de test tiré d'un fil de discussion de la communauté ([lien vers l'article du forum que le support t'a donné, tu peux le remettre ici si tu veux]). Ce script est censé attendre que jQuery soit chargé et afficher un message en console. Le code ajouté, placé juste après l'inclusion de jQuery et avant </body>, était le suivant:
<script>
setTimeout(() => {
while (!window.jQuery) {
console.log('Waiting for jQuery...');
}
window.jQuery( document ).ready(function() {
console.log('Astellow - jQuery est bien chargé !');
});
}, 100);
</script>
Résultat du test : Après avoir rechargé la page, le message "Astellow - jQuery est bien chargé !" n'apparaît pas dans la console. Les erreurs jquery-ui.js:14 Uncaught ReferenceError: jQuery is not defined et vendor.js:142 Uncaught (in promise) Error... persistent. Cela suggère que l'erreur jQuery is not defined est si bloquante qu'elle empêche même l'exécution du code de test, et donc de tout autre script JavaScript qui suit.
Hypothèses et pistes explorées / observées dans le forum :
Mon site est accessible ici : https://astellow.com/blogs/blogstellow-spiritualite-esoterisme-bien-etre/20h20-signification-heure-m.... Vous pouvez y observer les erreurs en console.
Je joins également une capture d'écran de ma console et le contenu de mon fichier theme.liquid actuel pour une analyse complète.
-----------------------------------
Mon fichier theme.liquid :
<!doctype html>
<html class="no-js" lang="{{ request.locale.iso_code }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="theme-color" content="">
<link rel="canonical" href="{{ canonical_url }}">
{%- if settings.favicon != blank -%}
<link rel="icon" href="{{ settings.favicon | image_url: width: 32, height: 32 }}" type="image/png">
<link rel="apple-touch-icon-precomposed" href="{{ settings.favicon | image_url: width: 48, height: 48 }}" type="image/png">
<link rel="nokia-touch-icon" href="{{ settings.favicon | image_url: width: 48, height: 48 }}" type="image/png">
{%- endif -%}
<title>{{- shop.name -}}</title>
{%- if page_description -%}
<meta name="description" content="{{ page_description | escape }}">
{%- endif -%}
{%- render 'meta-tags' -%}
{%- render 'header-fonts' -%}
{%- render 'header-styles' -%}
{%- render 'header-javascript' -%}
{{ content_for_header }}
{%- render 'css-variables' -%}
<script>document.documentElement.className = document.documentElement.className.replace('no-js', 'js');</script>
{% include 'pagefly-app-header' %}</head>
<body id="{{ page_title | handle }}" class="{% if customer %}customer-logged-in {% endif %}template-{{ request.page_type | handle }} {{ template.suffix }} gx-3 gx-md-4">
{%- if settings.preloading -%}
{% render 'preloading' %}
{%- endif -%}
{%- if settings.header_layout == "header2" -%}
{% sections 'header-group2' %}
{%- elsif settings.header_layout == "header3"-%}
{% sections 'header-group3' %}
{%- elsif settings.header_layout == "header4"-%}
{% sections 'header-group4' %}
{%- elsif settings.header_layout == "header5"-%}
{% sections 'header-group5' %}
{%- else -%}
{% sections 'header-group1' %}
{% endif %}
<main id="MainContent" class="main-content content-for-layout" tabindex="-1">
<div class="main-content__inner">
{{ content_for_layout }}
</div>
</main>
{%- if settings.footer_layout == "footer2" -%}
{% sections 'footer-group2' %}
{%- elsif settings.footer_layout == "footer3"-%}
{% sections 'footer-group3' %}
{%- else -%}
{% sections 'footer-group1' %}
{% endif %}
{% include 'footer-javascript' %}
{% include 'site-template' %}
{%- if settings.saleNotifi_enable and settings.collection_notification -%}
{% render 'sale-notification' %}
{%- endif -%}
{% render 'svg' %}
{%- if settings.header_bottom_mobile -%}
{%- render 'menu-fixed-mobile' -%}
{%- endif -%}
<script>
window.shopUrl = '{{ shop.url }}';
window.routes = {
origin: '{{ request.origin }}',
cart_url: '{{ routes.cart_url }}',
cart_add_url: '{{ routes.cart_add_url }}',
cart_clear_url: '{{ routes.cart_clear_url }}',
cart_change_url: '{{ routes.cart_change_url }}',
cart_update_url: '{{ routes.cart_update_url }}',
predictive_search_url: '{{ routes.predictive_search_url }}',
user_purchased: '{{ settings.user_purchased }}',
list_time: '{{ settings.time_suggest }}'
};
</script>
<script>
document.addEventListener("DOMContentLoaded", function() {
setTimeout(function() {
// Désactive l'élément "terms_conditions"
const termsCheckbox = document.querySelector("#terms_conditions");
const quickviewTermsCheckbox = document.querySelector("#quickview-terms-condition");
// Si la case existe, coche-la automatiquement et enlève "required"
if (termsCheckbox) {
termsCheckbox.checked = true;
termsCheckbox.removeAttribute("required");
termsCheckbox.style.display = "none"; // Cache la case, si nécessaire
}
if (quickviewTermsCheckbox) {
quickviewTermsCheckbox.checked = true;
quickviewTermsCheckbox.removeAttribute("required");
quickviewTermsCheckbox.style.display = "none"; // Cache la case, si nécessaire
}
// Forcer l'activation du bouton "continuer vers le paiement"
const checkoutButton = document.querySelector('button[name="checkout"]');
if (checkoutButton) {
checkoutButton.disabled = false;
checkoutButton.classList.remove("disabled");
checkoutButton.removeAttribute("disabled");
checkoutButton.style.pointerEvents = 'auto'; // Force l'activation
checkoutButton.style.opacity = 1; // S'assure que l'élément n'est pas "transparent"
}
// Forcer l'activation du bouton "justify-content-between"
const quickviewButton = document.querySelector("button.justify-content-between");
if (quickviewButton) {
quickviewButton.disabled = false;
quickviewButton.classList.remove("disabled");
quickviewButton.removeAttribute("disabled");
quickviewButton.style.pointerEvents = 'auto'; // Force l'activation
quickviewButton.style.opacity = 1; // Assure qu'il n'est pas transparent
}
}, 500); // Laisser le temps aux éléments de se charger
});
</script>
<script>
// Utilisation d'un setTimeout pour s'assurer que notre script s'exécute un peu après
// que le navigateur ait traité le reste du DOM et des scripts critiques du thème.
// Un délai de 100ms est souvent suffisant pour laisser le temps aux choses de se stabiliser.
setTimeout(function() {
// Encapsulation dans une IIFE pour isoler la portée des variables.
(function() {
const faqQuestions = document.querySelectorAll('.faq-question');
const richTextContents = document.querySelectorAll('.faq-answer-richtext-content');
// Important: Vérifier si le conteneur de la FAQ existe sur la page actuelle.
// Si cette balise est présente uniquement sur les articles de blog, cela évitera d'exécuter le script
// inutilement sur d'autres pages.
const faqSectionExists = document.querySelector('.faq-astellow-section');
if (!faqSectionExists) {
return; // Sortir si la section FAQ n'est pas présente sur cette page.
}
// Fonction pour rendre le contenu de l'éditeur de texte riche de Shopify (Lexical JSON)
function renderShopifyRichText(jsonContent) {
let html = '';
if (!jsonContent || jsonContent === 'null') {
return '';
}
try {
const data = JSON.parse(jsonContent);
if (data && Array.isArray(data.children)) {
data.children.forEach(node => {
if (node.type === 'paragraph' && Array.isArray(node.children)) {
let paragraphHtml = '';
node.children.forEach(textNode => {
if (textNode.type === 'text') {
let text = textNode.value || '';
if (textNode.bold) {
text = '<strong>' + text + '</strong>';
}
if (textNode.italic) {
text = '<em>' + text + '</em>';
}
paragraphHtml += text;
}
});
html += '<p>' + paragraphHtml + '</p>';
} else if (node.type === 'list' && Array.isArray(node.children)) {
let listHtml = '<ul>';
if (node.format === 'ordered') {
listHtml = '<ol>';
}
node.children.forEach(listItem => {
if (listItem.type === 'listitem' && Array.isArray(listItem.children)) {
let listItemContent = '';
listItem.children.forEach(liTextNode => {
if (liTextNode.type === 'text') {
let text = liTextNode.value || '';
if (liTextNode.bold) { text = '<strong>' + text + '</strong>'; }
if (liTextNode.italic) { text = '<em>' + text + '</em>'; }
listItemContent += text;
}
});
listHtml += '<li>' + listItemContent + '</li>';
}
});
html += listHtml + (node.format === 'ordered' ? '</ol>' : '</ul>');
}
});
}
} catch (e) {
console.error("Erreur de parsing JSON ou de rendu Rich Text:", e, jsonContent);
html = '<p>Erreur d\'affichage de la réponse.</p>';
}
return html;
}
// Rendre le contenu riche en texte pour chaque réponse
richTextContents.forEach(container => {
const jsonContent = container.dataset.richtextJson;
if (jsonContent) {
container.innerHTML = renderShopifyRichText(jsonContent);
}
});
// Gestion des collapsibles
faqQuestions.forEach(button => {
button.addEventListener('click', () => {
const answer = button.nextElementSibling;
button.classList.toggle('active');
if (button.classList.contains('active')) {
answer.style.maxHeight = answer.scrollHeight + "px";
} else {
answer.style.maxHeight = null;
}
});
});
})(); // Fin de l'IIFE
}, 100); // Délai de 100 millisecondes avant l'exécution du script.
</script>
{{ '//ajax.googleapis.com/ajax/libs/jquery/2.2.3/jquery.min.js' | script_tag }}
</body>
</html>
Bonjour, Depuis quelque temps, nous nous attachons à améliorer l’expérience Shopify e...
By Shopify May 12, 2025Maîtrisez l’expansion internationale de votre activité Shopify grâce au parcours d’appr...
By Shopify Feb 7, 2025Agrandissez la vente en gros avec le parcours d’apprentissage de Shopify Academy, B2B...
By Shopify Jan 30, 2025