# node-atlas # [](https://www.paypal.me/BrunoLesieur/5) [](https://travis-ci.org/Haeresis/NodeAtlas/) [](https://github.com/MachinisteWeb/NodeAtlas) **Vous êtes français ? Le README [derrière ce lien](README.fr.md) vous sera peut-être plus agréable.**  ## Overview ## NodeAtlas is a Server-side MVC(2) JavaScript Framework as an [npm module](https://www.npmjs.com/package/node-atlas) ([node-atlas](https://www.npmjs.com/package/node-atlas)) and designed to run with [Node.js](https://nodejs.org/) verision. NodeAtlas allows you to: - Create, maintain and document a set of assets HTML / CSS / JavaScript as user interfaces to provide solid guidelines for the realization of website or webapp (i.e. for brands). Exemple : [Pages, Componants and Web Interface](https://www.lesieur.name/test-case-atlas/) or official NodeAtlas website. - Create and maintain and run internationalized wesites without use a single JavaScript file. That's it's perfect for beginners or for develop presentational website with high performance quickly. Exemple : [Simple Web Page](http://bruno.lesieur.name/) - Develop Node.js internationalized websites or scalable [Node.js](https://nodejs.org/en/) applications running of all sizes with server-based source code for high performance, indexability for SEO and W3C compliancy. Distant REST APIs are also easily to create. Exemple : [Blog](http://blog.lesieur.name/), [Portfolio](http://www.lesieur.name/) or [Distant API](http://www.lesieur.name/api/) ### Why NodeAtlas ### NodeAtlas is designed to create scalable websites and to allow Front-end and Back-end developers to embrace [Node.js](https://nodejs.org/en/). Starting with a single HTML page, - then internationalize it, - then create other pages, - then minify/obfuscate/optimized your sources, - then use Less or/and Stylus, - then use files for drive back-end part with code, - then connect you to [MySQL](https://www.mysql.fr/), [MongoDB](https://www.mongodb.org/), [ElasticSearch](https://www.elastic.co/)..., - then use [Socket.io](http://socket.io/) for real time, - then be component-based with [ComponentAtlas](https://github.com/MachinisteWeb/ComponentAtlas) - then let your customer edit website itself with [EditAtlas](https://github.com/MachinisteWeb/EditAtlas) - then create plugins, - then... ### And what about others JavaScript Frameworks? ### In opposition to others JavaScript Frameworks like Vue, Angular or React, NodeAtlas run server-side and provide some real urls by Response HTTP. Websites are indexale and W3C compliant that means each page are construct by HTTP response and after by AJAX or Websocket mechanisms. So, NodeAtlas is not an alternative to others Front-end JavaScript Frameworks that only use [Node.js](https://nodejs.org/en/) for use after [npm](https://www.npmjs.com/), [jspm](http://jspm.io/) or [gulp](http://gulpjs.com/). So, NodeAtlas is same as Sails or Meteor. And that means NodeAtlas is a substituant to PHP, JAVA or C# server-side. In the same way as [Meteor.js](https://www.meteor.com/), NodeAtlas allow you to set your working environment and you have not need of [gulp](http://gulpjs.com/) but to oposition of [Meteor.js](https://www.meteor.com/), the `NA` object is not provided client-side. It's your responsability to spread server-side mechanism to front. To comparate NodeAtlas with others JavaScript Server-side Library/Framework/API, [you could check this grid](#nodeatlas-vs-others). ### Examples of websites with NodeAtlas ### This is a list of repository you could analyse to understand NodeAtlas: - [Generation and HTML template maintenance](https://github.com/MachinisteWeb/ResumeAtlas/). - [UI Test and UI Documentation](https://github.com/MachinisteWeb/TestCaseAtlas/). - [HTML website maintenance (no Back-end)](https://github.com/MachinisteWeb/NodeAtlas/tree/gh-pages/). - [Node.js website with Websocket and PopState](https://github.com/MachinisteWeb/BookAtlas/). - [Node.js website with MongoDB database and Redis](https://github.com/MachinisteWeb/BlogAtlas/). - [Node.js example of content filling in real time without Back-office](https://github.com/MachinisteWeb/EditAtlas/). - [Simple web server for a file](https://github.com/MachinisteWeb/SimpleAtlas/). - [API REST example](https://github.com/MachinisteWeb/ApiAtlas/). - [CSS-driven usage with Less preprocessor with CSS Framework](https://github.com/MachinisteWeb/LessAtlas/). - [Plugin to boost standard capabilities](https://github.com/MachinisteWeb/ComponentAtlas/). ### Table of Contents ### - [Overview](#overview) - [Why NodeAtlas](#why-nodeatlas) - [And what about others JavaScript Frameworks?](#and-what-about-others-javascript-frameworks) - [Examples of websites with NodeAtlas](#examples-of-websites-with-nodeatlas) - [Table of Contents](#table-of-contents) - [Documentation](#documentation) - [Contributing](#contributing) - [Installing](#installing) - [Install NodeAtlas](#install-nodeatlas) - [Install Node.js](#install-nodejs) - [Start with NodeAtlas](#start-with-nodeatlas) - [Fileset](#fileset) - [Minimum Requirements](#minimum-requirements) - [Run the site with NodeAtlas](#run-the-site-with-nodeatlas) - [View and Template Part](#view-and-template-part) - [More one page](#more-one-page) - [Template shortcut](#template-shortcut) - [Host images, fonts, CSS, JS, etc.](#host-images-fonts-css-js-etc) - [Manage inclusions to avoid redundancy code](#manage-inclusions-to-avoid-redundancy-code) - [Manage variations within the same template](#manage-variations-within-the-same-template) - [Manage Multilingual](#manage-multilingual) - [Change the url parameters](#change-the-url-parameters) - [Create your own webconfig variables](#create-your-own-webconfig-variables) - [NodeAtlas use to generate HTML assets](#nodeatlas-use-to-generate-html-assets) - [Controller and Model Part](#controller-and-model-part) - [Lifecycle and Hooks](#lifecycle-and-hooks) - [Use Websocket instead of AJAX](#use-websocket-instead-of-ajax) - [Use MySQL Database (SQL)](#use-mysql-database-sql) - [Use MongoDB Database (NoSQL)](#use-mongodb-database-nosql) - [More features](#more-features) - [Manage routing (URL Rewriting)](#manage-routing-url-rewriting) - [Manage a page not found](#manage-a-page-not-found) - [Manage redirects](#manage-redirects) - [Manage Headers](#manage-headers) - [Run Website with HTTPs](#run-website-with-https) - [Minify CSS / JS](#minify-css--js) - [CSS generation with Less](#css-generation-with-less) - [CSS generation with Stylus](#css-generation-with-stylus) - [Optimize Images files](#optimize-images-files) - [CSS Inline Injection for Manage Email Assets](#css-inline-injection-for-manage-email-assets) - [Allow / Disallow GET / POST requests](#allow--disallow-get--post-requests) - [Allow / Disallow PUT / DELETE requests](#allow--disallow-put--delete-requests) - [Change settings of Sessions](#change-settings-of-sessions) - [External Storage Sessions](#external-storage-sessions) - [Changing the template engine brackets <% %>](#Changing-the-template-engine-brackets--) - [Change the url hostname and listening port](#change-the-url-hostname-and-listening-port) - [Generate urls dynamically](#generate-urls-dynamically) - [CLI / Running commands](#cli--running-commands) - [--directory <path>](#--directory-path) - [--webconfig <webconfigName>](#--webconfig-webconfigname) - [--browse [subpath]](#--browse-subpath) - [--httpHostname <httpHostname>](#--httphostname-httphostname) - [--httpPort <httpPort>](#--httpport-httpport) - [--generate](#--generate) - [--lang <culture-country>](#--lang-culture-country) - [--init [path]](#--init-path) - [--httpSecure [pathName]](#--httpsecure-pathname) - [API / NodeAtlas as npm module](#api--nodeatlas-as-npm-module) - [<node-atlas-instance>.init()](#node-atlas-instanceinit) - [<node-atlas-instance>.config(Object)](#node-atlas-instanceconfigobject) - [<node-atlas-instance>.run(Object)](#node-atlas-instancerunobject) - [<node-atlas-instance>.started(Function)](#node-atlas-instancestartedfunction) - [<node-atlas-instance>.generated(Function)](#node-atlas-instancegeneratedfunction) - [<node-atlas-instance>.created(Function)](#node-atlas-instancecreatedfunction) - [NodeAtlas as a simple web server](#nodeatlas-as-a-simple-web-server) - [Development Environment](#development-environment) - [Front-end Debug](#front-end-debug) - [Back-end Debug](#back-end-debug) - [Devices Tests](#devices-tests) - [Production Environment](#production-environment) - [In a Windows Server environment with iisnode](#in-a-windows-server-environment-with-iisnode) - [In a Unix environment with forever](#in-a-unix-environment-with-forever) - [In a Unix environment with Nginx](#in-a-unix-environment-with-nginx) - [Proxy](#proxy) - [More About NodeAtlas](#more-about-nodeatlas) - [NodeAtlas VS Others](#nodeatlas-vs-others) ### Documentation ### In addition to this README, you also have access to, - [tl;dr](https://www.npmjs.com/package/node-atlas), - [details of functions in the NA object](https://node-atlas.js.org/v1.x/doc/index.html) and you could - [discuss on chat and ask asistance for NodeAtlas](https://gitter.im/NodeAtlas/Help). ### Contributing ### If you would like to contribute with: - Code enhancements and fixes, - French correct spelling mistake or - Decent english translation Please do the following: 1. Fork the NodeAtlas repository. 2. Hack on a separate topic branch created from the latest master. 3. Commit and push the topic branch. 4. Make a pull request. 5. Be patient. ;-) Please note that modications should follow these coding guidelines: - [Pass Sonarqube JS with rank A](http://www.sonarqube.org/) : Bugs, Vulnerabilities and Debt Ratio. Thank you for helping out! ## Installing ## Before install NodeAtlas, install [Node.js](https://nodejs.org/), we will see this in the section : [Install Node.js](#install-nodejs) bellow. ### Install NodeAtlas ### *Note: With Linux, add `sudo` before all commands if you're not logged with root user.* There are several ways to install NodeAtlas: - `npm install node-atlas` (recommended for [use as a module](#api--nodeatlas-as-npm-module) in a project). *This will install* **NodeAtlas** *in the `node_modules/node-atlas` directory of the execution of the command.* - `npm install -g node-atlas` (recommended for [use as a module](#api--nodeatlas-as-npm-module) in large amount of project or for [a command line utilisation](#cli--running-commands)). *This will install* **NodeAtlas** *in the global `node_modules/node-atlas`.* - Clone the directory from [GitHub](https://github.com/MachinisteWeb/NodeAtlas/) (recommended for participating to project). *This will install* **NodeAtlas** *in cloning home folder.* **Start at least once NodeAtlas the with the command line `\> node node-atlas/`, to install the _node_modules_.** - Download NodeAtlas from the official repository [NodeAtlas](https://node-atlas.js.org/). *Once downloaded, unzip* **NodeAtlas** *in the folder that will suit you.* **Use `npm install` command from `node-atlas/` directory to install all dependencies.** ### Install Node.js ### NodeAtlas is developed as a [Node.js Module Package](https://www.npmjs.com/) that means its require Node.js to work. Node.js allows us to quickly and efficiently run JavaScript code outside the browser, making it possible to use the same language on both the front-end and the back-end. *Note: Python 2.6 or 2.7 is required to build from source tarballs.* #### Install on Windows #### Using a package: - [Download Windows Installer](https://nodejs.org/en/download/). Using [chocolatey](http://chocolatey.org/) to install Node: ``` cinst nodejs ``` or for full install with NPM: ``` cinst nodejs.install ``` #### Install on OSX #### Using a package: - [Download Macintosh Installer](https://nodejs.org/en/download/). Using [homebrew](https://github.com/mxcl/homebrew): ``` brew install node ``` Using [macports](http://www.macports.org/): ``` port install nodejs ``` #### Install on Linux #### Using a package: - [Download Linux Binaries](https://nodejs.org/en/download/). Example install with apt-get: ``` sudo apt-get install python-software-properties python g++ make curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash - sudo apt-get install -y nodejs ``` There is a naming conflict with the node package (Amateur Packet Radio Node Program), and the nodejs binary has been renamed from node to nodejs. You'll need to symlink /usr/bin/node to /usr/bin/nodejs or you could uninstall the Amateur Packet Radio Node Program to avoid that conflict. ## Start with NodeAtlas ## We will start with a how to set minimal files to perform a `hello-world`. ### Fileset ### After installing NodeAtlas somewhere on your machine, you create a set of files representing a site anywhere else like structure below. ``` site-hello-world/ ├─ templates/ │ └─ index.htm └─ webconfig.json ``` Here is the "/site-hello-world/templates/index.htm" file: ```html
This is the home page.
It is the Members page.
This is the home page.
" } ``` *variations/members.json* ```js { "titlePage": "List of members", "classPage": "members", "content": "It is the Members page.
" } ``` you will have access to the addresses: - *http://localhost/* - *http://localhost/list-of-members/* *Note : If* ***variationsRelativePath*** *is not present in "webconfig.json", default variations folder is* ***variations***. ***variationsRelativePath*** *is useful only to change the name/path of directory.* ### Manage Multilingual ### #### All languages on the same site #### On the same principle, the variations can be used to create the same page, but in different languages: ```js { "languageCode": "en-gb", "variationsRelativePath": "languages", "routes": { "/": { "template": "landing.htm", "variation": "landing.json" }, "/home/": { "template": "home.htm", "variation": "home.json" }, "/accueil/": { "template": "home.htm", "variation": "home.json", "languageCode": "fr-fr" } } } ``` *Note : In this example I decided to do without a common variation file, because I did not specify* ***commonVariation***. *I also completely arbitrarily decided to rename my folder* ***variations*** *to* ***languages***. with the following files: ``` ├─ components/ │ ├─ head.htm │ └─ foot.htm ├─ languages/ │ ├─ landing.json │ ├─ en-gb │ │ └─ home.json │ └─ fr-fr │ └─ home.json ├─ templates/ │ ├─ landing.htm │ └─ home.htm └─ webconfig.json ``` *components/head.htm* ```htmlThis is a home page.
" } ``` *languages/fr-fr/home.json* ```js { "titlePage": "Bienvenue", "classPage": "home", "content": "C'est la page d'accueil.
" } ``` you will have access to the addresses: - *http://localhost/* - *http://localhost/home/* - *http://localhost/accueil/* *Note : By default is the* ***languageCode*** *root that determines the display language of the wesite. However, specifically by page language, we can be changed also the* ***languageCode****. *You should also know that once the site or page has a* ***languageCode*** *in the configuration, variations files must be placed in a subdirectory named with the* ***languageCode***. #### Use only changes with the active multilingual #### You may have noticed in the previous example that the `landing.json` file was not in the `en-gb/` or `fr-fr/`. This is quite possible and means that will be used in languages that do not have it in their file. Also, when a `languageCode` is specified, NodeAtlas seek first hand the value in the corresponding folder file. If it was not there, so he went to fetch the parent folder (the one used as standard for variations without multilingual). This will allow you, for example, to manage master language directly in the variation folder. So with the following example: ``` │ ┊┉ ├─ variations/ │ ├─ common.json │ ├─ home.json │ ├─ fr-fr │ │ ├─ common.json │ │ └─ home.json ┊┉ ``` you can - manage the version `en-gb` directly to the root of `variations/` (as NodeAtlas find nothing in` en-gb` then it uses the values of the root files) and - manage the `fr-fr` release in the` fr-fr / `, thus, if a sentence has not yet translated into a file `fr-fr`, instead of returning an error, NodeAtlas return the root version or the version` en-gb`. #### Each language has its configuration #### You can also choose to configure each language in a "webconfig.json" different. With the following set of file: ``` ├─ components/ │ ├─ head.htm │ └─ foot.htm ├─ variations/ │ ├─ landing.json │ ├─ en-gb │ │ ├─ home.json │ │ └─ members.json │ └─ fr-fr │ ├─ home.json │ └─ members.json ├─ templates/ │ ├─ landing.htm │ ├─ home.htm │ └─ members.htm ├─ webconfig.json ├─ webconfig.en-gb.json └─ webconfig.fr-fr.json ``` you could have "webconfig.json» next: *webconfig.json* ```js { "routes": { "/": { "template": "landing.htm", "variation": "landing.json" } } } ``` *webconfig.en-gb.json* ```js { "httpPort": 81, "urlRelativeSubPath": "english", "languageCode": "en-gb", "routes": { "/": { "template": "home.htm", "variation": "home.json" }, "/members-list/": { "template": "members.htm", "variation": "members.json" } } } ``` *webconfig.fr-fr.json* ```js { "httpPort": 82, "urlRelativeSubPath": "francais", "languageCode": "fr-fr", "routes": { "/": { "template": "home.htm", "variation": "home.json" }, "/list-of-members/": { "template": "members.htm", "variation": "members.json" } } } ``` and have access to addresses: - *http://localhost/* - *http://localhost:81/english/* - *http://localhost:81/english/* - *http://localhost:81/english/members-list/* - *http://localhost:82/francais/* - *http://localhost:82/francais/list-of-members/* It is then possible to reverse proxy with [Bouncy](#proxy) (for example) to bring all urls on port 80 to obtain: - *http://www.website.ext/* - *http://www.website.ext/english/* - *http://www.website.ext/english/* - *http://www.website.ext/english/members-list/* - *http://www.website.ext/francais/* - *http://www.website.ext/francais/list-of-members/* ### Change the url parameters ### By default, if you use the following configuration: ```js { "routes": { "/": { "template": "index.htm" } } } ``` This is the same to using it: ```js { "httpHostname": "localhost", "httpPort": 80, "httpSecure": false, "urlRelativeSubPath": "", "routes": { "/": { "template": "index.htm" } } } ``` and you will be access to the url: *http://localhost/*. Then change the configuration to this: ```js { "httpHostname": "127.0.0.1", "httpPort": 7777, "httpSecure": true, "urlRelativeSubPath": "sub/folder", "routes": { "/": { "template": "index.htm" } } } ``` for access to : *https://127.0.0.1:7777/sub/folder/* ### Create your own webconfig variables ### Imagine two webconfigs in which we create our own variables as follows: 1. "webconfig.json" ```js { "routes": { "/": { "template": "index.htm" } }, "_minified": "" } ``` 2. "webconfig.prod.json" ```js { "routes": { "/": { "template": "index.htm" } }, "_minified": ".min" } ``` with this set of files ``` ├─ assets/ │ ├─ stylesheets/ │ │ ├─ common.css │ │ └─ common.min.css │ └─ javascript/ │ ├─ common.js │ └─ common.min.js ├─ templates/ │ └─ index.htm ├─ webconfig.json └─ webconfig.prod.json ``` and "index.htm" containing: ```htmlThis is the Home Page.
" } ``` *templates/index.htm* ```html <%- include('head.htm') %>This is Home Page.
" } ``` *templates/index.htm* ```htmlThis is Home Page.
I am using markdown.
Current date is:
" } ``` All work fine here, but see what we will do with controller part on Server-side and on Client-side. On server, we will use the following files: **controllers/common.js** ```js var privates = {}; // Load modules for this site in the NodeAtlas object. exports.loadModules = function () { // Find instance of « NodeAtlas » engine. var NA = this; // Associations of each module to access it anywhere. NA.modules.socketio = require('socket.io'); NA.modules.cookie = require('cookie'); }; // Example using 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; // Synchronizing sessions with Socket.IO. io.use(function(socket, next) { var handshakeData = socket.request; // Fallback if cookies are not supported. if (!handshakeData.headers.cookie) { return next(new Error('Session cookie required.')); } // Transformation of the cookie String to JSON object. handshakeData.cookie = cookie.parse(handshakeData.headers.cookie); // Verification of the signature of the cookie. handshakeData.cookie = cookieParser.signedCookies(handshakeData.cookie, NA.webconfig.session.secret); // Keep worn the Session ID. handshakeData.sessionID = handshakeData.cookie[NA.webconfig.session.key]; // Accept the cookie. NA.sessionStore.load(handshakeData.sessionID, function (error, session) { if (error || !session) { return next(new Error('No recovered session.')); } else { handshakeData.session = session; next(); } }); }); // Next. next(io); }; // Adding listener for a specific controller "index.js" (see example in the next file). privates.socketIoEvents = function (io, NA) { var params = {}; params.io = io; // Event for the index page (see example in the next file). require('./index').asynchrone.call(NA, params); }; // Configuration of all modules. exports.setConfigurations = function (next) { var NA = this, socketio = NA.modules.socketio; // Initialize Socket IO. privates.socketIoInitialisation(socketio, NA, function (io) { // Socket IO listening. privates.socketIoEvents(io, NA); // Next steps of engine. next(); }); }; ``` *Note : This is Socket.IO server global configuration.* **controllers/index.js** ```js // All Websocket action possible for this template. // Used not by "NodeAtlas" but with "common.js" (see previous file). exports.asynchrone = function (params) { var NA = this, io = params.io; // Once we have a valid connection between the client and our back-end... io.sockets.on('connection', function (socket) { // ...stay tuned on the "create-item-button" demand... socket.on("server-render", function (data) { var sessionID = socket.request.sessionID, session = socket.request.session, variation = {}; // Specific variations in the good language. variation = NA.addSpecificVariation("index.json", data.lang, variation); // Common variations in the good language. variation = NA.addCommonVariation(data.lang, variation); // HTML part from `componentsRelativePath` directory and render with variations. result = NA.newRender("index.htm", variation); // And responds to all customers with a set of data in data. io.sockets.emit('create-article-button', data); }); }); }; ``` And for client-side, we use the following files: **assets/javascript/common.js** ```js window.website = window.website || {}; (function (publics) { "use strict"; var privates = {}, optionsSocket, body = document.getElementsByTagName("body")[0]; // We configure Socket.IO client-side. 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)); // We execute specific JavaScript, here it's ["index"]. website[document.getElementsByTagName("body")[0].getAttribute("data-variation")].init(); ``` *Note : This is the global Socket.IO configuration client-side with `data-subpath` and `data-hostname`.* **assets/javascript/index.js** ```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]; // We associate on the button the action to contact server. 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") }); }); } // We create the code will be executed when page run. publics.init = function () { // We set action on button. setServerRender(); // When server response come back... website.socket.on("server-render", function (data) { // ...we update data content... layout.innerHTML = data.render; // ...and we set again button action. setServerRender(); }); }; }(website.index = {})); ``` Run your project and go on `http://localhost/` across multiple tab and/or multiple browser. You will see when you click on « Update », the page (current date) will be updated on all tabs open. Thanks to `NA.addSpecificVariation`, `NA.addCommonVariation` and `NA.newRender`, it's possible to generate a new template and variation compilation. If `data.lang` in this example is type of `undefined`, files will be search in rood directory. If `currentVariation` is type of `undefined` an empty object will be created. ### Use MySQL Database (SQL) ### We will see now how to use data from database. We will use the `mysql` npm module. And first, [install a MySQL server](https://dev.mysql.com/downloads/installer/). #### MySQL Database #### First, we will create a database `demo` on the server: ``` CREATE DATABASE demo; ``` and select it: ``` USE demo ``` and create a `user` table: ``` 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) ); ``` and fill it with this set of data: ``` 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" ); ``` #### NodeAtlas Files #### With the following data set: ``` ├─ assets/ │ └─ javascript/ │ └─ models/ │ └─ user.js ├─ controllers/ │ ├─ common.js │ └─ index.js ├─ models/ │ └─ user.js ├─ templates/ │ └─ index.htm ├─ variations/ │ ├─ common.json │ └─ index.json └─ webconfig.json ``` We will use the following `webconfig.json` with the custom `_mysqlConfig` variable which contain all informations for database connection: ``` { "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" } } ``` With following files to display page: **templates/index.htm** ```html`bruno` entry details.
" } ``` And last, we will be connect to the database with the common controller `controllers/common.js`: ```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(); }; ``` And display result via specific controller `controllers/index.js`: ```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); }); }); }; ``` with the `user` model via connect file to database `models/user.js`: ```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; ``` based on `user` classe shared between Front and Back part `assets/javascript/models/user.js`: ```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; } }; })); ``` You will get the following output: ```html`bruno` entry details.
Document `{ \"identity.firstname\": \"Bruno\" }` details.
" } ``` And last, we will be connect to the database with the common controller `controllers/common.js`: ```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(); }); }; ``` And display result via specific controller `controllers/index.js`: ```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); }); }; ``` based on `user` classe shared between Front and Back part `assets/javascript/models/user.js`: ```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 } }))); ``` You will get the following output: ```htmlCollection `{ "identity.firstname": "Bruno" }` details.
This line is red.
``` *assets/stylesheets/common.less* ```css p { color: #f00; } ``` you will build the `assets/stylesheets/common.css` by calling the url `http://localhost/` or `http://localhost/stylesheets/common.css`. #### Source Map and Minification #### By default, in the above example, a `common.css.map` file will be generated. This allows your browser to indicated you that line in `.less` file has generated the CSS property of the item you have selected in your debugger. Disable this with `enableLess.sourceMap` to `false`: ``` "enableLess": { "sourceMap": false }, "routes": { "/": "index.htm" } ``` You can also generate CSS files already minify with: ``` "enableLess": { "compress": true }, "routes": { "/": "index.htm" } ``` #### Compile Less files with `--generate` #### Because of Less are compilated on the fly, when a file is requested in http(s), modification needed running website for generate CSS output. Then you can use CSS. It's possible to skip running step and directly complated Less before minify CSS with `enableLess.less`. With the following `webconfig.json`: ```js { "enableLess": { "less": [ "stylesheets/common.less", "stylesheets/component-1.less", "stylesheets/component-2.less", "stylesheets/component-3.less" ] }, "routes": { "/": "index.htm" } } ``` or with the following `webconfig.json`: ```js { "enableLess": { "less": "less.json" }, "routes": { "/": "index.htm" } } ``` with `less.json` containing : ```js [ "stylesheets/common.less", "stylesheets/component-1.less", "stylesheets/component-2.less", "stylesheets/component-3.less" ] ``` The `@import` used by Less will be capable to walk into subdirectories : `styles`, `stylesheets` or `css`. It's possible to change that with : ```js { "enableLess": { "paths": [ "subdirectory/styles-files", ], "less": "less.json" }, "routes": { "/": "index.htm" } } ``` ### CSS generation with Stylus ### You can use the preprocessor Stylus to create your CSS. The operation is as follows: whenever a CSS request is made, if a Stylus equivalent exists it is read and it generates the CSS. Once done, the new CSS is responded. With the following structure: ``` ├─ assets/ │ └─ stylesheets │ └─ common.styl ├─ templates/ │ └─ index.htm └─ webconfig.json ``` and the following webconfig: ```js { "enableStylus": true, "routes": { "/": "index.htm" } } ``` and the following content in: *templates/index.htm* ```htmlThis line is red.
``` *assets/stylesheets/common.styl* ```css p color: #f00 ``` you will build the `assets/stylesheets/common.css` by calling the url `http://localhost/` or `http://localhost/stylesheets/common.css`. #### Source Map and Minification #### By default, in the above example, a `common.css.map` file will be generated. This allows your browser to indicated you that line in `.styl` file has generated the CSS property of the item you have selected in your debugger. Disable this with `enableLess.sourceMap` to `false`: ``` "enableStylus": { "sourceMap": false }, "routes": { "/": "index.htm" } ``` You can also generate CSS files already minify with: ``` "enableStylus": { "compress": true }, "routes": { "/": "index.htm" } ``` *Note:* More options on [stylus documentation for module](https://www.npmjs.com/package/stylus). #### Compile Stylus files with `--generate` #### Because of Stylus are compilated on the fly, when a file is requested in http(s), modification needed running website for generate CSS output. Then you can use CSS. It's possible to skip running step and directly complated Stylus before minify CSS with `enableLess.stylus`. With the following `webconfig.json`: ```js { "enableLess": { "stylus": [ "stylesheets/common.styl", "stylesheets/component-1.styl", "stylesheets/component-2.styl", "stylesheets/component-3.styl" ] }, "routes": { "/": "index.htm" } } ``` or with the following `webconfig.json`: ```js { "enableLess": { "stylus": "stylus.json" }, "routes": { "/": "index.htm" } } ``` with `stylus.json` containing : ```js [ "stylesheets/common.styl", "stylesheets/component-1.styl", "stylesheets/component-2.styl", "stylesheets/component-3.styl" ] ``` The `@import` used by Less will be capable to walk into subdirectories : `styles`, `stylesheets` or `css`. It's possible to change that with : ```js { "enableLess": { "paths": [ "subdirectory/styles-files", ], "stylus": "stylus.json" }, "routes": { "/": "index.htm" } } ``` ### Optimize Images files ### You can automatically generate optimized images files by creating Optimizations by referencing the file by input and output path. Of course you can do as much as you want. The optimization files is execute every time you start NodeAtlas either as a server or via the `--generate` command if an Optimization exists in the Webconfig. #### Creating Optimizations #### With the following configuration: ```js { "optimizations": { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` and the following set of file: ``` ├─ assets/ │ └─ media/ │ └─ images/ │ ├─ example.png │ ├─ example.jpg │ ├─ example.gif │ └─ example.svg ├─ templates/ │ └─ index.htm └─ webconfig.json ``` you will get the following new files: ``` ├─ assets/ │ └─ media/ │ └─ images/ │ ├─ example.png │ ├─ example.jpg │ ├─ example.gif │ ├─ example.svg │ └─ optimized/ ⤆ new folder │ ├─ example.png ⤆ new file │ ├─ example.jpg ⤆ new file │ ├─ example.gif ⤆ new file │ └─ example.svg ⤆ new file ├─ templates/ │ └─ index.htm └─ webconfig.json ``` #### Create Optimizations by group of file #### For example, not define file one by one, but in group: ```js { "optimizations": { "images": { "media/images/*.{gif,jpg,png,svg}": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` #### Add more options to Optimizations #### It is possible to redefine default options used for optimizations via this 4 objects: ```js { "optimizations": { "jpg": { "progressive": false }, "gif": { "interlaced": false }, "png": { "optimizationLevel": 1 }, "svg": { "multipass": false }, "images": { "media/images/*.{gif,jpg,png,svg}": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` To know all options it is here: - [Jpeg Options](https://www.npmjs.com/package/imagemin-jpegtran) - [Gif Options](https://www.npmjs.com/package/imagemin-gifsicle) - [Png Options](https://www.npmjs.com/package/imagemin-optipng) - [Svg Options](https://www.npmjs.com/package/imagemin-svgo) #### Optimizations in a shared file #### In order to not re-write a long Bundles configuration list in `webconfig.json` file to your development environment and` webconfig.prod.json` to your production environment, you can group files in a file of your choice. By convention, the name is `optimizations.json` file. For example: The following set of file ``` ├─ assets/ │ └─ media/ │ └─ images/ │ ├─ example.png │ ├─ example.jpg │ ├─ example.gif │ └─ example.svg ├─ templates/ │ └─ index.htm ├─ webconfig.json └─ webconfig.prod.json ``` with `webconfig.json` ```json { "httpPort": 7777, "optimizations": { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` and with `webconfig.prod.json` ```json { "httpPort": 7776, "httpHostname": "blog.lesieur.name", "urlPort": 80, "optimizations": { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` could be the following set of file ``` ├─ assets/ │ └─ media/ │ └─ images/ │ ├─ example.png │ ├─ example.jpg │ ├─ example.gif │ └─ example.svg ├─ templates/ │ └─ index.htm ├─ bundles.json ├─ webconfig.json └─ webconfig.prod.json ``` with `webconfig.json` ```json { "httpPort": 7777, "optimizations": "optimizations.json", "routes": { "/": { "template": "index.htm" } } } ``` with `webconfig.prod.json` ```json { "httpPort": 7776, "httpHostname": "blog.lesieur.name", "urlPort": 80, "optimizations": "optimizations.json", "routes": { "/": { "template": "index.htm" } } } ``` and `optimizations.json` ```json { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } } ``` *Note : it is possible to disable Optimizations by not including them in the `webconfig`.* #### Disable Optimizations #### It is also possible to not execute the optimization when run a website with NodeAtlas with `"imagesOptimizationsEnable": false`. ```js { "imagesOptimizationsEnable": false, "optimizations": { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` *Note : if your optimizations is in shared file, you could desactivated it also without the `"optimizations": "optimizations.json"`. Just remove it.* #### Re-generate Optimizations before each Page Response #### You can ask files to be regenerated before each page response with `"stylesheetsBundlesBeforeResponse": false` et `"javascriptBundlesBeforeResponse": false`` for each type of Bundle. ```js { "imagesOptimizationsBeforeResponse": false, "optimizations": { "images": { "media/images/example.png": "media/images/optimized/", "media/images/example.jpg": "media/images/optimized/", "media/images/example.gif": "media/images/optimized/", "media/images/example.svg": "media/images/optimized/" } }, "routes": { "/": { "template": "index.htm" } } } ``` *Note : this is not recommanded for production environment because it's slowed responses pages.* ### CSS Inline Injection for Manage Email Assets ### When you create templates for sending email newsletters, or even simple message, you can not attach stylesheet. The only way is to write the CSS instructions in the template within the `style` markup attribute. #### Specific Injection #### With `injectCss`, simply design your template as usual via a stylesheet and NodeAtlas inject each rendering styles in the attribute `style`. It will do more than generate templates. With for example the following configuration: ```json { "routes": { "/": { "template": "email.htm", "generate": "welcome.html", "injectCss": "stylesheets/email.css" } } } ``` and the following set of files: ``` ├─ generates/ ├─ assets/ │ └─ stylesheets/ │ └─ email.css ├─ templates/ │ └─ email.htm └─ webconfig.json ``` whose contents are : **stylesheets/common.css** ```css body { color: #f00; } ``` **templates/email.htm*** ```htmlThis is a template email.
``` output will be, with the command `node node-atlas/ --generate`, all following file: ``` ├─ generates/ │ └─ bienvenue.html <= template email generate ! ├─ assets/ │ └─ stylesheets/ │ └─ email.css ├─ templates/ │ └─ email.htm └─ webconfig.json ``` with as content for `generates/welcome.html` ```htmlThis is a template email.
``` This mechanism also works if you do not intend to generate anything but a site that is running. Convenient to change your live models before generating. > Test : From `./tests/examples/css-injection` run `node "../../../" --generate`. Result are into `generates`. #### Global Injection #### It is possible to use `injectCss` as global mechanism for all pages. ```json { "injectCss": "stylesheets/email.css", "routes": { "/welcome/": { "template": "email-a.htm", "generate": "welcome.html" }, "/good-bye/": { "template": "email-b.htm", "generate": "good-bye.html" } } } ``` ainsi les deux pages `welcome` et `good-bye` contiendront chacune ``. #### Multiple Injection #### It's possible to : - Attach global and specific files in same time. - Attach more one CSS file by `injectCss` property. ```json { "injectCss": ["stylesheets/reset.css", "stylesheets/email.css"], "routes": { "/welcome/": { "template": "email-a.htm", "generate": "welcome.html", "injectCss": "/stylesheets/welcome.css" }, "/good-bye/": { "template": "email-b.htm", "generate": "good-bye.html", "injectCss": ["stylesheets/good-bye.css", "/stylesheets/others.css"] } } } ``` > Test : From `./tests/examples/css-injection` run `node "../../../" --generate --webconfig webconfig.multiple.json`. Result are into `generates`. ### Allow / Disallow GET / POST requests ### You can also manager how the server will respond to requests GET/POST to a given page. For example, we will allow access to pages only GET for the whole site and allow a POST to one page only (and prohibited him GET). ```js { "getSupport": true, "postSupport": false, "routes": { "/": { "template": "index.htm" }, "/list-of-members/": { "template": "members.htm" }, "/write-comment/": { "template": "write-com.htm" }, "/save-comment/": { "template": "save-com.htm", "getSupport": false, "postSupport": true } } } ``` *Note : If nothing is set,* ***getSupport*** *and* ***postSupport*** *are set to* ***true*** *in global webconfig and by route.* ### Allow / Disallow PUT / DELETE requests ### Fonctionnant exactement de la même manière que `getSupport` et `postSupport`, les deux actions HTTP PUT et DELETE qui part défaut ne sont pas activé peuvent être activé avec `putSupport` et `deleteSupport`. ```js { "getSupport": false, "postSupport": false, "putSupport": true, "routes": { "/read-all-entry/": { "template": "display-json.htm", "variation": "all-entry.json", "getSupport": true, "putSupport": false }, "/read-entry/:id/": { "template": "display-json.htm", "variation": "entry.json", "getSupport": true, "putSupport": false }, "/create-entry/:id/": { "template": "display-json.htm", "variation": "entry.json", "postSupport": true, "putSupport": false }, "/update-entry/:id/": { "template": "display-json.htm", "variation": "entry.json" }, "/delete-entry/:id/": { "template": "display-json.htm", "variation": "entry.json", "deleteSupport": true, "putSupport": false } } } ``` With the configuration below, only one HTTP action is possible by route, this is a great way to create APIs REST easily with NodeAtlas. ### Change settings of Sessions ### #### Key and Secret #### NodeAtlas itself manages sessions stored on the server as initial settings: - Key : `nodeatlas.sid` - Secret : `1234567890bépo` that allow customers to stay connected through the pages to a single set of personal server side variable. It is possible to change the default settings (and even compulsory for productions sites) with the parameters of `webconfig.json` following: ```js { sessionKey: "personal key", sessionSecret: "personal secret" } ``` NodeAtlas also employs a memory storage object (MemoryStore) stoques that the information in the RAM of the server. #### Other Parameters #### It is possible to change all the parameters of the sessions (except MemoryStore) using the configuration of next `webconfig.json`: ```js { "session": { "key": "personal key", "secret": "personal secret", "cookie": { "path": '/', "httpOnly": true, "secure": false, "maxAge": null }, ..., ..., ... } } ``` The entirety of the possible configuration is located on the module documentation [express-session](https://github.com/expressjs/session). ### External Storage Sessions ### By default, this is NodeAtlas server that stores sessions in the RAM of the server application. This does not allow users to share sessions across multiple applications NodeAtlas (or other) and erases all current sessions for an application if you restart it. To address this concern, it should support the recording sessions via a base No SQL such as `Redis` or `MongoBD`. You just have to use the `setSessions` function in` controllers/common.js` of [Back-end part](#controller-and-model-part). #### Session managed with Redis #### Implement the following code in `controllers/common.js` to store your sessions in a local Redis. ``` var website = {}; (function (publics) { "use strict"; publics.loadModules = function (NA) { var NA = this; NA.modules.RedisStore = require('connect-redis'); }; publics.setSessions = function (next) { var NA = this, session = NA.modules.session, RedisStore = NA.modules.RedisStore(session); NA.sessionStore = new RedisStore(); next(); }; }(website)); exports.loadModules = website.loadModules; exports.setSessions = website.setSessions; ``` More information to [connect-redis](https://www.npmjs.org/package/connect-redis) page. #### Session managed with MongoDB #### Implement the following code in `controllers/common.js` to store sessions in the database `sessions` of a local MongoDB. ``` var website = {}; (function (publics) { "use strict"; publics.loadModules = function () { var NA = this; NA.modules.MongoStore = require('connect-mongo'); }; publics.setSessions = function (next) { var NA = this, session = NA.modules.session, MongoStore = NA.modules.MongoStore(session); NA.sessionStore = new MongoStore({ db: 'sessions' }); next(); }; }(website)); exports.loadModules = website.loadModules; exports.setSessions = website.setSessions; ``` More information to [connect-redis](https://www.npmjs.org/package/connect-mongo) page. ### Changing the template engine brackets <% %> ### For example, to include part of a file instruction is used ***<%- include('head.htm') %>***. It would be possible to do it with ***- include('head.htm') ?>*** with the configuration below: ```js { "templateEngineDelimiter": "?", "routes": { "/": { "template": "index.htm" } } } ``` See the exemple in files below: *components/head.htm* ```html
```
in absolute urls with variable `urlBasePath` as below:
```
```
Note that in the case of the following configuration:
```js
{
"routes": {
"/": {
"template": "index.htm"
}
}
}
```
`urlBasePath` return `http://localhost/` while in this configuration:
```js
{
"httpPort": 7777,
"urlRelativeSubPath": "sub/folder",
"routes": {
"/": {
"template": "index.htm"
}
}
}
```
`urlBasePath` return `http://localhost:7777/sub/folder/`.
#### The paths of templates ####
Using the following webconfig:
```js
{
"routes": {
"/index.html": {
"template": "index.htm"
},
"/contact.html": {
"template": "contact.htm"
}
}
}
```
and the corresponding template
```html
Link to home
Link to contact
```
I'd have to change my link in the template if I change the listening port or if I change the path of the url. The following configuration changes:
```js
{
"httpPort": 7777,
"routes": {
"/home.html": {
"template": "index.htm"
},
"/contact-us.html": {
"template": "contact.htm"
}
}
}
```
me contraindrait à modifier le template précédent comme suit :
```html
Link to home
Link to contact
```
You can solve this problem by giving a key to a specific path and deporting are way in the `url` property.
With the followinh webconfig:
```js
{
"routes": {
"index": {
"url": "/index.html",
"template": "index.htm"
},
"contact": {
"url": "/contact.html",
"template": "contact.htm"
}
}
}
```
I can now write the link in the dynamic template:
1. as follows
```html
Link to home
Link to contact
```
*Note : `.slice(1)` makes it easy to remove the dual `/` for standard url.*
2. or as follows
```html
Link to home
Link to contact
```
*Note : This would, for example `http://localhost/./home.html`, which is a standard url.*
3. ou comme suit
```html
Link to home
Link to contact
```
*Note : `urlBasePathSlice` return `http://localhost` in place of `http://localhost/` or `http://localhost:7777/sub/folder` in place of `http://localhost:7777/sub/folder/`.*
#### Utilisation de la clé pour mapper les pages ####
It's maybe useful to know the key used for the current page displayed for find the equivalent page in an other language.
With the following webconfig :
```js
{
"languageCode": "en-us",
"routes": {
"index_en-us": {
"url": "/",
"template": "/index.htm"
},
"index_fr-fr": {
"url": "/francais/",
"template": "index.htm",
"languageCode": "fr-fr"
},
"cv_en-us": {
"url": "/resume/",
"template": "cv.htm"
},
"cv_fr-fr": {
"url": "/francais/cv/",
"template": "index.htm",
"languageCode": "fr-fr"
}
}
}
```
and the common variation following :
```js
{
"language": [{
"name": "English",
"code": "en-us"
}, {
"name": "French",
"code": "fr-fr"
}]
}
```
in fr :
```js
{
"language": [{
"name": "Anglais",
"code": "en-us"
}, {
"name": "Français",
"code": "fr-fr"
}]
}
```
we could create link between each page as following :
```html