rendrjs / rendr-app-template

Basic skeleton for a Rendr app. Deprecated in favor of ./examples dir in rendr repo.
MIT License
285 stars 83 forks source link

Injecting data coming from an asynchronous call into __layout.hbs #24

Closed adeleinr closed 11 years ago

adeleinr commented 11 years ago

Is there a way to inject data from an asynchronous call into __layout.hbs? For example the data.logged_in below. Basically what I want to do is show the Login/Logout user state in the __layout.hbs so I make an asynchronous call to check if the user is logged in or not. I do this in the base view so that I have access to this.app to get the cookies info, and also so that all the views can share the data.

BaseView.postInitialize = function() {
  if (global.isServer) {
    var that = this;
    var url = 'http://localhost:5000/api/authentication/is_logged_in';
    var superagent = require('superagent');
    var http = superagent.agent();
    var handler = http.get(url);
    var request = handler.set('Cookie', this.app.req.headers.cookie); // Need to pull data from this.app
    var callback = function(res) {
      var data = JSON.parse(res.text);
      console.log(data.logged_in); // Make data.logged_in global to __layout.hbs
    };
    request.end(callback);
  }
};
spikebrehm commented 11 years ago

This is the perfect use case for Express middleware. Remember, a Rendr app is simply an Express app.

// server/middleware/checkCurrentUser.js
var superagent = require('superagent');

module.exports = function(req, res, next) {
  // You can always access `app` from the `req` as `rendrApp`.
  var app = req.rendrApp;

  var url = 'http://localhost:5000/api/authentication/is_logged_in';
  var http = superagent.agent();
  var handler = http.get(url);
  var request = handler.set('Cookie', req.headers.cookie);
  request.end(function(res) {
    var data = JSON.parse(res.text);
    // Set 'logged_in' on the app, which you can access in __layout.hbs as `_app`.
    app.set('logged_in', data.logged_in);
    next();
  });
};

__layout.hbs:

...
{{#if _app.attributes.logged_in}}
  ...
{{else}}
  ...
{{/if}}
...

But it makes me realize it would be good to expose app so you can do something like app.logged_in instead of _app.attributes.logged_in.

adeleinr commented 11 years ago

req.rendrApp is not defined when the checkCurrentUser middleware get executed, I'm adding the checkCurrentUser middleware after the errorHandler middleware.

// set the middleware stack
app.use(express.compress());
app.use(express.static(__dirname + '/../public'));
app.use(express.logger());
app.use(express.bodyParser());
app.use(app.router);
app.use(mw.errorHandler());
app.use(mw.checkCurrentUser());

Where should I include the checkCurrentUser middleware?

spikebrehm commented 11 years ago

Sorry for not being more clear -- notice lower in the file the part about preRendrMiddleware? These are middleware that just run before Rendr routes & API calls. Here you'll see the initApp middleware:

// Insert these methods before Rendr method chain for all routes, plus API.
var preRendrMiddleware = [
  // Initialize Rendr app, and pass in any config as app attributes.
  rendrMw.initApp(env.current.rendrApp)
];

function buildApiRoutes(app) {
  var fnChain = preRendrMiddleware.concat(rendrMw.apiProxy());
  fnChain.forEach(function(fn) {
    app.use('/api', fn);
  });
}

function buildRendrRoutes(app) {
  var routes, path, definition, fnChain;
  // attach Rendr routes to our Express app.
  routes = rendrServer.router.buildRoutes();
  routes.forEach(function(args) {
    path = args.shift();
    definition = args.shift();

    // Additional arguments are more handlers.
    fnChain = preRendrMiddleware.concat(args);

    // Have to add error handler AFTER all other handlers.
    fnChain.push(mw.errorHandler());

    // Attach the route to the Express server.
    app.get(path, fnChain);
  });
}

If you add your mw.checkCurrentUser() middleware after initApp, then you can access req.rendrApp:

var preRendrMiddleware = [
  rendrMw.initApp(env.current.rendrApp),
  mw.checkCurrentUser
];

If you want to access your user information in other, non-Rendr parts of your Express app, you'll want to do it slightly differently -- probably adding the information you want as a property on req, let's say req.user, and then you would add a little middleware in the preRendrMiddleware stack to pull the information from req.user and set it on req.rendrApp.set('user', req.user) so it's available in the client-side. Does that make sense?

adeleinr commented 11 years ago

Ah, thanks for clarifying that. It does work. Actually I had tried that because I saw that initApp was adding the app to req.rendrApp but it was failing because of my mistake in another part of the code! So this works for hard page refreshes but not for client (purely ajax calls) from view to view since _layout.hbs only gets refreshed on page reload. I will try the other trick of adding the req.user to see if I can get it to work on the client side as well. Thanks so much!!

adeleinr commented 11 years ago

One more question, {{_app.attributes.logged_in}} is exposed in the views, but it doesn't get updated even though the requests are passing though the checkCurrentUser middleware and req.rendrApp.set('logged_in', data.logged_in) is being set again, are you exposing this variable differently in the view?