This will be the first part of a series on using node and various libraries to set up a blog.
I'm actually setting up my own blog to replace this one. So I expect part 3 onwards to be written on my self hosted blog at raynos.org (Which of course is not ready yet)
I've set up express in the same manner that I did last time.
Github
You can find all the code that is referenced here at the github URL
app.js setup
Let's take a look at my setup.
(function() { global._ = require("./public/javascripts/lib/underscore.js"); var express = require('express'), routes = require("./app/app-routes.js"), configure = require('./app/app-configure.js'); var app = module.exports = express.createServer(); configure.configure(app, __dirname); routes.route(app); // Only listen on $ node app.js if (!module.parent) { app.listen(8080); console.log("Express server listening on port %d", app.address().port); } }());
A few things have changed from last time. I've personally decided to make underscore global for simplicity. I've moved the routes and configuration to their own files. Notice how I need to pass the __dirname of the root app to configure as configure has its own dirname.
for both configure and route I pass in a reference to the app and these modules will augment the app with the correct routes and configuration.
App configure is basically all the default configurations moved to their own file for tidyness.
app-route.js
App route has changed enough to show you.
(function() { // Routes var route = function(app) { app.get('/', function(req, res){ res.render('index', getViewData('index')); }); app.get('/about/', function(req, res) { res.render('about',getViewData('about')); }); }; var viewRE = new RegExp("(\/views\/)(.+)(\.ejs)"); var getViewData = function(view) { view = view.replace(viewRE, "$2"); console.log(view); switch(view) { case "index": return { title: 'A new experience with node.' }; case "about": return { title: 'A new experience with node.' }; } }; _.extend(module.exports, { "route": route, "getViewData": getViewData }); }());Here we have two routes. one for about and one for index. They both grab the relevant templates and fill them with view data.
You may ask yourself why I've got a method to getViewData instead of hard coding it in. Well it's a simple model concept. These views have static models attached to them. The reason I'm doing it this way is so that I can re use the views and model data on the client.
The getViewData function takes a view string. Which is either a simplistic string as seen above or a more complex "\views\name.ejs" string as seen from the regular expression. I use the regular expression to normalize it to the simpler version then return different static data depending on which view was asked for.
Client - Server communication with now.
Now how would you go about handling these views and data on the client? We're going to use now to make getViewData accessible to the client.
var everyone = require("now").initialize(app); everyone.now.getViewData = function(view, cb) { console.log(arguments); cb(routes.getViewData(view)); }
Here we simply tell now that everyone has access to the getViewData function. The function asks for a view and has a cb to handle the view. We then call the callback with our view data.
On the client we simply call this function as such.
now.getViewData(view, function(data) { console.log(data); new EJS({ url: view }).update('content', data); });In this particular case all we are doing is asking EJS which is the templating engine we are using on both the client and the server to render that view with the data we got from the server.
Now notice how easily we re used templates and view data on both the client and the server? It's beautiful isn't it?
main.js on the client
If you remember from last time main.js is the file that requireJS loads for us when it has finished loading itself.
(function() { require( [ "/javascripts/lib/underscore.js", "/javascripts/lib/jquery.js", "/nowjs/now.js", "/javascripts/lib/ejs.js" ], function() { $(go); } ); function go() { $("nav a").each(function() { var $this = $(this), view = $this.data("view"), span = $("<span></span>").text($this.text()); if (view) { span.click(function() { now.getViewData(view, function(data) { console.log(data); new EJS({ url: view }).update('content', data); }); }); $this.replaceWith(span); } }); } }());
Here were saying that we need underscore, jQuery, now and EJS before we can run our code. Our code which is the function go is itself wrapped in jQuery's on DOM ready handler.
Now let's show you a snippet from our view to give you some context. Now might also be a good time to mention that this project uses EJS instead of Jade as it's templating engine.
<header id="menu"> <nav> <a href="/" data-view="/views/index.ejs"> Home </a> <a href="/about/" data-view="/views/about.ejs"> About </a> <a href="/posts/"> Blog </a> </nav> <hr/> </header> <section id="content"> <%- body %> </section>
Yes I am using the HTML5 header and nav. Currently I'm only writing and testing in firefox/chrome. I'll write a later post on how to support legacy browsers like IE8.
The important thing to grab from the information above is that where possible we are telling our links that they can be rendered on the client by giving them a HTML5 data-view attribute.
Looking back at the code we are grabbing this view data property and if it exists we are creating a new span element to replace the link. When the span is clicked we grab the view data from the server and get EJS to render that view with said data to the element with id "content".
This is doing a similar thing as we would have done on the server by redirecting to the about URL. The express routing also renders that view with the same data.
Notice how we've upgraded the page with progressive enhancement here. If you have no JavaScript the link works. And if you have JavaScript we get the client to do all the rendering over an AJAX request.
Shamefully this type of set-up does not scale well because EJS doesn't have native support for partials. Partials are part of express, we have to find a full blown view engine for the client rather then a templating engine. I'm still working on that.
test