How to create a file from render html sails js - node.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});
});

Related

Export value from callback in node.js

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.

Display dynamically an image using express and EJS

I have a collection containing different URLs of images. I retrieve the URL I want and want to pass it to the jade template like:
app.get('/',function(req,res){
mongoDB.getUsedHomePageOne(function(err, result){
if(!err){
console.log("getUsedHomePageOne : ");
console.log(result);
app.locals['homePageImg'] = result.url;
}
});
app.render('userPageEjs.html',function(err,renderedData){
console.log(renderedData);
res.send(renderedData);
});
});
and the getUsedHomePageOne looks like:
DBMongo.prototype.getUsedHomePageOne = function(callback){
this.homePageColl.findOne({used:1}, callback);
};
and in the jade template:
<img src="<%= homePageImg %>"/>
So this won't work except if I load twice the page, I assume because it gets cached and is computed quickly enough or something.
What is the proper way of doing it?
PS: the 2nd time I load the page, everything will load correctly.
PS2: I don't want to delay the rendering for the image, I would like to load the image once it is ready, but render the HTML page before anyway.
From what I've gathered in your code:
app.get('/',function(req,res){
mongoDB.getUsedHomePageOne(function(err, result){
if(!err){
console.log("getUsedHomePageOne : ");
console.log(result);
app.locals['homePageImg'] = result.url;
app.render('userPageEjs.html',function(err,renderedData){
console.log(renderedData);
res.send(renderedData);
});
}
});
});
Basically, you have an async function to the DB and you quickly render the template before waiting for the DB function to complete. The normal pattern when using async functions whose results should be used down the line, you have to call the next function inside the async function. However, this might lead to callback hell (similar to how I've written the fix above), so an alternative like Promises or async.js is usually preferred.

NodeJS render result of MySQL query properly

I got a MySQL query where I ask for the values in a specific field with the help of the SELECT statement. My question is: How do I get the value of the query ? When I render the template with my current code I receive following output on my page [object Object]. I have the following Code:
var network;
function query(sql, callback) {
connection.query(sql, function(err, rows) {
if (err) {
callback(err, null);
} else {
callback(rows);
res.render('index.hjs', { Mysql : network });
console.log(network);
}
});
}
query("SELECT testString FROM test", function(results){
network = results;
});
There are a couple of corrections in your code. Nothing wrong but just good practice. In error callback, you are sending two arguments but in actual query callback function, you are using only one argument.
Second thing is that res will be undefined inside query function. (Of course, unless you have it inside request handler, which spoils the purpose of having it as a function)
Without your database structure and code structure, this is the best I can do to help you.

returning JSON Array in Node

Im still trying to get my head around the asynchronous flow in Node. Im trying to read list of files in db and convert result to json array and return back. The code works fine for one json object. But there is no output when reading the complete array
So this is the code
Function call - receive ajax request from browser and send back result
module.retrieveSourceContent(req, res, result, function(err, result){
if(err){
console.log(err);
return;
}
console.log(result);
res.contentType('json');
res.write(JSON.stringify(result));
res.end();
});
Code
retrieveSourceContent : function(req, res, sourceList, callback){
var sourceContent = new Array();
MongoClient.connect(config.mongoPath+config.dbName, function(err, db) {
if(err){
return callback(new Error("Unable to Connect to DB"));
}
var collection = db.collection(config.source);
for( i=0;i<sourceList['sources_FOR'].length;i++){
//build the source JSON Array
collection.find({'_id':sourceList['sources_FOR'][i]}).nextObject(function(err, doc) {
if(err)
return callback(new Error("Error finding data in DB"));
var sourceObject = {
title :doc.name,
tagCount :doc.tag.length,
tags :doc.tag,
format :doc.type, // Differentiate text, image, video and urls
content :doc.data // Content
};
sourceContent.push(sourceObject);
//if(i == sourceList['sources_FOR'].length - 1)
return callback(null, sourceContent);
});
}
});
}
This code return one json object to client. If i uncomment if(i == sourceList['sources_FOR'].length - 1) i have no output and no error. But sourceContent.push(sourceObject); does create the json array successfully.
Since this flow works on other language, i suspect it has something to do with asyn flow of node. Im at a loss here on how to solve this.
Any help would be great..
It's a little bit difficult following the flow of the program due to the formatting. But I think you are correct assuming that this has to do with async behaviour. If I'm not fooled by the formatting, it looks to me like you're doing the callback in every iteration of the loop, which would only work for the first iteration. I'm not sure what your sourcesList is, but I would try to construct a query that includes all the items in sourcesList. Then you would not need the foor loop. Maybe you can use the $in operator.

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