I have a partial view containing my login form. I would like to render it from an ajax call to my controller.
This is the sample where i would return my partial view:
postlogin: function (req,res) {
var username = req.param('username');
var password = req.param('password');
User.find({
username: username,
password: password.salt()
}).done(function(err, users){
if(users.length == 1){
// Here I want to return a partial view, not a view
res.view('home/login', {message: 'Login success!'});
}else{
// Here I want to return a partial view, not a view
res.view('home/login', {message: 'Login failed!'});
}
});
},
Ah! Found it!
If your view is a partial view, simply specify layout: null:
res.view('home/login', {message: 'Login failed!', layout: null});
Bottom of the page: http://sailsjs.org/#!documentation/views
What about using multiple layouts?
Express 3 removed native support for layouts. In Sails, we've managed to keep this around, but we don't officially support multiple layouts.
That said, at least in EJS, instead of indicating your custom layout with the layout local, you must use _layoutFile:
/**
* HomeController
*/
module.exports = {
index: function (req, res) {
res.view({
_layoutFile: '../layouts/other.ejs'
});
},
};
Sails v0.9.7
Related
I am building an application using MongoDB, Angular, Express, and Node (MEAN stack).
I used the MEAN.JS generator to scaffold my application.
I will use the articles module as a reference.
Suppose I have 7000 records in my articles collection, and each record has a date associated with it. It is inefficient to load all 7000 records into memory every time I load the page to view the records in a table and I am seeing terrible performance losses because of it. For this reason, I would only like to load records with a date in the range of (1 Month Ago) to (1 Year From Now) and display them in the table. I can currently do this with the following:
In my articles.client.controller.js:
$scope.find = function() {
$articles = Articles.query();
};
...and in my articles.server.controller.js:
var now = new Date();
var aYearFromNow = new Date(now.getTime() + 86400000*365); //add a year
var aMonthAgo = new Date(now.getTime() - 86400000*30); //subtract roughly a month
exports.list = function(req, res) { Article.find().where('date').lt(aYearFromNow).gt(aMonthAgo).sort('-created').populate('user', 'displayName').exec(function(err, articles) {
if (err) {
return res.send(400, {
message: getErrorMessage(err)
});
} else {
res.jsonp(articles);
}
});
};
The problem is that this is not a dynamic way of doing things. In other words, I want the user to be able to specify how far back and how far forward they want to see.
How can I bind to variables (e.g. 'aYearFromNow' and 'aMonthAgo') in my client view that will change the query parameters in my server controller?
Another way is to just pass the search parameters in the query method, like this:
$scope.searchart = function() {
Articles.query({start:$scope.startDate, end:$scope.endDate}, function(articles) {
$scope.articles = articles;
});
};
and then at the server side controller, read your query string parameters like this:
exports.searcharticle = function(req, res) {
Article.find().where('date').gt(req.query['start']).lt(req.query['end']).exec(function(err, articles) {
if (err) {
res.render('error', {
status: 500
});
} else {
res.jsonp(articles);
}
});
};
This way doesn't require more routes or services.
It's probably not the cleanest way, but you can create a new service (or edit the current one to work with several parameters):
.factory('ArticlesService2', ['$resource',
function($resource) {
return $resource('articles/:param1/:param2', {
param1: '',
param2: ''
}, {
update: {
method: 'PUT'
}
});
}
]);
Then call it in your controller :
$scope.findWithParams = function() {
$scope.article = ArticlesService2.query({
param1: $scope.aYearFromNow,
param2: $scope.aMonthAgo
});
};
On the back-end, you'll need to prepare a route :
app.route('/articles/:param1/:param2')
.get(articles.listWithParams)
Add a function to your back-end controller :
exports.listWithParams = function(req, res) {
Article.find()
.where('date')
.lt(req.params.param1)
.gt(req.params.param2)
.sort('-created').populate('user', 'displayName')
.exec(function(err, articles) {
if (err) {
return res.send(400, {
message: getErrorMessage(err)
});
} else {
res.jsonp(articles);
}
});
};
Should work, haven't tested it though.
I would like to perform server side validation, preferably with expressValidator. When saving a resource, I check to see if it is valid. If it's not valid what should I return?
There are examples:
http://blog.ijasoneverett.com/2013/04/form-validation-in-node-js-with-express-validator/
https://github.com/ctavan/express-validator
Unfortunately, I can't figure out my answer from that.
In Angular, I am using the $resource service. When I do a save, and there is a validation error, how should the server send this back? Note, this is a single page application.
Also, how should I handle this on the client side? Is this technically a success call?
Please, I am not looking for any instant, ajax, check per field solution. I want to submit save, if there is a problem, I would like to return the errors so that Angular can handle them. This does not need to be the perfect solution, just something to set me on the right track.
I am not handing the Angular code in an special way at the moment:
Controller:
$scope.saveTransaction = function (transaction) {
transactionData.saveTransaction(transaction);
}
Service
saveTransaction: function(transaction) {
return resource.save(transaction);
}
The server side code looks as follows:
app.post('/api/transactions', function (req, res) {
var transaction;
req.assert('amount', 'Enter an amount (numbers only with 2 decimal places, e.g. 25.50)').regex(/^\d+(\.\d{2})?$/);
var errors = req.validationErrors();
var mapped = req.validationErrors(true);
if (mapped) {console.log("MAPPED")};
//console.log(mapped);
if(!errors) {
console.log("Passed");
transaction = new TransactionModel({
date: req.body.date,
description: req.body.description,
amount: req.body.amount
});
transaction.save(function (err) {
if (!err) {
return console.log("created");
} else {
return console.log("err");
}
return res.send(transaction);
})
}
else {
console.log("Errors");
res.send(errors);
// res.render('Transaction', {
// title: 'Invalid Transaction',
// message: '',
// errors: errors
// });
}
});
You could send and handle "better" errors:
SERVER
res.json(500, errors)
CLIENT
resource.save(tran).then(function(){
//it worked
},
function(response) {
//it did not work...
//see response.data
});
i need to provide something like an association in my Model. So I have a Model called Posts with an userid and want to get the username from this username and display it.
So my ForumPosts.js Model looks like the following:
module.exports = {
schema: true,
attributes: {
content: {
type: 'text',
required: true
},
forumTopicId: {
type: 'text',
required: true
},
userId: {
type: 'integer',
required: true
},
getUsername: function(){
User.findOne(this.userId, function foundUser(err, user) {
var username = user.username;
});
console.log(username);
return username;
}
}
};
I know that this return will not work because it is asynchronus... But how can i display it in my view? At the Moment i retrive the value with:
<%= forumPost.getUsername() %>
And for sure get an undefined return...
So the question is: How can I return this value - or is there a better solution than an instanced Model?
Thanks in advance!
Off the top of my head, you can just load associated user asynchronously before rendering:
loadUser: function(done){
var that = this;
User.findOne(this.userId, function foundUser(err, user) {
if ((err)||(!user))
return done(err);
that.user = user;
done(null);
});
}
then in your controller action:
module.exports = {
index: function(req, res) {
// Something yours…
forumPost.loadUser(function(err) {
if (err)
return res.send(err, 500);
return res.view({forumPost: forumPost});
});
}
}
and in your view:
<%= forumPost.user.username %>
This is kind of a quick and dirty way. For a more solid and long-term solution (which is still in development so far) you can check out the alpha of Sails v0.10.0 with the Associations API.
So this particularly case of associations between your models. So here you have a User model and ForumPost model and you need the user object in place of your user_id as user_id works as a relationship mapping field to your User model.
So if your are using sails V0.9.8 or below you need to handle this logic in your controller where ever you want to access User model attributes in your view.
In your controller write your logic as:
model.export = {
//your getForumPosts method
getForumPosts : function(req,res){
var filters = {};
forumPost.find(filters).done(function(err,posts){
if(err) return res.send(500,err);
// Considering only one post obj
posts = posts[0];
postByUser(posts.user_id,function(obj){
if(obj.status)
{
posts.user = obj.msg;
delete posts.user_id;
res.view({post:posts});
}
else
{
res.send(500,obj.msg);
}
});
}
}
}
function postByUser(user_id,cb){
User.findOne(user_id).done(function(err,user){
if(err) return cb({status:false, msg:err});
if(user){
cb({status:true, msg:user});
}
}
}
and then you can access your post object in your view.
Or else you can keep watch (at GitHub) on next version of sails as they have announced associations in V0.10 n it is in beta testing phase as if now.
I want to render a specific view in a sailsjs controller/action which should be sent out as email.
I have the following sample action:
function registerAction(req, res) {
// handle user registration
// email user
sendEmail({
to: newUser.email,
subject: "Welcome",
text: /* VIEW RENDER HERE */
});
// render view to the user
return res.view({
user: newUser
});
}
How can I render a view-template with the tools sailsjs provides so that I don't need to hardcode an email text or use other libraries?
Thanks in advance!
Express' res.render() is still accessible in your res object.
function registerAction(req, res) {
// handle user registration
res.render('/path/to/view', function (err, html) {
// email user
sendEmail({
to: newUser.email,
subject: "Welcome",
text: html
});
}
// render view to the user
return res.view({
user: newUser
});
}
To improve a little on crzrcn's answer, to ensure
function registerAction(req, res) {
// handle user registration
// don't include the .ejs of the end of your view, this assumes a file in path/to/view.ejs
res.render('path/to/view', function (err, html) {
// email user
sendEmail({
to: newUser.email,
subject: "Welcome",
html: html // rather than text in crzrcn's answer
});
}
// render view to the user
return res.view({
user: newUser
});
}
I have a REST API that read/save data from a MongoDB database.
The application I use retrieves a form and create an object (a job) from it, then save it to the DB. After the form, I have a button which click event triggers the saving function of my controller, then redirects to another url.
Once I click on the button, I am said that the job has well been added to the DB but the application is jammed and the redirection is never called. However, if I reload my application, I can see that the new "job" has well been added to the DB. What's wrong with this ??? Thanks !
Here is my code:
Sample html(jade) code:
button.btn.btn-large.btn-primary(type='submit', ng:click="save()") Create
Controller of the angular module:
function myJobOfferListCtrl($scope, $location, myJobs) {
$scope.save = function() {
var newJob = new myJobs($scope.job);
newJob.$save(function(err) {
if(err)
console.log('Impossible to create new job');
else {
console.log('Ready to redirect');
$location.path('/offers');
}
});
};
}
Configuration of the angular module:
var myApp = angular.module('appProfile', ['ngResource']);
myApp.factory('myJobs',['$resource', function($resource) {
return $resource('/api/allMyPostedJobs',
{},
{
save: {
method: 'POST'
}
});
}]);
The routing in my nodejs application :
app.post('/job', pass.ensureAuthenticated, jobOffers_routes.create);
And finally the controller of my REST API:
exports.create = function(req, res) {
var user = req.user;
var job = new Job({ user: user,
title: req.body.title,
description: req.body.description,
salary: req.body.salary,
dueDate: new Date(req.body.dueDate),
category: req.body.category});
job.save(function(err) {
if(err) {
console.log(err);
res.redirect('/home');
}
else {
console.log('New job for user: ' + user.username + " has been posted."); //<--- Message displayed in the log
//res.redirect('/offers'); //<---- triggered but never render
res.send(JSON.stringify(job));
}
});
};
I finally found the solution ! The issue was somewhere 18inches behind the screen....
I modified the angular application controller like this :
$scope.save = function() {
var newJob = new myJobs($scope.job);
newJob.$save(function(job) {
if(!job) {
$log.log('Impossible to create new job');
}
else {
$window.location.href = '/offers';
}
});
};
The trick is that my REST api returned the created job as a json object, and I was dealing with it like it were an error ! So, each time I created a job object, I was returned a json object, and as it was non null, the log message was triggered and I was never redirected.
Furthermore, I now use the $window.location.href property to fully reload the page.