templating

Si se desea enviar una página html estática o dinámica en función de los parámetros enviados en la solicitud la manera básica inicial de hacerlo sería algo parecido a:

const fs = require("fs");
const express = require("express");
const app = express();
app.get("/ayuda", (req, res) => {
    fs.stat("ayuda.html", (err, informacion) => {
        if (!err) {
            fs.readFile("ayuda.html", (err, datos) => {
                if (err) {
                    res.send(500, "error en el acceso al archivo");
                } else {
                    res.setHeader("content-type", "text/html");
                    res.send(200, datos);
                }
            })
        } else {
            res.send(404, "la página no existe");
        }
    });
});    

Por suerte, con express, el envío de página estáticas queda solucionado con el middelware static ya visto. Pero ¿que pasa con las páginas cuyo contenido depende de parámetros generalmente enviados en la solicitud por parte del cliente? Una primera aproximación consistiría en poner marcas dentro del archivo y una vez recuperado el contenido con el método readFile, sustituir las marcas por el contenido de la información dinámica. Por ejemplo, si en el archivo hemos puesto la marca @nombre@, con un simple replace podríamos cambiarlo por Andrés Gómez y luego enviarlo al cliente. La codificación para cada archivo y cada parámetro se antoja bastante ardua. Esta filosofía de trabajo es en la que se basa el templating o uso de plantillas. Existen varios middleware que nos ayudan a la gestión de contenido dinámico de una forma mucho más sencilla. Vamos a ver como se haría usando uno de estos middleware (todos son muy parecidos, variando prácticamente la sintaxis y las opciones entre ellos). Vamos a usar handlebars.

En handlebars las marcas se incluyen entre el código html encerrando el nombre de la marca entre dobles llaves:

    <h1> Informe de ventas del año {{anio}}</h1>

Cuando handlebars interprete el código, sustituirá la marca {{anio}} por el valor que se le pase con nombre anio. Si el parámetro contiene código html encerraremos la marca entre tres llaves {{{marca}}} para que se interprete correctamente el código html.

La mayoría de las páginas de nuestra aplicación web, van a tener una distribución y componentes parecidos. En el o los archivos de layout es donde vamos a poner esta parte común que se mezclarán con las vistas (views), se interpretarán las marcas sustituyendo por su contenido y se enviarán al cliente. Este proceso se denomina renderización (render). El siguiente es un ejemplo de archivo de layout:

main.handlebars

<!doctype html>
<html>
    <head>
        <title>Biblioteca Provincial</title>
        <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
        <link rel="stylesheet" href="/css/main.css">
    </head>
    <body>
    {{{body}}}
    </body>
</html>    
            

Ahora si tenemos un view llamado home.handlebars con el siguiente contenido:

<h1>Director {{nombre}}</h1>
<p>lore ipsum .....</p>

E invocamos el método render del objeto response:

app.get("/", (req, res) => {
    res.render("home", { nombre: "Alfredo Sánchez" });
})

Se le enviará al cliente el siguiente código html:

<!doctype html>
<html>
    <head>
        <title>Biblioteca Provincial</title>
        <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
        <link rel="stylesheet" href="/css/main.css">
    </head>
    <body>
        <h1>Director Alfredo Sánchez</h1>
        <p>lore ipsum .....</p>
    </body>
</html>    

Los archivos que contienen las vistas de handlebars son buscadas, por defecto, en la carpeta views debajo de la carpeta de la aplicación. Los archivos de layout bajo la carpeta layouts que estará bajo views. Los archivos de handlebars por defecto deben tener la extensión .handlebars

Nuestro archivo index.js quedaría

const express = require("express");
const app = express();
const hb = require("express-handlebars");

// activar handlebars
app.set("view engine", "handlebars");
app.engine("handlebars", hb({
    defaultLayout: "main",
}));

app.get("/", (req, res) => {
    res.render("home", { nombre: "Alfredo Sánchez" });
})

//si llega aquí, es una página no encontrada
app.use(function(req, res, next) {
    res.type("text/plain");
    res.status(404);
    res.send("no econtrado");
});

app.listen(8080, () => {
    console.log('servidor funcionando en puerto 8080');
});

Nótese como es activado handlebars primero indicando a express que el motor de vistas es handlebars:

app.set("view engine", "handlebars");     

Y luego configurando el motor:

app.engine("handlebars", hb({
    defaultLayout: "main",
}));

La opción de configuración del motor defaultLayout indica cual es al archivo de layout que por defecto se usara para renderizar las vistas. Cuando se renderiza una vista se puede elegir un layout distinto de la siguiente manera:

res.render(“datos”, {layout:”otro”});    

Existen otras opciones además de defaultLayout de configuración del motor:

Para activar el caching al servir vistas debemos ejecutar:

app.enable('view cache');

Ya hemos visto como se referencian los parámetros que se pasan a la vista para su sustitución:

El nombre del parámetro se encierra entre pares de llaves {{dato}} y si el contenido del parámetro tiene código html y deseamos que el navegador lo interprete como tal, lo encerramos entre triples llaves {{{dato}}}.

Si el parámetro pasado es a su vez un objeto con diferentes propiedades, incluso con arrays, podemos referir estas propiedades haciendo uso de la notación punto igual a la utilizada en javascript. Por ejemplo, si se solicita la renderización de la vista cliente.handlebars de la siguiente forma:

res.render(“cliente”,{ perfil:{nombre:”Andrés”,apellidos:”García Pérez”}});
            

Dentro del archivo de vista, la referencia a {{perfil.nombre}} será sustituida por Andrés y {{perfil.apellidos}} por García Pérez.

handlebars además del uso de estas expresiones simples permite el uso de otras expresiones más elaboradas.

Se pueden codificar plantillas parciales o trozos de plantillas (partials) en archivos separados que luego son incrustados dentro de plantillas más completas. Por ejemplo, podríamos tener un partial con la descripción de la cabecera de todas las páginas del sitio en un archivo header.handlebars e incorporarlo a nuestro layout por defecto main.handlebars de la siguiente forma:

index.js

const express = require("express");
const app = express();
const hb = require("express-handlebars");

// activar handlebars
app.set("view engine", "handlebars");
app.engine("handlebars", hb({
    defaultLayout: "main",
}));


app.get("/", (req, res) => {
    res.render("home", { nombre: "Alfredo Sánchez" });
})


//si llega aquí, es una página no encontrada
app.use(function(req, res, next) {
    res.type("text/plain");
    res.status(404);
    res.send("no econtrado");
});

app.listen(8080, () => {
    console.log('servidor funcionando en puerto 8080');
});    

home.handlebars

<p>lore ipsum .....</p>
            

main.handlebars

<!doctype html>
<html>
<head>
    <title>Biblioteca Provincial</title>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <link rel="stylesheet" href="/css/main.css">
</head>
<body>
    {{>header}} 
    {{{body}}}
</body>
</html>                
            

header.handlebars

<header>
    <h1>Biblioteca provincial</h1>
    <h2>Director {{nombre}}</h2>
</header>

Por defecto los partials se buscan en la carpeta views/partials. La notación para incluir un partial es {{>partial}}.

Tenemos también la posibilidad de definir funciones que devuelven el valor a incrustar, que puede ser código html. en handlebars se denominan helpers. Se registran en el engine:

index.js

    
const express = require("express");
const app = express();
const hb = require("express-handlebars");

// activar handlebars
app.set("view engine", "handlebars");
app.engine("handlebars", hb({
    defaultLayout: "main",
    helpers: {
        "monedas": (precio) => {
            const monedas = ["$", "€", "£", "¥"];
            const cambio = [1, 0.86, 0.73, 113.59];
            let strHTML = "<UL>";
            cambio.map((valor, indice) => {
                strHTML += "<li>" + Intl.NumberFormat("de-DE", 
                    { minimumFractionDigits: 2, 
                        maximumFractionDigits: 2 }).format((precio * valor)) 
                    + " " + monedas[indice] + "</li>";
            })
            strHTML += "</UL>";
            return strHTML;
        }
    }
}));

app.get("/", (req, res) => {
    res.render("home", { nombre: "Alfredo Sánchez", precio: 100.10 });
})

//si llega aquí, es una página no encontrada
app.use(function(req, res, next) {
    res.type("text/plain");
    res.status(404);
    res.send("no econtrado");
});

app.listen(8080, () => {
    console.log('servidor funcionando en puerto 8080');
});     
     

Y se usan en las plantillas, ya sean view, layout o partial:

home.handlebars

<p>lore ipsum .....</p>
<p>
    precio {{{monedas precio }}}
</p>                
            

Se invocan indicando el nombre del helper y a continuación la lista de argumentos separados por espacio. Se ha encerrado la llamada entre triples llaves porque el helper devuelve código html.

handlebars dispone de varios helper predefinidos (built-in).

e-mail:manjarrés