De l'Ajax dans Magento

  • De le 14 décembre 2011
  • Difficulté : 4/4

De l'Ajax dans Magento Petit tutoriel sur l'utilisation d'Ajax dans Magento à l'aide de Prototype. Dans cet exemple nous allons modifier le système d’inscription à la newsletter afin de gérer l'ajout d'un nouvel e-mail en Ajax.

Ajax est parfois bien utile. Avec Prototype il est plutôt assez facile d'implémenter de nouvelles fonctionnalités en Ajax. Voici un exemple concret sur l'utilisation d'Ajax avec Magento.

L'objectif de notre module est de gérer l'inscription à la newsletter en Ajax. Nous allons donc apporter quelques modifications à l'encart Newsletter disponible de base dans Magento :

Gestion de l'inscription à la newsletter en Ajax

Architecture du module

Développement du module

Template

Nous commençons par modifier le fichier du template souhaité. Il faut apporter quelques modifications au fichier subscribe.phtml, contenant le code HTML de l'encart Newsletter.

Notez qu'il est recommandé de ne pas modifier directement les fichiers du template base, mais de créer un nouveau template contenant les fichiers modifiés.

On ajoute un identifiant (ajax-send) sur l'élément dont nous souhaitons modifier l'affichage (il contiendra de magnifiques alertes vertes et rouges), ainsi que le Javascript chargé d’exécuter notre requête Ajax. Le constructeur de la classe SubscriberAjax (que nous détaillerons par la suite) contient 3 paramètres :

  • L'identifiant du formulaire (newsletter-validate-detail)
  • L'identifiant de l'élément à modifier (ajax-send)
  • L'URL à exécuter (le contrôleur du module)

Nous ajoutons pour finir un observer sur l'action de soumission du formulaire. La méthode newEmail sera alors exécutée. Notons l'utilisations indispensable de Event.stop(event) évitant que l'internaute ne soit redirigé vers le contrôleur et provoquant ainsi l'inutilité de tout ce qui va suivre.

app/design/frontend/base/default/template/newsletter/subscribe.phtml

<div class="block block-subscribe">
    <div class="block-title">
        <strong><span><?php echo $this->__('Newsletter') ?></span></strong>
    </div>
    <form action="<?php echo $this->getFormActionUrl() ?>" method="post" id="newsletter-validate-detail">
        <div class="block-content">
            <label for="newsletter"><?php echo $this->__('Sign Up for Our Newsletter:') ?></label>
            <div class="input-box">
               <input type="text" name="email" id="newsletter" title="<?php echo $this->__('Sign up for our newsletter') ?>" class="input-text required-entry validate-email" />
            </div>
            <div class="actions" id="ajax-send"> <!-- Ajout de l'identifiant -->
                <button type="submit" title="<?php echo $this->__('Subscribe') ?>" class="button"><span><span><?php echo $this->__('Subscribe') ?></span></span></button>
            </div>
        </div>
    </form>
    <script type="text/javascript">
    //<![CDATA[
        /* Instanciation de la classe SubscriberAjax */
        var subscriberAjax = new SubscriberAjax('newsletter-validate-detail','ajax-send','<?php echo $this->getUrl('subscriberajax/subscriber/new') ?>');

        /* Observer sur la soumission du formulaire */
        Event.observe('newsletter-validate-detail','submit',function(event) {
            /* Méthode chargée d’exécuter la requête */
            subscriberAjax.newEmail();
            /* On ne fait rien d'autre */
            Event.stop(event);
        });
    //]]>
    </script>
</div>
JavaScript

Nous pouvons maintenant nous pencher sur la classe SubscriberAjax. Celle-ci est ajoutée dans le fichier ajax.js du dossier js/subscriber fraîchement créé.

initialize représente le constructeur, avec les variables décrites ci-dessus (identifiant du formulaire, identifiant de l'élément à modifier et l'URL du contrôleur). Nous définissons alors 4 variables associées à notre objet : formObj, sendObj, url et button. La variable button nous est utile pour conserver le code HTML du bouton de soumission, réutilisé par la suite.

js/subscriber/ajax.js

SubscriberAjax = Class.create();

SubscriberAjax.prototype = {

    initialize: function(id,send,url) {
        this.formObj = $(id);
        this.sendObj = $(send);
        this.url = url;
        this.button = $(send).innerHTML;
    },

    newEmail: function() {
        var send = this.sendObj;
        var button = this.button;

        /* Mise à jour de l'URL du formulaire */
        this.formObj.action = this.url;

        /* Requête */
        this.formObj.request({
            method: 'post',
            onLoad:this.contentLoad(),
            onComplete: function(transport) {
                if(transport.status == 200)     {
                    var data = transport.responseText.evalJSON();
                    send.update(button+'<div class="send-message '+data.class+'">'+data.message+'</div>');
                }
            }
        });
    },

    contentLoad: function() {
        this.sendObj.update('<div class="ajax-loading">Enregistrement en cours...</div>');
    }

}

newEmail se charge d’exécuter la requête et d'afficher le résultat à l'internaute. Dans un premier temps il nous faut modifier l'URL du formulaire. Il aurait été possible de modifier directement le contenu du paramètre action du formulaire, mais par mesure de sécurité je préfère le modifier ici (moteur javascript défaillant, javascript désactivé, conflits, fin du monde...). Ainsi il sera toujours possible de s'inscrire à la newsletter, en passant au pire par le contrôleur initial de Magento.

N'oublions pas que le but est de spammer un maximum de monde, quelque soit son système.

Nous pouvons maintenant utiliser la méthode request de Prototype pour initialiser et exécuter la requête Ajax. Le contrôleur à exécuter a été spécifié lors de l'instanciation de la classe : subscriberajax/subscriber/new. A ce stade il n'existe pas encore.

Nous prenons soin également d'afficher un message indiquant que la requête est en cours de chargement. C'est l'option onLoad qui s'en charge, j'exécute la méthode contentLoad.

Si tout se passe bien (transport.status == 200), nous affichons à l'internaute un message indiquant le statut de son abonnement. Sinon on ne fait rien. C'est le bon moment pour annoncer que ce code est perfectible.

Pour faciliter un peu la compréhension du résultat, l'utilisation du format JSON s'impose. La réponse du contrôleur sera un tableau contenant une chaîne de caractères nommée class et une chaîne nommée message. La chaîne class est utilisée pour colorer le message : rouge si l'inscription a échouée, vert si tout s'est bien passé.

Layout

Il nous faut ajouter le fichier subscriber/ajax.js dans l'en-tête de la page. Le layout subscriberajax.xml est là pour çà.

Il est bien sûre possible de déclarer le fichier JavaScript dans n'importe quelle layout déjà existant (je m'adresse aux fainéants), mais la bonne pratique est d'ajouter systématiquement un nouveau layout pour chaque module. La raison est évidente : si le module est amené à être désactivé, il est inutile de charger le fichier.

app/design/frontend/base/default/layout/subscriberajax.xml

<layout version="0.1.0">
    <default>
        <reference name="head">
            <action method="addJs"><script>subscriber/ajax.js</script></action>
        </reference>
    </default>
</layout>
Configuration

Le fichier de configuration du module est assez simple, nous déclarons le nouveau layout pour le frontend, ainsi qu'un nouveau contrôleur.

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

<?xml version="1.0"?>
<config>
    <modules>
        <Magentix_SubscriberAjax>
            <version>0.1.0</version>
        </Magentix_SubscriberAjax>
    </modules>
    <frontend>
        <layout>
            <updates>
                <subscriberajax>
                    <file>subscriberajax.xml</file>
                </subscriberajax>
            </updates>
        </layout>
        <routers>
            <subscriberajax>
                <use>standard</use>
                <args>
                    <module>Magentix_SubscriberAjax</module>
                    <frontName>subscriberajax</frontName>
                </args>
            </subscriberajax>
        </routers>
    </frontend>
</config>
Contrôleur

Le contrôleur contient la méthode newAction. Celle ci est une copie de la méthode initiale utilisée par Magento pour ajouter un nouvel e-mail dans la base de données. Elle se trouve dans la classe Mage_Newsletter_SubscriberController (app/code/core/Mage/Newsletter/controllers/SubscriberController.php). Vérifiez qu'elle correspond bien à celle de la version de votre Magento, car elle est amenée à évoluer.

Nous adaptons ensuite cette méthode pour en modifier le comportement. Initialement la méthode opère aux différentes vérifications de l'adresse e-mail, associe un message à la session puis redirige l'internaute vers la page sur laquelle il se trouvait.

Nous supprimons les redirections et les messages de session. Ces messages deviennent le corps de la réponse HTTP. Nous les plaçons dans un tableau, accompagnés de la chaîne class indiquant s'il s'agit d'un message d'erreur ou de validation (utilisé pour la couleur du message). Nous encodons le tout avec JSON.

app/code/local/Magentix/SubscriberAjax/controllers/SubscriberController.php

<?php

class Magentix_SubscriberAjax_SubscriberController extends Mage_Core_Controller_Front_Action {

    public function newAction() {
        if ($this->getRequest()->isPost() && $this->getRequest()->getPost('email')) {
            $session            = Mage::getSingleton('core/session');
            $customerSession    = Mage::getSingleton('customer/session');
            $email              = (string) $this->getRequest()->getPost('email');

            try {
                if (!Zend_Validate::is($email, 'EmailAddress')) {
                    $this->_setBody('failed',$this->__('Please enter a valid email address.'));
                    return;
                }

                if (Mage::getStoreConfig(Mage_Newsletter_Model_Subscriber::XML_PATH_ALLOW_GUEST_SUBSCRIBE_FLAG) != 1 &&
                    !$customerSession->isLoggedIn()) {
                    $this->_setBody('failed',$this->__('Sorry, but administrator denied subscription for guests. Please <a href="%s">register</a>.', Mage::helper('customer')->getRegisterUrl()));
                    return;
                }

                $ownerId = Mage::getModel('customer/customer')
                    ->setWebsiteId(Mage::app()->getStore()->getWebsiteId())
                    ->loadByEmail($email)
                    ->getId();
                if ($ownerId !== null && $ownerId != $customerSession->getId()) {
                    $this->_setBody('failed',$this->__('This email address is already assigned to another user.'));
                    return;
                }

                $status = Mage::getModel('newsletter/subscriber')->subscribe($email);
                if ($status == Mage_Newsletter_Model_Subscriber::STATUS_NOT_ACTIVE) {
                    $this->_setBody('failed',$this->__('Confirmation request has been sent.'));
                } else {
                    $this->_setBody('success',$this->__('Thank you for your subscription.'));
                }
            }
            catch (Mage_Core_Exception $e) {
                $this->_setBody('failed',$this->__('There was a problem with the subscription: %s', $e->getMessage()));
            }
            catch (Exception $e) {
                $this->_setBody('failed',$this->__('There was a problem with the subscription.'));
            }
        } else {
            $this->_setBody('failed',$this->__('Please enter a valid email address.'));
        }
        return;
    }

    private function _setBody($class,$message) {
        $this->getResponse()->setBody(
            Zend_Json::encode(array('class'=>$class,'message'=>$message))
        );
    }

}

Note : afin de gagner en productivité lors de mes copiers/collers, j'ai ajouté la méthode _setBody.

Module

Déclaration du nouveau module, le fameux fichier que l'on copie/colle systématiquement d'un ancien module.

app/etc/modules/Magentix_SubscriberAjax.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Magentix_SubscriberAjax>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Newsletter />
            </depends>
        </Magentix_SubscriberAjax>
    </modules>
</config>
CSS

Un peu de CSS pour égayer le tout à l'aide de couleurs chatoyantes. send-message est l'élément contenant le message. Pour la couleur associé au type du message nous utilisons failed et success. ajax-loading est quand à lui utilisé lors du chargement de la requête (avec un magnifique loading récupéré sur preloaders.net).

skin/frontend/default/default/css/styles.css

.send-message,.ajax-loading{text-align:center;font-weight:bold;border:1px solid #ccc;background:#efefef;padding:5px;margin-top:5px}
.ajax-loading{text-align:left;padding-left:30px;background:#efefef url(../images/ajaxload.gif) no-repeat 5px}
.failed{color:#c00}
.success{color:#060}
Résumé

Pour de l'Ajax avec Magento il nous faut donc :

  • Un nouveau module avec au minimum un contrôleur
  • Une nouvelle classe JavaScript

Dans cet exemple le contrôleur se charge d'afficher de simples messages, mais il pourrait également charger un layout avec les blocs qui le composent :

Chargement du layout

public function printAction() {
    $this->loadLayout(false); // false pour ne pas charger les handles par défaut
    $this->renderLayout();
}
Bonus

En bonus :

  • Un lien vers la documentation de Prototype pour tout comprendre sur l'objet Request : Ajax.Request.
  • Un lien vers une page qui explique les statuts HTTP avec des chats : HTTP Status Cats
commentaires

Commentez cet article : De l'Ajax dans Magento