Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Handlebars.js work with jQuery and Nodejs Express?

I am trying to create a basic website using Express JS and Handlebars. I have two files, page1Router.js and page1Router.handlebars. In the Handlebars file I have this:

<!DOCTYPE html>

<html lang="en">

    <head>
        <title>Page 1</title>
    </head>

    <script id="page1_template" type="text/x-handlebars-template">
        <body>
            {{#data.info}}
            <h1>{{reportMessage 1}}</h1> <br>
            {{/data.info}}
            <a href="http://localhost:3030/anotherpage">Click here to go to another page.</a>
        </body>
    </script>

</html>

And in the JS file I have:

var config = require("../config/config").config,
   express = require("express"),
    router = express.Router(),
    logger = require("../logger/logger").logger,
     jsdom = require("jsdom"),
         $ = require('jquery')(jsdom.jsdom().createWindow()),
Handlebars = require('handlebars'),
    source = $("#page1_template").html(),
  template = Handlebars.compile(source);

router.get('/page1', homepage);

var data = {
    info: [
        {message:"Hey this is a message"},
        {message:"This is a different message"},
        {message:"This is a final message"}
    ]
};

Handlebars.registerHelper('reportMessage', function(mes) {
    if (mes == 1) {
        return info[0].message;
    } else if (mes == 2) {
        return info[1].message;
    }

    return "This is an error message";
});

function homepage(req, res) {
    res.render(__dirname + "/page1Router.handlebars", data);

    logger.info(config.Message1);
}

$('body').append(template(data));

module.exports = router;

When I try to run the server with "node app" I get the following error:

/Users/iantait/BroboticsForever/practice/node_modules/handlebars/dist/cjs/handlebars/compiler/compiler.js:436
    throw new Exception("You must pass a string or Handlebars AST to Handlebar
      ^
Error: You must pass a string or Handlebars AST to Handlebars.compile. You passed undefined
    at new Error (<anonymous>)
    at Error.Exception (/Users/iantait/BroboticsForever/practice/node_modules/handlebars/dist/cjs/handlebars/exception.js:13:41)
    at compile (/Users/iantait/BroboticsForever/practice/node_modules/handlebars/dist/cjs/handlebars/compiler/compiler.js:436:11)
    at HandlebarsEnvironment.hb.compile (/Users/iantait/BroboticsForever/practice/node_modules/handlebars/dist/cjs/handlebars.js:19:12)
    at Object.<anonymous> (/Users/iantait/BroboticsForever/practice/routers/page1Router.js:9:27)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:364:17)

My question is this: How does jquery know where to look for the "page1_template"? I believe this error is occuring because jquery is not properly finding the "page1_template" script, but I have no idea why. Any suggestions??

like image 990
Ian Tait Avatar asked Jun 10 '14 15:06

Ian Tait


1 Answers

It looks like you're mixing up server side and client side logic, which is causing some issues.

The biggest issue is that you call this line:

source = $("#page1_template").html()

before you've actually rendered the page--in fact, you call it when your web app is starting up--instead of after the page has been rendered. There is no element #page1_template accessible to your program at that time, so source is going to be null or undefined probably.

So to answer your question, "How does jquery know where to look for the "page1_template"?", it doesn't know, and can't access it.

Another logical issue is that you're calling $('body').append(template(data)); outside of the homepage request handler, which won't work. For that to have any chance of working, you would have to at least put it inside of the request handler.

I don't know a lot about jsdom, but from what I do know about it I don't think it's the solution you're looking for. There's no need to use it, or jQuery, in your server side code. Your server logic should be able to render a complete page without needing to use jQuery.


You have a couple of options, all which kill jsdom and jQuery from the server.

  1. Render the complete page on the server, and return the result with res.render in your homepage request handler (preferred).

  2. Just send down the static part of your file, make an AJAX request and use the client side Handlebars script to update your page.

  3. Switch to express' preferred templating language, Jade, and be like #1 except without the extra overhead.

  4. There are more, but I won't enumerate them.


Since it seems like you like using Handlebars, I'll answer #1. Again, the crux of your problem was that the Handlebars.compile(source) line was failing because source wasn't a string, because you were trying to use jQuery to get the template from your jsdom instance (which gets made long before your page ever renders. Remember that this JS code we're writing lives on the server, not in the browser!).

Step 1: Getting the right packages

Get rid of jsdom, jQuery, and Handlebars, and download 'express3-handlebars' (it works with express 4 too).

Step 2: Configuring express

Here's a bare-bones look of what your express app should look like. Take special note of how I configure a view engine, so it knows how to handle .handlebars files.

// Get the dependencies we need
var express = require('express'),
    expressHbs = require('express3-handlebars');

// Create a new express app
var app = express();

// Tell express where our base directory is (for static files)
// usually, we do __dirname + '/public', so we have a public folder for public content
app.use(express.static(__dirname)); 

// Configure a view engine for express, so it knows how to render .handlebars files
// I set the default layout to be main.handlebars
app.engine('handlebars', expressHbs({ defaultLayout: 'main', extname: '.handlebars' }));
app.set('view engine', 'handlebars');

// Set up your route
app.get('/page1', homepage);

// Set up a request handler
function homepage(req, res) {
    res.render('page1'); // We'll come back to this in a minute.
}

// Start listening
app.listen(8080);

Step 3: getting the right files in the right places

express3-handlebars looks for view files in certain locations. It assumes you have a views/ directory, with a views/layouts folder for layouts.

Since we configured express to use main.handlebars as our template, this is our "master layout." It will get used for every page, and the layout engine will replace {{{body}}} with whatever your page specific information is.

views/layouts/main.handlebars:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>{{title}}</title>
    </head>
    <body>
        {{{body}}}
    </body>
</html>

Again, express3-handlebars starts looking for views in the views folder. Whatever content you want in your page goes here, and will get inserted into {{{body}}}. For simplicity's sake I'm not using your Handlebars helper, but you certainly can do that (read the docs for more info). However, you really should try to keep logic in your server code and out of your templates.

views/page1.handlebars:

<h1>{{message}}</h1>
<a href="http://localhost:3030/anotherpage">Click here to go to another page.</a>

Step 4: tying it all together

Finally, we just have to send some data down to your view for it to render properly. Let's update the homepage request handler from above:

// Set up a request handler
function homepage(req, res) {
    // Ideally, here is where you would determine what message to send down
    var context = {
        title: 'Page 1',
        message: 'This is a message'
    };
    // Render the view in views/page1.handlebars with this data
    res.render('page1', context); 
}

That should be all! Now, we're rendering a complete HTML page before sending it to the client, without needing to use jQuery or JIT compilation of Handlebars templates.

Here's some relevant documentation:

  • express3-handlebars documentation
  • express documentation
like image 90
Ryan Erdmann Avatar answered Nov 15 '22 06:11

Ryan Erdmann