Express.js, le Play!>Framework du Javascript ?

Pas tout seul, mais en lui ajoutant 2 ou 3 petites choses, on s’en approche.

Introduction

Pour dĂ©montrer ce que j’ai Ă©crit dans mon titre, je vais “rĂ©aliser” une application Ă  l’aide de :

  • Node.js
  • Express.js
  • Backbone.js
  • 
 et d’autres, mais vous verrez ça plus loin

Cette application, permettra (pour cette fois) :

  • de saisir des messages en markdown avec la possibilitĂ© de coloriser les portions de codes prĂ©sentes dans les messages
  • de garder ces messages en mĂ©moire (je vais simuler un systĂšme de persistance en mĂ©moire, mais je rĂ©-initialise tous les 5 messages)

Donc, nous allons :

  • crĂ©er des routes, des vues, des modĂšles, 

  • faire des requĂȘtes de types REST, avec des POST, PUT, GET, DELETE

Si vous arrivez au bout, vous aurez de quoi vous amuser. Et je tenterais d’aller plus loin pour les prochains articles (cf. fin de cet article).

Par avance, dĂ©solĂ©, je ne fait aucune gestion d’erreur, j’utilise les id dans mon code html, etc. 


Bon, on s’y colle. J’ai appelĂ© mon application stykkekode qui veut dire “bout de code” en norvĂ©gien.

Installation cÎté serveur

Pré-requis

Tout d’abord, vous devez installer Nodejs : http://nodejs.org/# download, l’installeur en profite pour installer npm (node package manager), ce qui nous permettra d’installer le reste des composants.

Une fois Nodejs installĂ©, nous allons installer Express.js (http://expressjs.com/), “petit” framework un peu dans le mĂȘme esprit que Play!> qui permet de gĂ©nĂ©rer des applications web sous Nodejs.

Pour installer Express.js, ouvrez un terminal et tapez la commande suivante :

npm install -g express

Ensuite nous allons générer le squelette de notre application :

express stykkekode

Puis installer les dépendances :

cd stykkekode
npm install -d

Puis installer le moteur de template ejs : (express “arrive” avec le moteur jade, mais ejs Ă  l’avantage de permettre l’utilisation de code html, ce qui permet de ne pas ĂȘtre trop perdu)

npm install ejs

Ensuite, je vous conseille d’installer nodemon (https://github.com/remy/nodemon) qui dĂ©marre, arrĂȘte et redĂ©marre automatiquement pour vous, votre application Ă  chaque fois qu’il dĂ©tecte un changement dans le code source (en fait Ă  chaque fois que vous sauvegardez).

Express.js a gĂ©nĂ©rĂ© pour vous tout le squelette et le code de dĂ©part de votre application (vous irez voir par vous mĂȘme l’arborescence gĂ©nĂ©rĂ©e), pour lancer votre application il faudra, dans le rĂ©pertoire de celle ci, tapez la commande nodemon app.js, oĂč app.js et le script principal gĂ©nĂ©rĂ© par Express.js.

WARNING : Dans le reste de l’article, je l’ai renommĂ© server.js (c’est pour une question technique d’hĂ©bergement que je suis en train de tester). Donc vous mĂȘme, n’oubliez pas de renommer app.js en server.js.

VoilĂ , voilĂ , nous pouvons commencer.

1ùre view & 1ùre route 
 1er contrîleur

Aller dans le répertoire views, et créer 2 fichiers index.ejs et layout.ejs

Dans layout.ejs saisissez le code suivant :

<html>
	<head>
		<title>styKKeKode</title>
	</head>
 	<%- body %>
</html>

Dans index.ejs saisissez le code suivant :

<H1>styKKeKode</H1>

<% if (message) { %>
	<h2><%= message %></h2>
<% } %>

si vous allez dans server.js (ou app.js) vous trouverez la ligne suivante :

app.get('/', routes.index);

Qu’est ce que ça fait ? DĂšs que vous appeler votre “domaine” dans l’url (la page principale) c’est la mĂ©thode index de routes qui est appelĂ©e. Et vous trouvez l’implĂ©mentation de cette mĂ©thode dans /routes/index.js, que nous allons tout de suite modifier. Remplacer le code de /routes/index.js par :

/*
 * GET home page.
 */

exports.index = function(req, res){
  res.render('index.ejs', { message : 'soon 
' })
};

On peut considĂ©rer que c’est l’équivalent de nos contrĂŽleurs Java.

Donc Ă  chaque appel de http://localhost:3000/ vous serez redirigĂ© vers la view/vue index.ejs. Et on passe la variable message Ă  la vue index.ejs. Vous notez que je prĂ©cise l’extension de la vue, cela signifie que l’on peut utiliser plusieurs moteurs de template dans la mĂȘme application.

Pour essayer :

  • ouvrez un terminal, positionnez vous dans le rĂ©pertoire de votre application et tapez : nodemon server.js
  • appelez http://localhost:3000/ dans votre navigateur

Nous avons donc rapidement vu les aspects, routes, vue et contrĂŽleur, nous reviendrons plus en dĂ©tail dessus, mais maintenant, allons un peu plus loin dans la construction de notre “stack”.

Installer les librairies javascript


 Cela va nous servir pour plus tard

Dans le répertoire public/javascripts copiez les librairies suivantes :

Dans le répertoire public/stylesheets copiez les css suivantes :

Twitter Bootstrap nous permettra de donner une “bonne tĂȘte” Ă  notre application, sans effort.

Allons ensuite déclarer les librairies javascript dans views/index.ejs :

<H1>styKKeKode</H1>

<% if (message) { %>
	<h2><%= message %></h2>
<% } %>


<!-- js libs client -->
<script src="javascripts/jquery.js"></script>
<script src="javascripts/underscore.js"></script>
<script src="javascripts/backbone.js"></script>
<script src="javascripts/tempo.js"></script>
<script src="javascripts/showdown.js"></script>

Puis les feuilles de styles dans views/layout.ejs :

<html>
	<head>
		<title>styKKeKode</title>
		<link rel="stylesheet" href="stylesheets/bootstrap.css">
		<link rel="stylesheet" href="stylesheets/bootstrap-responsive.css">
    	<link rel="stylesheet" href="stylesheets/default.min.css">
        <style type="text/css">
            body {
                padding-top: 60px;
                padding-bottom: 40px;
            }
            .sidebar-nav {
                padding: 9px 0;
            }
        </style>
	</head>
	 <%- body %>
</html>

Les modĂšles

On ne vas pas s’occuper tout de suite de la persistance, mais nous allons crĂ©er des modĂšles et simuler cette persistance dans un 1er temps.

Créer dans votre répertoire applicatif un répertoire models sans lequel vous allez ajouter un fichier snippet.js qui sera donc notre modÚle, avec le code suivant :

/* SNIPPET MODEL */
var snippetsCounter = 1;

var snippet = function(title, code, user) {
	this.id = null;
	this.title = title ? title : "";
	this.code = code ? code : "";
	this.user = user ? user : "";
}

//static
snippet.list = [];

snippet.prototype.save = function(callBack) {
	if(this.id === undefined || this.id === null || this.id === "") {
	//new snippet to save

		//Je ré-initialise tous les 5 snippets
		if(snippetsCounter > 5) {
			snippet.list = [];
			snippetsCounter = 1;
		}

		this.id = snippetsCounter++;

		snippet.list.push(this)
	} else {//snippet exists => to be updated in list
		var
			that = this,
			tmp = snippet.list.filter(function(record){ return record.id === that.id; })[0];
			if(tmp){
				snippet.list.splice(snippet.list.indexOf(this), 1);
				snippet.list.push(this) ;
			}
	}

	callBack(this);
};

snippet.prototype.delete = function(callBack) {
	var
		that = this,
    	tmp = snippet.list.filter(function(record){ return record.id === that.id; })[0];
    	if(tmp){
    		snippet.list.splice(snippet.list.indexOf(this), 1);
    	}
    callBack(this);
}

//static
snippet.findAll = function(callBack) {
	callBack(snippet.list);
}

//static
snippet.findById = function(id, callBack) {
	var tmp = snippet.list.filter(function(record){ return record.id === id; })[0];
	console.log("snippet.findById", tmp)
	callBack(tmp);
}


/*=== Bootstrap with data ===*/

var snippet_one = new snippet("essai 1","//FOO","k33g_org");
var snippet_tow = new snippet("essai 2","//Hello World","k33g_org");
var snippet_three = new snippet("essai 3","//Me again !","k33g_org");

snippet_one.save(function(m){console.log(m);});
snippet_tow.save(function(m){console.log(m);});
snippet_three.save(function(m){console.log(m);});

exports.snippet = snippet;

Dans routes/index.js, ajoutez la ligne suivante en tout début de fichier (on fait un include) :

var snippet = require('../models/snippet').snippet;

Sauvegardez. Si votre application tourne encore (sinon relancez) vous pourrez voir dans la console la liste des donnĂ©es “bootstrapĂ©es”.

Alt "express01.png"

Maintenant allons Ă©crire quelques routes et contrĂŽleur(s)

Routes et ContrĂŽleurs

Routes

Préparons le travail pour Backbone.

Allez dans server.js (ou app.js) et copiez les routes suivantes (juste aprĂšs app.get('/', routes.index);) :

app.post("/snippet", routes.createSnippet);
app.put("/snippet", routes.updateSnippet);
app.get("/snippet", routes.getSnippet);
app.del("/snippet", routes.deleteSnippet);

app.get("/snippets", routes.allSnippets);

ContrĂŽleurs

Allez dans routes/index.js et modifiez le code comme ceci :

var snippet = require('../models/snippet').snippet;

/*
 * GET home page.
 */

exports.index = function(req, res){
  res.render('index.ejs', { message : 'soon 
' });
};


exports.createSnippet = function(req, res) {
	console.log("CREATE SNIPPET");

	var model_from_client = JSON.parse(req.param("model", null));
	console.log(model_from_client);

	var server_model = new snippet(model_from_client.title, model_from_client.code, model_from_client.user);

	server_model.save(function(m){
		console.log(m);
		res.json(m);
	});

};

exports.updateSnippet = function(req, res) {
	console.log("UPDATE SNIPPET");

	var model_from_client = JSON.parse(req.param("model", null));
	console.log(model_from_client);

	var server_model = new snippet(model_from_client.title, model_from_client.code, model_from_client.user);

	server_model.id = model_from_client.id;

	server_model.save(function(m){
		console.log(m);
		res.json(m);
	});


};

exports.getSnippet = function(req, res) {
	console.log("GET SNIPPET");

	var model_from_client = JSON.parse(req.param("model", null));
	console.log(model_from_client);

	var server_model = snippet.findById(model_from_client.id, function(m) {
		console.log(m);
		res.json(m);
	});

};

exports.deleteSnippet = function(req, res) {
	console.log("DELETE SNIPPET");
	var model_from_client = JSON.parse(req.param("model", null));
	console.log(model_from_client);

	var server_model = snippet.findById(model_from_client.id, function(model) {
		console.log(model);

		model.delete(function(m){
			res.json(m);
		});
	});


};

exports.allSnippets = function(req, res) {
	console.log("ALL SNIPPETS");

  	snippet.findAll(function(snippets){
		res.json(snippets);
	});
};

Testons :

Allez dans votre navigateur, ouvrez la console, et essayez les commandes suivantes :

# createSnippet

$.ajax({
	type: "POST",
	url: "/snippet",
	data: {"model":JSON.stringify({
		title:"Hello World in Kotlin",
		code : "println('Hello world')",
		user : "@BobMorane"
	})},
	dataType: 'json',
	error: function () {
		console.log("oups");
	},
	success: function (dataFromServer) {
		console.log(dataFromServer);
	}
});

Alt "express02.png"

on peut voir que le serveur nous a affecté un id

# updateSnippet

$.ajax({
	type: "PUT",
	url: "/snippet",
	data: {"model":JSON.stringify({
		id : 4, /*vérifier que l'id existe*/
		title:"Hello World in Kotlin",
		code : "println('Hello world $name')",
		user : "@BOBMORANE"
	})},
	dataType: 'json',
	error: function () {
		console.log("oups");
	},
	success: function (dataFromServer) {
		console.log(dataFromServer);
	}
});

Alt "express03.png"

Le serveur nous a renvoyé notre modÚle modifié

# getSnippet

$.ajax({
	type: "GET",
	url: "/snippet",
	data: {"model":JSON.stringify({id:1})},
	dataType: 'json',
	error: function () {
		console.log("oups");
	},
	success: function (dataFromServer) {
		console.log(dataFromServer);
	}
});

Alt "express04.png"

Le serveur nous a renvoyĂ© le modĂšle ayant l’id 1

# deleteSnippet

$.ajax({
	type: "DELETE",
	url: "/snippet",
	data: {"model":JSON.stringify({id:1})},
	dataType: 'json',
	error: function () {
		console.log("oups");
	},
	success: function (dataFromServer) {
		console.log(dataFromServer);
	}
});

Alt "express05.png"

Et maintenant nous allons appeler la liste de l’ensemble de nos “snippets” pour vĂ©rifier que nos modifications ont bien Ă©tĂ© prises en compte.

# allSnippets

$.ajax({
	type: "GET",
	url: "/snippets",
	data: null,
	dataType: 'json',
	error: function () {
		console.log("oups");
	},
	success: function (dataFromServer) {
		dataFromServer.forEach(function(model){
			console.log(model)
		});
	}
});

Alt "express06.png"

Mise en musique avec BackBone.js

On re-Ă©crit Backbone.sync

Vous devez donc crĂ©er un fichier backbone.sync.js au mĂȘme endroit que backbone.js :

(function() {
    Backbone.sync = function(method, model, options) {

        // sympa pour comprendre ce qu'il se passe
        console.log(method, model, options);

        var methodMap = {
            'create': 'POST',
            'update': 'PUT',
            'delete': 'DELETE',
            'read':   'GET'
        }, dataForServer = null;

        if(model.models) {//c'est une collection
            dataForServer:null
        } else {//c'est un modĂšle

            dataForServer = { model : JSON.stringify(model.toJSON()) };

            console.log(dataForServer);
        }

        return $.ajax({
            type: methodMap[method],
            url: model.url,
            data: dataForServer,
            dataType: 'json',
            error: function (dataFromServer) { //vérifier que cela retourne une erreur
                options.error(dataFromServer);
            },
            success: function (dataFromServer) {
				if(!model.models){
					dataFromServer.id = dataFromServer._id;
					console.log(dataFromServer);
				} else {
					//collection
					dataFromServer.reverse();
				}
                options.success(dataFromServer);
            }
        });
    };
})();

Il faudra penser à ajouter dans ‘index.ejs’ :

<script src="javascripts/backbone.sync.js"></script>

Nous allons donc modifier la vue index.ejs :

HTML

<!-- ma barre de titre -->
<div class="navbar navbar-fixed-top">
	<div class="navbar-inner">
		<div class="container">

			<a class="brand" href="# ">styKKeKode
				<% if (message) { %>
					<%= message %>
				<% } %>
			</a>

        </div>
	</div>
</div>

<div class="container">

	<!-- mon formulaire de saisie -->
   	<div id="snippet-form">
   		<h2>Go ...</h2>
       <form action="/" class="well">
       		<label>Title : </label>
           	<input id="title" type="text" class="span3" placeholder="title"/>
           	<label>Code Snippet : (with markdown) </label>
           	<textarea id="code" placeholder="code" style="width:100%" rows="5"></textarea>
           	<label>User : </label>
           	<input id="user" type="text" placeholder="user"/>
           	<button type="submit" class="btn">Ajouter un Snippet</button>
           	<!--<input type="submit" value="Ajouter un Snippet" />-->
       </form>
   	</div>

	<ul id="snippet-list" style="list-style: none;">
       <li data-template>
       	<h2> by </h2><br>
       	<hr>
       </li>
   	</ul>

</div>

<!-- js libs client -->
<script src="javascripts/jquery.js"></script>
<script src="javascripts/underscore.js"></script>
<script src="javascripts/backbone.js"></script>
<script src="javascripts/backbone.sync.js"></script>
<script src="javascripts/tempo.js"></script>
<script src="javascripts/showdown.js"></script>
<script src="javascripts/highlight.min.js"></script>

Javascript (Backbone & co)

Donc Ă  la suite :

<!-- Application BackBone -->

<script type="text/javascript">
	$(document).ready(function() {

        window.Snippet = Backbone.Model.extend({
            url : 'snippet',
            defaults : {
                id: null,
                title : "",
                code : "",
				user : ""
            }
        });

        window.Snippets = Backbone.Collection.extend({
            model : Snippet,
            url : 'snippets'
        });

		/*=== VIEWS ===*/
		window.SnippetsView = Backbone.View.extend({

	        initialize : function() {
	            this.template = Tempo.prepare('snippet-list');
	        },

	        render : function() {
	            this.template.render(this.collection.toJSON());
	            return this;
	        }

	    });


		window.converter = new Showdown.converter();

		window.SnippetFormView = Backbone.View.extend({
	        el : $('# snippet-form'),

	        initialize : function() {
	            this.form = arguments[0].form;
	        },
	        events : {
	            'submit form' : 'addSnippet'
	        },
	        addSnippet : function(e) {
	            e.preventDefault();
				var that = this;
				var tmpSnippet = new Snippet({
					title : this.$('# title').val(),
					code : converter.makeHtml(this.$('# code').val()),
					user : this.$('# user').val()
				});

				tmpSnippet.save({},{
					success : function() {
						that.collection.fetch({
							success: function() {
								that.form .render();
								$('pre code').each(function(index,e) {hljs.highlightBlock(e, '    ')});
							}
						})
					}
				});
				//on vide le form
	            this.$('input[type="text"]').val('');
	            this.$('textarea').val('');
	        },
	        error : function(model, error) {
	            console.log(model, error);
	            return this;
	        }

	    });

		/*=== ROUTER ===*/

	    window.SnippetsRouter = Backbone.Router.extend({

	        initialize : function() {
	            /* 1- Création d'une collection */
	            window.snippets = new Snippets();
				this.collection = snippets;
				var that = this;
	            /* 2- Chargement de la collection */
	            snippets.fetch({
					success:function() {
						/* 3- Création des vues + affichage */
						window.snippetsView = new SnippetsView({ collection : snippets });
						window.snippetForm = new SnippetFormView({ collection : snippets, form : snippetsView });
						snippetsView.render();
						/*4- un peu de couleur */
						$('pre code').each(function(index,e) {hljs.highlightBlock(e, '    ')});

					},
					error:function(){

					}
				});

	        },
			routes : {}

	    });

        /*--- initialisation du router ---*/

        router = new SnippetsRouter();

    });
</script>

Remarques :

  • window.converter = new Showdown.converter(); et converter.makeHtml(this.$('# code').val()) servent Ă  transformer le code markdown saisi en code html
  • $('pre code').each(function(index,e) {hljs.highlightBlock(e, ' ')}); sert Ă  “coloriser” les parties “code source”

Allez on teste :

  • VĂ©rifiez que votre application est lancĂ©e
  • Ouvrez l’url localhost:3000/ dans votre navigateur

Alt "express07.png"

On voit que l’on a encore nos donnĂ©es “bootstrapĂ©es”.

Saisissons des données au format markdown :

Alt "express08.png"

On ajoute et on obtient :

Alt "express09.png"

Vous pouvez allez vérifier dans un autre navigateur que vos données sont bien là.

That’s all 


Pour aujourd’hui, mais la prochaine fois, nous verrons comment “socialiser” notre application : seules les personnes authentifiĂ©es avec Twitter, pourront saisir des snippets. Nous mettrons quelques contrĂŽles de validation. Et enfin (pas forcĂ©ment dans le mĂȘme article), nous verrons comment ajouter une vĂ©ritable persistance avec une base NOSQL (je n’ai pas encore fait mon choix, mais j’ai un gros penchant pour CouchDB).

Si vous voulez voir tourner l’application “en vrai”, je l’ai hĂ©bergĂ©e ici : http://stykkekode.cloudno.de/.

@+ & Bon code.

blog comments powered by Disqus

Related posts