Partie Controller et Model

NodeAtlas ne se contente pas que de faciliter la génération de page web en fonction de variable dans les fichiers de variation. NodeAtlas vous permet également d'intéragir avec le contenu des fichiers variations ou avec le DOM généré en fonction ;

Cycle de vie et Hooks

Pour cela, il vous est possible d'intéragir à divers endroit (Hooks) du cycle de vie de création d'une page grâce à un contrôleur commun (commonController) et à un controlleur spécifique à chaque page (routes[<route>].controller).

Voici à quoi peut ressembler un webconfig.json permettant d'atteindre tous les points du cycle de vie d'une page.

{
    "controllersRelativePath": "controllers",
    "commonController": "common.js",
    "routes": {
        "/": {
            "template": "index.htm",
            "variation": "index.json",
            "controller": "index.json"
        }
    }
}

Note : Si controllersRelativePath n'est pas présent dans « webconfig.json », par défaut le dossier des controlleurs est bien controllers. controllersRelativePath est donc utile seulement pour changer le nom/chemin du répertoire.

et voici le détail des endroits ou vous pouvez intervenir :

Démarrage de NodeAtlas

Initialisation des modules

Initialisation des Sessions

Initialisation de la configuration du serveur

Initialisation des routes

Lancement du serveur web

Requête/Réponse HTTP de NodeAtlas

Traitement de la Request du Client

Assemblage des Templates et Compilation des Variations => DOM complet de la Réponse.

Envoi de la Response au Client

changeVariation

Pour intercepter les variations, vous pouvez soit utiliser le contrôleur commun pour tout le site et/ou également le contrôleur par page.

Voici un exemple utilisant les deux interceptions, d'abord la commune au deux pages, puis celle de chaque page :

{
    "commonController": "common.js",
    "commonVariation": "common.json",
    "routes": {
        "/": {
            "template": "index.htm",
            "variation": "index.json",
            "controller": "index.js"
        }
    }
}

avec cet ensemble de fichier :

├─ components/
│  ├─ head.htm
│  └─ foot.htm
├─ variations/
│  ├─ common.json
│  └─ index.json
├─ controllers/
│  ├─ common.js
│  └─ index.js
├─ templates/
│  └─ index.htm
└─ webconfig.json

En demandant la page http://localhost/?title=Haeresis en POST avec une variable example=Ceci+est+un+test dans le corp de requête, les fichiers suivants (entre autre) seront utilisés :

variations/common.json

{
    "titleWebsite": "Titre du site"
}

variations/index.json

{
    "titlePage": "Bienvenue",
    "content": "<p>C'est la page d'accueil.</p>"
}

templates/index.htm

    <%- include('head.htm') %>

    <div class="title"><%- common.titleWebsite %></div>

    <div>
        <h1><%- specific.titlePage %></h1>
        <%- specific.content %>
    </div>

    <%- include('foot.htm') %>

controllers/common.js

// On intervient avant que les variables soient injectées dans le système de template.
// Ce code sera exécuté pour toute request HTTP, toute page confondue.
exports.changeVariation = function (params, next) {
    var variation = params.variation,
        request = params.request,
        response = params.response;

    // Ici on modifie les variables de variations.

    console.log(variation.common.titleWebsite); // "Titre du site"
    console.log(variation.specific.titlePage); // "Bienvenue"
    console.log(variation.specific.content); // "C'est la page d'accueil."

    if (request.query["title"]) {
        variation.specific.titlePage = variation.specific.titlePage + " " + request.query.title;
    }
    if (request.body["example"]) {
        variation.specific.content = request.body.example;
    }

    console.log(variation.common.titleWebsite); // "Titre du site"
    console.log(variation.specific.titlePage); // "Bienvenue Haeresis"
    console.log(variation.specific.content); // "Ceci est un test"

    // On ré-injecte les modifications.
    next(variation);
};

controllers/index.js

// On intervient avant que les variables soient injectées dans le système de template.
// Ce code sera exécuté uniquement lors de la demande de la page « / ».
exports.changeVariation = function (params, next) {
    var variation = params.variation,
        request = params.request,
        response = params.response;

    // Ici on modifie les variables de variations.

    console.log(variation.common.titleWebsite); // "Titre du site"
    console.log(variation.specific.titlePage); // "Bienvenue Haeresis"
    console.log(variation.specific.content); // "Ceci est un test"

    variation.common.titleWebsite = "C'est l'accueil, c'est tout.";
    variation.specific.content = "C'est l'accueil, c'est tout.";

    console.log(variation.common.titleWebsite); // "C'est l'accueil, c'est tout."
    console.log(variation.specific.titlePage); // "Bienvenue Haeresis"
    console.log(variation.specific.content); // "C'est l'accueil, c'est tout."

    // On ré-injecte les modifications.
    next(variation);
};

ce qui produit la sortie suivante :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>C'est l'accueil, c'est tout.</title>
    </head>
    <body>
        <div class="title">C'est l'accueil, c'est tout.</div>
        <div>
            <h1>Bienvenue Haeresis</h1>
            C'est l'accueil, c'est tout.
        </div>
    </body>
</html>

Si vous décidez de désabonner la variation spécifique avec le webconfig suivant :

{
    "commonController": "common.js",
    "commonVariation": "common.json",
    "routes": {
        "/": {
            "template": "index.htm",
            "variation": "index.json"
        }
    }
}

alors la sortie sera :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>Titre du site</title>
    </head>
    <body>
        <div class="title">Titre du site</div>
        <div>
            <h1>Bienvenue Haeresis</h1>
            Ceci est un test
        </div>
    </body>
</html>

changeDom

Pour intercepter le DOM avant qu'il ne soit renvoyé, vous pouvez soit utiliser le contrôleur commun pour tout le site et/ou également le contrôleur par page.

Voici un exemple utilisant les deux interceptions, d'abord la commune au deux pages, puis celle de chaque page :

{
    "commonController": "common.js",
    "commonVariation": "common.json",
    "routes": {
        "/": {
            "template": "index.htm",
            "variation": "index.json",
            "controller": "index.js"
        }
    }
}

avec cet ensemble de fichier :

├─ variations/
│  └─ index.json
├─ controllers/
│  ├─ common.js
│  └─ index.js
├─ templates/
│  └─ index.htm
└─ webconfig.json

En demandant la page http://localhost/ les fichiers suivants (entre autre) seront utilisés :

variations/common.json

{
    "titleWebsite": "Titre du site"
}

variations/index.json

{
    "titlePage": "Bienvenue",
    "content": "<p>C'est la page d'accueil.</p>"
}

templates/index.htm

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title><%- common.titleWebsite %></title>
    </head>
    <body>
        <div class="title"><%- common.titleWebsite %></div>
        <div>
            <h1><%- specific.titlePage %></h1>
            <%- specific.content %>
        </div>
    </body>
</html>

controllers/common.js

// On intervient avant que le DOM ne soit renvoyé au Client.
// Ce code sera exécuté pour toute request HTTP, toute page confondue.
exports.changeDom = function (params, next) {
    var NA = this,
        dom = params.dom,
        request = params.request,
        response = params.response,
        cheerio = NA.modules.cheerio, // Récupération de jsdom pour parcourir le DOM avec jQuery.
        $ = cheerio.load(dom, { decodeEntities: false }); // On charge les données pour les manipuler comme un DOM.

    // Après tous les h1 de la sortie HTML « dom »,
    $("h1").each(function () {
        var $this = $(this);

        // ...on créé une div,
        $this.after(
            // ... on injecte le contenu du h1 dans la div,
            $("<div>").html($this.html())
        );
        // ...et supprime le h1.
        $this.remove();
    });

    // On recrée une nouvelle sortie HTML avec nos modifications.
    dom = $.html();

    // On réinjecte les modifications.
    next(dom);
};

controllers/index.js

// On intervient avant que le DOM ne soit renvoyé au Client.
// Ce code sera exécuté uniquement lors de la demande de la page « / ».
exports.changeDom = function (params, next) {
    var NA = this,
        dom = params.dom,
        request = params.request,
        response = params.response,
        cheerio = NA.modules.cheerio, // Récupération de jsdom pour parcourir le DOM avec jQuery.
        $ = cheerio.load(dom, { decodeEntities: false }); // On charge les données pour les manipuler comme un DOM.

    // On modifie tous les contenu des noeuds avec la classe `.title`.
    $(".title").text("Modification de Contenu");

    // On recrée une nouvelle sortie HTML avec nos modifications.
    dom = $.html();

    // On réinjecte les modifications.
    next(dom);
};

ce qui produit la sortie suivante :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8">
        <title>Titre du site</title>
    </head>
    <body>
        <div class="title">Modification de Contenu</div>
        <div>
            <div>Bienvenue</div>
            <p>C'est la page d'accueil.</p>
        </div>
    </body>
</html>

loadModules

Pour charger d'autres modules qui ne sont pas fournis avec NodeAtlas vous pouvez utiliser le contrôleur commun pour tout le site afin de les charger une seule fois et de les rendres disponible dans tous vos controlleurs.

Voici un exemple utilisant un module externe à NodeAtlas :

{
    "commonController": "common.js",
    "routes": {
        "/": {
            "template": "index.htm",
            "controller": "index.js"
        }
    }
}

avec cet ensemble de fichier :

├─ controllers/
│  ├─ common.js
│  └─ index.js
├─ templates/
│  └─ index.htm
└─ webconfig.json

En demandant la page http://localhost/ les fichiers suivants (entre autre) seront utilisés :

templates/index.htm

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>Test Module</title>
    </head>
    <body>
        <div class="title">Test Module</div>
        <div>
            <h1>Test Module</h1>
            <%- example %>
        </div>
    </body>
</html>

controllers/common.js

// On intervient avant que la phase de chargement des modules ne soit achevée.
// Ce code sera exécuté au lancement de NodeAtlas.
exports.loadModules = function () {
    // Récupérer l'instance « NodeAtlas » du moteur.
    var NA = this;

    // Associations de chaque module pour y avoir accès partout.
    NA.modules.marked = require('marked');
};

controllers/index.js

// On intervient avant que les variables soient injectées dans le système de template.
// Ce code sera exécuté uniquement lors de la demande de la page « / ».
exports.changeVariation = function (params, next) {
    // Récupérer l'instance « NodeAtlas » du moteur.
    var NA = this,
        variation = params.variation,
        marked = NA.modules.marked;

    variation.example = marked("I am using __markdown__.");

    // On ré-injecte les modifications.
    next(variation);
};

ce qui produit la sortie suivante :

<!DOCTYPE html>
<html lang="fr-fr">
    <head>
        <meta charset="utf-8" />
        <title>Test Module</title>
    </head>
    <body>
        <div class="title">Test Module</div>
        <div>
            <h1>Test Module</h1>
            <p>I am using <strong>markdown</strong>.</p>
        </div>
    </body>
</html>

setConfigurations

Pour configurer le serveur web de NodeAtlas (ExpressJs) vous pouvez utiliser le contrôleur commun pour tout le site afin de les charger une seule fois et de les rendres disponible dans tous vos controlleurs.

Voici un exemple utilisant un middleware pour ExpressJs :

{
    "commonController": "common.js",
    "routes": {
        "/": {
            "template": "index.htm",
            "controller": "index.js"
        }
    }
}

avec cet ensemble de fichier :

├─ controllers/
│  └─ common.js
├─ templates/
│  └─ index.htm
└─ webconfig.json

En demandant la page http://localhost/ les fichiers suivants (entre autre) seront utilisés :

templates/index.htm

<%- content %>

controllers/common.js

// On intervient au niveau du serveur avant que celui-ci ne soit démarré.
// Ce code sera exécuté au lancement de NodeAtlas.
exports.setConfigurations = function (next) {
    // Récupérer l'instance « NodeAtlas » du moteur.
    var NA = this;

    // Middleware utilisé lors de chaque requête.
    NA.httpServer.use(function (request, response, next) {
        response.setHeader("X-Frame-Options", "ALLOW-FROM http://www.lesieur.name/");
        next();
    });

    // On ré-injecte les modifications.
    next();
};

controllers/index.js

// On intervient avant que les variables soient injectées dans le système de template.
// Ce code sera exécuté uniquement lors de la demande de la page « / ».
exports.changeVariation = function (params, next) {
    var variation = params.variation;

    // On prépare le fichier pour un affichage JSON.
    variation.currentRouteParameters.headers = {
        "Content-Type": "application/json; charset=utf-8"
    };
    variation.content = JSON.stringify(variation, null, "    ");

    // On ré-injecte les modifications.
    next(variation);
};

ce qui produit la sortie suivante :

{
    "urlBasePathSlice": "http://localhost",
    "urlBasePath": "http://localhost/",
    "urlPath": "http://localhost/",
    "pathname": /* ... */,
    "filename": /* ... */,
    "params": {},
    "currentRouteParameters": { /* ... */ },
    "currentRoute": "/",
    "webconfig": { /* ... */ }
}

setSessions

Pour configurer les sessions client-serveur de NodeAtlas vous pouvez utiliser le contrôleur commun pour tout le site afin de les charger une seule fois et de les rendres disponible dans tous vos controlleurs, voici un exemple de management de Session avec Redis.

Voici l'ensemble de fichier suivant :

├─ controllers/
│  └─ common.js
├─ templates/
│  └─ index.htm
├─ variations/
│  ├─ common.json
│  └─ index.json
└─ webconfig.json

Avec le webconfig.json :

{
    "commonController": "common.js",
    "commonVariation": "common.json",
    "routes": {
        "/": {
            "template": "index.htm",
            "variation": "index.json"
        }
    }
}

et avec le fichier « common.js » contenant par exemple :

// On intervient avant que la phase de chargement des modules ne soit achevée.
// Ce code sera exécuté au lancement de NodeAtlas.
exports.loadModules = function () {
    // Récupérer l'instance « NodeAtlas » du moteur.
    var NA = this;

    // Associations de chaque module pour y avoir accès partout.
    NA.modules.RedisStore = require('connect-redis');
};

// On intervient au niveau du serveur pendant la configuration des Sessions.
// Ce code sera exécuté au lancement de NodeAtlas.
exports.setSessions = function (next) {
    var NA = this,
        session = NA.modules.session,
        RedisStore = NA.modules.RedisStore(session);

    // On remplace la session par default.
    NA.sessionStore = new RedisStore();

    // On redonne la main à NodeAtlas pour la suite.
    next();
};

setRoutes

Pour configurer les routes de NodeAtlas dynamiquement vous pouvez utiliser le contrôleur commun pour tout le site afin de les charger une seule fois et de les rendres disponible dans tous vos controlleurs.

Voici l'ensemble de fichier suivant :

├─ controllers/
│  └─ common.js
├─ templates/
│  ├─ content.htm
│  └─ index.htm
├─ variations/
│  └─ common.json
└─ webconfig.json

Avec le webconfig.json :

{
    "commonController": "common.js",
    "commonVariation": "common.json",
    "routes": {
        "/index.html": {
            "template": "index.htm"
        }
    }
}

et avec le fichier « common.js » contenant par exemple :

// On intervient au niveau des routes pendant qu'elles sont ajoutées.
// Ce code sera exécuté au lancement de NodeAtlas.
exports.setConfigurations = function (next) {

    // On récupère l'instance de NodeAtlas en cours.
    var NA = this,

        // Et nous récupérons les routes en provenance du webconfig...
        route = NA.webconfig.routes;

    // ...pour ajouter la route "/content.html" à la liste de nos routes.
    route["/content.html"] = {
        "template": "content.htm"
    };

    // On redonne la main à NodeAtlas pour la suite.
    next(); 
};

Utiliser les Websocket à la place des échanges AJAX

Afin de conserver une liaison ouverte entre la partie Frontale et la partie Serveur de vos applications, NodeAtlas est à même d'utiliser Socket.IO dont vous trouverez plus de détail sur le site officiel.

Grâce à cela, vous pourrez changer des informations en temps réel sur votre page, mais également sur toutes les autres page ouvertes à travers tous les autres navigateurs.

Avec l'ensemble de fichier suivant :

├─ assets/
│  └─ javascript/
│     ├─ common.js
│     └─ index.js
├─ components/
│  ├─ foot.htm
│  ├─ head.htm
│  └─ index.htm
├─ controllers/
│  ├─ common.js
│  └─ index.js
├─ variations/
│  ├─ common.json
│  └─ index.json
├─ templates/
│  └─ index.htm
└─ webconfig.json

Contenant le webconfig.json suivant :

{
    "commonController": "common.js",
    "commonVariation": "common.json",
    "routes": {
        "/": {
            "template": "index.htm",
            "variation": "index.json",
            "controller": "index.js"
        }
    }
}

et contenant les fichiers de template suivant :

components/head.htm

<!DOCTYPE html>
<html lang="<%= languageCode %>">
    <head>
        <meta charset="utf-8" />
        <title><%- common.titleWebsite %></title>
    </head>
    <body data-hostname="<%= webconfig.urlWithoutFileName %>" data-subpath="<%= webconfig.urlRelativeSubPath.slice(1) %>" data-variation="<%= currentRouteParameters.variation.replace(/\.json/,'') %>">

Note : data-hostname et data-subpath va nous aider à paramètrer Socket.io côté Front.

components/foot.htm

        <script type="text/javascript" src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
        <script src="javascript/common.js"></script>
    </body>
</html>

Note : Le fichier frontal de Socket.IO s'injecte ici en global.

components/index.htm

        <div class="title"><%- common.titleWebsite %></div>
        <div>
            <h1><%- specific.titlePage %></h1>
            <%- specific.content %>
            <div><%- new Date() %></div>
        </div>
        <button>Update</button>

Note : Chaque clique sur button raffraichira le contenu de components/index.htm.

templates/index.htm

    <%- include('head.htm') %>
    <div class="layout">
    <%- include('index.htm') %>
    </div>
    <script src="javascript/index.js"></script>
    <%- include('foot.htm') %>

Note : On construit ici la page d'accueil /.

ainsi que les fichiers de variations suivant :

variations/common.json

{
    "titleWebsite": "Socket.IO Exemple"
}

variations/index.json

{
    "titlePage": "Date",
    "content": "<p>La date actuelle est :</p>"
}

Jusque là, rien d'inhabituel et tout fonctionnerait sans partie contrôleur. Mais nous allons mettre en place la communication via Socket.IO côté Serveur puis côté Client.

Côté serveur, nous utiliserons les fichiers suivant :

controllers/common.js

var privates = {};

// Chargement des modules pour ce site dans l'objet NodeAtlas.
exports.loadModules = function () {
    // Récupérer l'instance « NodeAtlas » du moteur.
    var NA = this;

    // Associations de chaque module pour y avoir accès partout.
    NA.modules.socketio = require('socket.io');
    NA.modules.cookie = require('cookie');
};

// Exemple d'utilisation de Socket.IO.
privates.socketIoInitialisation = function (socketio, NA, next) {
    var optionIo = (NA.webconfig.urlRelativeSubPath) ? { path: NA.webconfig.urlRelativeSubPath + '/socket.io', secure: ((NA.webconfig.httpSecure) ? true : false) } : undefined,
        io = socketio(NA.server, optionIo),
        cookie = NA.modules.cookie,
        cookieParser = NA.modules.cookieParser;

    // Synchronisation des Sessions avec Socket.IO.
    io.use(function(socket, next) {
        var handshakeData = socket.request;

        // Fallback si les cookies ne sont pas gérés.
        if (!handshakeData.headers.cookie) {
            return next(new Error('Cookie de session requis.'));
        }

        // Transformation de la String cookie en Objet JSON.
        handshakeData.cookie = cookie.parse(handshakeData.headers.cookie);

        // Vérification de la signature du cookie.
        handshakeData.cookie = cookieParser.signedCookies(handshakeData.cookie, NA.webconfig.session.secret);

        // Garder à portée l'ID de Session.
        handshakeData.sessionID = handshakeData.cookie[NA.webconfig.session.key];

        // Accepter le cookie.
        NA.sessionStore.load(handshakeData.sessionID, function (error, session) {
            if (error || !session) {
                return next(new Error('Aucune session récupérée.'));
            } else {
                handshakeData.session = session;
                next();
            }
        });
    });

    // Suite.
    next(io);
};

// Ajout d'évènements d'écoute pour un controller spécifique « index.js » (voir exemple dans le fichier d'après).
privates.socketIoEvents = function (io, NA) {
    var params = {};

    params.io = io;

    // Evènements pour la page index (voir exemple dans le fichier d'après).
    require('./index').asynchrone.call(NA, params);
};

// Configuration de tous les modules.
exports.setConfigurations = function (next) {
    var NA = this,
        socketio = NA.modules.socketio;

    // Initialisation de Socket IO.
    privates.socketIoInitialisation(socketio, NA, function (io) {

        // Écoute d'action Socket IO.
        privates.socketIoEvents(io, NA);

        // Étapes suivante du moteur.
        next();
    });
};

Note : Ceci est la configuration global de Socket.IO côté serveur.

controllers/index.js

// Intégralité des actions Websocket possible pour ce template.
// Utilisé non pas par « NodeAtlas » mais par « common.js » (voir fichier précédent).
exports.asynchrone = function (params) {
    var NA = this,
        io = params.io;

    // Dès qu'on a un lien valide entre le client et notre back...
    io.sockets.on("connection", function (socket) {

        // ...rester à l'écoute de la demande « create-article-button »...
        socket.on("server-render", function (data) {
            var sessionID = socket.request.sessionID,
                session = socket.request.session,
                variation = {};

            // On récupère les variations spécifiques dans la bonne langue.
            variation = NA.addSpecificVariation("index.json", data.lang, variation);

            // On récupère les variations communes dans la bonne langue.
            variation = NA.addCommonVariation(data.lang, variation);

            // On récupère le fragment HTML depuis le dossier `componentsRelativePath` et on applique les variations.
            data.render = NA.newRender("index.htm", variation);

            // Et on répond à tous les clients avec un jeu de donnée dans data.
            io.sockets.emit('server-render', data);
        });
    });
};

Quand au côté client, nous utiliserons les fichiers suivant :

assets/javascript/common.js

window.website = window.website || {};

(function (publics) {
    "use strict";

    var privates = {},
        optionsSocket,
        body = document.getElementsByTagName("body")[0];

    // On configure Socket.IO côté Client.
    optionsSocket = (body.getAttribute("data-subpath") !== "") ? { path: "/" + body.getAttribute("data-subpath") + ((body.getAttribute("data-subpath")) ? "/" : "") + "socket.io" } : undefined;
    publics.socket = io.connect((body.getAttribute("data-subpath") !== "") ? body.getAttribute("data-hostname") : undefined, optionsSocket);
}(website));

// On exécute le JavaScript Spécifique à la page en cours, ici ["index"].
website[document.getElementsByTagName("body")[0].getAttribute("data-variation")].init();

Note : Ceci est la configuration global de Socket.IO côté client en ce basant sur data-subpath et data-hostname.

assets/javascript/index.js

window.website = window.website || {};

(function (publics) {
    "use strict";

    var html = document.getElementsByTagName("html")[0],
        body = document.getElementsByTagName("body")[0],
        layout = document.getElementsByClassName("layout")[0];

    // On associe sur le bouton l'action de communiquer avec le serveur en cliquant dessus.
    function setServerRender() {
        var button = document.getElementsByTagName("button")[0];
        button.addEventListener("click", function () {
            website.socket.emit("server-render", {
                lang: html.getAttribute("lang"),
                variation: body.getAttribute("data-variation")
            });
        });
    }

    // On créer le code qui s'exécutera au lancement de la page.
    publics.init = function () {

        // On affecte l'action au bouton.
        setServerRender();

        // Quand le serveur répond après notre demande auprès de lui...
        website.socket.on("server-render", function (data) {

            // ...on met à jour le contenu...
            layout.innerHTML = data.render;

            // ...et ré-affectons l'action au bouton du nouveau contenu.
            setServerRender();
        });
    };
}(website.index = {}));

Lancer votre projet et rendez-vous à l'adresse http://localhost/ dans deux onglets différent, voir même, dans deux navigateurs différent. Vous constaterez alors qu'à chaque clique sur « Update », la page se remettra à jour (comme le montre la date courante) sur tous les onglets ouvert.

Grâce à NA.addSpecificVariation, NA.addCommonVariation et NA.newRender, il est possible de générer une nouvelle compilation d'un template (composant) et d'une variation commune et spécifique.

Si data.lang dans notre exemple est de type undefined, alors les fichiers seront cherchés à la racine. Si variation est de type undefined alors un objet contenant uniquement le scope demandé sera renvoyé.

Utiliser une base de données MySQL (SQL)

Nous allons voir à présent comment utiliser des informations venant d'une base de donnée. Pour cela nous allons utiliser le module npm mysql. Il va également nous falloir installer un serveur MySQL.

Base de données MySQL

Tout d'abord, nous allons alimenter la base de données avec la base demo :

CREATE DATABASE demo;

et la sélectionner :

USE demo

puis créer la table user :

CREATE TABLE user
(
    id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
    lastname VARCHAR(100),
    firstname VARCHAR(100),
    email VARCHAR(255),
    birthdate DATE,
    gender TINYINT(1),
    country VARCHAR(255),
    town VARCHAR(255),
    zipcode VARCHAR(5),
    address VARCHAR(255)
);

et la remplir avec un jeu de données :

INSERT INTO user (
    lastname,
    firstname,
    email,
    birthdate,
    gender,
    country,
    town,
    zipcode,
    address
) VALUES (
    "Lesieur",
    "Bruno",
    "bruno.lesieur@gmail.com",
    "1988/07/18",
    true,
    "France",
    "Annecy",
    74000,
    "66 avenue de Genève"
);

Fichiers NodeAtlas

Avec le jeu de fichier suivant :

├─ assets/
│  └─ javascript/
│     └─ models/
│        └─ user.js
├─ controllers/
│  ├─ common.js
│  └─ index.js
├─ models/
│  └─ user.js
├─ templates/
│  └─ index.htm
├─ variations/
│  ├─ common.json
│  └─ index.json
└─ webconfig.json

Nous allons utiliser le webconfig.json suivant avec une variable custom _mysqlConfig qui contiendra toutes les informations pour se connecter à la base de données :

{
    "commonController": "common.js",
    "commonVariation": "common.json",
    "routes": {
        "/": {
            "template": "index.htm",
            "variation": "index.json",
            "controller": "index.js"
        }
    },
    "_mysqlConfig": {
        "host": "localhost",
        "user": "root",
        "password": "root",
        "database": "demo"
    }
}

Avec les fichiers suivant pour afficher la page :

templates/index.htm

<!DOCTYPE html>
<html lang="<%- languageCode %>">
    <head>
        <meta charset="utf-8" />
        <title><%- common.titleWebsite %></title>
    </head>
    <body>
        <div class="title"><%- common.titleWebsite %></div>
        <div>
            <h1><%- specific.titlePage %></h1>
            <%- specific.content %>
            <ul>
                <li>Id: <strong><%- id %></strong></li>
                <li>Lastname: <strong><%- lastname %></strong></li>
                <li>Firstname: <strong><%- firstname %></strong></li>
                <li>Email: <strong><%- email %></strong></li>
                <li>Birthdate: <strong><%- birthdate %></strong></li>
                <li>Gender: <strong><%- gender %></strong></li>
                <li>Country: <strong><%- country %></strong></li>
                <li>Town: <strong><%- town %></strong></li>
                <li>Zipcode: <strong><%- zipcode %></strong></li>
                <li>Address: <strong><%- address %></strong></li>
            </ul>
        </div>
    </body>
</html>

variations/common.json

{
    "titleWebsite": "Exemple MySql",
    "male": "Homme",
    "female": "Femme"
}

variations/index.json

{
    "titlePage": "Table User",
    "content": "<p>Détail de l'entrée `bruno`.</p>"
}

Enfin nous allons nous connecter à la base de données avec le controlleur globale controllers/common.js :

exports.loadModules = function () {
    var NA = this;

    NA.modules.mysql = require('mysql');
    NA.models = {};
    NA.models.User = require('../models/user.js');
};

exports.setConfigurations = function (next) {
    var NA = this,
        mysql = NA.modules.mysql;

    NA.mySql = mysql.createPool(NA.webconfig._mysqlConfig);

    next();
};

Et afficher les résultats via le controlleur spécifique controllers/index.js :

exports.changeVariation = function (params, next) {
    var NA = this,
        variation = params.variation,
        User = NA.models.User,
        bruno = User();

    NA.mySql.getConnection(function(err, connection) {
        if (err) {
            console.log(err);
            return false;
        }

        bruno
        .setConnection(connection)
        .firstname("bruno")
        .readFirst(function () {

            variation.id = bruno.id();
            variation.lastname = bruno.lastname();
            variation.firstname = bruno.firstname();
            variation.email = bruno.email();
            variation.birthdate = bruno.birthdate();
            variation.gender = (bruno.gender()) ? variation.common.male : variation.common.female;
            variation.country = bruno.country();
            variation.town = bruno.town();
            variation.zipcode = bruno.zipcode();
            variation.address = bruno.address();

            next(variation);
        });
    });
};

en utilisant le model user via le fichier de connexion à la base de données models/user.js :

/* jslint esversion: 6 */
var user = require('../assets/javascript/models/user.js');

function User(connection) {
    var privates = {},
        publics = this;

    privates.connection = connection;

    if (!(publics instanceof User)) {
        return new User();
    }

    publics.setConnection = function (connection) {
        privates.connection = connection;
        return publics;
    };

    user.call(publics);

    publics.readFirst = function (callback) {
        var select = `SELECT
                    id,
                    lastname,
                    firstname,
                    email,
                    birthdate,
                    gender,
                    country,
                    town,
                    zipcode,
                    address
                FROM user`, 
            where = "", 
            limit = " LIMIT 0,1 ",
            addWhere = " WHERE ";

        if (publics.id()) { where += addWhere + "`id` = '" + publics.id().replace(/'/g, "''") + "'"; addWhere = ' && '; }
        if (publics.lastname()) { where += addWhere + "`lastname` = '" + publics.lastname().replace(/'/g, "''") + "'"; addWhere = ' && '; }
        if (publics.firstname()) { where += addWhere + "`firstname` = '" + publics.firstname().replace(/'/g, "''") + "'"; addWhere = ' && '; }
        if (publics.email()) { where += addWhere + "`email` = '" + publics.email().replace(/'/g, "''") + "'"; addWhere = ' && '; }
        if (publics.birthdate()) { where += addWhere + "`birthdate` = '" + publics.birthdate().replace(/'/g, "''") + "'"; addWhere = ' && '; }
        if (publics.gender()) { where += addWhere + "`gender` = '" + publics.gender().replace(/'/g, "''") + "'"; addWhere = ' && '; }
        if (publics.country()) { where += addWhere + "`country` = '" + publics.country().replace(/'/g, "''") + "'"; addWhere = ' && '; }
        if (publics.town()) { where += addWhere + "`town` = '" + publics.town().replace(/'/g, "''") + "'"; addWhere = ' && '; }
        if (publics.zipcode()) { where += addWhere + "`zipcode` = '" + publics.zipcode().replace(/'/g, "''") + "'"; addWhere = ' && '; }
        if (publics.address()) { where += addWhere + "`address` = '" + publics.address().replace(/'/g, "''") + "'"; addWhere = ' && '; }

        privates.connection.query(select + where + limit, function(err, rows, fields) {
            if (err) console.log(err);

            if (rows[0]) {
                publics.id(rows[0].id);
                publics.lastname(rows[0].lastname);
                publics.firstname(rows[0].firstname);
                publics.email(rows[0].email);
                publics.birthdate(rows[0].birthdate);
                publics.gender((rows[0].gender) ? true : false);
                publics.country(rows[0].country);
                publics.town(rows[0].town);
                publics.zipcode(rows[0].zipcode);
                publics.address(rows[0].address);
            }

            callback();
        });
    };
}

User.prototype = Object.create(user.prototype);
User.prototype.constructor = User;

module.exports = User;

basé sur une classe user partagé entre le Front et le Back assets/javascript/models/user.js :

(function (expose, factory) {
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = factory;
    } else {
        expose.User = factory;
    }
}(this, function User() {
    var privates = {},
        publics = this;

    if (!(publics instanceof User)) {
        return new User();
    }

    publics.id = function (id) {
        if (typeof id === 'undefined') {
            return privates.id;
        } else {
            privates.id = id;
            return publics;
        }
    };

    publics.lastname = function (lastname) {
        if (typeof lastname === 'undefined') {
            return privates.lastname;
        } else {
            privates.lastname = lastname;
            return publics;
        }
    };

    publics.firstname = function (firstname) {
        if (typeof firstname === 'undefined') {
            return privates.firstname;
        } else {
            privates.firstname = firstname;
            return publics;
        }
    };

    publics.email = function (email) {
        if (typeof email === 'undefined') {
            return privates.email;
        } else {
            privates.email = email;
            return publics;
        }
    };

    publics.birthdate = function (birthdate) {
        if (typeof birthdate === 'undefined') {
            return privates.birthdate;
        } else {
            privates.birthdate = birthdate;
            return publics;
        }
    };

    publics.gender = function (gender) {
        if (typeof gender === 'undefined') {
            return privates.gender;
        } else {
            privates.gender = gender;
            return publics;
        }
    };

    publics.country = function (country) {
        if (typeof country === 'undefined') {
            return privates.country;
        } else {
            privates.country = country;
            return publics;
        }
    };

    publics.town = function (town) {
        if (typeof town === 'undefined') {
            return privates.town;
        } else {
            privates.town = town;
            return publics;
        }
    };

    publics.zipcode = function (zipcode) {
        if (typeof zipcode === 'undefined') {
            return privates.zipcode;
        } else {
            privates.zipcode = zipcode;
            return publics;
        }
    };

    publics.address = function (address) {
        if (typeof address === 'undefined') {
            return privates.address;
        } else {
            privates.address = address;
            return publics;
        }
    };
}));

Vous obtiendrez la sortie suivante :

<!DOCTYPE html>
<html lang="">
    <head>
        <meta charset="utf-8" />
        <title>Exemple MySql</title>
    </head>
    <body>
        <div class="title">Exemple MySql</div>
        <div>
            <h1>Table User</h1>
            <p>Détail de l'entrée `bruno`.</p>
            <ul>
                <li>Id: <strong>1</strong></li>
                <li>Lastname: <strong>Lesieur</strong></li>
                <li>Firstname: <strong>Bruno</strong></li>
                <li>Email: <strong>bruno.lesieur@gmail.com</strong></li>
                <li>Birthdate: <strong>Mon Jul 18 1988 00:00:00 GMT+0200 (Paris, Madrid (heure d’été))</strong></li>
                <li>Gender: <strong>Homme</strong></li>
                <li>Country: <strong>France</strong></li>
                <li>Town: <strong>Annecy</strong></li>
                <li>Zipcode: <strong>74000</strong></li>
                <li>Address: <strong>66 avenue de Genève</strong></li>
            </ul>
        </div>
    </body>
</html>

Utiliser une base de données MongoDB (NoSQL)

Nous allons voir à présent comment utiliser des informations venant d'une base de données non sql. Pour cela nous allons utiliser le module npm mongoose. Il va également nous falloir installer un serveur MongoDB.

Base de données MongoDB

Tout d'abord, nous allons alimenter la base de données avec la base demo et la sélectionner :

use demo

puis créer la collection user :

db.createCollection("user")

et la remplir avec un document :

db.user.insert({
    email: "bruno.lesieur@gmail.com",
    identity: {
        lastname: "Lesieur",
        firstname: "Bruno",
        gender: true,
        birthdate : new Date("1988/07/18")
    },
    location: {
        country: "France",
        town: "Annecy",
        zipcode: "74000",
        address: "66 avenue de Genève"
    }
})

Fichiers NodeAtlas

Avec le jeu de fichier suivant :

├─ assets/
│  └─ javascript/
│     └─ models/
│        └─ user.js
├─ controllers/
│  ├─ common.js
│  └─ index.js
├─ templates/
│  └─ index.htm
├─ variations/
│  ├─ common.json
│  └─ index.json
└─ webconfig.json

Nous allons utiliser le webconfig.json suivant avec une variable custom _mongodbConfig qui contiendra toutes les informations pour se connecter à la base de données :

{
    "commonController": "common.js",
    "commonVariation": "common.json",
    "routes": {
        "/": {
            "template": "index.htm",
            "variation": "index.json",
            "controller": "index.js"
        }
    },
    "_mongodbConfig": {
        "host": "localhost",
        "port": "27017",
        "database": "demo"
    }
}

Avec les fichiers suivant pour afficher la page :

templates/index.htm

<!DOCTYPE html>
<html lang="<%- languageCode %>">
    <head>
        <meta charset="utf-8" />
        <title><%- common.titleWebsite %></title>
    </head>
    <body>
        <div class="title"><%- common.titleWebsite %></div>
        <div>
            <h1><%- specific.titlePage %></h1>
            <%- specific.content %>
            <ul>
                <li>Id: <strong><%- id %></strong></li>
                <li>Lastname: <strong><%- lastname %></strong></li>
                <li>Firstname: <strong><%- firstname %></strong></li>
                <li>Email: <strong><%- email %></strong></li>
                <li>Birthdate: <strong><%- birthdate %></strong></li>
                <li>Gender: <strong><%- gender %></strong></li>
                <li>Country: <strong><%- country %></strong></li>
                <li>Town: <strong><%- town %></strong></li>
                <li>Zipcode: <strong><%- zipcode %></strong></li>
                <li>Address: <strong><%- address %></strong></li>
            </ul>
        </div>
    </body>
</html>

variations/common.json

{
    "titleWebsite": "MongoDB Exemple",
    "male": "Homme",
    "female": "Femme"
}

variations/index.json

{
    "titlePage": "Collection User",
    "content": "<p>Détail du document `{ \"identity.firstname\": \"Bruno\" }`.</p>"
}

Enfin nous allons nous connecter à la base de données avec le controlleur globale controllers/common.js :

exports.loadModules = function () {
    var NA = this,
        path = NA.modules.path;

    NA.modules.mongoose = require('mongoose');
    NA.models = {};
    NA.models.User = require('../assets/javascript/models/user.js');
};

exports.setConfigurations = function (next) {
    var NA = this,
        mongoose = NA.modules.mongoose,
        config = NA.webconfig._mongodbConfig;

    mongoose.Promise = global.Promise;
    mongoose.model("user", NA.models.User, "user");
    mongoose.connect("mongodb://" + config.host + ":" + config.port + "/" + config.database, function (error) {
        next();
    });
};

Et afficher les résultats via le controlleur spécifique controllers/index.js :

exports.changeVariation = function (params, next) {
    var NA = this,
        variation = params.variation,
        mongoose = NA.modules.mongoose,
        User = mongoose.model('user');

    User
    .findOne({ "identity.firstname": "Bruno" })
    .exec(function (err, bruno) {

        variation.id = bruno._id;
        variation.lastname = bruno.identity.lastname;
        variation.firstname = bruno.identity.firstname;
        variation.birthdate = bruno.identity.birthdate;
        variation.email = bruno.email;
        variation.gender = (bruno.identity.gender) ? variation.common.male : variation.common.female;
        variation.country = bruno.location.country;
        variation.town = bruno.location.town;
        variation.zipcode = bruno.location.zipcode;
        variation.address = bruno.location.address;

        next(variation);
    });
};

en utilisant sur une classe user partagé entre le Front et le Back assets/javascript/models/user.js :

var mongoose;
if (typeof module !== 'undefined' && module.exports) {
     mongoose = require('mongoose');
}

(function (expose, factory) {
    if (mongoose) {
        module.exports = factory;
    } else {
        expose.User = factory;
    }
}(this, new mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    email: { type : String, match: /^\S+@\S+$/ },
    identity: {
        lastname: String,
        firstname: String,
        gender: Boolean,
        birthdate : { type : Date, default : Date.now }
    },
    location: {
        country: String,
        town: String,
        zipcode: String,
        address: String
    }
})));

Vous obtiendrez la sortie suivante :

<!DOCTYPE html>
<html lang="">
    <head>
        <meta charset="utf-8" />
        <title>Exemple MongoDB</title>
    </head>
    <body>
        <div class="title">Exemple MongoDB</div>
        <div>
            <h1>Collection User</h1>
            <p>Détail de l'entrée `{ "identity.firstname": "Bruno" }`.</p>
            <ul>
                <li>Id: <strong>5804d4d530788ee2e52ea1c7</strong></li>
                <li>Lastname: <strong>Lesieur</strong></li>
                <li>Firstname: <strong>Bruno</strong></li>
                <li>Email: <strong>bruno.lesieur@gmail.com</strong></li>
                <li>Birthdate: <strong>Mon Jul 18 1988 00:00:00 GMT+0200 (Paris, Madrid (heure d’été))</strong></li>
                <li>Gender: <strong>Homme</strong></li>
                <li>Country: <strong>France</strong></li>
                <li>Town: <strong>Annecy</strong></li>
                <li>Zipcode: <strong>74000</strong></li>
                <li>Address: <strong>66 avenue de Genève</strong></li>
            </ul>
        </div>
    </body>
</html>