Handlebar helper in nodejs not working - node.js

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.

Related

Node JS: How to catch the individual errors while reading files, in case multiple files are read on Promise.all?

I am having 10 different files and I need to read their content and merge it in one object (in NodeJS). I am successfully doing that with the code below:
const fs = require('fs');
const path = require('path');
const { promisify } = require("util");
const readFileAsync = promisify(fs.readFile);
let filePathArray = ['path/to/file/one', ... , 'path/to/file/ten'];
Promise.all(
filePathArray.map(filePath => {
return readFileAsync(filePath);
})
).then(responses => { //array of 10 reponses
let combinedFileContent = {};
responses.forEach((itemFileContent, index) => {
let tempContent = JSON.parse(itemFileContent);
//merge tempContent into combinedFileContent
}
});
But what I wonder is, how to catch if there is some error while trying to read the files? When reading a single file, this works like:
fs.readFile(singleFilePath, (singleFileErr, singleFileContent) => {
if (singleFileErr) {
//do something on error, while trying to read the file
}
});
So my question here is, how can I access to the error inn the first code snippet, which corresponds to singleFileErr from this second code snippet?
The issue I am facing is: in case some of the files does not exists, I want to check the error and to skip this file, but since I can not detect the error with current implementation, my whole block crashes and I am not able to merge the other 9 files because of this one. I want to use the error check I mentioned in the second snippet.
Check out the Promise.allSettled function, which will run every Promise passed to it, and will tell you at the end which ones succeeded and which ones failed.
Maybe try something like this:
in the map() callback, return a promise that resolves to null if the file is not found.
Introduce a middle stage in the promise chain filtering out null responses.
This would look something like this:
Promise.all(
filePathArray.map(filePath => {
return readFileAsync(filePath).catch(function(error){
if(isErrorFileDoesNotExist(error)) return null
throw error;
})
});
).then(responses => {
return responses.filter(response => response != null)
})
.then(filteredResponses => {
// .. do something
});
Would that work for you? Note this presupposes you are actually able to discriminate between missing file errors from other errors the promise returned by readFileAsync() may reject - presumably via the isErrorFileDoesNotExist() function in this snippet.

How to create a file from render html sails js

Trying to make a file that render into ejs how can i do this
let makeFile = res.view('file.ejs',{result:result});
fs.writeFile(sails.config.myconf.path+'file.xml', makeFile, function (err, result) {
if(err){
console.log(err)
return
}
});
Tried this way getting undefined always can any one please understand why this is causing issue thanks a ton in advance
res.view is really meant to come at the end of your method. It facilitates a return sent by the res object, and I don't think it returns anything useful.
What you want is likely res.render - you can use that to get (and then work with) the output html as a string.
res.render('file.ejs', {result: result}, function(err, renderedHtml) {
if (err) { /* handle the error */ }
// renderedHtml should be the html output from your template
// use it to write a new file, or whatever is required
console.log(renderedHtml);
return res.send({fileCreated: true});
});

validating lack of parameters in feathresjs

I've read the feathersjs documentation, but after doing a find method in a service I realized that if I don't give any query parameters, the service returns all the data, which is something I don't want. How can I define a hook to validate that there are at least one query parameter in order to proceed; otherwise, send back a 403 error (bad request).?
I have doubts in the way to do it I tried this:
app.service('myService')
.before(function(hook) {
if (hook.params.query.name === undefined){
console.log('There is no name, throw an error!');
}
})
.find({
query: {
$sort: {
year: -1
}
}
})
And I tried in hook file on hooks this (that seemed really desperate & | stupid):
function noparams (hook) {
if (hook.params.query.name === undefined){
console.log('There is no name, throw an error!');
}
}
module.exports = {
before: {
find: [ noparams(this) ] ...
}
}
but it does not compile (I don't know what to send as a parameter there), and the examples seemed to be for pre 2.0 version and on top of that the code I found seemed to be in the app.js, but all is differently coded using feathers-cli, so the examples, even in the book, aren't against the scaffolded version, which is confusing because they shows the code in a different file were should be.
Thanks.
I ended using a before hook, so the code used is this:
const errors = require('feathers-errors');
module.exports = function () {
return function (hook) {
if(hook.method === 'find'){
if (hook.params.query.name === undefined || hook.params.query.length == 0){
throw new errors.BadRequest('Invalid Parameters');
}else{
return hook;
}
}
}
};
If have used feathers-cli to generate your application (feathers v2.x) you don't need to do anything else. If is an earlier version you maybe need to add the Express error handler and it is pointed out in the documentation|Errors|REST.
Thanks.

NodeJS Express app.locals are not directly accessible in function

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.

nodejs express fs iterating files into array or object failing

So Im trying to use the nodejs express FS module to iterate a directory in my app, store each filename in an array, which I can pass to my express view and iterate through the list, but Im struggling to do so. When I do a console.log within the files.forEach function loop, its printing the filename just fine, but as soon as I try to do anything such as:
var myfiles = [];
var fs = require('fs');
fs.readdir('./myfiles/', function (err, files) { if (err) throw err;
files.forEach( function (file) {
myfiles.push(file);
});
});
console.log(myfiles);
it fails, just logs an empty object. So Im not sure exactly what is going on, I think it has to do with callback functions, but if someone could walk me through what Im doing wrong, and why its not working, (and how to make it work), it would be much appreciated.
The myfiles array is empty because the callback hasn't been called before you call console.log().
You'll need to do something like:
var fs = require('fs');
fs.readdir('./myfiles/',function(err,files){
if(err) throw err;
files.forEach(function(file){
// do something with each file HERE!
});
});
// because trying to do something with files here won't work because
// the callback hasn't fired yet.
Remember, everything in node happens at the same time, in the sense that, unless you're doing your processing inside your callbacks, you cannot guarantee asynchronous functions have completed yet.
One way around this problem for you would be to use an EventEmitter:
var fs=require('fs'),
EventEmitter=require('events').EventEmitter,
filesEE=new EventEmitter(),
myfiles=[];
// this event will be called when all files have been added to myfiles
filesEE.on('files_ready',function(){
console.dir(myfiles);
});
// read all files from current directory
fs.readdir('.',function(err,files){
if(err) throw err;
files.forEach(function(file){
myfiles.push(file);
});
filesEE.emit('files_ready'); // trigger files_ready event
});
As several have mentioned, you are using an async method, so you have a nondeterministic execution path.
However, there is an easy way around this. Simply use the Sync version of the method:
var myfiles = [];
var fs = require('fs');
var arrayOfFiles = fs.readdirSync('./myfiles/');
//Yes, the following is not super-smart, but you might want to process the files. This is how:
arrayOfFiles.forEach( function (file) {
myfiles.push(file);
});
console.log(myfiles);
That should work as you want. However, using sync statements is not good, so you should not do it unless it is vitally important for it to be sync.
Read more here: fs.readdirSync
fs.readdir is asynchronous (as with many operations in node.js). This means that the console.log line is going to run before readdir has a chance to call the function passed to it.
You need to either:
Put the console.log line within the callback function given to readdir, i.e:
fs.readdir('./myfiles/', function (err, files) { if (err) throw err;
files.forEach( function (file) {
myfiles.push(file);
});
console.log(myfiles);
});
Or simply perform some action with each file inside the forEach.
I think it has to do with callback functions,
Exactly.
fs.readdir makes an asynchronous request to the file system for that information, and calls the callback at some later time with the results.
So function (err, files) { ... } doesn't run immediately, but console.log(myfiles) does.
At some later point in time, myfiles will contain the desired information.
You should note BTW that files is already an Array, so there is really no point in manually appending each element to some other blank array. If the idea is to put together the results from several calls, then use .concat; if you just want to get the data once, then you can just assign myfiles = files directly.
Overall, you really ought to read up on "Continuation-passing style".
I faced the same problem, and basing on answers given in this post I've solved it with Promises, that seem to be of perfect use in this situation:
router.get('/', (req, res) => {
var viewBag = {}; // It's just my little habit from .NET MVC ;)
var readFiles = new Promise((resolve, reject) => {
fs.readdir('./myfiles/',(err,files) => {
if(err) {
reject(err);
} else {
resolve(files);
}
});
});
// showcase just in case you will need to implement more async operations before route will response
var anotherPromise = new Promise((resolve, reject) => {
doAsyncStuff((err, anotherResult) => {
if(err) {
reject(err);
} else {
resolve(anotherResult);
}
});
});
Promise.all([readFiles, anotherPromise]).then((values) => {
viewBag.files = values[0];
viewBag.otherStuff = values[1];
console.log(viewBag.files); // logs e.g. [ 'file.txt' ]
res.render('your_view', viewBag);
}).catch((errors) => {
res.render('your_view',{errors:errors}); // you can use 'errors' property to render errors in view or implement different error handling schema
});
});
Note: you don't have to push found files into new array because you already get an array from fs.readdir()'c callback. According to node docs:
The callback gets two arguments (err, files) where files is an array
of the names of the files in the directory excluding '.' and '..'.
I belive this is very elegant and handy solution, and most of all - it doesn't require you to bring in and handle new modules to your script.

Resources