[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"theme-db":3,"$fKnz2vuX4bZz1LbUTiuFsvSZ3e07l5_5fqNYp4Tzdhi8":22,"$fkrQbMnipRgx7J02W0yviPI9BcApycCG-LER__zrIVYw":103,"megamenu":172,"footer-db":228,"header-db":246,"$fqQirW-dKFCZDO9sUSyHHDzUhMBYL3YbhgFi8MN5J1Ks":257},{"theme":4},{"colors":5,"typography":13,"ui":17,"defaultColorMode":21},{"primary":6,"secondary":7,"background":8,"foreground":9,"muted":10,"headerBg":11,"footerBg":12,"topBarBg":9,"topBarText":11},"#4F46E5","#0D9488","#F9FAFB","#111827","#6B7280","#ffffff","#020617",{"fontFamily":14,"fontUrl":15,"baseFontSize":16},"Inter, system-ui, sans-serif","https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=Inter:wght@400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,700;0,800;0,900;1,400;1,700&display=swap","16px",{"borderRadius":18,"contentWidth":19,"shadow":20},"lg","7xl",true,"light",{"columns":23},[24,40,70,91],{"title":25,"links":26},"Plateforme",[27,31,34,37],{"label":28,"href":29,"external":30},"Offre Starter (2 500 €)","\u002Foffre-starter",false,{"label":32,"href":33,"external":30},"Devenir Ambassadeur","\u002Fambassadeur",{"label":35,"href":36,"external":30},"Modules PrestaShop","\u002Fmodules",{"label":38,"href":39,"external":20},"CodeMyShop.com","https:\u002F\u002Fcodemyshop.com",{"title":41,"links":42},"Le Synedre",[43,46,49,52,55,58,61,64,67],{"label":44,"href":45,"external":30},"L'histoire","\u002Fsynedre",{"label":47,"href":48,"external":30},"Constitution","\u002Fsynedre\u002Fconstitution",{"label":50,"href":51,"external":30},"L'équipe","\u002Fequipe",{"label":53,"href":54,"external":30},"Le réacteur en direct","\u002Freacteur",{"label":56,"href":57,"external":30},"Le Drill (entraînement)","\u002Fdrill",{"label":59,"href":60,"external":30},"Protocole de réunion","\u002Fsynedre\u002Freunion",{"label":62,"href":63,"external":30},"Les agents IA","\u002Fagents-ia",{"label":65,"href":66,"external":30},"La Conduite","\u002Fsynedre\u002Fconduite",{"label":68,"href":69,"external":30},"Charte plateforme","\u002Fsynedre\u002Fcharte",{"title":71,"links":72},"Ressources",[73,76,79,82,85,88],{"label":74,"href":75,"external":30},"Blog","\u002Fblog",{"label":77,"href":78,"external":30},"Academy","\u002Facademy",{"label":80,"href":81,"external":30},"Dictionnaire","\u002Fdictionnaire",{"label":83,"href":84,"external":30},"Expertise PrestaShop","\u002Fexpertise",{"label":86,"href":87,"external":30},"Flywheel","\u002Fflywheel",{"label":89,"href":90,"external":30},"Manifeste","\u002Fmanifeste",{"title":92,"links":93},"À propos",[94,97,100],{"label":95,"href":96,"external":30},"Alexandre Carette","\u002Fa-propos",{"label":98,"href":99,"external":30},"Dossier de presse","\u002Fpresse",{"label":101,"href":102,"external":30},"Contact","\u002Fcontact",{"title":104,"slug":105,"metaDescription":106,"category":107,"tags":108,"difficulty":116,"psVersions":117,"content":120,"faq":121,"tldr":167,"readingTime":168,"generatedAt":169,"publishDate":169,"relatedArticles":170,"sourceCategory":171},"Requête AJAX onChange dans PrestaShop : select dynamique en cascade","requete-ajax-onchange-select-cascade-prestashop","Implémentez des selects en cascade avec AJAX dans PrestaShop 8.x. Code complet : contrôleur, JavaScript et template Smarty pour un filtrage dynamique.","developpement",[109,110,111,112,113,114,115],"ajax","javascript","smarty","module prestashop","select dynamique","jquery","controller","intermediaire",[118,119],"1.7","8.x","\u003Ch2>Problématique : rafraîchir dynamiquement un champ selon la sélection utilisateur\u003C\u002Fh2>\n\u003Cp>Un besoin récurrent dans le développement PrestaShop : afficher un second \u003Ccode>\u003Cselect>\u003C\u002Fcode> dont les options dépendent de la valeur choisie dans un premier champ. C'est le pattern classique des \u003Cstrong>selects en cascade\u003C\u002Fstrong> — par exemple, sélectionner une catégorie parente puis charger dynamiquement ses sous-catégories.\u003C\u002Fp>\n\u003Cp>La difficulté réside dans l'articulation entre le template Smarty (côté serveur), le JavaScript (côté client) et le contrôleur AJAX du module. Voyons comment assembler ces trois briques proprement.\u003C\u002Fp>\n\u003Ch2>Architecture de la solution\u003C\u002Fh2>\n\u003Cp>Le flux est le suivant :\u003C\u002Fp>\n\u003Col>\n\u003Cli>L'utilisateur change la valeur du premier `\u003Cselect>`\u003C\u002Fli>\n\u003Cli>Un événement `change` déclenche une requête AJAX vers le contrôleur du module\u003C\u002Fli>\n\u003Cli>Le contrôleur interroge la base de données et retourne les données en JSON\u003C\u002Fli>\n\u003Cli>Le JavaScript met à jour le second `\u003Cselect>` sans rechargement de page\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>Étape 1 : déclarer les assets JavaScript dans le contrôleur\u003C\u002Fh2>\n\u003Cp>La méthode \u003Ccode>setMedia()\u003C\u002Fcode> du contrôleur front est le point d'entrée pour charger vos scripts. C'est la bonne pratique PrestaShop — elle garantit que jQuery est déjà disponible et que vos fichiers sont correctement mis en file d'attente.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\n&lt;?php\n\u002F\u002F modules\u002Fmonmodule\u002Fcontrollers\u002Ffront\u002Fdisplay.php\n\nclass MonModuleDisplayModuleFrontController extends ModuleFrontController\n{\n    public function setMedia()\n    {\n        parent::setMedia();\n\n        \u002F\u002F jQuery est déjà chargé par PrestaShop en front — inutile de le ré-inclure\n        \u002F\u002F En back-office également, jQuery est natif\n        $this-&gt;addJS($this-&gt;module-&gt;getPathUri() . 'views\u002Fjs\u002Fcascade-select.js');\n        $this-&gt;addCSS($this-&gt;module-&gt;getPathUri() . 'views\u002Fcss\u002Fcascade-select.css');\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>Point important :\u003C\u002Fstrong> jQuery est inclus nativement par PrestaShop, tant en front-office qu'en back-office. N'incluez jamais une seconde copie de jQuery — cela provoquerait des conflits et des comportements erratiques.\u003C\u002Fp>\n\u003Cp>Sur PrestaShop 8.x, \u003Ccode>_MODULE_DIR_\u003C\u002Fcode> reste fonctionnel, mais \u003Ccode>$this->module->getPathUri()\u003C\u002Fcode> est préférable car il est contextualisé à l'instance du module.\u003C\u002Fp>\n\u003Ch2>Étape 2 : exposer l'URL AJAX dans le template Smarty\u003C\u002Fh2>\n\u003Cp>Le JavaScript côté client a besoin de connaître l'URL du contrôleur AJAX. La bonne pratique consiste à la générer côté serveur via Smarty et à la transmettre au JS :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-smarty\">\n{* views\u002Ftemplates\u002Ffront\u002Fjavascript.tpl *}\n&lt;script type=\"text\u002Fjavascript\"&gt;\n    var ajaxEndpoint = \"{$link-&gt;getModuleLink('monmodule', 'ajax', [], true)|escape:'javascript':'UTF-8'}\";\n&lt;\u002Fscript&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Le quatrième paramètre \u003Ccode>true\u003C\u002Fcode> de \u003Ccode>getModuleLink()\u003C\u002Fcode> force le protocole SSL. L'échappement \u003Ccode>escape:'javascript'\u003C\u002Fcode> prévient les injections XSS.\u003C\u002Fp>\n\u003Cp>Sur \u003Cstrong>PrestaShop 8.x\u003C\u002Fstrong>, vous pouvez aussi utiliser l'approche par \u003Ccode>data-attribute\u003C\u002Fcode> sur un élément HTML, plus propre et compatible CSP :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-smarty\">\n&lt;div id=\"cascade-config\" \n     data-ajax-url=\"{$link-&gt;getModuleLink('monmodule', 'ajax', [], true)|escape:'htmlall':'UTF-8'}\"\n     style=\"display:none;\"&gt;&lt;\u002Fdiv&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"language-javascript\">\nconst ajaxEndpoint = document.getElementById('cascade-config').dataset.ajaxUrl;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Étape 3 : créer le contrôleur AJAX du module\u003C\u002Fh2>\n\u003Cp>Le contrôleur AJAX reçoit la requête POST, interroge la base et retourne du JSON :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\n&lt;?php\n\u002F\u002F modules\u002Fmonmodule\u002Fcontrollers\u002Ffront\u002Fajax.php\n\nclass MonModuleAjaxModuleFrontController extends ModuleFrontController\n{\n    \u002F** @var bool Désactive le rendu de la page complète *\u002F\n    public $ajax = true;\n\n    public function initContent()\n    {\n        parent::initContent();\n\n        $action = Tools::getValue('action');\n\n        if ($action === 'getSubcategories') {\n            $this-&gt;getSubcategories();\n        }\n    }\n\n    private function getSubcategories(): void\n    {\n        $idCategory = (int) Tools::getValue('id_category');\n        $idLang = (int) $this-&gt;context-&gt;language-&gt;id;\n\n        if ($idCategory &lt;= 0) {\n            $this-&gt;ajaxRender(json_encode([\n                'success' =&gt; false,\n                'message' =&gt; 'Catégorie invalide',\n            ]));\n            return;\n        }\n\n        \u002F\u002F Récupérer les sous-catégories\n        $subcategories = Category::getChildren(\n            $idCategory,\n            $idLang,\n            true  \u002F\u002F active uniquement\n        );\n\n        $result = array_map(function ($cat) {\n            return [\n                'id' =&gt; (int) $cat['id_category'],\n                'name' =&gt; $cat['name'],\n            ];\n        }, $subcategories);\n\n        $this-&gt;ajaxRender(json_encode([\n            'success' =&gt; true,\n            'subcategories' =&gt; $result,\n        ]));\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>Bonnes pratiques :\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Toujours caster les entrées utilisateur avec `(int)` pour prévenir les injections SQL\u003C\u002Fli>\n\u003Cli>Utiliser `$this->ajaxRender()` plutôt que `die(json_encode(...))` — c'est la méthode officielle PrestaShop\u003C\u002Fli>\n\u003Cli>Retourner une structure JSON cohérente avec un flag `success`\u003C\u002Fli>\n\u003Cli>Définir `public $ajax = true` pour désactiver le rendu du layout complet\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>Étape 4 : le JavaScript — événement onChange et requête AJAX\u003C\u002Fh2>\n\u003Cp>Voici le script complet qui orchestre les selects en cascade :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">\n\u002F\u002F views\u002Fjs\u002Fcascade-select.js\n\n$(document).ready(function () {\n\n    \u002F\u002F Sélection de la catégorie parente\n    $(document).on('change', '.select-category-parent', function (e) {\n        e.preventDefault();\n\n        var $parentSelect = $(this);\n        var idCategory = $parentSelect.val();\n        var productId = $parentSelect.data('product-id');\n\n        \u002F\u002F Référence vers le select enfant\n        var $childSelect = $('#select-subcategory-' + productId);\n        var $childLabel = $('#label-subcategory-' + productId);\n\n        \u002F\u002F Masquer et réinitialiser le select enfant\n        $childSelect.hide().empty();\n        $childLabel.hide();\n\n        if (!idCategory || idCategory === '0') {\n            return;\n        }\n\n        $.ajax({\n            type: 'POST',\n            url: ajaxEndpoint,\n            dataType: 'json',\n            cache: false,\n            data: {\n                action: 'getSubcategories',\n                id_category: idCategory,\n                ajax: 1\n            },\n            beforeSend: function () {\n                $parentSelect.prop('disabled', true);\n                \u002F\u002F Optionnel : afficher un indicateur de chargement\n                $childSelect.html('&lt;option&gt;Chargement...&lt;\u002Foption&gt;').show();\n            },\n            success: function (response) {\n                if (response.success && response.subcategories.length &gt; 0) {\n                    $childSelect.empty();\n                    $childSelect.append(\n                        '&lt;option value=\"\"&gt;-- Choisir une sous-catégorie --&lt;\u002Foption&gt;'\n                    );\n\n                    $.each(response.subcategories, function (index, cat) {\n                        $childSelect.append(\n                            '&lt;option value=\"' + cat.id + '\"&gt;' + cat.name + '&lt;\u002Foption&gt;'\n                        );\n                    });\n\n                    $childSelect.show();\n                    $childLabel.show();\n                } else {\n                    $childSelect.hide();\n                    $childLabel.hide();\n                }\n            },\n            error: function (xhr, status, error) {\n                console.error('Erreur AJAX:', status, error);\n                $childSelect.hide();\n            },\n            complete: function () {\n                $parentSelect.prop('disabled', false);\n            }\n        });\n    });\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Étape 5 : le template Smarty\u003C\u002Fh2>\n\u003Cp>Regroupez vos selects dans un template dédié pour maintenir une séparation claire entre logique et présentation :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-smarty\">\n{* views\u002Ftemplates\u002Ffront\u002Fcascade-select.tpl *}\n\n&lt;div class=\"cascade-select-wrapper\"&gt;\n    &lt;div class=\"form-group\"&gt;\n        &lt;label for=\"select-cat-parent-{$id_product}\"&gt;\n            {l s='Catégorie principale' mod='monmodule'}\n        &lt;\u002Flabel&gt;\n        &lt;select class=\"form-control select-category-parent\" \n                id=\"select-cat-parent-{$id_product}\"\n                data-product-id=\"{$id_product}\"&gt;\n            &lt;option value=\"\"&gt;{l s='-- Sélectionner --' mod='monmodule'}&lt;\u002Foption&gt;\n            {foreach from=$categories item=category}\n                &lt;option value=\"{$category.id_category}\"&gt;\n                    {$category.name|escape:'html':'UTF-8'}\n                &lt;\u002Foption&gt;\n            {\u002Fforeach}\n        &lt;\u002Fselect&gt;\n    &lt;\u002Fdiv&gt;\n\n    &lt;div class=\"form-group\"&gt;\n        &lt;label id=\"label-subcategory-{$id_product}\" \n               for=\"select-subcategory-{$id_product}\" \n               style=\"display:none;\"&gt;\n            {l s='Sous-catégorie' mod='monmodule'}\n        &lt;\u002Flabel&gt;\n        &lt;select class=\"form-control\" \n                id=\"select-subcategory-{$id_product}\" \n                style=\"display:none;\"&gt;\n        &lt;\u002Fselect&gt;\n    &lt;\u002Fdiv&gt;\n&lt;\u002Fdiv&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Gestion du back-office : différences à connaître\u003C\u002Fh2>\n\u003Cp>Si vous implémentez des selects en cascade dans un module back-office, quelques points changent :\u003C\u002Fp>\n\u003Cul>\n\u003Cli>**jQuery est toujours présent** — pas besoin de l'inclure\u003C\u002Fli>\n\u003Cli>Utilisez `AdminController` au lieu de `ModuleFrontController`\u003C\u002Fli>\n\u003Cli>Le token de sécurité est obligatoire : ajoutez `token: adminToken` dans vos données AJAX\u003C\u002Fli>\n\u003Cli>Préférez `AdminModuleAjaxController` pour les routes AJAX back-office\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode class=\"language-php\">\n\u002F\u002F En back-office, ajoutez le token dans votre template\nvar adminToken = '{$token|escape:\"javascript\":\"UTF-8\"}';\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Sécurité et performance\u003C\u002Fh2>\n\u003Ch3>Validation côté serveur\u003C\u002Fh3>\n\u003Cp>Ne faites jamais confiance aux données reçues en AJAX. Validez systématiquement :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\n\u002F\u002F Validation stricte dans le contrôleur\n$idCategory = (int) Tools::getValue('id_category');\nif ($idCategory &lt;= 0 || !Validate::isUnsignedId($idCategory)) {\n    $this-&gt;ajaxRender(json_encode(['success' =&gt; false]));\n    return;\n}\n\n\u002F\u002F Vérifier que la catégorie existe et est active\n$category = new Category($idCategory, $this-&gt;context-&gt;language-&gt;id);\nif (!Validate::isLoadedObject($category) || !$category-&gt;active) {\n    $this-&gt;ajaxRender(json_encode(['success' =&gt; false]));\n    return;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Mise en cache côté client\u003C\u002Fh3>\n\u003Cp>Pour éviter des requêtes répétitives (l'utilisateur navigue entre les catégories), implémentez un cache JavaScript simple :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">\nvar subcategoryCache = {};\n\n\u002F\u002F Dans le callback change, avant l'appel AJAX :\nif (subcategoryCache[idCategory]) {\n    renderSubcategories(subcategoryCache[idCategory]);\n    return;\n}\n\n\u002F\u002F Dans le success AJAX :\nsubcategoryCache[idCategory] = response.subcategories;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Migration vers PrestaShop 8.x\u003C\u002Fh2>\n\u003Cp>Sur PrestaShop 8.x, l'architecture Symfony est plus présente. Pour les modules modernes, vous pouvez utiliser les routes Symfony au lieu des \u003Ccode>ModuleFrontController\u003C\u002Fcode> classiques :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yaml\">\n# modules\u002Fmonmodule\u002Fconfig\u002Froutes.yml\nmonmodule_ajax_subcategories:\n    path: \u002Fmodule\u002Fmonmodule\u002Fsubcategories\n    methods: [POST]\n    defaults:\n        _controller: 'PrestaShop\\Module\\MonModule\\Controller\\SubcategoryController::getSubcategories'\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Cependant, l'approche classique avec \u003Ccode>ModuleFrontController\u003C\u002Fcode> reste parfaitement fonctionnelle et rétrocompatible sur PrestaShop 8.x. C'est souvent le choix le plus pragmatique si votre module doit aussi tourner sur 1.7.\u003C\u002Fp>",[122,125,128,131,134,137,140,143,146,149,152,155,158,161,164],{"q":123,"a":124},"Pourquoi ma requête AJAX renvoie une page HTML au lieu du JSON dans PrestaShop ?","Ce problème survient quand la propriété `$ajax` n'est pas définie à `true` dans votre contrôleur, ou quand le paramètre `ajax=1` manque dans les données POST. PrestaShop utilise ces indicateurs pour désactiver le rendu du layout complet. Vérifiez aussi que vous utilisez `$this->ajaxRender()` et non `echo` ou `return` directement.",{"q":126,"a":127},"Faut-il inclure jQuery manuellement dans un module PrestaShop ?","Non. jQuery est chargé nativement par PrestaShop en front-office comme en back-office. Inclure une seconde copie provoque des conflits (double initialisation, événements perdus). Utilisez `setMedia()` uniquement pour vos propres scripts. Si vous avez besoin d'une version spécifique de jQuery, c'est un signe que votre code doit être adapté à la version embarquée.",{"q":129,"a":130},"Comment sécuriser un contrôleur AJAX dans un module PrestaShop ?","Trois niveaux de protection sont essentiels : 1) Castez toutes les entrées avec `(int)` ou `Tools::getValue()` combiné à `Validate::isGenericName()` pour prévenir les injections. 2) Vérifiez l'existence des objets avec `Validate::isLoadedObject()`. 3) En back-office, exigez un token de sécurité. Vous pouvez aussi ajouter une vérification de l'en-tête `X-Requested-With: XMLHttpRequest` pour limiter les appels directs.",{"q":132,"a":133},"Comment gérer trois niveaux de selects en cascade dans PrestaShop ?","Le principe est identique au double select, mais chaîné : le changement du select niveau 2 déclenche un second appel AJAX pour charger le niveau 3. Utilisez des attributs `data-*` pour identifier chaque niveau et factorisez votre code JavaScript dans une fonction réutilisable. Pensez au cache côté client pour éviter de multiplier les requêtes serveur à chaque changement.",{"q":135,"a":136},"Quelle est la différence entre ajaxRender et die(json_encode()) dans PrestaShop ?","La méthode `$this->ajaxRender()` est la méthode officielle PrestaShop. Elle gère correctement les headers HTTP (`Content-Type: application\u002Fjson`), exécute les hooks de fin de contrôleur, et permet aux autres modules d'intercepter la réponse. Utiliser `die()` court-circuite tout cela et peut provoquer des effets de bord — c'est une mauvaise pratique même si elle fonctionne techniquement.",{"q":138,"a":139},"Comment débugger une requête AJAX qui échoue silencieusement dans PrestaShop ?","Ouvrez l'onglet Network de vos DevTools et inspectez la requête : vérifiez le code HTTP (200, 302, 500), le corps de la réponse, et les headers. Une redirection 302 signale souvent un problème de réécriture d'URL ou un contrôleur introuvable. Un 500 nécessite de consulter les logs PHP (`var\u002Flogs\u002F` sur PrestaShop 8.x). Ajoutez temporairement `error_reporting(E_ALL)` et `ini_set('display_errors', 1)` en haut de votre contrôleur pour capturer les erreurs.",{"q":141,"a":142},"Peut-on utiliser fetch() au lieu de jQuery AJAX dans un module PrestaShop ?","Oui, l'API Fetch est supportée par tous les navigateurs modernes et fonctionne parfaitement avec PrestaShop. C'est même recommandé pour les nouveaux développements, car cela réduit la dépendance à jQuery. Utilisez `fetch(url, { method: 'POST', body: new FormData(...) })` pour les requêtes simples. Attention cependant : si votre module cible aussi des boutiques avec des thèmes anciens qui désactivent jQuery, assurez-vous que le Fetch API est bien disponible.",{"q":144,"a":145},"Comment tester un contrôleur AJAX PrestaShop sans interface graphique ?","Utilisez cURL en ligne de commande : `curl -X POST 'https:\u002F\u002Fvotre-boutique.com\u002Fmodule\u002Fmonmodule\u002Fajax' -d 'action=getSubcategories&id_category=3&ajax=1'`. Vous pouvez aussi utiliser Postman ou Insomnia. Pour le back-office, ajoutez le paramètre `token` correspondant. Cela permet de valider le comportement du contrôleur indépendamment du frontend.",{"q":147,"a":148},"Comment afficher un loader pendant le chargement AJAX du select dans PrestaShop ?","Utilisez les callbacks `beforeSend` et `complete` de jQuery AJAX. Dans `beforeSend`, désactivez le select parent et affichez un spinner (une option 'Chargement...' ou une icône CSS). Dans `complete`, réactivez le select. Cela améliore considérablement l'expérience utilisateur en donnant un feedback visuel immédiat, surtout sur les connexions lentes.",{"q":150,"a":151},"Pourquoi mon select en cascade ne fonctionne pas après un rechargement AJAX de la page dans PrestaShop ?","Si le contenu est rechargé dynamiquement (via AJAX ou navigation SPA), les événements liés avec `.on('change', '.selectCat1', ...)` directement sur l'élément ne fonctionneront plus. Utilisez la délégation d'événements en attachant le listener au document ou à un conteneur parent stable : `$(document).on('change', '.selectCat1', function() {...})`. Cela garantit que les éléments injectés dynamiquement sont bien écoutés.",{"q":153,"a":154},"Quelle est la limite de données qu'on peut envoyer en POST AJAX dans PrestaShop ?","La limite n'est pas fixée par PrestaShop mais par la configuration PHP du serveur. La directive `post_max_size` (souvent 8 Mo par défaut) et `max_input_vars` (1000 par défaut) sont les deux paramètres à surveiller. Pour des selects en cascade, vous êtes loin de ces limites. En revanche, si vous envoyez des formulaires complexes avec de nombreux champs, augmentez `max_input_vars` dans votre `php.ini`.",{"q":156,"a":157},"Comment pré-sélectionner une valeur dans un select chargé dynamiquement en AJAX ?","Passez la valeur à pré-sélectionner comme attribut `data-selected` sur le select enfant dans votre template Smarty. Après le chargement AJAX des options, utilisez `$childSelect.val($childSelect.data('selected'))` pour appliquer la sélection. Cette technique est utile pour les formulaires d'édition où les valeurs précédentes doivent être restaurées.",{"q":159,"a":160},"Est-il possible d'utiliser les routes Symfony pour l'AJAX dans un module PrestaShop 8 ?","Oui, PrestaShop 8.x supporte les routes Symfony dans les modules via un fichier `config\u002Froutes.yml`. C'est l'approche moderne recommandée pour les nouveaux modules ciblant exclusivement PS 8+. Cependant, si votre module doit rester compatible avec PrestaShop 1.7, conservez l'approche classique avec `ModuleFrontController` — elle fonctionne sur les deux versions sans modification.",{"q":162,"a":163},"Comment éviter les requêtes AJAX multiples si l'utilisateur change rapidement de sélection ?","Implémentez un mécanisme de debounce ou d'annulation de requête. Avec jQuery, stockez l'objet XHR retourné par `$.ajax()` et appelez `.abort()` avant chaque nouvelle requête. Alternative plus élégante : utilisez un délai de 300 ms avec `setTimeout` avant de lancer l'appel, en annulant le timeout précédent à chaque changement. Cela réduit la charge serveur et évite les conditions de concurrence.",{"q":165,"a":166},"Comment rendre un select en cascade accessible (ARIA) dans PrestaShop ?","Ajoutez les attributs ARIA appropriés : `aria-label` sur chaque select, `aria-live='polite'` sur le conteneur du select enfant pour annoncer les changements aux lecteurs d'écran, et `aria-busy='true'` pendant le chargement AJAX. Utilisez aussi `role='status'` sur un élément invisible pour annoncer le nombre d'options chargées. L'accessibilité est souvent négligée dans les interactions AJAX mais reste une obligation légale dans de nombreux pays européens.","Implémentation complète de selects en cascade avec AJAX dans PrestaShop : contrôleur AJAX avec ajaxRender(), exposition de l'URL via Smarty, et gestion JavaScript avec cache client et feedback visuel. Compatible PrestaShop 1.7 et 8.x.",6,"2026-03-21T14:42:29.000Z",[],"PrestaShop pour les développeurs",{"items":173},[174,183,189,195,203,211,217,222],{"id":175,"type":176,"label":177,"href":84,"icon":179,"description":179,"badge":179,"groupTitle":179,"style":179,"gridColumns":179,"cssClass":179,"psCategoryId":179,"showPsChildren":30,"position":180,"children":181,"psChildren":182},41,"link",{"fr":178},"Expertise",null,0,[],[],{"id":184,"type":176,"label":185,"href":75,"icon":179,"description":179,"badge":179,"groupTitle":179,"style":179,"gridColumns":179,"cssClass":179,"psCategoryId":179,"showPsChildren":30,"position":186,"children":187,"psChildren":188},42,{"fr":74},1,[],[],{"id":190,"type":176,"label":191,"href":36,"icon":179,"description":179,"badge":179,"groupTitle":179,"style":179,"gridColumns":179,"cssClass":179,"psCategoryId":179,"showPsChildren":30,"position":192,"children":193,"psChildren":194},43,{"fr":35},2,[],[],{"id":196,"type":176,"label":197,"href":199,"icon":179,"description":179,"badge":179,"groupTitle":179,"style":179,"gridColumns":179,"cssClass":179,"psCategoryId":179,"showPsChildren":30,"position":200,"children":201,"psChildren":202},44,{"fr":198},"Outils IA","\u002Foutils-ia",3,[],[],{"id":204,"type":176,"label":205,"href":29,"icon":179,"description":179,"badge":179,"groupTitle":179,"style":207,"gridColumns":179,"cssClass":179,"psCategoryId":179,"showPsChildren":30,"position":208,"children":209,"psChildren":210},45,{"fr":206},"Offre Starter ✨",{"highlight":20},4,[],[],{"id":212,"type":176,"label":213,"href":78,"icon":179,"description":179,"badge":179,"groupTitle":179,"style":179,"gridColumns":179,"cssClass":179,"psCategoryId":179,"showPsChildren":30,"position":214,"children":215,"psChildren":216},46,{"fr":77},5,[],[],{"id":218,"type":176,"label":219,"href":96,"icon":179,"description":179,"badge":179,"groupTitle":179,"style":179,"gridColumns":179,"cssClass":179,"psCategoryId":179,"showPsChildren":30,"position":168,"children":220,"psChildren":221},47,{"fr":92},[],[],{"id":223,"type":176,"label":224,"href":102,"icon":179,"description":179,"badge":179,"groupTitle":179,"style":179,"gridColumns":179,"cssClass":179,"psCategoryId":179,"showPsChildren":30,"position":225,"children":226,"psChildren":227},48,{"fr":101},7,[],[],{"footer":229},{"theme":230,"description":179,"hours":179,"logo":231,"contact":234,"social":235,"bottomBar":245},"dark",{"src":232,"href":233,"alt":95},"\u002Flogo-ac.svg","\u002F",{"email":179,"phone":179,"address":179,"cta":179},[236,239,242],{"platform":237,"href":238,"label":237},"linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falexandre-carette\u002F",{"platform":240,"href":241,"label":240},"malt","https:\u002F\u002Fwww.malt.fr\u002Fprofile\u002Falexandrecarette",{"platform":243,"href":244,"label":243},"github","https:\u002F\u002Fgithub.com\u002Fprest4cafe",{"copyright":179},{"header":247},{"logo":248,"topBar":251,"contactEmail":254,"features":255,"navBar":179},{"src":232,"alt":249,"text":95,"href":233,"class":250},"Alexandre Carette — Architecte E-commerce Souverain","h-10 w-10",{"message":179,"showLanguages":30,"align":252,"languages":253},"left",[],"contact@alexandrecarette.fr",{"showSearch":30,"showWishlist":30,"showLogin":20,"showContact":30,"showCart":30,"stickyHeader":20,"headerLayout":256},"inline",{"academy":258,"blog":259,"expertise":270},[],[260,264,267],{"title":261,"url":262,"score":186,"type":263},"PrestaShop headless avec Nuxt 3 : pourquoi séparer back et front","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fprestashop-headless-nuxt-separation-front-back","blog",{"title":265,"url":266,"score":186,"type":263},"PrestaShop headless : Nuxt 3, pas Next.js — le choix souverain","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fprestashop-headless-nuxt-nextjs-souverainete",{"title":268,"url":269,"score":186,"type":263},"Sylius rachète PrestaShop : ce que ça change pour vous","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fsylius-rachat-prestashop-headless-souverainete",[]]