2

My question is how to make sure my variables get loaded before the page gets rendered ? I use node.js with express and everyauth.

This is the code:

app.get('/', function(req, res) {
   app.locals.rep = global.repos;
   res.render('index'); 
});

Currently, the page gets loaded, but the paragraph containing the variable is empty and I get an error: "Cannot read property '...' of undefined [...]". As I refresh the page, the content flows in.

index.jade

extends layout
block content
  h1= title
  - if (!everyauth.loggedIn)
    a(href="/auth/github") Login with github
  - else
    h3
      a(href='/logout') Logout
      h3 GitHub User Data
      - each r in rep
        p= r.name
        p= r.description

This is where I set the repos variable:

var repos;
global.repos = [];

everyauth.github
.appId(config.gh_clientId)
.appSecret(config.gh_secret)
.findOrCreateUser( function (sess, accessToken, accessTokenExtra, ghUser) {

if (typeof usersByGhId[ghUser.id] === 'undefined') {

  usersByGhId[ghUser.id] = addUser('github', ghUser);

  var options = { 
            host: "api.github.com", 
            path: "/users/cmarius02/repos",
            method: "GET",
            headers: {
               "User-Agent": "github-connect" 
            }
  };

  var request= https.request(options, function(response){
    var body='';
    response.on("data", function(chunk){
      body+=chunk.toString("utf8");
    });

    response.on("end", function(){
      var json=JSON.parse(body);

      // console.log(json);  

      global.repos = [];
      for (var k in json)
        if ({}.hasOwnProperty.call(json, k)) {
          global.repos.push({
            name: json[k].name,
            description: json[k].description
          });
        }
    });
  });
  request.end();
  return usersByGhId[ghUser.id];

} else {
  return usersByGhId[ghUser.id];
}   })

.redirectPath('/');

It's my first day of node.js so please be pacient. Thank you in advance.

Marius Cotofana
  • 1,149
  • 1
  • 11
  • 14
  • What is `global.repos`? Show where it's defined. You would also typically set `app.locals` when you configure the server before startup. – Ryan Endacott Jul 02 '13 at 19:28
  • Could you also post your view? – Ryan Endacott Jul 02 '13 at 19:34
  • If I were you, I would add some logging throughout the GitHub API call and then see if it was being called before or after the first request to your server. That should help you determine where the issue is taking place. Report back with your findings and I'll try to help further :) Are you sure that the load repos function is being called on first load of the server? If no one is logged in? – Ryan Endacott Jul 02 '13 at 20:12
  • The GitHub API request is done only when the login button is pressed, if that is what you were asking. A console.log() confirmed that. – Marius Cotofana Jul 03 '13 at 09:07

2 Answers2

1

I use passport instead of everyauth, but I think the approach should be similar

Express uses middleware to chain up code, and each middleware is responsable of calling the next one (even if they dont know which is it)

I do something like this:

    app.all ('*', function (req,res,next) {
      // Add the user from the session
      if req.user?
          res.locals.session_user = req.user
      // Add the flash messages
      res.locals.flash_messages = req.flash()

      next()
    });

    app.get('/', function(req, res) {
      res.render('index'); 
    });

The app.all means that the route will match get, put and post request. The * is a regexp that matches all routes, so the first callback will be called before all your routes

req.user is filled by passport, I imagine everyauth has something similar.

res.locals is the one that allows you to pass information to the view. Then, in the view, you can just reference the element as this.session_user (I dont use jade, but you get the point).

Also note that with the same approach, I pass flashes between requests to the view.

The order is important!. And next() assures you that your actual route wont be handled until the previous one hasnt resolved. If you need, you can call next inside another callback in that method.

Hernan Rajchert
  • 896
  • 7
  • 19
  • It reacts in the same way. It looks like it does the variable setting and rendering in parallel. Isn't there any way to bound the two commands ? – Marius Cotofana Jul 02 '13 at 23:01
  • I have also added 'while (app.locals.z != 1) { app.locals.rep = global.repos; app.locals.z = 1; } ' before 'res.render(...)', but still no result. – Marius Cotofana Jul 02 '13 at 23:33
  • Ive read around that everyauth has the options to use promises... The aproach I gave you is kind of syncronic. The second route wont be called until the firstone calls next(), but maybe req.user is not an object but the promise to an object. Check what the value is doing console.log typeof req.user and things alike. For the promises, here is a link http://stackoverflow.com/questions/8761230/whats-everyauth-promise – Hernan Rajchert Jul 03 '13 at 06:12
  • Thx Hernan. But I have decided to switch to using passport. Can I have a link to your project ? – Marius Cotofana Jul 04 '13 at 09:31
  • Sadly Marius, its not an open source project, but I extracted the necesary parts with some hints of how I order my code in this gist (dunno if good or bad, but it works for me). Have any doubt, just ask :) https://gist.github.com/hrajchert/5935329 – Hernan Rajchert Jul 05 '13 at 15:37
0

By using Express middleware, you can override render by doing:

  app.use(function(req, res, next) {
    // Grap render
    var _render = res.render;

    // Override logic
    res.render = function(view, options, cb) {
      // Add logic (Example)
      if (!options.foo) {
        options.bar = true
      }

      // Original call
      _render.call(this, view, options, cb)
    }
    next()
  })
Arthur
  • 4,870
  • 3
  • 32
  • 57