This is a weird kind of a question but follow me along. I have a nodejs server with express application. Inside the application, I set my locals as follows:
var moment = require('moment');
app.locals.moment = moment;
The ejs is being rendered as:
exports.page = function (req, res) {
res.render('first-page');
};
Then, in my ejs, I have the following code:
<%
if (!moment) {
throw new Error('moment is not defined');
}
function formatDate(date) {
return moment(date).format();
}
%>
<p><%= formatDate(1435856054045); %></p>
The interesting that happens is that moment does not raise the exception. Thus, it is defined in the scope of ejs, just as documentation says. However, an exception is raised by ejs saying that moment is not defined at formatDate. If I change formatDate to the following, everything works.
function formatDate(date) {
return locals.moment(date).format();
}
My question is how are the functions, defined in ejs, are scoped and which context is applied to them. Does ejs apply a different context to the function than to the floating javascript? I'm assuming it does something like formatDateFunctionPointer.call(ejsScope, ...);
The problem becomes clear when you have ejs output the generated function (to which a template is compiled):
with (locals || {}) {
if (!moment) {
throw new Error('moment is not defined');
}
function formatDate(date) {
return moment(date).format();
}
...
}
The problem is that your formatDate function is hoisted to outside the with block; inside that block, moment is actually locals.moment, so your test to see if it exists works.
However, when you can formatDate, it's not run within the context of the with block, and therefore, moment doesn't exist (but locals.moment does, as you already found out).
Here's a standalone example of the problem:
var obj = { test : 123 };
with (obj) {
if (test !== 123) throw new Error('test does not equal 123');
function showTest() {
console.log('test', test);
}
showTest();
}
One way to resolve this is to use a function expression:
<%
if (typeof moment === 'undefined') {
throw new Error('moment is not defined');
}
var formatDate = function(date) {
return moment(date).format();
};
%>
<p><%= formatDate(1435856054045); %></p>
(it also fixes your test to see if moment is actually defined)
Or you can set the EJS _with option to false.
Related
I need to attach if condition in my handlebar template which checks the equality of string. I have registered a handlebar helper in my script file and using that within my templates. Following is my code.
Test.js file
"use strict"
const handlebars = require('handlebars');
const writeSourceFile = (filename, type) =>
new Promise((resolve,reject) =>
fs.writeFile(filename, type, function(err) {
return err ? reject(err) : resolve();
}));
handlebars.registerHelper('is_status', function(msg, matchMsg, options)
{
if(msg === matchMsg)
return true;
else
return false;
});
const tpl = handlebars.compile(fs.readFileSync('resources/my.html.hbs').toString('utf-8'));
fs.writeFileSync('/home/malintha/tracks.html', tpl(dm));
console.log("Generated source")
res.end();
..............
my.html.hbs file
{{#is_status (location this "mylocation")}}yes{{/is_status}}
I am not getting any output due to an error which is not obvious to me. My template is working fine without this custom is_status check.
What is the problem with my helper or template? Appreciate your insight.
Rather than returning true or false in your helper, try returning options.fn(this) or options.inverse(this).
And, try calling it in your .hbs file with
{{#is_status location "mylocation"}}yes{{/is_status}}
Here is an example of this being done in the documentation.
Also here is a really helpful stackoverflow answer of a more flexible way to compare variables.
I am building a small node.js website with a user interface that features a dropdown with a list of countries.
Previously the list of countries was hard coded in a json file that I would read:
exports.countries = require('./json/countries.json');
Then I realized I shouldn't hard code it like that when I can do a distinct query to the the list from the mongodb database.
db.collection.distinct('c', {}, function(err, data) {
// something
});
But then there's the question of how to extract the value of the data variable in that callback function. I discovered that this works:
db.collection.distinct('c', {}, function(err, data) {
if (err) {
throw Error('mongodb problem - unable to load distinct values');
} else {
exports.countries = data;
}
});
I am new to node.js and this seems fishy to me. Is this OK code? Is it better do this with generators or promises? If I wanted to use generators or promises to do this, how would I do that?
The end result where this is used is in a template. ref.countries is the actual list of countries using my fishy code. If I had a Promise instead of the list of countries, how would I change this code?
<% ref.countries.forEach(function(c) { -%>
<option value="<%= c %>">
<%= ref.isoCodes[c] -%>
</option>
<% }); -%>
I am using node v6.10.3.
Your export that you say "works" is impossible to use because the code that loads your module would have no idea when the exports.countries value has actually been set because it is set in an asynchronous call that finishes some indeterminate time in the future. In addition, you have no means of handling any error in that function.
The modern way of doing this would be to export a function that, when called, returns a promise that resolves to the data (or rejects with an error). The code loading your module, then calls that exported function, gets the promise, uses .then() on the promise and uses the data in the .then() handler. That could look something like this:
function getCountries() {
return new Promise(function(resolve, reject) {
db.collection.distinct('c', {}, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
}
}
module.exports.getCountries = getCountries;
The caller would then do something like this:
const myModule = require('myModule');
myModule.getCountries().then(function(countries) {
console.log(countries);
// use country data here
}).catch(function(err) {
// deal with error here
});
Most databases for node.js these days have some level of promise support built in so you often don't have to create your own promise wrapper around your DB functions like was shown above, but rather can use a promise directly returned from the DB engine. How that works is specific to the particular database/version you are using.
If you are using the list of countries in a template rendering operation, then you will have to fetch the list of countries (and any other data needed for the template rendering) and only call res.render() when all the data has been successfully retrieved. This probably also leads to what you should do when there's an error retrieving the necessary data. In that case, you would typically respond with a 5xx error code for the page request and may want to render some sort of error page that informs the end-user about the error.
I am using Node 6.10 so I don't have async and await but if I did they would help me here:
https://developers.google.com/web/fundamentals/getting-started/primers/async-functions
Instead I can use the asyncawait library:
https://github.com/yortus/asyncawait
Code looks like this:
var async = require('asyncawait/async');
var await = require('asyncawait/await');
const db = require('_/db');
function getDistinctValues(key) {
return new Promise((resolve, reject) => {
db.collection.distinct(key, {}, function(err, data) {
if (err) {
throw Error('mongodb problem - unable to load distinct values');
} else {
resolve(data);
}
});
});
};
async(function () {
exports.countries = await(getDistinctValues('c'));
exports.categories = await(getDistinctValues('w'));
})();
Now I can be sure ref.countries and ref.categories are available after this is loaded.
I have the following recursive function saved in a file called helpers.js. When it is loaded into the main app.js file using:
var helpers = require('./helpers');
calling it only works partially. The line:
s+=recurseJSON(o[a]);
doesn't get called so the JSON parsing doesn't recurse into nested levels.
I have also tried the following which still doesn't work:
s+=helpers.recurseJSON(o[a]);
If I move the code below into the main app.js file, the recursion works perfectly, obviously changing
recurseJSON: function(o) {...
to
function recurseJSON(o) {..
Your thoughts are appreciated. Here is the whole code:
module.exports = {
recurseJSON: function(o){
var s = '';
for(var a in o){
if (typeof o[a] == 'object'){
s+=a+':';
console.log('JSON>', a, ":");
s+=recurseJSON(o[a]); // This line should recurse but doesn't
}else{
s+=a+':'+o[a]+' ';
console.log('JSON>', a, ":", o[a]);
}//end if
}//end for
return s;
}
};
PS: Credit to Recursively parsing JSON for the original recursive code.
While leaving the export statement like this:
module.exports = {
recurseJSON: function(o){
...
}
};
You can call the function recursively using the statement s+=this.recurseJSON(o[a]), but only assuming that the only way you invoke the recurseJSON() function outside the file is
helpers.recurseJSON(obj)
so that recurseJSON() is the calling member of helpers, making the this in recurseJSON() refer to helpers.
If you cannot guarantee this, then the correct way to invoke it, which is more verbose, is
s+=module.exports.recurseJSON(o[a])
Update
Another simpler solution is to just name the function you're exporting:
module.exports = {
recurseJSON: function recurseJSON(o){
...
}
};
Then you can just use s+=recurseJSON(o[a]) like you had before.
I am using node.js / express and returning an empty object if no data exists on server (i.e.: res.send({}) on the express side).
However, since I am using Backbone myCollection.fetch(....)
I am still get a model back, only that it's empty.
As in:
_getLines: function () {
var self = this;
self.m_linesCollection.fetch({
success: function (data) {
$(Elements.FASTERQ_CUSTOMER_LINES).empty();
if (_.size(data.models["0"].attributes) == 0)
return;
},
error: function () {
log('error loading collection data');
}
});
}
as you can see I am doing a dirty check on client side via:
if (_.size(data.models["0"].attributes) == 0)
which works fine... to check if no real models came back, but I am sure there is a better way to check if model is empty, or send something else from server side :/ ?
Just seems so trivial... I must be missing something...
thx,
Sean.
It's a matter of preference, but the easiest way around this is to have your server return an empty array instead of an array of empty objects.
Marionettejs is an opinionated Backbone framework. In it, they define the utility function:
isEmpty: function() {
return !this.collection || this.collection.length === 0;
},
which you would simply add to your view. You could define it in your initialize, as this.isEmpty, or by extending into your view
var MyView = Backbone.View.extend({ ... });
_.extend( MyView, { isEmpty: function() { ... } });
If you return an empty array from your server, the length property of your collection will equal zero and your isEmpty funtion will return true.
Of course, you could just use
if (this.collection.length == 0) { ... }
I'm trying to load and render additional views async and append them to the ItemView.
Simplified code - why is $el not defined in the require() block in render() - what am I missing here? Am I not using RequireJS properly, or Marionette, or just my inexperience with javascript?
What is the recommended way of doing this? It needs to be dynamic as additional section views could be available at runtime that I don't know about yet as registered by plugins.
define(['require','marionette', 'App', 'swig', 'backbone.wreqr','text!./settings.html'],
function (require,Marionette, App,Swig, Wreqr, settingsHtml )
{
var sectionViews = ['./settingscontent/GeneralView'];
var SettingsView = Marionette.ItemView.extend(
{
template: Swig.compile(settingsHtml),
commands: new Wreqr.Commands(),
initialize: function ()
{
this.commands.addHandler('save', function (options, callback)
{
callback();
});
Marionette.ItemView.prototype.initialize.apply(this, arguments);
},
render: function()
{
Marionette.ItemView.prototype.render.call(this);
var $el = this.$el;
var self = this;
require(sectionViews, function (View)
{
$el.find('div.tab-content').append(new View(self.model).render().$el);
// $el is not defined
// self != outer this - $el is an empty div
});
return this;
}
}
return SettingsView;
})
Why are you trying to overload itemview.render?
Why not use the built in onrender event
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.itemview.md#render--onrender-event
from that documentation :
Backbone.Marionette.ItemView.extend({
onRender: function(){
// manipulate the `el` here. it's already
// been rendered, and is full of the view's
// HTML, ready to go.
}
});
seems easier and more typical of marionette usage
You need to bind this inside the function to the SettingsView object. Something like:
render: function()
{
Marionette.ItemView.prototype.render.call(this);
var $el = this.$el;
var self = this;
require(sectionViews, _.bind(function (View)
{
...
}, this));
return this;
}
The local variables will not be visible inside the bound function. You can use this and this.$el safely however.