How to override flash messages in express.js using dynamic helpers? - node.js

I have been working through the tutorial by Alex Young on using flash messages. According to this tutorial, one is able to override the default flash messages formatting using dynamicHelpers. Unfortunately there are no details provided on what is going on, and it is not possible to post any comments on the tutorial page to ask relevant questions.
What I fail to see is the relationship between the call to req.flash() in the file 'app.js', and the FlashMessage object exported in file 'helpers.js'. Why would a regular call to req.flash(), which is a standard function in express.js, link to this FlashMessage prototype in the first place? I can't see how this is happening when I look at the code.
At first I thought the FlashMessage object might have been provided to req.flash() by express.js, in which case we are just extending or overriding it in our helper file. The problem with this is that I cannot find any reference to FlashMessage in the source code of express.js.
I would be really grateful if someone could explain it to me.
The flash message is set in file 'apps.js' by calling:
req.flash('info', 'Document created.');
The FlashMessage obect is exported 'helpers.js':
FlashMessage.prototype = {
// Get css definition string for icon.
get icon() {
switch (this.type) {
case 'info':
return 'ui-icon-info';
case 'error':
return 'ui-icon-alert';
}
},
// Get css class for message container.
get stateClass() {
switch (this.type) {
case 'info':
return 'ui-state-highlight';
case 'error':
return 'ui-state-error';
}
},
// Returns HTML formatted message.
toHTML: function() {
return '<div class="ui-widget flash">' +
'<div class="' + this.stateClass + ' ui-corner-all">' +
'<p><span class="ui-icon ' + this.icon + '"></span>' + this.messages.join(', ') + '</p>' +
'</div>' +
'</div>';
}
};
exports.dynamicHelpers = {
flashMessages: function(req, res) {
var html = '';
['error', 'info'].forEach(function(type) {
var messages = req.flash(type);
if (messages.length > 0) {
html += new FlashMessage(type, messages).toHTML();
}
});
return html;
}
};
In app.js file the full routing function which calls req.flash is as follows:
// Attach dynamicHelpers to app.
app.dynamicHelpers(require('./helpers.js').dynamicHelpers);
// Routing function which calls req.flash.
app.post('/documents', loadUser, function(req, res) {
// Create Document object and assign value.
console.log('Document content: %s', req.body['document']);
var document = new Document(req.body['document']);
document.save(function() {
// Redirect to another page.
req.flash('info', 'Document created.');
res.redirect('/documents');
}
});
});

There are 2 different things here:
a) req.flash() which is implemented by Express itself - not by you, you're just using that function
b) your dynamic helper:
From the Express guide:
Dynamic view helpers are simply functions which accept req, res, and
are evaluated against the Server instance before a view is rendered.
The return value of this function becomes the local variable it is
associated with.
app.dynamicHelpers({
session: function(req, res){
return req.session;
}
});
Let's "translate" that into your code:
// Attach dynamicHelpers to app.
app.dynamicHelpers(require('./helpers.js').dynamicHelpers);
That means that when you call the variable flashMessages in your view code, you'll get the html representation of those flash variables defined.
So the most important thing here is to consider you are only using req.flash(), not implementing it. You are implementing a helper that is using that function.

Related

Nodejs + Handlebars - Pull current url path and pass to Helper

I am trying to create a meta tag handlebars helper that grabs the url pathname and uses that value for a switch statement which will then return the string to my meta tag in HTML head, but I am not sure the best way to currently grab the url path for my switch statement. I tried window.location.pathname, but get an error that window is not defined. I know that the path module requires you to pass something to it to parse out, but I'm not sure what the best value would be for that. Can anyone help me?
Here is my handlebars helper file:
var path = require('path');
var metaHelpers = function(hbs) {
hbs.registerHelper('metaTitle', function(){
var urlPath = path.dirname();
console.log(urlPath);
switch(urlPath) {
case "/": {
return 'Index Test'
}
break;
case "/login": {
return 'Login Test'
}
break;
default: {
return 'No Meta Tag'
}
}
});
};
module.exports = metaHelpers;
Since your templates are executed on the server there is no window object to query. Instead you must get the URL path from the current Request object. Fortunately, in Express the Request object has a path property.
However in order to implement your solution as a Handlebars helper you would need to find a way to pass the Request object (or at least its .path) to the helper from the view. I think a better solution would be to execute your helper logic and construct the title before the response is rendered.
Express has the concept of middleware which are functions that can modify the Request and Response objects on a per request basis. We could write a middleware function to construct the title for each request and then add it to the locals property of the Response object. Properties of the res.locals object will be available to the view(s) rendered for the current response.
app.use(function (req, res, next) {
switch (req.path) {
case '/':
res.locals.title = 'Index Test';
break;
case '/login':
res.locals.title = 'Login Test';
break;
default:
res.locals.title = 'No Meta Tag';
}
next();
});
In our layout, we can access the title property as we would any other property of our view model.
<title>{{title}}</title>
For reference, this answer provides a similar solution, except that it assigns the req object to the res.locals.

how to publish a page using node.js

I have just begun to learn node.js. Over the last two days, I've been working on a project that accepts userinput and publishes a ICS file. I have all of that working. Now consider when I have to show this data. I get a router.get to see if I am at the /cal page and..
router.get('/cal', function(req, res, next)
{
var db = req.db;
var ical = new icalendar.iCalendar();
db.find({
evauthor: 'mykey'
}, function(err, docs) {
docs.forEach(function(obj) {
var event2 = ical.addComponent('VEVENT');
event2.setSummary(obj.evics.evtitle);
event2.setDate(new Date(obj.evics.evdatestart), new Date(obj.evics.evdateend));
event2.setLocation(obj.evics.evlocation)
//console.log(ical.toString());
});
});
res.send(ical.toString());
// res.render('index', {
// title: 'Cal View'
// })
})
So when /cal is requested, it loops through my db and creates an ICS calendar ical. If I do console.log(ical.toString) within the loop, it gives me a properly formatted calendar following the protocol.
However, I'd like to END the response with this. At the end I do a res.send just to see what gets published on the page. This is what gets published
BEGIN:VCALENDAR VERSION:2.0
PRODID:calendar//EN
END:VCALENDAR
Now the reason is pretty obvious. Its the nature of node.js. The response gets sent to the browser before the callback function finishes adding each individual VEVENT to the calendar object.
I have two related questions:
1) Whats the proper way to "wait" till the callback is done.
2) How
do I use res to send out a .ics dynamic link with
ical.toString() as the content. Do I need to create a new view for
this ?
edit: I guess for number 2 I'd have to set the HTTP headers like so
//set correct content-type-header
header('Content-type: text/calendar; charset=utf-8');
header('Content-Disposition: inline; filename=calendar.ics');
but how do I do this when using views.
Simply send the response, once you got the neccessary data! You are not required to end or send directly in your route but can do it in a nested callback as well:
router.get('/cal', function(req, res, next) {
var db = req.db;
var ical = new icalendar.iCalendar();
db.find({
evauthor: 'mykey'
}, function(err, docs) {
docs.forEach(function(obj) {
var event2 = ical.addComponent('VEVENT');
event2.setSummary(obj.evics.evtitle);
event2.setDate(new Date(obj.evics.evdatestart), new Date(obj.evics.evdateend));
event2.setLocation(obj.evics.evlocation)
});
res.type('ics');
res.send(ical.toString());
});
});
I also included sending the proper Content-Type by using res.type.
Also: Don't forget to add proper error handling. You can for example use res.sendStatus(500) if an error occured while retrieving the documents.

jsdom.jsdom missing a "createWindow" method

I am trying to use jsdom to analyze some html content. Examples that i have seen use the .createWindow() method of a jsdom.jsdom document based on html content.
But when I try to follow these examples, my document does not have a .createWindow() method.
var getaPage=function (req, res, callback) {
jsdom.defaultDocumentFeatures={
FetchExternalResources : ['script'],
ProcessExternalResources : ['script'],
MutationEvents : '2.0',
QuerySelector : false
};
var htmlDoc = '<html lang="en-US">' +
'</html>';
var tstDocument=jsdom.jsdom(htmlDoc);
for (var attr in tstDocument){
if (attr == 'createWindow') {
console.log('Found it');
}else{
console.log('not it');
};
};
};
When I run this, I get a bunch of 'not it' and no 'Found it'.
Why do I not have a .createWindow() method?
jsdom's API changed quite a bit with the 1.0 release. The createWindow() method is from the old API. You should be able to get the document's window just by accessing tstDocument.defaultView, just like you'd do if you were in a browser.

Parameters passed in Express.js middleware gets cached?

I've been building my first node.js app using express.js.
It has been fun :)
I must be having some kind of misconception going, so here goes.
I have some route defined as such:
app.all('/account/summary', apiPOST('AccountSummary', {FromDate: 'default', ToDate: 'default'}), function(req, res){
var data=req.apiJSON;
console.log(data);
res.render('accountsummary', {locals: data, layout: 'layouts/layout'});
});
apiPOST() is defined as such:
apiPOST = function (operation, postParams) {
return function (req, res, next){
console.log('RIGHT AT START');
console.log(postParams);
console.log('END');
var currentDate = new Date();
var day = ('0'+currentDate.getDate()).slice(-2);
var month = ('0'+(currentDate.getMonth() + 1)).slice(-2);
var year = ('0'+currentDate.getFullYear()).slice(-4);
console.log('BEFORE DATES');
console.log(postParams);
if (typeof(postParams.FromDate)!='undefined' && (postParams.FromDate=='default' || postParams.FromDate=='')){
postParams.FromDate=year+'-'+month+'-'+day;
}
if (typeof(postParams.ToDate)!='undefined' && (postParams.ToDate=='default' || postParams.ToDate=='')){
postParams.ToDate=year+'-'+month+'-'+day;
}
//automatically add all posted data to postParams
if (typeof(req.body)!='undefined'){
for (var key in req.body){
if (req.body.hasOwnProperty(key)){
postParams[key]=req.body[key];
}
}
}
// here is do some talking to an XML web service and convert it to JSON;
// we use our postParams variable to POST
next();
}
}
First off this works fine. When reaching the page on a GET request, it defaults both FromDate and ToDate to today. This page has a form that you may post to specify a new FromData and ToDate. the posted data automatically get added to the postParams and that also works fine.
The problem that I am experiencing is that the next time a user visits the page using GET, the previously POSTed data is still around and so it default to that and not to today.
I cannot figure out why that data is still available. By the looks of it, it is not being posted, but rather remembered in postParams. Is postParams now global?
Thanks!
What's happening is that you're calling apiPOST only once, during the app.all call to configure that route and that one call creates the one postParams parameter object that all future invocations of apiPOST's returned function will share.

Node.js: Using multiple moustache templates

I'm trying to break a moustache template up into various components so I can reuse them, and get the assembled text returned through node.js. I cannot find anyone that has done this.
I can return must ache pages imply with:
function index(request, response, next) {
var stream = mu.compileAndRender('index.mu',
{name: "Me"}
);
util.pump(stream, response);
}
I just cannot figure out how to render a template and use it in another template. I've tried rendering it separately like this:
function index(request, response, next) {
var headerStream = mu.compileAndRender('header.mu', {title:'Home page'});
var headerText;
headerStream.on('data', function(data) {
headerText = headerText + data.toString();
});
var stream = mu.compileAndRender('index.mu',
{
heading: 'Home Page',
content: 'hello this is the home page',
header: headerText
});
util.pump(stream, response);
}
But the problem is that the header is not rendered before the page and even if I get that to happen. The header is seen as display text rather than html.
Any help appreciated.
You need to put the lines var stream = ... and util.pump(... in headerStream.on('end', function () { /* here */ });, so that they are ran after the headerStream has sent all the data to your data listener.
To include raw HTML you have to use the triple braces: {{{raw}}} in your template as http://mustache.github.com/mustache.5.html states.
The moustache guys came back with the answer. I can do it by using partials like this:
{{> index.mu}}
Inside the moustache template rather than trying to do it in node.js.

Resources