Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

How do I make a variable passed to a Jade template by an Express route accessible for use in js on the rendered page?

I'm building my first app in Node/Express and am using Jade as the templating engine.

When a user visits a URL, I hit an external API and get some JSON data. I would then like to pass that data as a variable to make it accessible for use by Javascript file which is linked on the rendered HTML (This is just testing while I convert the app from static HTML to running on the node server) However, I've been unable to work out how to do this so far. Any ideas?

My route looks like this

/* GET home page. */
router.get('/workflows/:workflowid', function(req, response) {

  options = {
   // Options
  }

  var workflowUrl = url.format(options);
  request(workflowUrl, function(err, res, body) {
    var workflowData = JSON.parse(body);
    response.render('workflows', {title: workflowData.name, workflowData: workflowData});
  });

});

I'm calling in my external javascript file like this

  script(src='/javascripts/js.js')

but in order for this script to work, I need to have that workflowData variable accessible, which I haven't yet been able to achieve.

My main attempts so far have been around syntax like

  script.
    var workflowData = #{workflowData}

3 Answers

This is an interesting problem. I can't find any documentation on this, but judging from the behavior, I think the problem you are running into is that jade interprets the variables you reference in your template in string context. The implication of this is that this section:

script.
    var workflowData = #{workflowData}

is rendering like this:

<script>
  var workflowData = [Object object];
</script>

There are a couple of problems here. The first is that assigning [Object object] to a variable is not valid. It would have to be enclosed in quotes:

script.
  var workflowData = '#{workflowData}';

Will render like this:

<script>
  var workflowData = '[Object object]';
</script>

This is at least a valid assignment. But this leads me to your second problem. This is obviously not what you want. I'm not sure what your JSON data looks like, but lets assume it looks like this:

workflowData = {
  name: 'Workflow Data',
  content: 'Workflow Content'
}

You could rebuild your object in your script like this:

script.
  var workflowData = {
    name: '#{workflowData.name}',
    content: '#{workflowData.content}'
  }

Depending on the complexity of your JSON object, this could be a real nightmare. What is your javascript file doing with this data? Is it possible to either use some Ajax in your javascript to get the data from the API on the client itself, or could you just embed the workflowData directly into your html using jade?

I don't really like the idea of rebuilding this object in your jade template if for no other reason than it is creating a very tight coupling between the workflowData returned from the API, your jade template, and your js.js file. It just seems like there should be a better way to do this. Do either of the other two solutions seem possible?

Hi Aaron - thanks for getting back, and explaining the issue in such depth! You are correct, Jade is rendering it out as you describe.

I can't use Ajax to grab the API data unfortunately, as the API has blocked client-side code from connecting to it. I was considering directly embedding the workflow data into the HTML, but that would require rewriting quite a lot of code that is currently JS in Jade, and from working with previous MVC frameworks, I am of the understanding that you want to keep as little as possible actual scripting and processing on the template as possible.

The JSON I'm getting from the API looks something like the below example

      {
        id: XXXXXX,
        portalId: XXXXX,
        insertedAt: 1347469287616,
        updatedAt: 1347749461441,
        name: "tes",
        allowContactToTriggerMultipleTimes: false,
        onlyExecOnBizDays: true,
        enabled: false,
        internal: false,
        legacyMigration: false,
        legacyCampaignId: 0,
        nurtureTimeRange: {
          enabled: false,
          startHour: 9,
          stopHour: 10
        },
        unenrollmentSetting: {
          type: "NONE",
          excludedWorkflows: [ ]
        },
        steps: [ ],
        triggers: [ ],
        triggerSets: [ ]
      }

I am then parsing it out to look like this (on document.ready) by building the HTML as jQuery objects and then appending them to the dom.

workflow image

What would be the standard method of achieving something like this? I'm pretty certain I'm doing it strangely.

An idea - how about I create a route which grabs the info from the API and then use AJAX on my external script or Jade template to go get it? I'm going to try that out.

Update on this: I did the above. The downside is that the flow is

  • Page loads
  • Page uses AJAX to call external API
  • Page THEN uses my Javascript to parse the JSON from the API

This makes the page's time-till-ready much longer than if I could simply find a way to load the JSON onto the page as a variable on the initial page load, but it will have to do for now I suppose.

I was thinking the same thing about creating another route to specifically get the data with AJAX. The main things I would suggest there is that you make sure you aren't in violation of the API provider's terms of use, and that you aren't just creating an open proxy to their API. It sounds like, given your limitations, this might be your only (or at least most reasonable) solution. There might also be a way to do this with socket.io that would keep you from having to make another route. I don't really have any experience with it, so I can't really say for sure, but you might give it a look.

As far as using template engines to build your pages goes, I think that as long as you are gathering the data on the server side, it is pretty acceptable to use your template engine to insert the data into your pages. If there is some processing to be done that exceeds the ability of your template engine, you can always build your own module to do the processing and return the processed data to your template engine for rendering. At any rate, I'm glad you found a solution that seems to be working!

small version: 1) app.js on server -> res.render('index', { workflowData_server: JSON.stringify(workflowData) }); 2) index.jade on client -> script var workflowData_client = !{workflowData_server} 3) angular.js on client -> this.workflowData = workflowData_client;

I had the same implementation in pug-php. Was wondering why it wasn't working for me in node with jade until I caught myself doing !#{myData} (had the pound sign included). Anyways, Stefan 's answer is correct and still correct to this day. Example markup for those wanting to see the code in visual code blocks:

Back-end

 1  // Define a variable to store data. I'm going to use response.body
 2  // in this example because I like to return the response.body.
 3  response.body = {};
 4
 5  // Store the data into a property on response.body (using my_data).
 6  response.body.my_data = JSON.stringify({
 7      hello: "world"
 8  });
 9
10  // Render the response and send the response.body with it so that the
11  // Jade template has access to the response.body variables. In this
12  // example, I want Jade to know what my_data is from the response.
13  response.render('my_template', response.body);
  • Line 6 does the JSON.stringify call on my_data which prevents Jade from rendering the JSON as [object Object]. Instead, Jade will render the JSON as {&quot;hello&quot;:&quot;world&quot;}.

Front-end (my_template)

html
    head
        title Test Application
    body
        script.
            var myData = !{my_data};
            // => Outputs {"hello":"word"}
            //      It does not output [object Object]
  • The variable my_data is prefixed with !. This tells Jade to convert {&quot;hello&quot;:&quot;world&quot;} to {"hello":"world"}.

1)On the server side javascript file you put the object as a JSON string on the request:-> res.render('index', { workflowData_server: JSON.stringify(workflowData) });

2) on the template view file,for example index.jade file you put script var workflowData_client = !{workflowData_server} the ! is needed to unescape it to html 3) now you can use the workflowData_client on your client javascript file,for example a public/angular.js file angular.module('TestApp', []); angular.module('TestApp') .controller('MainController', ctrlFunc);

function ctrlFunc() { this.workflowData = workflowData_client; }