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 :
underscore.js
, nécessaire pour faire tournerbackbone.js
: http://documentcloud.github.com/underscore/- jquery.js : http://docs.jquery.com/Downloading_jQuery
backbone.js
: http://documentcloud.github.com/backbone/tempo.js
: moteur de template cÎté client, simple, mais puissant (+ que mustache) : http://tempojs.com/showdown.js
: qui permet de transformer des chaĂźnes au format markdown en code html https://github.com/coreyti/showdown/tree/master/srchighlight.min.js
: librairie magique qui permet de âcoloriserâ le code dans les pages html http://softwaremaniacs.org/soft/highlight/en/
Dans le répertoire public/stylesheets
copiez les css suivantes :
bootstrap.css
(http://twitter.github.com/bootstrap/)- ainsi que
bootstrap-responsive.css
default.min.css
la feuille de style qui âvientâ avechighlight.min.js
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â.
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);
}
});
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);
}
});
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);
}
});
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);
}
});
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)
});
}
});
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();
etconverter.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
On voit que lâon a encore nos donnĂ©es âbootstrapĂ©esâ.
Saisissons des données au format markdown :
On ajoute et on obtient :
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.
Tweet