SailsJS : Redirecting to controller action with variables - node.js

I am using sailsJS with ejs engine and i want to redirect the user back to the input page with messages ( validation errors ... ) .
i used to use this easily with laravel in PHP ( return redirect('dashboard')->with('status', 'Profile updated!'); )
i.e : i need to redirect the user back saying that this site dont exist
find : function(req,res){
var id = req.param(íd');
Site.find(id).where({isDeleted : null })
.exec(function(err,siteFound){
if(err) console.log(err);
if(siteFound) {
return res.view('site/show', {
site : siteFound
});
} else {
return res.redirect('/site');
}
})
},
i searched in sails documentation but found nothing. how can this be performed in SailsJS ?
thanks
UPDATE : i found what i needed exactly by installing sails-hook-flash . the feature i needed is called flash messages.
Thank you for your help !
Blockquote

I can't quite tell if you want a true browser redirect. A browser redirect means sending a message back to the browser that says "use this other url instead", and then it gets fresh data (meaning new req and res objects) from your app. If this is what you want, I'd say the only real options for passing data are query strings, like:
return res.redirect('/site?message=notfound');
Then in your recieving controller action for site you can access this via req.param('message').
However, if you just want to return the appropriate content now without getting the browser to redirect, you can just call whatever view or controller action you like:
if(siteFound) {
return res.view('site/show', {
site : siteFound
});
} else {
// uncomment one of the options:
// ONE: return another view
// return res.view('site/notfound, {requested: id});
// TWO: pass to another controller action in same controller
// req.options.message = 'not found';
// return module.exports.someOtherAction(req, res);
// THREE: pass to a controller action in another controller
// req.options.message = 'not found';
// var OtherController = require('./OtherController');
// return OtherController.someOtherAction(req, res);
}
Any of those three options will keep you at the user's requested url ("/site/abc123" or whatever), but display the content you specify.

res.notfound("my message string"); should do it right?
You can work with res.json() if it is an ajax request expecting a custom response.
Read the docs about the res object HERE and the custom notfound response HERE.

Related

Error: Can't set headers after they are sent - Ajax, Node, Express

I'm trying to using Airtable, node.js, express.js and jquery to create a simple user authentication functionality but I'm fairly new at this and I'm running into a problem I can't seem to fix and the articles I've read I can't seem to grasp or adapt to my particular situation.
I have this Ajax call in my html doc:
$("#checkUser").submit(function(e) {
var studentID = $('input[name="student"]').val()
e.preventDefault(); // avoid to execute the actual submit of the form.
var form = $(this);
var url = form.attr('action');
$.ajax({
type: "POST",
url: url,
data: form.serialize(), // serializes the form's elements.
success: function(data) {
$(window).attr("location", window.location.href + 'Dashboard?student=' + studentID);
},
error: function(data){
console.log("User not found. Try again");
}
});
});
This call sends the inputted username and data to the server which then processes it in the following way:
app.post('/checkUser', urlencodedParser, function(request,response){
var user = JSON.stringify(request.body);
user = JSON.parse(user);
base('RegisteredStudents').select({
filterByFormula: '{UserID} = ' + user.student,
view: "Grid view"
}).eachPage(function page(records, fetchNextPage) {
records.forEach(function(record) {
response.sendStatus(200);
});
fetchNextPage();
}, function done(error) {
response.sendStatus(404);
});
});
If the user exists in the database of Airtable, it should send '200' which the Ajax then reacts by redirecting accordingly to the user's profile. Otherwise, if the user does not exist, the server should respond with '404', which the Ajax call should react to by printing a statement in the console. While it does do these two things well, the server breaks down when, after a student puts in the wrong user ID and the Ajax prints the statement, the student tries to put once more a userID. I get the " Can't set headers after they are sent. " message. Please, how can I solve this?
Thank you!
You have two response.send..., you can only send data once. Either make sure only one runs with some conditional or add return before all response.send... so if any of them runs, the program will return and the other response.send.. will not run.

Passing data from route handler to jade template

I'm putting together a basic project admin/management site and decided to finally learn to use node/express/monk/jade/redis, the works. Everything was going fine but I've run into a problem trying to get data passed between the route handler in index.js and the jade template file.
in index.js
exports.auth = function( db )
{
return function( req, res )
{
var userName = req.body.username,
userPassword = req.body.password,
authenticated = false;
// check credentials code
// ...
if (authenticated)
{
// set some session stuff
res.redirect( "home" ); // good to go
}
else
{
res.locals.err = "Authentication error";
res.redirect( "login" ); // show err on login page
}
}
}
in login.jade
- if (typeof( locals.err ) !== 'undefined' ) {
p.errormsg #{ locals.err }
- }
Iterating over locals in the jade template it doesn't show an entry for err. Does res.redirect() wipe out the err entry I made in index.js? Or am I actually dealing with two different objects (res.locals in index.js and locals in the jade template)?
My original approach was to use res.render( "login", { "err" : "Authentication err" } ) instead of redirecting, but I cannot figure out how to get the browser to show /login and not /auth when the error happens. I tried
res.location( "login" );
res.render( "login", { "err" : "Authentication err" });
but the browser still shows /auth.
The only other approach I found was using session data. The session object is available in both places and I can set/read the information from it as needed. The solution is inelegant though since the session info persists through reloads of the login page so the browser just keeps showing the error message for the original attempt rather than reloading/rendering a clean login page.
Any help is appreciated, and thanks in advance!
Yes - the redirect is returning a redirect to the client, which makes a separate request from the client. Your prior res.locals.err is long gone. You may want to read the doc on res.redirect().
Session data would be a sensible way to handle this unless you are a hardcore about statelessness. I am not sure why you find it inelegant. Why don't you reset that element of the session data after you render the next page?
There are different ways you can handle your issue about what the location bar shows if you search around for some javascript. Feels like a bit of kludge though.
Personally, I just have a /login path - called via GET it displays the login page, called via POST it authenticates, redirects if successful, or renders the login template with the error if the login is bad. No session data necessary.

Direct linking to route (e.g., /articles/:articleid) returns plain JSON, skips AngularJS and DOM

I have a multipage blog based on https://github.com/linnovate/mean.
Right now when I go directly to a /articles/:articleid type url, all that I see is plain JSON ({"title":"this is the title","_id":"12345","user":"me"}) returned from my database. If I go to /articles/:articleid from my main page / -> clicking a link, the page parses fine since Angular and the DOM have already loaded from being at the main page, so Angular reads and parses the JSON that's returned.
Ideally, I want to be able to enter a direct URL to an article (e.g., /articles/:articleid) and have the server load the DOM and then have AngularJS return and parse the JSON. Or have some way for my server to load the html/css/etc. if it hasn't been already, before parsing the JSON (and thus avoiding plain json outputs).
Node routes:
var articles = require('../app/controllers/articles');
app.get('/articles', articles.all);
app.get('/articles/:articleId', articles.show);
app.param('articleId', articles.article); //This is called when I directly link to an article
Articles model code:
exports.article = function(req, res, next, id) {
Article.load(id, function(err, article) {
if (err) return next(err);
if (!article) return next(new Error('Failed to load article ' + id));
console.log(req.headers['x-requested-with']);
if ( req.headers["x-requested-with"] === "XMLHttpRequest" ) {
console.log('sending jsonp' + req.article.title);
res.jsonp(req.article);
} else {
console.log("sending page");
res.render('index', { //my default JADE skeleton page
user: req.user ? JSON.stringify(req.user) : "null"
});
}
//req.article = article;
next();
});
};
exports.show = function(req, res) {
console.log(req.headers['x-requested-with']);
if ( req.headers["x-requested-with"] === "XMLHttpRequest" ) {
res.jsonp(req.article);
} else {
res.render('index', { //default page
user: req.user ? JSON.stringify(req.user) : "null"
});
}
};
Angular route:
when('/articles/:articleId', {
templateUrl: 'views/articles/view.html'
}).
The controller that handles the individual article is:
$scope.findOne = function() {
Articles.get({
articleId: $routeParams.articleId
}, function(article) {
$scope.article = article;
});
//$scope.htmlReady();
};
Thanks in advance!
EDIT:
I did some checking for the content type in the headers. I'm now able to discern whether it's a direct link or if it's coming from another page in the app, but I don't know how both render the template page (jade), start Angular, and supply JSON to it all in one go if it's a direct link.
My folder structure:
public
-- css
-- img
-- js
-- lib
-- views
My log output when I direct link (it seems to be using /articles as a base):
GET /articles/72119103c2e3a932b51e000201 304 619ms
GET /articles/css/blogstyle.css 404 134ms
GET /articles/lib/angular-resource/angular-resource.js 404 126ms
GET /articles/lib/jquery/jquery.js 404 136ms
GET /articles/lib/angular/angular.js 404 134ms
GET /articles/lib/angular-cookies/angular-cookies.js 404 136ms
GET /articles/lib/bootstrap/js/bootstrap.js 404 148ms
GET /articles/lib/angular-bootstrap/ui-bootstrap-tpls.js 404 58ms
GET /articles/lib/angular-ui-utils/modules/route.js 404 67ms
You need to look at the request content-type and return the complete angular page when it is content/html and JSON when it is jsonp request. This is for the route /articles/:articleId, of course.

Unable to make an update on the server with an id

I am updating a form and i want to make an update request on the serverwith an id
my model is:
var CampaignEditModel = Backbone.Model.extend({
urlRoot:"http://localhost:3033/campaign/update/",
url : function(){
var url = this.urlRoot + this.id;
return url;
},
idAttribute: "_id",
defaults:{
"id":null ,
"Name" :""
}
});
render function is called here:
$contents.empty().append(new EditView({model:editCampaigns}).render({id:id}).el);
and render function is:
render: function(options){
this.$el.append( _.template(EditTemplate));
this.model.set({"id":options.id})
console.log(this.model.get("id"));
this._modelBinder.bind(this.model, this.el);
return this;
},
events: {
'click .saveCampaign ': 'save'
},
save:function(){
this.model.set({
"Name" :$('#edname').val(),
});
this.model.save(null, {success: function(data){
console.log("data:" + data);
require(['campaignroute'],function(routes){
var router = routes.pageRouter;
router.navigate('gridView', {trigger: true});
});
}});
return false;
}
the problem is even i have set an id in the model still when save method is called
the request go like this
http://localhost:3033/campaign/update/undefined
and console shows the eror:
Failed to load resource: the server responded with a status of 404 (Not Found)
how to solve this problem?
Instead of passing options to your custom render(options) function and setting the model id there, set the it directly on the editCampaigns model, before entering render(options):
editCampaigns.set('id', id);
$contents.empty().append(new EditView({model:editCampaigns}).render().el);
and remove the extra
this.model.set({"id":options.id})
from render(options) together with the options parameter. It should look like similar to this:
render: function(){
this.$el.append( _.template(EditTemplate));
console.log(this.model.get("id"));
this._modelBinder.bind(this.model, this.el);
return this;
}
Your model also has an extra url function:
url : function(){
var url = this.urlRoot + this.id;
return url;
}
you don't need this one since the models' id is automatically appended after urlRoot.
Unrelated to you problem I see you used
http://localhost:3033/campaign/update
to define your update URL. The HTTP method you use, already says what kind of action will be executed, this is the reason why you can (and should) write URLs without verbs. Just remove the extra /update.
Here is a quick summary about best-practices:
How to create REST URLs without verbs?
Double check that the request is a post request and not a put request. 'Failed to load resource' errors is usually related to a missing request handler.

Zombiejs how using with specifity situations

I started using zombiejs, but i have some begginer questions:
1.) How testing ajax calls ?
For example i have php ajax action (Zend)
public function ajaxSomeAction()
{
$oRequest = $this->getRequest();
if($oRequest->isXmlHttpRequest() === false || $oRequest->isPost() === false) {
throw new Zend_Controller_Action_Exception('Only AJAX & POST request accepted', 400);
}
//process check params...
}
My zombiejs testing code throws http 400.
2.) How fire jquery plugins public methods ? For example i have code:
(function($) {
$.manager.addInvitation = function()
{
//some code ....
}
$.manager = function(options)
{
//some code
}
})(jQuery);
I try:
Browser.visit(url, function(err, browser, status)
{
// not work
browser.window.jQuery.manager.addInviation();
// also not work
browser.document.jQuery.manager.addInvitation();
browser.window.$.manager.addInvitation();
browser.evaluate('$.manager.addInvitation();');
})
3.) How modifiy header with zombiejs ? For exmaple i want add header x-performace-bot:zombie1 to request send using visit method
Browser = require('zombie');
Browser.visit(url, {debug:true}, function(err, browser, status)
{
//send request witch header x-performace-bot
});
After quick testing (on zombie 0.4.21):
ad 1.
As you're checking ($oRequest->isXmlHttpRequest()) if request is an xml http request, you have to specify (in zombie) X-Requested-With header with a value of XMLHttpRequest.
ad 2.
// works for me (logs jQuery function - meaning it's there)
console.log( browser.window.jQuery );
// that works to...
browser.window.$
Your code must be undefined or there are some other errors in Javascript on your page.
ad 3.
There's a header option, which you can pass just as you do with debug.

Resources