Dynamic-Mess.com


"The world is a dynamic mess of jiggling things..."

Application Backbone.js : communication avec un serveur REST

Article posté le 22-10-2014 dans la catégorie Backbone.js

Pour faire suite au tutoriel pour pratiquer les bases de Backbone.js, voici un autre tutoriel pour améliorer notre application en la faisant communiquer avec le serveur.

Nous ne montrerons donc que le code modifié/ajouté.

1- Principe

Pour rappel notre application servait juste a saisir le nom d'une tâche et l'afficher dans une liste. Il y avait également un bouton supprimer pour la retirer. Cependant, aucune sauvegarde : au chargement de la page tout était à refaire.

Ici nous allons donc non seulement stocker notre information sur un serveur, mais également ajouter la possibilité de dire si cette tâche est terminée ou toujours en cours.

2- Le Code

A- Entités

Nos tâches sont stockées dans une table nommée liste, contenu dans une base nommée taches. Chaque enregistrement comprend :

B- Côté serveur

Le serveur est un script PHP simplifié pour comprendre les requêtes de type REST. Je passerai sur la méthode pour utiliser et vous connecter à la base...

Tout d'abord, déclarons une classe tache :

class tache {
    private $id = 0;
    private $titre = "";
    private $statut = "En cours";
    function getId() {
        return $this->id;
    }
    function getTitre() {
        return $this->titre;
    }
    function setId($id) {
        $this->id = $id;
    }
    function setTitre($titre) {
        $this->titre = $titre;
    }
    
    function getStatut() {
        return $this->statut;
    }
    function setStatut($statut) {
        $this->statut = $statut;
    }
}

La première partie du script détecte le type de requête et lance la méthode appropriée :

//Infos de la requête
$method = $_SERVER['REQUEST_METHOD']; //Type de la requête
$demande = $_SERVER['REQUEST_URI']; //Url demandée
//Connexion BDD, je vous laisse déclarer et implémenter cette fonction comme bon vous semble
$conn = dbConnect();
//En fonction du type de la requête
switch ($method) {
    case 'PUT':
        rest_put();
        break;
    case 'POST':
        rest_post();
        break;
    case 'GET':
        rest_get();
        break;
    case 'DELETE':
        rest_delete();
        break;
    default:
        break;
}

La méthode DELETE

function rest_delete() {
    global $conn; //Récuperer la connexion BDD
    global $demande; //Récuperer les infos sur la demande
    $arrayDemande = explode("/", $demande);
    $detailDemande = $arrayDemande[count($arrayDemande)-1]; //On a à présent l'id de la tache à supprimer
    
    $laTache = new tache();
    $laTache->setId($detailDemande);
    dbWrite($conn, "DELETE FROM liste WHERE id = :id", $laTache); //Méthode qui va s'occuper d'écrire dans la base, ici pour supprimer l'enregistrement
}

La méthode PUT

function rest_put() {
    global $conn;
    global $demande;
    $arrayDemande = explode("/", $demande);
    $detailDemande = $arrayDemande[count($arrayDemande)-1]; //id de la tache à supprimer
    $detailDemande = @file_get_contents('php://input'); //Avec backone, l'objet jSon se trouve dans le body de la requête
    $arrayDemande = json_decode($detailDemande); //On transforme notre objet jSon en objet PHP
    $laTache = new tache();
    $laTache->setTitre($arrayDemande->titre);
    $laTache->setId($arrayDemande->id); //Facultatif
    $laTache->setStatut("Terminé"); //Mise à jour du statut
    $laRequeteSQL = "UPDATE liste SET statut=:statut WHERE id=:id"; //Requete de mise à jour
    dbWrite($conn, $laRequeteSQL, $laTache); //Exécution de la requête
    $var = '{"id" : "' . $laTache->getId() .  '", "titre":"' . $arrayDemande->titre . '", "statut":"' . $laTache->getStatut() . '"}'; //On construit notre nouvel objet
    echo $var; //Et on l'affiche pour backbone
}

La méthode POST

function rest_post() { //Création d'une tâche
    global $conn;
    $detailDemande = @file_get_contents('php://input'); //Contenu du body
    $arrayDemande = json_decode($detailDemande);
    $laTache = new tache();
    $laTache->setTitre($arrayDemande->titre);
    $laRequeteSQL = "INSERT INTO liste VALUES ('', :titre, :statut)"; //Requête d'insertion
    $idNouvelleTache = dbWrite($conn, $laRequeteSQL, $laTache); //Récuperer l'id du nouvel enregistrement
    $var = '{"id" : "' . $idNouvelleTache .  '", "titre":"' . $arrayDemande->titre . '", "statut":"' . $laTache->getStatut() . '"}'; //Et on retourne l'objet en ajoutant l'id. //Cela permet à Backbone, donc côté client, de se mettre à jour.
    echo $var;
}

Et enfin la méthode GET

function rest_get() { //Affichage de toutes les taches de la base
    global $conn;
    $laChainejSon = '[';
    try {
        $sql = "SELECT * from liste";
        $req = $conn->query($sql);
        while ($row = $req->fetch()) {
            $laChainejSon = $laChainejSon . '{"titre":"' . $row['titre'] . '", "id":"' . $row['id'] . '", "statut" : "' . $row['statut'] .'"},';
        }
        $req->closeCursor();
    } catch (Exception $e) {
        echo "Probleme lecture des taches";
        //echo $e;
        exit;
    }
    $laChainejSon = substr($laChainejSon, 0, $laChainejSon - 1);
    $laChainejSon = $laChainejSon . ']';
    echo $laChainejSon; //On affiche un tableau d'objet jSon
}

C- Côté client

Nous allons à présent pouvoir nous concenter sur ce qui nous intéresse ici : la partie client avec Backbone.js. Je vais donc présenter les modifications et ajouts de code par rapport à la version précédente de notre application.

Le model :

Les attributs id et statut font leur apparition :

var Tache = Backbone.Model.extend({
    defaults: {
        id: null,
        titre: 'Une tâche vide',
        statut: 'En cours'
    }
});

Dans la collection, on ajoute l'URL du serveur REST :

var collectionDeTaches = Backbone.Collection.extend({
    url: 'taches.php', //On spécifie l'URL de gestion de la collection
    model: Tache
});

Dans la liste, on ajoute un identifiant (HTML), pour des raisons de commodité, mais surtout on lui demande d'afficher chaque élément de la collection :

var List = Backbone.View.extend({
    tagName: 'ul', //Pour la création du conteneur de la liste : balise <ul>
    id: 'maListeDeTaches', //Identifiant
    // Constructeur
    initialize: function () {
        this.model.bind('add', this.ajouteObjet, this); //Ici , on réécrit la fonction ajout du modèle
        //en la remplacant par celle déclarée plus bas dans la classe, puisque c'est ici que se verront
        //les ajouts

    },
    render: function () {
        this.model.each(this.addItem, this); //Rajout pour afficher chaque élément de la collection
        return $(this.el);//Obligatoire sinon Vue non mise à jour!
    },
    //Méthode pour gérer l'ajout d'une nouvelle tâche
    ajouteObjet: function (notreObjet) {
        $(this.el).append(new Objet({
            model: notreObjet
        }).render());
    }
});

On rajoute ici deux choses : un détail qui permet de vider la zone de saisie après validation, mais surtout la partie pour sauvegarder dans la collection et sur le serveur.

var Form = Backbone.View.extend({
    tagName: 'form', //balise <form>
    template: _.template('<input name="titre" type="text" placeholder="Saissez le titre de la tâche"></input>'),
    initialize: function () {
    },
    render: function () {
        $(this.el).html(this.template()); //Affiche le template de l'input
        return $(this.el);//Obligatoire sinon vue non mise à jour!
    },
    events: {
        'submit': 'envoi' //Evènement par défaut pour détecter la touche entrée, et la méthode à appeler le cas échéant
    },
    envoi: function (e) {
        e.preventDefault(); //Méthode jQuery qui empêche l'appel direct de cette méthode (en fait l'action n'est pas exécutée)
        //Pour chaque ajout, on envoie tout au model
        var model = new Tache();
        _.each($(e.target).serializeArray(), function (value) {
            if (value.value !== '') {
                model.set(value.name, value.value); //Création d'une nouvelle tâche
            }
        });
        $(this.el).html(this.template()); //On réaffiche la vue pour vider le formulaire
        this.model.add(model); //puis on appelle la méthode "add", spécifiée dans la liste (List) déclarée précédemment
        model.save(null, {
            success: function (model, response) {
                console.log('success');
                console.log(model);
                console.log(response);
            },
            error: function (model, response) {
                console.log('error');
            },
            wait: true
        });
    }
});

Afin de pouvoir travailler proprement, nous allons créer une nouvelle vue : refresh. Cela nous permettra de réactualiser la vue de la liste, via un bouton Refresh

var Refresh = Backbone.View.extend({
    template: _.template('Refresh'),
    initialize: function () {
    },
    render: function () {
        $(this.el).html(
                this.template());
        return $(this.el);
    },
    events: {
        'click': 'click'
    },
    click: function () {
        this.model.fetch();
    }
});

Maintenant, nous allons travailler sur des changements plus conséquents : l'Objet.

Nous ajoutons à son template une représentation de son champ statut, pour pouvoir cliquer dessus quand la tâche est terminée. Ensuite dans le constructeur, nous définissons la méthode destroy() qui sert à supprimer un objet. Nous rajoutons un évènement : le changement de statut. Enfin, il faut modifier la méthode de suppression qui remplace destroy() : laSuppression() et implémenter la nouvelle : changementStatut().

var Objet = Backbone.View.extend({
    tagName: 'li',
    template: _.template('<%= Tache.titre %> <span id="Statut">[<%= Tache.statut %>]</span><span id="Effacer">[Supprimer]</span>'),
    initialize: function () {
        this.model.bind('destroy', this.remove, this); //On modifie les méthodes de destruction et de changement
    },
    render: function () { //Méthode qui met en forme le message dans le template et le retourne pour affichage
        $(this.el).html(this.template({
            Tache: this.model.toJSON()
        }));
        return $(this.el);
    },
    events: {//Gestion des évènements particuliers
        'click #Effacer': 'laSuppression',
        'click #Statut': 'changementStatut'
    },
    laSuppression: function () { //Fonction pour gérer la suppression
        var self = this;
        this.model.destroy({//On appelle la méthode de destruction, avec, en cas de succès, mise à jour de l'affichage
            success: function () {
                self.remove();
            }
        });
    },
    changementStatut: function () { //Gestion du changement de statut
        var self = this;
        if (this.model.get('statut') === "En cours") {
            this.model.set("statut", "terminée");
            this.model.save(null, {
                success: function () {
                    console.log('success');
                    self.$('#Statut').html("[Terminée]");
                },
                error: function (model, response) {
                    console.log('error');
                },
                wait: true
            });
        } else {
            console.log("Tache déja finie");
        }
    }
});

Enfin, il faut modifier notre conteneur HTML : le tableau des tâches. Il doit instancier une vue Refresh, pour l'afficher un peu plus loin, mais également remplir la collection depuis le serveur.

var TableauDesTaches = Backbone.View.extend({
    // Le constructeur (à la création de la vue) :
    initialize: function () {
        this.views = {};
        //Cette vue comprend à présent les trois objets nécessaires : list, form et refresh
        this.views.list = new List({model: this.model});
        this.views.form = new Form({model: this.model});
        this.views.refresh = new Refresh({model: this.model}); //Vue refresh
        this.model.fetch(); //Remplissage de la collection
    },
    // Et la méthode render
    render: function () {
        $(this.el).append(this.views.refresh.render());
        $(this.el).append(this.views.list.render()); //Demande à la liste de se générer pour affichage
        $(this.el).append(this.views.form.render()); //Idem pour le formulaire et le refresh
        return $(this.el); //Obligatoire sinon vue non mise à jour!
    }
});

Voilà, à présent vous pouvez améliorer visuellement et techniquement votre todo-list maison!

 


Cet article vous a plu? Découvrez d'autres articles


Tweet
comments powered by Disqus