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??
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.
Render the complete page on the server, and return the result with res.render
in your homepage
request handler (preferred).
Just send down the static part of your file, make an AJAX request and use the client side Handlebars script to update your page.
Switch to express' preferred templating language, Jade, and be like #1 except without the extra overhead.
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With