I am building a small app primarily with socket io, however with a few things from expressjs.
One function of the socket io piece is to send an email when a certain event occurs. I've got this working fine with node_mailer.
The problem I'm running into is that I want to use the express view engine to render the emails from template files. The render method seems to be explicitly attached to the res object prototype.
What I've done feels pretty dirty:
// setup express server
var render;
app.get('/', function (req, res) {
if (typeof render == 'undefined') render = res.render;
res.end('Welcome to app');
});
// socket io code
socket.on('event', function (data) {
var email_content;
render('template', {}, function (err, result) { email_content = result; });
});
Is there a better way to gain access to expressjs's components outside the context of an http request, or even a better way to approach this problem? I tried rigging up a call to the exported express.view.compile function but that both didn't work and seemed like a high hoo
Here is where the information you seek comes from:
https://github.com/Ravelsoft/node-jinjs/wiki
With templates as modules
To have node load your templates as if they were modules, you first have to register your module extension :
require("jinjs").registerExtension(".tpl");
If you want your file to be transformed prior to being submitted to jinjs, you can pass a callback ;
var pwilang = require("pwilang");
require("jinjs").registerExtension(".pwx", function (txt) {
return pwilang.parse(txt);
});
You can now write this to user Jin:
var my_template = require("./mytemplate");
var context = { foo: "foo", bar: "bar" };
var result = my_template.render(context);
Because you are sticking Jin into express (as opposed to making express work with Jin) this is your best option. The res variable is only available in the route callback.
On express 3.x there is the alias app.render
// socket io code
socket.on('event', function (data) {
var email_content;
app.render('template', {}, function (err, result) { email_content = result; });
});
Related
I'm working on a word game and am trying to return a random wordpair (my collection) on a page load. I'm using Express and have adapted my code from this tutorial if that's of any use.
A GET request renders my page just fine, and I'm trying to send a random WordPair object alongside the title:
router.get('/', function(req, res, next) {
res.render('play', { title: 'play', random_wordpair: wordpair_controller.wordpair_random});
});
The wordpair_random function is here inside a controller file I've made (which also successfully manages listing the wordpairs and creating new ones etc).
// Get random WordPair
exports.wordpair_random = function() {
WordPair.aggregate(
[{
$sample: {
size: 1
}
}]
)
.exec(function(err, random_wordpair) {
if (err) {
return next(err);
}
console.log(random_wordpair);
return random_wordpair;
});
};
Then inside a play.pug template, I'm simply trying to display this result:
h3 random wordpair selection is: #{random_wordpair}
But all I can see is the function rendered as HTML text. Can anyone tell me what I'm doing wrong?
I also understand looking at the documentation for MongoDB $sample aggregation that I need to be calling my function on the database object, but I've seen various examples and some don't do this. When I try calling db.wordpair.aggregate(...) (or WordPair or wordpairs as it appears in mLab) directly after initializing db in my app.js file, I get undefined errors. My db object doesn't seem to contain the correct data for this request.
Thanks!
I guess you're writing this in Node.JS. A core feature in Node.JS is non-blocking IO model. That means, the code won't wait for a database call to complete to move on.
Another concept you need to get it right is that Node.JS, being a variation of JavaScript, in nature is a functional programming. Assigning a function to a property of a JSON object like below won't cause the function to execute. It simply creates a pointer to the function body, that's why your application prints the function itself.
{ title: 'play', random_wordpair: wordpair_controller.wordpair_random}
To fix this, use a callback
exports.wordpair_random = function(callback) {
WordPair.aggregate([$sample: {size: 1}}]).exec(callback);
};
Then in you web function:
router.get('/', function(req, res, next) {
wordpair_controller.wordpair_random(function(err, result) {
//Handle errors if needed.
res.render('play', { title: 'play', random_wordpair:result });
})
});
I am using Redux and trying to make a call to Facebook API with their JS SDK. I've only ever used promises with Redux and so since the method FB.getLoginStatus just returns a simple JS object, I'm not sure how to ensure that the payload doesn't return undefined.
With redux-promise, you add it to the applyMiddleware(ReduxPromise)... and then it ensures nothing is returned until the promise resolves. But I don't know how to do that here.
I've also used async/await functions with React Native without an issue, but I tried using them here and for some reason the code still returns the payload, before the asynchronous request (await ...) is finished. So I tried working with redux-await, but couldn't get it to work.
export function getLoginStatus() {
var res = FB.getLoginStatus(function(res) {
console.log(res);
});
console.log("res ", res);
return {
type: GET_LOGIN_STATUS,
payload: res
}
}
Hm, things can get a little tricky as I've not used redux-promise. And I can't tell exactly what else you have tried. But this would be my first shot:
async function _getLoginStatus() {
var payload = new Promise( (resolve, fail) => {
FB.getLoginStatus((res)=>resolve(res));
});
return {
type: GET_LOGIN_STATUS,
payload: payload
}
}
// Last time I exported an async function I needed this HYMMV
export let getLoginStatus = _getLoginStatus;
And then elsewhere in the code:
import {getLoginStatus} from 'whatever.js';
var payloadResult = await getLoginStatus();
I am trying to send a request parameter through to an 'exports' method for a mongodb find in an express.js, backbone.js application. I am having a difficult
time getting the parameters to pass through to mongodb and with '#'.
The breakage is the passing of parameters into the exported mongodb function.
Here is the flow of data:
First the request is successfully routed to the 'upcoming' function:
"upcoming/uni/:uni" : "upcoming",
It flows on to the 'upcoming' function without a problem.
upcoming: function(uni) {
console.log("uni: "+uni);
pag.reset();
console.log("Hit upcoming list target");
setCollectionType('upcoming');
var upcomingCourses = buildCollection();
// ------------------------------------------------------------------------
// here is the problem how do I pass the parameter value through the fetch?
// Although it may also have to do with '#' please read on.
// ------------------------------------------------------------------------
upcomingCourses.fetch({success: function(){
$("#content").html(new ListView({model: upcomingCourses, page: 1}).el);
}});
this.headerView.selectMenuItem('home-menu');
},
The routing for the mongo methods is:
app.get('/upcoming/uni/:uni', mongomod.findUpcoming);
So the following method is exported from the mongodb js file and is executed reliable. However the req.params are not passed through.
Interspersed in the code I have described its' runtime behaviour:
exports.findUpcoming = function(req, res) {
console.log("university", req.params.uni); // This consistently is unpopulated
var uni = req.params.uni;
console.log("Size: "+req.params.length); // This will always be 0
for (var i=0; i < req.params.length; i++) {
console.log("Parameters: "+req.params[i]);
}
db.collection('upcoming', function(err, collection) {
if (typeof uni === 'undefined') {
console.log("The value is undefined");
uni = "Princeton University"; // here we add a string to test it it will work.
}
collection.find({university:uni}).toArray(function(err, items) {
if (err) {
console.log("Error: "+err);
} else {
console.log("No Error");
console.log("Count: "+items.length);
console.log(items[0]['university']);
res.send(items);
}
});
});
};
On additional and important note:
The url, in a working, runtime environment would be:
http://localhost:3000/#upcoming/uni/Exploratorium
This one fails, but the following URL will work in passing the params through these functions however it returns the JSON to the screen rather then
the rendered version:
http://localhost:3000/upcoming/uni/Exploratorium
The problem could be a miss understanding of # and templates. Please, if you see the error enlightenment would be greatly appreciated.
Nothing after the # gets passed to the server. See How to get hash in a server side language? or https://stackoverflow.com/a/318581/711902.
I found a solution to the problem of passing the parameters from the client side to the server side. By changing the url of the collection the parameters will be passed to the server side:
upcomingCourses.url = "/upcoming/uni/"+uni; // <-- here's the ticket where uni is param
upcomingCourses.fetch({success: function(){
$("#content").html(new ListView({model: upcomingCourses, page: 1}).el);
}});
This can be made more elegant but it is a way to pass the parameters on to the server.
Thanks
Everything I can find for rending a page with mongoose results says to do it like this:
users.find({}, function(err, docs){
res.render('profile/profile', {
users: docs
});
});
How could I return the results from the query, more like this?
var a_users = users.find({}); //non-working example
So that I could get multiple results to publish on the page?
like:
/* non working example */
var a_users = users.find({});
var a_articles = articles.find({});
res.render('profile/profile', {
users: a_users
, articles: a_articles
});
Can this be done?
You're trying to force a synchronous paradigm. Just does't work. node.js is single threaded, for the most part -- when io is done, the execution context is yielded. Signaling is managed with a callback. What this means is that you either have nested callbacks, named functions, or a flow control library to make things nicer looking.
https://github.com/caolan/async#parallel
async.parallel([
function(cb){
users.find({}, cb);
},
function(cb){
articles.find({}, cb);
}
], function(results){
// results contains both users and articles
});
I'll play the necromancer here, as I still see another, better way to do it.
Using wonderful promise library Bluebird and its promisifyAll() method:
var Promise = require('bluebird');
var mongoose = require('mongoose');
Promise.promisifyAll(mongoose); // key part - promisification
var users, articles; // load mongoose models "users" and "articles" here
Promise.props({
users: users.find().execAsync(),
articles: articles.find().execAsync()
})
.then(function(results) {
res.render('profile/profile', results);
})
.catch(function(err) {
res.send(500); // oops - we're even handling errors!
});
Key parts are as follows:
Promise.promisifyAll(mongoose);
Makes all mongoose (and its models) methods available as functions returning promises, with Async suffix (.exec() becomes .execAsync(), and so on). .promisifyAll() method is nearly-universal in Node.JS world - you can use it on anything providing asynchronous functions taking in callback as their last argument.
Promise.props({
users: users.find().execAsync(),
articles: articles.find().execAsync()
})
.props() bluebird method takes in object with promises as its properties, and returns collective promise that gets resolved when both database queries (here - promises) return their results. Resolved value is our results object in the final function:
results.users - users found in the database by mongoose
results.articles - articles found in the database by mongoose (d'uh)
As you can see, we are not even getting near to the indentation callback hell. Both database queries are executed in parallel - no need for one of them to wait for the other. Code is short and readable - practically corresponding in length and complexity (or rather lack of it) to wishful "non-working example" posted in the question itself.
Promises are cool. Use them.
The easy way:
var userModel = mongoose.model('users');
var articleModel = mongoose.model('articles');
userModel.find({}, function (err, db_users) {
if(err) {/*error!!!*/}
articleModel.find({}, function (err, db_articles) {
if(err) {/*error!!!*/}
res.render('profile/profile', {
users: db_users,
articles: db_articles
});
});
});
Practically every function is asynchronous in Node.js. So is Mongoose's find. And if you want to call it serially you should use something like Slide library.
But in your case I think the easiest way is to nest callbacks (this allows f.e. quering articles for selected previously users) or do it completly parallel with help of async libraries (see Flow control / Async goodies).
I have a function that I use quite a bit as a return to Node functions.
function freturn (value, callback){
if(callback){
return callback(value);
}
return value;
};
Then I have an optional callback parameter in all of the signatures.
I was dealing with a very similar thing but using socket.io and DB access from a client. My find was throwing the contents of my DB back to the client before the database had a chance to get the data... So for what it's worth I will share my findings here:
My function for retrieving the DB:
//Read Boards - complete DB
var readBoards = function() {
var callback = function() {
return function(error, data) {
if(error) {
console.log("Error: " + error);
}
console.log("Boards from Server (fct): " + data);
}
};
return boards.find({}, callback());
};
My socket event listener:
socket.on('getBoards', function() {
var query = dbConnection.readBoards();
var promise = query.exec();
promise.addBack(function (err, boards) {
if(err)
console.log("Error: " + err);
socket.emit('onGetBoards', boards);
});
});
So to solve the problem we use the promise that mongoose gives us and then once we have received the data from the DB my socket emits it back to the client...
For what its worth...
You achieve the desired result by the following code. Hope this will help you.
var async = require('async');
// custom imports
var User = require('../models/user');
var Article = require('../models/article');
var List1Objects = User.find({});
var List2Objects = Article.find({});
var resourcesStack = {
usersList: List1Objects.exec.bind(List1Objects),
articlesList: List2Objects.exec.bind(List2Objects),
};
async.parallel(resourcesStack, function (error, resultSet){
if (error) {
res.status(500).send(error);
return;
}
res.render('home', resultSet);
});
I'm using "express" and "cradle" in "nodejs". If I request my database I have to define a callback to handle the response. Unfortunately I have no access to res (response) in my callback function. What is the best practice for this problem? Here is my code.
var cradle = require('cradle');
var db = new cradle.Connection().database('guestbook');
app.get('/guestbook', function(req, res) {
db.view('guestbook/all', function(err, doc) {
console.log(doc);
// How can I use res in this callback
// to send the response?
});
});
You can just use res inside the inner callback.
In JavaScript the inner function "inherits" the variables of the outer function. Or more precisely, the function forms a closure, which is an expression that can have free variables. The closure binds the variables from its outer scope, which can be the scope of another function or the global scope.
You may try this.
Most important (perhaps your pitfall?) keep in mind that 'db.view' will mereley register a callback closure and continue. Do not close your request (by calling 'req.end') anywhere outside this closure. If you do, quite likely the request have been closed as the db returns. Once the http response object is closed any data written to it goes void.
var cradle = require('cradle');
var db = new cradle.Connection().database('guestbook');
app.get('/guestbook', function(req, res) {
// Register callback and continue..
db.view('guestbook/all', function(err, guests) {
// console.log('The waiting had an end.. here are the results');
guests.forEach(function(guest) {
if (guest.name) {
res.write('Guest N: ' + guest.name);
}
});
// Close http response (after this no more output is possible).
res.end('That is all!')
});
console.log('Waiting for couch to return guests..');
// res.end('That is all!'); // DO NOT DO THIS!!!
});
With this snippet you really should have access to res here. You should be able to use res.render() or res.send() because the db callback is wrapped in the closure of the app.get callback function.