iterative render with node and express - node.js

New to node and async and still struggling with concepts.
Trying to use express/handlebars render with a callback to iteratively build an html body with content from an array. End goal is to send a response with a number of emails each one individually rendered using a view.hbs.
Got this far but realised it was never going to work. res.render can't pass my html variable back in the callback and res.send would run before the renders have completed???
function buildRes (req, res, email) {
var html = '';
Object.keys(email).forEach(function (i) {
res.render('emailPanel', {subject: email[i].subject, body: email[i].body},
function(err, renOut) {
if err throw err;
html=html+renOut;
}
)
})
res.send(html);
}
Any suggestions on how I should be approaching this problem?
Started out trying to use handlebars #each helper to do the iteration but all of the examples show a simple list whereas in my case there a multiple array parameters to be passed to the render.

I'm still not sure what you're trying to accomplish with this, but one is for sure, I think it's better for you to do all looping inside your view by passing the entire array (filtered) with res.render to your view. Also note that you can respond only once per request.

Related

Make data persist in form using mongodb node express

Hi I can connect to and store to mongodb using mlab but I can't get the data in the form to remain on refresh.
The form takes in user input from input-boxes.
Any help would be great.
Thanks
If being able to get the same form data for all the requests (no matter where they are made from)is what you want, then a possible workaround could be as follows:
1) Store the initial form data(probably empty) in the database, get the ObjectId and hard code it in your code, this way all the updates would be made to that specific MongoDB document only.
2) The form route (below one) should do nothing except for serving the file which has the form
app.get('/form1', function (req, res) {
res.sendFile(__dirname + '/public');
//record.find({"_id": ObjectId("MLAB ID")})......dont do this
});
3) There should be another route that sends form data
app.get('/getdata', function(req, res) {
record.find({"_id": ObjectId("MLAB ID")}, function(err, doc) {
res.send(doc)
})
});
4)The static file that you send to the client should have a javascript function such that it asks for the form data as soon as your window loads and then sets the value of input elements from that data recieved:
window.onload = function() {
//make a GET request to /getdata to get form data....store it in obj
//then set the input values
document.getElementById("yourchoice").value = obj.yourchoice;
//and set the value of other input fields ....in similar amnner
}
5) The POST request you make should update that specific document( findOneAndUpdate?? )
Disadvantage of this method:
For one operation we request a file from server and then another request to get form data, so two requests for one operation, since we cannot use both res.sendFile() and res.json() together...one way to get around this is to hide the form data as well in the HTML document using a template engine. More about this can easily be found. Anyways the above method does solve your purpose.
I hope I correctly understood the problem statement.
Happy coding!!
Edit:Although the above mentioned points are explainatory, I have written a sample snippet at : https://pastebin.com/AvgVyx7b
It works perfectly fine

NodeJS and Express - How to store data in Array and not render in a web browser DOM?

I have a route in node - express and it is working properly - getting data from a database.
app.get('/getdata/:key', function(req, res){
console.log("Search key(s):", req.originalUrl.split("/")[2]);
keys = req.originalUrl.split("/")[2]
Keys = keys
arr = keys.split(",");
client.mget(arr, function (err, Values) {
res.send({ Keys, Values});
});
});
But I do not want to display all data (records) into the browser screen (DOM) - just want to get it as array and parse it later with some JavaScript in a browser.
How to achieve that?
If you are sending all data at once, so the client will not do another request. You must filter the data at the client's side.
As it is probably an array of objects, you could use .filter function.
Here you have some info about how to do the filtering: w3chools - JavaScript Array filter() Method
As #jfreind00 recommended - return it as JSON.
Perform a GET request with JS in your Front-end:
http.get('/getdata/${key}', (err, res) => {
// do something with your data here
}
Depending on your used frameworks this code could differ.

Node.JS run routes synchronously

In this example, I have two routes - the first is a Get route and the second is a Post route. I want the information gathered in the get route to be included in the post route. I tried using .then and some basic boolean if logic but I cannot get these routes to run synchronously.
leadFormObj = {};
$.get("/getID/"+leadFormObj.parentEmail, function(event){
console.log("getting an ID");
console.log(event[0].id);
leadFormObj.parentID = event[0].id;
});
console.log(leadFormObj);
$.post("/addChild", leadFormObj, function(data) {
console.log(leadFormObj);
console.log("sent");
});
In the example above, I have a standard object (i've shown it blank in this example). The first get route will run and pass in a new key value pair to the object. I then want to pass this updated object to the post route but I'm not sure how to do this.
Would I use nested routes to do this?
Thanks!
Why don't you put the post request inside the callback of the get request
$.get("/getID/"+leadFormObj.parentEmail, function(data){
$.post("/addChild", {parentID: data[0].id}, function(data) {
console.log("sent");
});
});

Return view and send data to react after passport.js oauth

First let me say that sorry if this is a simple question, I'm new to react and express and couldn't find the answer on SO.
I'm trying to pass data to a react object as well as render a view based off of a passport.js return route.
I have the object i need to pass I just can't figure out how to pass it.
First the user hits the auth route
router.get('/steam',
passport.authenticate('steam'),
(req, res) => {
//Not used
});
Then after they login through steam they're returned to this route:
router.get('/steam/return',
passport.authenticate('steam', { failureRedirect: '/' }),
(req, res) => {
res.redirect('/dashboard');
});
From here I'm passing them to the /dashboard route where im taking the user object and creating another call to grab their library and then sending the data to the view:
router.get('/', (req, res, next) => {
var OwnedGamesReqUri = 'http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=' + process.env.STEAM_API + '&steamid=' + req.user.steamid + '&format=json&include_appinfo=1';
request(OwnedGamesReqUri, (error, response, body) => {
if (!error && response.statusCode == 200) {
var resObj = JSON.parse(body);
//name, playtime_forever, playtime_2weeks, img_icon_url, img_logo_url
res.render('dashboard', { user: req.user, games: resObj.response.games});
}
});
});
I'm able to grab all of the data I need in the dashboard view via handlebars but the problem I'm facing is what I can do to pass the ownedgames data to a react object in another JS file. I have a react component set up in another file that is loaded to the dashboard via a bundle file. I'm assuming this needs to go to the server but I'm just not exactly sure how to achieve this.
Your question isn't about reactjs or passportjs. It's basically how we can pass a variable in server-side javascript to client-side javascript. So you can use that variable in whatever the framework you use in the client-side.
The simplest way to achieve this is rendering your value as variable declaration within an inline script tag in your template. So your client js will have a global variable with that value, which can access from anywhere in your client side scripts.
As an example, if have a variable with string value as follows
var test = 'abc';
You can render it in your template as a variable declaration within a script tag.
res.render('template', test)
template file:
<script>
var test = '{{test}}';
</script>
Now this template will generate an HTML page as follows.
<script>
var test = 'abc';
</script>
So test variable will be a global client side javascript variable with value 'abc', which can access from any other javascript code on the same page.
Even though this is simple as above for a string variable, things get complicated when you are trying to send an object. Because object variables render in the template as [Object object]. To avoid this, you need to send stringify version of your object before sending it the handlebar template.
var data = JSON.stringify({ user: req.user, games: resObj.response.games});
res.render('dashboard', data);
Then your dashboard template needs to have script tag with the variable declaration in addition to its original content.
//...
<script>
var data = {{data}};
</script>
//...
Now, data will be a global variable in your client side and you can use it in your reactjs code to access user and games.
There are several ways to do this, depending on how your React component manages its state.
Reactive state (RxJS, mobx, nx-observe):
The easiest to deal with, your component as well as the non-js code can both access these stores and catch up on the updates together. This provides a very loose coupling between the data provider and consumer.
Component with a flux pattern:
If your component is using Redux, Reflux or any other flux based library to manage state, you can export corresponding actions/functions from your flux code which any JavaScript code can call to update your component's state.
State maintained within the component:
Somewhat messy. In this case your component can export a callback which your script can call to send it new data.

JadeJS and pre written form values at re-rendering views

I'm using Node/Express and Jade to build and App.
I have a POST route that sanitize and validate form input and then save this to a MongoDB.
If this form input is'nt validated, the route will throw and error and the error handler will re-render this same page...
And here come the problem. In this re-render and want the form values be pre written and ready to be corrected by the user... I don't want a clean form where the user has to re-write everything.
I have tried to submit the req.body (sanitized) data to the re-rendered page, which works.
But if try to use this data in my Jade view, Node will output and error when this req.body data is not defined... Like when you enter this page for the first time, and have'nt entered any wrong inputs yet.
How do i solve this in a good way?
edit - Without a code sample I'm not sure if my example is more or less than you need.
If you render the form template immediately within the the form's POST request handler, you probably don't need to involve req.session. Just save the appropriate locals and render your template.
If you must redirect or otherwise need to have the values available across multiple requests, you can save them in req.session as shown below.
Either way make sure your Jade template handles all cases; in my example I test if(locals.savedValues) to decide whether to write defaults or saved values into the form.
Finally if the error is not jade related please paste that error.
Use req.session to save the values. Set up a locals variable to represent the saved values or null before you render the form.
app.get('/form', function(req, res){
res.locals.savedValues = req.session.savedValues || null;
res.locals.savedErr = req.session.savedErr || null;
res.render('form');
});
app.post('/form', function(req, res){
var values = {
name: req.body.name,
email: req.body.email,
};
validateForm(values, function(err, processed){
if(err){
req.session.savedValues = processed;
req.session.savedErr = err;
// req.session.savedValues = values, if you dont want to propose changes
res.redirect('back');
} else {
delete req.session.savedValues;
delete req.session.savedErr;
res.redirect('/success');
};
});
});
In your jade template, handle both cases:
if(locals.savedErr)
span.error=locals.savedErr
form(action='form', method='post')
if(locals.savedValues)
input#name(type='text')=locals.savedValues.name
input#email(type='text')=locals.savedValues.email
else
input#name(type='text')
input#email(type='text')

Resources