ExpressJS + EJS: Avoid "variable is not defined" globally - node.js

How to globally avoid EJS throw error when context variable is not defined ? I mean a way to set (in middleware?) for example space or null value for all variables not defined. Now i always use on template something like below. Is better way ?
<% if(typeof locals.variable != 'undefined') { %>
...
<% } %>

I usually just have a middleware that sets some default values for a particular set of routes via res.locals.variable = undefined; or similar. Then you can successfully check if (variable === undefined) in the template for those routes.

Related

Where should I define JS function to call in EJS template

I am working on a template where I am trying to render template using express and ejs. As to the standard structure of node app, I have app.js file which which contains functions like following:
app.locals.getFlag = function(country) {
var flag_img_name = "";
if (country.toLowerCase() == "us") {
flag_img_name = "flag_us16x13.gif";
}
else if (country.toLowerCase() == "ca"){
flag_img_name = "flag_ca16x13.gif";
}
return flag_img_name;
}
I have some_template.ejs file which calls this function like follows:
<img src="http://some_url_path/<%=getFlag(data_point[0].country_name) %>" width="16" height="14" alt="country" >
and it works just fine. However, I have around 15-20 functions like this and I don't want to define all of them in app.js. Is there any other place where I can define these functions and call them in the template same way as I am doing now? If yes, what would be the way to define them so that they are accessible like they are right now.
I am new to node, express and ejs and not sure of different techniques. If someone could shed a light over it, it would be great. Thank you in advance.
Just posting this answer here for someone who would might end up on this question while resolving same issue.
All you have to do is create new file (say functions.ejs) and include it in the .ejs file where you want to call that function. So, I have function like this in file named functions.ejs:
<%
getPriceChgArrow = function(value) {
arrow_img_name = "";
if (value < 0) {
arrow_img_name = "arrow_down12x13.gif";
}
else {
arrow_img_name = "arrow_up12x13.gif";
}
return arrow_img_name;
}
%>
Then include functions.ejs into the file you want to call function from. Say, I want to call this function in quote.ejs file. So, I would include it as follows:
<% include *file_path*/functions %>
Just use this function at appropriate location in your ejs file from where you want to call it. For example:
<img src = "http:/some_url/<% getPriceChgArrow(data_point[0].value) %>" />
and you are all set. Hope this helps someone.
Create common functions in js file helper.js.
function common1() {
//function body
}
function common2(key) {
//function body
}
module.exports = {
common1: common1,
common2: common2
}
And then require this file in your node function
var helper = require('./helper');
and then pass this helper with ejs render
res.render('index', { helper:helper });
And use your function is ejs file
<%= helper.common1() %>
That's It
Well, for some reason the accepted answer didn't worked out for me. Also it doesn't makes sense to create a separate *.ejs file for each of my functions and then importing the file in view - specially when I have very simple logic to be implemented.
In fact it is very simple and easy to define function and use it in the view
I did this:
<%
// ------ Define a function
get_tree = function(tree){
for(var i in tree){
%>
<%= tree[i].title %>
<%
}
}
// ----- Call the above declared function
get_tree(tree);
%>
And it works!
Thanks
You can just require a separate file and set app.locals to this
app.locals = require('./path/helpers')
In helpers.js:
getFlag = function(country) {
var flag_img_name = "";
if (country.toLowerCase() == "us") {
flag_img_name = "flag_us16x13.gif";
}
else if (country.toLowerCase() == "ca"){
flag_img_name = "flag_ca16x13.gif";
}
return flag_img_name;
}
anotherFunction=function(x){
return 'hello '+x
}
module.exports={getFlag, anotherFunction}
It seems the easiest way to do this is to pass the function in attached to the object with all the data for the render:
in your js:
const data = {
...all other data,
getFlags: function(country) {
var flag_img_name = "";
if (country.toLowerCase() == "us") {
flag_img_name = "flag_us16x13.gif";
} else if (country.toLowerCase() == "ca"){
flag_img_name = "flag_ca16x13.gif";
}
return flag_img_name;
}
};
ejs.render(template, data);
in your template:
<img src="http://some_url_path/<%=getFlag(data_point[0].country_name) %>" width="16" height="14" alt="country" >
The order you setup your file has an importance on how the functions are defined. The execution is top to bottom not on document evaluation. Example below to setup your functions.
document.html
<section class="container">
<%- include('./helpers/common') %>
<%- include('./home') %>
</section>
common.ejs
<%
MyFunction = function() {
// Write your code here
}
%>
home.ejs
<% if(MyFunction() ) { %>
<!-- Write your HTML markup -->
<% }%>
In a js file, create a instance of the function like: if your function name is test (), Var ren = test (); will create a reference to this function.
Before rendering data to the ejs page add the reference variable ren to that object:
Data.ren = ren();
Res.render(Data)
And now in ejs while when you call <% ren() %> it will call the fonction.

To judge whether a variable in the ejs

Here's my application:
index.js
function index(req, res) {
res.render('admin/index');
}
module.exports = index;
index.ejs
<%
if(data) {
%>
<div class="alert alert-danger" role="alert">login fail</div>
<%
}
%>
I got an error saying:
data is not defined
I want to check whether the variable exists, and to display the dialog if it does. What should I do?
Either rewrite the check as follows:
<% if (typeof data !== 'undefined') { %>
... or check the property on locals (local variables object) instead:
<% if (locals.data) { %>
Explanation: when EJS compiles a template into a function, it does not populate its variables' stack based on the options supplied. Instead, it wraps this function with with statement:
with (locals || {}) {
(function() {
// ... here goes the template content
})();
}
Now, the data object (second parameter of render) is passed into the template function as locals variable, it's against this object all the checks are made. The point is, if accessed somevar is never defined in the local template scope (by var statement), and doesn't exist in locals object either, it'll cause a ReferenceError: somevar is not defined error.
(one can disable with-wrapping, setting _with option of template to false, but by default it's just undefined)

Inside Express/EJS templates, what is cleanest way to loop through an array?

I have an Express.js app set up using EJS templates. I successfully looped through an array with classic JS syntax:
<% for (var i = 0; i < myArray.length; i++) {
this = myArray[i];
// display properties of this
} %>
But I'm wondering, is there a cleaner way to do this?
Specifically, can I use Underscore or Lodash to loop through with .each ? thank you
You can use forEach method
myArray.forEach(function(el, index) {
// el - current element, i - index
});
#wachme's answer is correct, but to stick with the original question, here is the version using Underscore's _.each (and integrated in template syntax):
<% _.each(myArray, function(el, index) { %>
Value is <%= el %>, index is <%= index %>.
<% }) %>
The advantage of Array.prototype.forEach is that you don't need to depend on Underscore in your templates. The advantage of _.each is that it has some additional tricks up its sleeves (for example, it also works on objects) and that it works in older JS environments without any need for polyfills.
As an aside, Underscore's _.template can be used instead of EJS, although it has fewer features. Also, the meaning of <%= and <%− is swapped between the two libraries. Naturally, you can always use _.each in Underscore templates.

Using interpolation within Node.js EJS includes

My Express app is using EJS, and my views directory looks like this:
./views
./contents
home.ejs
./includes
header.ejs
footer.ejs
layout.ejs
I'm trying to load home.ejs in my layout.ejs view conditionally based on a local variable named contents in my routes/index.js. That file looks like this:
/*
* GET home page.
*/
exports.index = function(req, res){
res.render('index', { title: 'Home', contents: 'home.ejs' });
};
Ideally I could simply write (in layout.ejs):
<% include '/contents' + contents %>
where the trailing "contents" is the local variable which contains the relative path to the body text to load.
But alas, it appears EJS always interprets the text following an include directive literally, and there is no chance for any interpolation magic to happen.
I've also tried to no avail:
<% function yieldContent(contents){ %>
<% var contentPath = 'contents/' + contents; %>
<% include contentPath %>
<% }; %>
<% loadContent(); %>
Does anyone have a creative solution for conditionally including a view based on a variable passed in routes?
I think there is no way to do this kind of dynamic includes in EJS. It might break the separation of business logic and view.
The solution can be to rendering the subtemplate in the controller, and passing its content to the layout.
For rendering subtemplate in the controller use something like this:
var ejs = require('ejs'),
, fs = require('fs')
, home = ejs.render(fs.readFileSync("contents/home.ejs", "utf-8"))
In the version 2 of EJS, the include function does it well. With it, includes are inserted at runtime so variables can be used as pathnames.
In this case, the solution may be :
<%- include('contents/' + contents) %>
The function can also have another argument if necessary :
<%- include('mypathname', {foo:"bar"}) %>
The pathname has to be relative to the template which calls the function.
Currently this hasn't been implemented into ejs but, there is this discussion and pull request that offers the functionality.
https://github.com/visionmedia/ejs/issues/93
in your render function you can include fs.readFileSync and __dirname.
Render your page with options like this
res.render('pages/'+req.file,{file_get_contents:fs.readFileSync,__dirname:__dirname});
Then you can use it in your .ejs page like this. This remains in server side.
<% var products=JSON.parse(file_get_contents(__dirname+'/web/data/products.json','utf8')) %>
You can print the data on client HTML like this.
<%- JSON.stringify(products)%>
Note : Using this method means you have fs included somewhere at the top of your script.
var fs = require('fs')

Express, EJS, challenge with testing for undefined

I'm creating a simple website with nodejs using the express framework, couchdb for a database and EJS for templating. There will be times when some of my fields are null or undefined in some of my JSON docs, and I need to handle that.
<% if (typeof test !== 'undefined') { %>
<p><%= test %></p>
<% } %>
That bit of code seems to handle the 'test' field being undefined just fine, but the code below throws an error that says 'test is undefined'
<% if (test) { %>
<p><%= test %></p>
<% } %>
Why doesn't javascript understand test to be undefined and then just put false in the if clause?
Because the concept of "undefined" is different than the state of a variable being defined in the JavaScript language. The reasons for this are understandable but the effects can be confusing, especially regarding variable names vs object properties.
You have demonstrated how trying to access an undefined variable will throw an exception. Don't confuse this state (a variable is not defined) with the "undefined" type:
if (bogusVariable) { // throws ReferenceError: bogusVariable is not defined.
typeof(bogusVariable); // => undefined - wow, that's confusing.
However, properties of objects which are not defined can be tested safely:
var x = {}; // an object
x.foo; // => undefined - since "x" has no property "foo".
typeof(x.foo); // => undefined
if (!x.foo) { /* true */ }
You could take advantage of this property by noting that all variables are actually properties of the "global" object (either "global" or "window", in web browsers).
bogus; // => ReferenceError: bogus is not defined.
global.bogus; // => undefined (on node/rhino)
window.bogus; // => undefined (on web browsers)
So you might be able to write your EJS code as such:
<% if (global.test) { %>
<p><%= test %></p>
<% } %>
Yes, it's confusing, as are many parts of the JavaScript language.
most languages are like this:
irb
>> foo
NameError: undefined local variable or method `foo' for main:Object
from (irb):1
To check if test is defined, you need to do that:
<% if (this.test) { %>
here, test is defined
<% } %>

Resources