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')
Related
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
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.
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.
Currently a user comes to my register page with an invitation ID
Example: http://www.mysite.com/user/register/50e203eb4d37bdfd22000042
The page loads and the form loads etc.... all is great. I then do some back end validation and if validation fails I call the res.render with the messages and the form data:
res.render('user/register', {
messages: messages
, form: form
});
It all works as expected however when the render occurs it puts the user back to:
http://www.mysite.com/user/register (its missing the invite_id)
How can I pass along a URL param with the res render?
If I do:
res.render('user/register/50e203eb4d37bdfd22000042', {
messages: messages
, form: form
});
Its actually looking for that 50e203.... file and giving me the 404 not found
Thanks!
Are you perhaps confusing res.render() with app.get()? It looks like you're trying to specify a route in that second example: res.render('user/register/50e203eb4d37bdfd22000042'... - but res.render doesn't specify routes, you need app[METHOD] for that, where app is an express instance.
In order to handle a GET request to /user/register/someinvite, use:
app.get('/user/register/:invitation', function(req, res, next) {
// in here you can access the invitation id with req.params.invitation
res.render('user/register');
});
I am testing with a very simple application in node.js where I create and save an application. I show the post form with the newPost function and I receive the post with the data in the savePost method. In the latter one I do a validation (with iform module) and I want to go show again the same page as before but filling the form with the data sent by the user and also with the errors found.
I have a similar code like this one. In it I render the same jade page if I find any error. It works though I want to apply the pattern redirect and get there as I don't want to send again the post request when the user presses F5.
So, how is the usual way to make a post redirect and get from the post method passing them all the parameters I have received adding the errors? Is there any module which can help to do so?
var prepareObject = function(req, res){
var errors = {};
if('iform' in req){
errors = req.iform.errors;
}
return {title: 'Nuevo Post', body:req.body, errors: errors};
};
// mapped as /newPost (type GET)
exports.newPost = function(req, res){
//show form to create post
res.render('newPost', prepareObject(req, res));
}
// mapped as /savePost (type POST)
exports.savePost = function(req, res){
if(req.iform.errors) {
//there are errors: show form again to correct errors
res.render('newPost', prepareObject(req, res));
}else{
//no errors: show posts
res.redirect('/posts');
}
}
You can redirect to GET "/newPost" instead of rendering the "newPost" template.
To have autocomplete working, you may either add the data to the redirect query (faster) and render it, or add the data to the session (don't forget to delete it after rendering), but the later option requires a session store.