70

Curious if I'm doing this right and if not how you guys would approach this.

I have a Jade template that needs to render some data retrieved from a MongoDB database and I also need to have access to that data inside a client side JavaScript file.

I'm using Express.js and sending the data to the Jade template as follows :

var myMongoDbObject = {name : 'stephen'};
res.render('home', { locals: { data : myMongoDbObject } });

Then inside of home.jade I can do things like :

p Hello #{data.name}!

Which writes out :

Hello stephen!

Now what I want is to also have access to this data object inside a client side JS file so I can manipulate the Object on say a button click before POSTing it back to the server to update the database.

I've been able to accomplish this by saving the "data" object inside a hidden input field in the Jade template and then fetching the value of that field inside my client-side JS file.

Inside home.jade

- local_data = JSON.stringify(data) // data coming in from Express.js
input(type='hidden', value=local_data)#myLocalDataObj

Then in my client side JS file I can access local_data like so :

Inside myLocalFile.js

var localObj = JSON.parse($("#myLocalDataObj").val());
console.log(localObj.name);

However this stringify / parsing business feels messy. I know I can bind the values of my data object to DOM objects in my Jade template and then fetch those values using jQuery, but I'd like to have access to the actual Object that is coming back from Express in my client side JS.

Is my solution optimal, how would you guys accomplish this?

braitsch
  • 14,906
  • 5
  • 42
  • 37
  • 4
    I actually created an npm package a couple of months ago to solve this exact problem: https://github.com/brooklynDev/JShare – BFree Sep 07 '12 at 01:35

5 Answers5

74

When rendering is done, only the rendered HTML is send to the client. Therefore no variables will be available anymore. What you could do, is instead of writing the object in the input element output the object as rendered JavaScript:

script(type='text/javascript').
    var local_data =!{JSON.stringify(data)}

EDIT: Apparently Jade requires a dot after the first closing parenthesis.

Amberlamps
  • 39,180
  • 5
  • 43
  • 53
  • I'm still seeing "data" as undefined inside the script tag. – braitsch Jun 06 '12 at 22:01
  • Ok, this is better than my input field solution as it doesn't require parsing the JSON string back into an object or looking up the value of the input field with jQuery. – braitsch Jun 13 '12 at 19:34
  • This isn't working for me, I get [object Object]. Might I be doing something wrong on the server side with stringify or parse? – Costa Michailidis Mar 30 '13 at 00:32
  • 4
    @Costa depending on your view engine you may not be able to call JSON.stringify from the view. Instead you should stringify it in your controller and pass the string as a variable like `{{{jsonData}}}` (in case of handlebars). – Tom Sep 16 '13 at 10:38
  • Proposed solution works, I also recommend [express-expose](https://github.com/visionmedia/express-expose) for a more clean and integrated way to expose server-side vars. – xmikex83 Nov 03 '13 at 20:12
  • To safely embed JSON in HTML, you need to escape a few characters to avoid errors/vulnerabilities as detailed in this answer: http://stackoverflow.com/a/4180424/266795. FYI the sharify npm module handles this correctly. – Peter Lyons Oct 01 '14 at 04:33
  • protip: just use additional ' or " to inject it directly as string value ex: ```var address="!{address}"``` – Krzysztof Kaczor May 18 '15 at 04:50
  • @Tom is this way supported by ejs template engine? – Frank Fang Aug 09 '16 at 01:55
  • Got an answer to my own question above, post it here in case other guy also has this question:) In ejs, do it this way: `` – Frank Fang Aug 16 '16 at 06:38
14

I do it a little differently. In my contoller I do this:

res.render('search-directory', {
  title: 'My Title',
  place_urls: JSON.stringify(placeUrls),
});

And then in the javascript in my jade file I use it like this:

var placeUrls = !{place_urls};

In this example it's used for the twitter bootstrap typeahead plugin. You can then use something like this to parse it if you need to :

jQuery.parseJSON( placeUrls );

Notice also that you can leave out the locals: {} .

jabbermonkey
  • 1,680
  • 4
  • 19
  • 37
  • +1 on the note on leaving out the locals, I didn't know you could do that. – braitsch Jun 13 '12 at 19:35
  • 1
    What does it mean to *leave out locals*? @braitsch – mathakoot Aug 14 '16 at 13:37
  • @iamrudra It's not necessary to preface the locals object in the render function with the word "locals". ``res.render('home', { { data : myObject } })`` is fine vs ``res.render('home', { locals : { data : myObject } })`` although I might suggest you do it anyway for the sake of clarity. – braitsch Aug 15 '16 at 20:07
  • @braitsch : this is something new to me. I have done node and express projects at school but never ever I have used the locals object. Could you point me to the right docs/reading material please? – mathakoot Aug 15 '16 at 20:22
3

Using Jade templating:

If you are inserting @Amberlamps snippet of code above an included static HTML file, remember to specify !!! 5 at the top, to avoid having your styling broken, in views/index.jade:

!!! 5
script(type='text/javascript')
    var local_data =!{JSON.stringify(data)}

include ../www/index.html

This will pass in your local_data variable before the actual static HTML page loads, so that the variable is available globally from the start.

Serverside (using Jade templating engine) - server.js:

app.set('views', __dirname + '/views');
app.set('view engine', 'jade');

app.get('/', ensureAuthenticated, function(request, response){
    response.render('index', { data: {currentUser: request.user.id} });
});

app.use(express.static(__dirname + '/www'));
AmpT
  • 2,146
  • 1
  • 24
  • 25
1

You don't need to pass the locals variables in render call, locals variables are globals. On your pug file call don't put keys expression e.g #{}. Just use something like: base(href=base.url)

where base.url is app.locals.base = { url:'/' };

Adrian Miranda
  • 315
  • 3
  • 9
-1

Have you heard of socket.io? (http://socket.io/). An easy way to access the object from express would be to open a socket between node.js and your javascript. This way data can be easily passed to the client side and then easily manipulated using javascript. The code wouldn't have to be much, simply a socket.emit() from node.js and a socket.on() from the client. I think that'd be an effective solution!

Alex Roe
  • 586
  • 5
  • 11