Une navigation par filtre en Ajax

  • De le 26 janvier 2014
  • Difficulté : 3/4

Une navigation par filtre en Ajax Il existe de nombreux modules sur Magento Connect pour une navigation par filtre en Ajax, parfois assez lourds avec un temps d'intégration non négligeable. Le plus simple reste de le développer soit même, et cela ne demande que quelques lignes de code...

Démonstration

Une démonstration du module décrit dans cet article est disponible à l'adresse suivante :

Magentix Demo Store - Layer Ajax

Architecture du module

Dans cet exemple, le thème par défaut est magentix, il conviendra d'adapter en fonction de votre site.

Développement du module

Le principe du module est le suivant :

  • 1. Observer le clic de l'internaute sur un lien du layer
  • 2. Exécuter une requête Ajax avec en paramètres l'identifiant de la catégorie et le filtre sélectionné
  • 3. Mettre à jour le layer et la liste de produits

config.xml

Le fichier de config (config.xml) déclare un nouveau contrôleur. Ce contrôleur est utilisé pour notre requête Ajax. La configuration déclare également un nouveau layout spécifique à notre module.

app/code/local/Magentix/LayerAjax/etc/config.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Magentix_LayerAjax>
            <version>0.1.0</version>
        </Magentix_LayerAjax>
    </modules>
    <frontend>
        <routers>
            <layerajax>
                <use>standard</use>
                <args>
                    <module>Magentix_LayerAjax</module>
                    <frontName>layer</frontName>
                </args>
            </layerajax>
        </routers>
        <layout>
            <updates>
                <layerajax>
                    <file>layerajax.xml</file>
                </layerajax>
            </updates>
        </layout>
    </frontend>
</config>

layerajax.xml

Nous avons besoin pour la page catégorie (catalog_category_layered), d'un nouveau JS, et d'un nouveau bloc placé avant la fermeture de la balise body. Le template associé au bloc permet d'instancier la classe Prototype développé par la suite.

Le handle de la route de notre module requiert des blocs du handle catalog_category_layered. Plutôt que de déclarer une nouvelle fois ces blocs, nous utilisons le noeud update. De cette façon nous ne sommes pas dépendant des mises à jour éventuelles des blocs de ce handle.

app/design/frontend/default/magentix/layout/layerajax.xml

<?xml version="1.0"?>
<layout version="0.1.0">
    <catalog_category_layered>
        <reference name="head">
            <action method="addJs"><script>layer/ajax.js</script></action>
        </reference>
        <reference name="before_body_end">
            <block type="core/template" name="ajax.list.init" template="layerajax/init.phtml" />
        </reference>
    </catalog_category_layered>
    <layerajax_ajax_list>
        <update handle="catalog_category_layered" />
    </layerajax_ajax_list>
</layout>
Layer Ajax Head

init.phtml

Ce template instancie simplement la classe LayerAjax. Cette classe est décrite dans le paragraphe suivant. En paramètres nous indiquons l'URL de la route nécessaire à la requête, ainsi que l'identifiant de la catégorie dans laquelle nous nous trouvons.

app/design/frontend/default/magentix/template/layerajax/init.phtml

<script type="text/javascript">
//<![CDATA[
new LayerAjax(
    '<?php echo $this->getUrl('layer/ajax/list') ?>',
    '<?php echo Mage::registry('current_category')->getId() ?>'
);
//]]>
</script>
Layer Ajax Before Body End

ajax.js

C'est ici que les choses sérieuses commencent. Pour respecter le choix de Magento nous utilisons Prototype et non jQuery. Notre nouvelle classe doit permettre l'exécution d'une requête Ajax. Le résultat de cette requête contient le code HTML du layer et de la liste produit que nous mettons alors à jour.

Notez que les classes du DOM du layer et de la liste produit sont ici ceux du template par défaut de Magento (CE 1.8). Il convient d'adapter en fonction de votre template.

js/layer/ajax.js

var LayerAjax = Class.create();

LayerAjax.prototype = {
    ajaxRequest: null,
    url: null,
    id: null,
    initialize: function(url, id) {
        this.url = url; // URL de la route
        this.id = id; // Identifiant de la catégorie
        this.init(); // Initiallisation
    },
    init: function() {
        $$('.block-layered-nav a').each(function(element) {
            // Observer sur les liens du layer
            element.observe('click', function(event) {
                // Ne pas rediriger l'internaute vers la page
                Event.stop(event);

                // Annulation de la requête Ajax en cours
                this.abort();

                var layer = $$('.block-layered-nav').first(); // Layer
                var list = $$('.category-products').first(); // Liste

                // Objet JSON contenant les paramètres du filtre, par exemple {color:'27',price:'20-30'}
                var parameters = element.href.toQueryParams();

                // Ajout de l'identifiant de la catégorie
                parameters.id = this.id;

                // Effet visuel sur la liste des produits
                list.fade({ duration: 0.2, to: 0.6 });

                // Requête Ajax
                this.ajaxRequest = new Ajax.Request(this.url, {
                    onSuccess: function(response) {
                        if(response.responseText) {
                            // Object JSON contenant le code HTML du layer et de la liste
                            data = response.responseText.evalJSON();
                            if(Object.keys(data).length) {
                                layer.replace(data.layer);
                                list.replace(data.list);
                                this.init();
                            }
                        }
                        this.ajaxRequest = null;

                        /* Swatch Manager Since Magento CE 1.9.1.0 */
                        if (typeof ProductMediaManager !== 'undefined') {
                            ProductMediaManager.init();
                        }

                    }.bind(this),
                    parameters: parameters
                });
            }.bind(this));
        }.bind(this));
    },
    abort: function() {
        if (this.ajaxRequest !== null) {
            this.ajaxRequest.transport.onreadystatechange = Prototype.emptyFunction;
            this.ajaxRequest.transport.abort();
        }
    }
};

Cette classe est un exemple simple, vous pouvez par exemple y ajouter un loader. La documentation de l'API Prototype pour l'Ajax est disponible à cette adresse : Prototype v1.7.1 API documentation - Ajax section.

AjaxController.php

L'action list du contrôleur accueille les paramètres du filtre puis génère les 2 blocs à mettre à jour.

Pour faciliter les développements nous héritons de la classe Mage_Catalog_CategoryController. Nous évitons ainsi l'écriture de code inutile et ne sommes pas dépendant des mises à jour sur l'initialisation de la catégorie et la gestion des filtres.

Le comportement est donc strictement identique à celui utilisé par Magento lorsque l'URL d'une page catégorie est appelée en direct (categorie.html?color=27&price=40-).

Il convient cependant de spécifier l'alias de la catégorie pour la réécriture d'URL. Nous partons du principe que celle ci est activée.

app/code/local/Magentix/LayerAjax/controllers/AjaxController.php

<?php

require_once Mage::getModuleDir('controllers', 'Mage_Catalog').DS.'CategoryController.php';

class Magentix_LayerAjax_AjaxController extends Mage_Catalog_CategoryController
{
    
    public function listAction()
    {
        if (($category = $this->_initCatagory())) {
            /* Alias de la catégorie */
            $this->getRequest()->setAlias(
                Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS,
                $category->getUrlPath()
            );
            
            /* Gestion des paramètres des filtres pour le bloc layer */
            $params = $this->getRequest()->getParams();
            if(isset($params['id'])) { unset($params['id']); }

            $this->getRequest()->setQuery($params);

            $this->loadLayout();
            $layout = $this->getLayout();

            $this->_configurableSwatches();
            
            /* Appel des blocs et nettoyage des URLs selon la configuration (?___SID=U) */
            $layer = Mage::getSingleton('core/url')->sessionUrlVar(
                $layout->getBlock('catalog.leftnav')->toHtml()
            );
            $list = Mage::getSingleton('core/url')->sessionUrlVar(
                $layout->getBlock('product_list')->toHtml()
            );

            /* La réponse contient l'objet JSON contenant le code HTML des blocs */
            $this->getResponse()->setBody(Mage::helper('core')->jsonEncode(array(
                'layer' => $layer,
                'list'  => $list,
            )));
        }
    }

    /**
     * Convert block if configurable swatches is enabled (Since Magento CE 1.9.1.0 and Magento EE 1.14.0)
     */
    protected function _configurableSwatches()
    {
        if (Mage::helper('core')->isModuleEnabled('Mage_ConfigurableSwatches')) {
            $edition = Mage::getEdition();
            $blockName = $edition == Mage::EDITION_ENTERPRISE ? 'enterprisecatalog.leftnav' : 'catalog.leftnav';

            Mage::helper('configurableswatches/productlist')->convertLayerBlock($blockName);
        }
    }
    
}

Attention : si vous avez modifié le nom des blocs catalog.leftnav et product_list, il convient d'adapter.

Magentix_LayerAjax.xml

Il nous reste enfin à déclarer le nouveau module.

app/etc/modules/Magentix_LayerAjax.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Magentix_LayerAjax>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Catalog />
            </depends>
        </Magentix_LayerAjax>
    </modules>
</config>

Conclusion

Nous nous sommes efforcés pour ce module de respecter le principe de modularité de Magento. Aucun fichier des templates n'a été modifié, aucune surcharge n'a été ajoutée. Une mise à jour de Magento n'aura de plus aucun impact sur le module, les comportements de base ayant été conservés. Nous pourrions aller encore plus loin en proposant une configuration depuis l'administration (par exemple pour spécifier les classes utilisées pour les blocs).

Le comportement au clic sur le lien d'un filtre peut être bien entendu amélioré, avec par exemple l'ajout d'un loader. L'utilisation du Pushing en HTML5 pour la mise à jour de l'URL serait également une belle amélioration.

commentaires

Commentez cet article : Une navigation par filtre en Ajax