2

I've created an external file that's handling the dynamic helpers, which includes a bunch of functions that I want to be able to use from all my views.

In on of the functions I'm running a query and fetch tags from the database, which I want to use in my layout.jade file (that all other views is using). In the console, all seems ok. The query returns the tags object, but by some strange reason I can't return the object to the view, which gives me an error message telling me that "tags" is undefined".

This is the code in my file dynamicHelpers.js:

exports.tags = function(req, res){
    var environment = require('../environment');
    var service = require('../service');
        service.init(environment);

    var model = service.useModel('tag');

    var query = model.Tag.find({});
    query.exec(function (err, tags) {
        if (err) {
            console.log(err);
            // do something
        }
        console.log(tags);
        return tags;
    });
}

in layout.jade i'm getting the object in this way (this shows undefined):

p
    #{tags}

What I actually want to do is to loop through all the tags with 'each'. So a follow-up issue would be if this is allowed and possible (if 'tags' were not undefined)?

ul.tags
    each tag in #{tags}
        a(href='/tag/' + tag._id.toHexString())
            li.tag= tag.name

UPDATE: I've tried @Linus G Thiel's solution (see below), but can't get it to work (res.tags gets undefined). With console.log(tags) in the middleware, it prints out the object. However, in the dynamicHelper function below it gets undefined. Seems like res.tags doesn't get passed to the dynamicHelper by some reason.

dynamicHelpers.js:

exports.tags = function(req, res) {
    console.log(req.tags); <--- undefined
    return req.tags;
};

configuration.js:

module.exports = function(app, express, next){
    app.configure(function() {
        // lots of more app.use functions (eg. express.cookieParser());)
        // ...

        app.use(function(req, res, next) {
            var environment = require('./environment');
            var service = require('./service');
                service.init(environment);

            var model = service.useModel('tag');

            var query = model.Tag.find({});
            query.exec(function (err, tags) {
                if (err) {
                    return next(err);
                }
                req.tags = tags;
                next();
            console.log(req.tags); <--- works fine
            });
        });
    });
};
holyredbeard
  • 19,619
  • 32
  • 105
  • 171

3 Answers3

4

As @Exploit says in his comment, your tags function makes an async call to query.exec, which will finish after your tags function has returned. Express' helpers and dynamicHelpers can't be async, so you need to refactor this somehow. One way would be to put it on req in an earlier middleware or route, and then have a simple dynamicHelper which returns that:

dynamicHelpers.js:

exports.tags = function(req, res) {
    return req.tags;
};

Middleware (you might not want to do this for all routes, you could look into e.g. Route specific middleware or Route param pre-conditions):

app.use(function(req, res, next) {
    var environment = require('../environment');
    var service = require('../service');
        service.init(environment);

    var model = service.useModel('tag');

    var query = model.Tag.find({});
    query.exec(function (err, tags) {
        if (err) {
            return next(err);
        }
        req.tags = tags;
        next();
    });
});
Community
  • 1
  • 1
Linus Thiel
  • 38,647
  • 9
  • 109
  • 104
  • Thanks! I've tried the code above, but I get undefined on req.tags? – holyredbeard Apr 20 '12 at 22:37
  • And you use the middleware before the route you're hitting? – Linus Thiel Apr 20 '12 at 23:20
  • Yeah, I think so at least. Please look at my now updated question that explains how it's now implemented. Maybe I've done something wrong? – holyredbeard Apr 21 '12 at 12:27
  • Try doing a `console.log` in the route you're testing, and see which log statement comes first. Might be the case that it fetches tags after responding. – Linus Thiel Apr 21 '12 at 13:46
  • Iv'e tested it now, and it runs the code in the dynamicHelpers before the code in the middleware runs, ie. they run in the wrong order and therefore req.tags gets undefined. Do you know how this can be solved? – holyredbeard Apr 21 '12 at 14:15
  • You need to `app.use` the middleware before you mount any routes. – Linus Thiel Apr 22 '12 at 10:58
  • 2
    @JasonCraig -- if indeed you want to solve this: If you have a line `app.use(app.router)`, make sure that this comes _after_ the middleware in my example. If you don't, make sure you call your `configuration` module _before_ you do any `app.get`, `app.post` and so on. – Linus Thiel Apr 24 '12 at 14:35
  • I'll try it asap. Thanks a lot for your time and efforrt! – holyredbeard Apr 24 '12 at 22:11
1

It's kinda dirty but you can do this to wait for tags to be set. The solution Linus G Thiel provided is better though because this will block your application.

tags: function(req, res) {
    var environment = require('../environment');
    var service = require('../service');
    service.init(environment);

    var model = service.useModel('tag');
    var query = model.Tag.find({});
    var _tags = false;

    query.exec(function (err, tags) {
        if (err) {
            console.log(err);
            // do something
        }

        console.log(tags);
        _tags = tags;
    });

    while(_tags == false);

    return _tags;
}

Have you tried it like this?

exports = function(app) {
    app.dynamicHelpers({
        tags: function(req, res) {
            var environment = require('../environment');
            var service = require('../service');
            service.init(environment);

            var model = service.useModel('tag');

            var query = model.Tag.find({});
            query.exec(function (err, tags) {
            if (err) {
                console.log(err);
                // do something
            }
            console.log(tags);
            return tags;
            });
        }
    });
}

require("helpers")(app);
Exploit
  • 108
  • 1
  • 9
  • Thanks for answering. The thing is that the dynamicHelper works fine, so it's not a "require" problem. It's just that the object itself don't seem to be returned.:/ – holyredbeard Apr 20 '12 at 09:58
  • 2
    That's probably because query.exec is async and the function returns before the query has finished. – Exploit Apr 20 '12 at 10:46
  • The thing is that the object is printed out fine with console.log(tags), before it returns. – holyredbeard Apr 20 '12 at 22:19
  • @JasonCraig it will show in your log statement, but that's after your `tags` function has returned, because `query.exec` is asynchronous. – Linus Thiel Apr 20 '12 at 23:21
1

You need to defined the middleware before the other routes

module.exports = function(app, express, next){
    app.configure(function() {

        app.use(function(req, res, next) {
            var environment = require('./environment');
            var service = require('./service');
                service.init(environment);

            var model = service.useModel('tag');

            var query = model.Tag.find({});
            query.exec(function (err, tags) {
                if (err) {
                    return next(err);
                }
                req.tags = tags;
                next();
            console.log(req.tags); <--- works fine
            });
        });

        // lots of more app.use functions (eg. express.cookieParser());)
        // ...

    });
};
250R
  • 35,945
  • 7
  • 33
  • 25