Play Framework - Initiation - Partie 4

Au menu d’aujourd’hui

Nous allons voir comment :

  • permettre à l’utilisateur de s’authentifier ou se déconnecter à partir de la page d’accueil
  • permettre à l’utilisateur authentifié d’ajouter des informations (en ajax avec jQuery et en utilisant GSON de Google)

ATTENTION : la manière de coder utilisée dans ce tuto n’est pas forcément la plus appropriée. L’objectif étant de mettre les mains dans le cambouis de la façon la plus simple possible afin d’apprendre rapidement, ce n’est pas grave (ça c’est moi qui le dit). Gardez à l’esprit, qu’ensuite il va falloir faire travailler vos méninges pour faire plus pro.

PS : encore à propos du code, et tout particulièrment en ce qui concerne le javascript, je vais faire se pâmer certains, mais :

  • je suis en train d’apprendre le js
  • j’utilise les “options” les plus simples (compréhensibles rapidement)
  • commentez, commentez !!! ça fait vivre le tuto et progresser tout le monde
  • commentez “utile” ! (pourquoi, comment, …)

Et surtout bonne lecture

1) Ajout du mécanisme de connexion

Dans le controller Application.java :

Modifier le code de cette manière :

public class Application extends Controller {

    //START : notre modification
    @Before
    static void setConnectedUser(){
         if(Security.isConnected()){
             User user = User.find("byFullname",Security.connected()).first();
             renderArgs.put("user", user);
             renderArgs.put("security",Security.connected());
         }else {
             renderArgs.put("user", new User("","","John Doe"));
         }
     }
    //END

    public static void index() {
        List<Item> allItems = Item.findAll();
        render(allItems);
    }

}

Dans la vue index.html (views/Application/index.html)

Modifier le code de cette manière :

#{extends 'main.html' /}
#{set title:'Home' /}

<!--START : notre modification -->
Bonjour ${user.fullname} / 
<a href="@{Secure.login()}">Login</a> /
<a href="@{Secure.logout()}">Logout</a>
<hr>
<!--END -->

<h1>Liste des liens</h1>
<ul>
    #{list items:allItems, as:'anItem'}
    <li><b>${anItem.label} : </b>${anItem.url} <i>(by ${anItem.author.fullname})</i></li>
    #{/list}
</ul>

Lancez la bête !

Ouvrez l’url http://localhost:9000/

Alt text

Clickez sur Login, authentifiez vous, paf ! on revient sur la page :

Alt text

Vous êtes bien reconnu :) J’ai rarement vu plus facile pour gérer une connexion utilisateur (je n’ai même jamais du voir ça … peut-être avec Grails, il faudra que je vérifie)

2) Donner la possibilité d’ajouter des informations

C’est bien beau d’être authentifié, mais il faut que cela serve un chouilla.
Donc nous allons donner la possibilité aux utilisateurs authentifiés de pouvoir ajouter des liens et des bundles (catégories), et comme on est des Play!Champions, on va se le faire en “ajax”.

Play! arrive avec tout un lot de fonctionnalités, d’apis, de frameworks, dont jQuery, alors autant en profiter.

Nous allons dans un 1er temps créer un service bundles

Créer un controller “JSonBundles”

avec une méthode getList

package controllers;

import models.Bundle;
import play.mvc.Controller;

public class JSonBundles extends Controller {
        public static void getList(){
        renderJSON(Bundle.find(
                "SELECT " +
                "bundle.id, bundle.label " +
                "FROM Bundle bundle").fetch());
    }
}

Modifier routes :

on ajoute la ligne : GET /bundles.json JSonBundles.getList

appelez l’url http://localhost:9000/bundles.json et vous obtenez dans votre navigateur la sortie suivante :

[[1,"JavaScript"],[2,"HTML5"],[3,"Java"],[4,"MacOSX"],[5,"Mobiles"],[6,"Design"]]

Retourner dans la vue index.html (views/Application/index.html)

Alors nous allons faire un peu de jQuery.

  • Nous allons construire l’IHM de saisie tout en jQuery (on pourrait faire du HTML, mais on s’entraine)
  • le code n’est pas forcément élégant ni optimisé, mais c’est pour comprendre (ps: j’utilise l’attribut id, je sais "cay mal", @mklabs, si tu me lis …)
  • l’IHM ne doit s’afficher que si on est authentifié
  • l’IHM sera composé de : <!-- détailler ici -->
  • nous allons “peupler” notre combobox avec la liste des bundles
  • une fois que cela fonctionne nous verrons comment enregistrer nos liens en base
Ajout dans le code comme ci-dessous :

Remarque : il est inutile de faire une référence à jQuery dans notre page index.html car elle hérite de main.html (#{extends 'main.html' /}) où la référence est déjà faite.

#{extends 'main.html' /}
#{set title:'Home' /}

Bonjour ${user.fullname} / <!--/ ${security} -->
<a href="@{Secure.login()}">Login</a> /
<a href="@{Secure.logout()}">Logout</a>
<hr>

<h1>Liste des liens</h1>
<ul>
    #{list items:allItems, as:'anItem'}
    <li><b>${anItem.label} : </b>${anItem.url} <i>(by ${anItem.author.fullname})</i></li>
    #{/list}
</ul>

<!-- c'est à partir d'ici que j'ajoute mon code -->

#{if security != null}
    <!--Script chargé uniquement si utilisateur authentifié -->
    <script type="text/javascript">

        /*
            je rajoute un div qui contiendra mes zones de saisie
            oui, j'aurais pu faire un formulaire
         */
        $('<div></div>').attr('id','inputthings').appendTo('body');

        /*
            j'ajoute une zone de texte
            dans mon div inputthings
         */
        $('<input type="text"/>')
            .attr('id','label')
            .attr('placeholder','saisir titre').width(360)
            .appendTo('#inputthings');

        $('<BR>').appendTo('#inputthings'); /* je saute une ligne */

        /*
            j'ajoute une 2ème zone de texte
            dans mon div inputthings
         */
        $('<input type="text"/>')
            .attr('id','url')
            .attr('placeholder','saisir lien').width(360)
            .appendTo('#inputthings');

        $('<BR>').appendTo('#inputthings'); /* je saute une ligne */

        /*
            j'ajoute une zone de texte de type textarea
            dans mon div inputthings
         */
        $('<textarea/>')
            .attr('id','details')
            .attr('placeholder','saisir détails').width(360).height(50)
            .appendTo('#inputthings');

        $('<BR>').appendTo('#inputthings'); /* je saute une ligne */

        /*
            j'ajoute une zone de liste
            pour le choix du bundle
            dans mon div inputthings
         */
        $('<select><option value="default">Choisir un Bundle</option></select>')
                .attr('id','bundles')
                .appendTo('#inputthings');

        /*
            j'ajoute un bouton
            qui me servira à publier les informations saisies
            dans mon div inputthings
         */
        $('<input type="submit"/>')
        .attr('id','addLink')
        .attr('value','Ajouter ...')
        .click(
            function(){
                /* création d'un objet lien qui récupère les infos saisies */
                var monlien = {
                    label:$('#label').attr('value'),
                    url:$('#url').attr('value'),
                    details:$('#details').attr('value'),
                    bundle:{
                        id:$('#bundles').attr('value'),
                        label:$('#bundles option:selected').text()
                    }
                }
                /* affichage de l'objet dans la console pour vérification*/
                console.log(monlien);
            }
        )
        .appendTo('#inputthings');

        /*--------------------------------------*/
        /* C'EST ICI QUE CA DEVIENT INTERESSANT */
        /*--------------------------------------*/

        /*
            je fais une requête ajax qui appelle mon service bundles.json
            avec les données obtenues, je "peuple" ma liste de choix
         */
        $.ajax({ cache: false,
            type: "GET",
            url: "/bundles.json",
            dataType: "json",
            error: function () {
                alert("oups, y'a un pb !");
            },
            success: function (data) {
                var maliste = $('#bundles')[0];
                $.each(data,function(){
                    var option = new Option(this[1], this[0]);
                    maliste.add(option);
                });
            }
        });

    </script>
#{/if}
  • dans le navigateur : http://localhost:9000/
  • s’authentifier : magique : le formulaire de saisie apparaît
  • saisir des infos (on s’aperçoit que la zone de liste contient bien nos bundles), clicker sur le bouton
  • afficher la console du navigateur (j’avais oublié, il vaut mieux être sous safari ou chrome, désolé pour les autres, en même temps vous pouvez remplacer console.log par alert)
  • normalement vous avez ceci :

Alt text

Trop bien, allez on continue.

Ajouter les données en base

Nous allons donc modifier le controller Items (pas JSonItems) car il est sécurisé (@With(Secure.class)) et que seuls les utilisateurs authentifiés peuvent faire des ajouts en base.

a) 1er ajout de code dans Items.java
public static void addItem(String item){
   renderJSON(item);
}

en gros, quand on appellera notre service, cela nous retournera ce que l’on a passé en paramètre (ça sert à rien, mais c’est pour vérifier)

b) ajout d’une route dans routes :

Ajouter ceci :

POST /items.auth.json.add Items.addItem
c) modification du code js de index.html :

Dans le code du click du bouton, modifier le code de la façon suivante :

$('<input type="submit"/>')
.attr('id','addLink')
.attr('value','Ajouter ...')
.click(
    function(){
        /* création d'un objet lien qui récupère les infos saisies */
        var monlien = {
            label:$('#label').attr('value'),
            url:$('#url').attr('value'),
            details:$('#details').attr('value'),
            bundle:{
                id:$('#bundles').attr('value'),
                label:$('#bundles option:selected').text()
            }
        }
        /* affichage de l'objet dans la console pour vérification*/
        console.log(monlien);

        /*---------------------*/
        /* NOTRE AJOUT EST ICI */
        /*---------------------*/
        /*
            1-on fait une requête de type POST
            2-on appelle notre service
            3-on lui passe notre objet monlien de façon "jsonisée"
            4-si tout va bien, on voit les données retournées 
              par le serveur dans la console
        */
        $.ajax({ cache: false,
            type: "POST",
            url: "/items.auth.json.add",
            data:{item:JSON.stringify(monlien)},
            error: function () {
                alert("oups, y'a un pb !");
            },
            success: function (dataFromServer) {
                /* affichage des données retournées */
                console.log(dataFromServer);
            }
        });
    }
)
.appendTo('#inputthings');
  • dans le navigateur : http://localhost:9000/
  • s’authentifier : magique : le formulaire de saisie apparaît
  • saisir des infos , clicker sur le bouton
  • afficher la console du navigateur
  • normalement vous avez ceci :

Alt text

Les données ont bien été envoyées au serveur, qui nous a bien répondu. Il nous reste donc à enregistrer tout ça en base.

d) modification du controller Items pour persister les données en base :

Nous allons utiliser GSON de Google (embarqué avec Play!) pour retransformer notre string en un objet Item :

public static void addItem(String item){

    JsonObject o = new JsonObject();
    /* penser à : 
         - import com.google.gson.JsonObject;
         - import com.google.gson.Gson;
    */
    Gson gson = new Gson();

    Item anItem = new Item();

    anItem = gson.fromJson(item,Item.class);
    anItem.author = User.find("byFullname",Security.connected()).first();
    anItem.save();

    renderJSON(anItem);

}
  • dans le navigateur : http://localhost:9000/
  • saisir des infos , clicker sur le bouton
  • afficher la console du navigateur
  • normalement vous avez ceci :

Alt text

Oups! I did it again, mot de passe est en clair, mais on peut voir que cela fonctionne

  • recharger la page

Alt text

Notre nouvelle saisie apparaît !

e) une dernière retouche

Dans le controller Items supprimer renderJSON(anItem);

Dans index.html, dans le code qui fait la requête ajax lors du click du bouton, on va faire quelque chose de bien “dirty” : nous ajoutons le code window.location.reload(); pour forcer le rechargement de la page (maintenant vous avez tout ce qu’il faut pour “ajaxifier” l’affichage des liens, un peu à vous de bosser les gars !).

$.ajax({ cache: false,
    type: "POST",
    url: "/items.auth.json.add",
    data:{item:JSON.stringify(monlien)},
    error: function () {
        alert("oups, y'a un pb !");
    },
    success: function (dataFromServer) {
        /* affichage des données retournées */
        //console.log(dataFromServer);
        window.location.reload();
    }
});
  • dans le navigateur : http://localhost:9000/
  • saisir des infos , clicker sur le bouton
  • afficher la console du navigateur
  • normalement ça fonctionne

PS: si vous envoyez des données vides ça “plante”, pensez à rajouter un peu de code …

Maintenant, vous avez tout ce qu’il faut, faudra coder plus propre et plus élégant que moi.


Warning: INSERT command denied to user 'kgkyojmt001'@'10.0.75.108' for table 'drp_watchdog' query: INSERT INTO drp_watchdog (uid, type, message, variables, severity, link, location, referer, hostname, timestamp) VALUES (0, 'php', '%message in %file on line %line.', 'a:4:{s:6:\"%error\";s:12:\"user warning\";s:8:\"%message\";s:243:\"UPDATE command denied to user &#039;kgkyojmt001&#039;@&#039;10.0.75.108&#039; for table &#039;drp_node_counter&#039;\nquery: UPDATE drp_node_counter SET daycount = daycount + 1, totalcount = totalcount + 1, timestamp = 1369018542 WHERE nid = 75\";s:5:\"%file\";s:60:\"/homez.312/kgkyojmt/www/modules/statistics/statistics.module\";s:5:\"%line\";i:54;}', 3, '', 'http://www.k33g.org/?q=node/75', '', '107.22.127.92', 1369018542) in /homez.312/kgkyojmt/www/includes/database.mysql.inc on line 128

Warning: INSERT command denied to user 'kgkyojmt001'@'10.0.75.108' for table 'drp_watchdog' query: INSERT INTO drp_watchdog (uid, type, message, variables, severity, link, location, referer, hostname, timestamp) VALUES (0, 'php', '%message in %file on line %line.', 'a:4:{s:6:\"%error\";s:12:\"user warning\";s:8:\"%message\";s:391:\"INSERT command denied to user &#039;kgkyojmt001&#039;@&#039;10.0.75.108&#039; for table &#039;drp_accesslog&#039;\nquery: INSERT INTO drp_accesslog (title, path, url, hostname, uid, sid, timer, timestamp) values(&#039;Play Framework - Initiation - Partie 4&#039;, &#039;node/75&#039;, &#039;&#039;, &#039;107.22.127.92&#039;, 0, &#039;b84cf55badd08595a9b0775f63516352&#039;, 1022, 1369018542)\";s:5:\"%file\";s:60:\"/homez.312/kgkyojmt/www/modules/statistics/statistics.module\";s:5:\"%line\";i:64; in /homez.312/kgkyojmt/www/includes/database.mysql.inc on line 128

Warning: INSERT command denied to user 'kgkyojmt001'@'10.0.75.108' for table 'drp_watchdog' query: INSERT INTO drp_watchdog (uid, type, message, variables, severity, link, location, referer, hostname, timestamp) VALUES (0, 'php', '%message in %file on line %line.', 'a:4:{s:6:\"%error\";s:12:\"user warning\";s:8:\"%message\";s:1304:\"UPDATE command denied to user &#039;kgkyojmt001&#039;@&#039;10.0.75.108&#039; for table &#039;drp_sessions&#039;\nquery: UPDATE drp_sessions SET uid = 0, cache = 0, hostname = &#039;107.22.127.92&#039;, session = &#039;messages|a:1:{s:5:\\&quot;error\\&quot;;a:2:{i:0;s:333:\\&quot;user warning: UPDATE command denied to user &amp;#039;kgkyojmt001&amp;#039;@&amp;#039;10.0.75.108&amp;#039; for table &amp;#039;drp_node_counter&amp;#039;\\nquery: UPDATE drp_node_counter SET daycount = daycount + 1, totalcount = totalcount + in /homez.312/kgkyojmt/www/includes/database.mysql.inc on line 128