how to avoid 'this' issue in function used as express route callback - javascript

I am trying to write a module that creates generic handlers for express routes
e.g.
//create a new route handler with some config
//every routeHanlder method needs to be able to access this config
var handler = new routeHandler({config: "value"});
//handle a get route ("Example 1")
app.get('route', handler.read)
//handle a get route with params ("Example 2")
app.get('route.:id', function(req, res){
handler.read(req,res,{query: {_id: req.params.id}});
});
I am having trouble making "example 1" work...
app.get('route', handler.read)
...as I loose the value of 'this' inside handler.read
I understand why the value of 'this' is different, but I can't figure out how to make it work, or another way to get the desired results without using 'this'.
Here is a plunker link
To summarise I am trying to find a way to make my routeHandler objects (see plunker above, and code paste below) work when used as the callback of an express route (see "example 1" above).
var routeHandler = function(config){
if (!(this instanceof(routeHandler))) {
return new routeHandler(config);
}
config = config || {};
if(config.configData){
this.configData = config.configData;
}
};
routeHandler.prototype = {
read: function(req, res, options){
//The problem: accessing configData without using this
console.log("inside callback", this, this.configData);
options = options || {};
}
};
Edit: I would like the ability to create multiple instances of the route handler with different config data e.g.
var handlerOne = new RouteHandler("configDataOne");
var handlerTwo = new RouteHandler("configDataTwo");
app.get('/firstRoute', handlerOne.read);
app.get('/secondRoute', handlerTwo.read);

You can save routeHandler's configData in express object "app" like below:
app.set("routeHandlerConfigData", "identifier or whatever value you want to store");
then make your routeHandler a simple middleware
var routeHandler = function(req, res, next){
var configData = req.app.get("routeHandlerConfigData");
//Do whatever you want
};

I was inspired by a great comment form yerforkferchips who suggested adding my routerHandler functions inside the constructor like this
this.read = (function read(...) { ... }).bind(this);
Which lets me do exactly what I wanted in my question
app.get('route', handler.read);
BUT i realised that I could use closures in my prototype functions which would sort my 'this' problem and that I would also be able to take in configuration data without having to wrap handler.read in a separate callback function on app.get
RouteHandler.prototype = {
read: function(config){
return function(req, res){
//I have access to req, res and config
}
}
}
so now I can do this
app.get('route', handler.read("configData"));
instead of this
app.get('route', function(req, res){
hander.read(req, res, "configData");
});

Related

Express : Calling functions on app.param in multiple routes with same para name

I have 2 routes defined in 2 separate files but a parameter RecordId is same for both the routes:
I am expecting that :
1) whenever I call /api/CountryMaster/:RecordId,
only RecordByIdCtry function should be called. &
2) when I am calling /api/commonMaster/:MasterName/:RecordId,
only RecordByIdCmn function should be called.
However, both functions are getting called with the order being as set in javascript.
i.e.
require('../app/routes/commonMaster.server.routes.js')(app);
require('../app/routes/countryMaster.server.routes.js')(app);
How can i stop these & ensure that only one method is called.
//CountryMaster.js
var ctrl = require('../../app/controllers/CountryMaster.server.ctrl.js');
var users = require('../../app/controllers/user.server.ctrl.js');
module.exports = function (app)
{
app.route('/api/CountryMaster')
.get(users.requiresLogin,ctrl.list)
.post(users.requiresLogin,ctrl.create);
app.route('/api/CountryMaster/:RecordId')
.get(ctrl.read)
.put(users.requiresLogin, ctrl.hasAuthorization, ctrl.update)
.delete(users.requiresLogin, ctrl.hasAuthorization, ctrl.delete);
app.param('RecordId', ctrl.RecordByIdCtry);
}
//CommonMaster.js
var ctrl = require('../../app/controllers/commonMaster.server.ctrl.js');
var users = require('../../app/controllers/user.server.ctrl.js');
module.exports = function (app)
{
app.route('/api/commonMaster/:MasterName')
.get(users.requiresLogin,ctrl.list)
.post(users.requiresLogin,ctrl.create);
app.route('/api/commonMaster/:MasterName/:RecordId')
.get(ctrl.read)
.put(users.requiresLogin, ctrl.hasAuthorization, ctrl.update)
.delete(users.requiresLogin, ctrl.hasAuthorization, ctrl.delete);
app.param('MasterName', ctrl.MasterName);
app.param('RecordId', ctrl.RecordByIdCmn);
}
How can I ensure that only one method is called..
In your code, app is always the same app, so you're basically declaring two handlers for the same parameter, which isn't going to work.
You should use entirely separate routers instead:
// CountryMaster.js
...
module.exports = function(app) {
var router = require('express').Router();
router.route('/')
.get(...)
.post(...);
router.route('/:RecordId')
.get(...)
.put(...)
.delete(...);
// The magic:
router.param('RecordId', ctrl.RecordByIdCtry);
// Mount the router on `/api/CountryMaster`
app.use('/api/CountryMaster', router);
};
And similar for CommonMaster.js.

Node.js + Append to model before rendering a view

I am building a website using Node.js and Express. For my view engine, I'm using Vash.
In my app, I have a layout.vash file I'm using as the main layout for my app. It looks something like this:
layout.vash
<!DOCTYPE html>
<html lang="en">
<head>
<title>#model.title</title>
</head>
<body>
#html.block('content')
</body>
</html>
I then have views that look something like this:
view.vash
#html.extend('layout', function(model){
#html.block('content', function(model){
<h1>Hello</h1>
})
})
The view is referenced via Express with a route similar to this:
var router = require('express').Router();
router.get('/', function(req, res) {
var viewModel = {
title: 'Hello'
};
res.render('view', viewModel);
});
My viewModel will be unique to each view. However, there are some properties that are shared across all views. For example, for development purposes, I would like to know if the request is local or not. If I was using ASP.NET, I know I could say:
#if (Request.IsLocal) {
...
}
However, I'm not sure how to do something similar with Node and Vash. My instincts tell me that I somehow need to append a property called isLocal to my model. However, I'm not sure if that's the correct idea, or if that's even possible.
I use the approach you are considering in my personal code.
If the values you want to attach to your model are common across multiple routes, consider using a function to extend your model:
function appendRouteData(model) {
model.routes = {};
model.routes.controller = "SomeController";
model.routes.action = "index";
model.routes.parameters = { foo: "bar" };
return model;
}
...
viewModel = appendRouteData(viewModel);
In response to your comments, there are multiple ways to include such a function in your users.js.
Either expose the method as another module and require it, or, if we're going full crazy, inject a function that performs the same, but this requires some refactoring. I anticipate you doing only some of this insanity...
// routes.js
var UserRoutes = require('./users');
function appendRouteData(controllerName) {
// some crazy javascript here, might want to contemplate how this actually works first.
return function (viewModel) {
viewModel.routes.controller = controllerName;
return viewModel;
};
}
module.exports = {
users: UserRoutes(appendRouteData('users'))
};
// users.js
function UserRoutes(appendRouteData) {
var router = require('express').Router();
router.get('/users', function (req, res) {
var viewModel = {};
viewModel = appendRouterData(viewModel);
res.render('users/index', viewModel);
});
return router;
}
module.exports = UserRoutes;

Express alias query strings as params

I am making a project with node, express, and jade. I want to access content through:
/Page/foo/bar
and
/Page?Foo=foo&Bar=bar
I want the top to be an alias for the bottom.
This is the solution I have now:
server.js
// some stuff
app.get('/Page/:Foo/:Bar',function(req,res){
res.render('Page.jade', {Foo: req.params.Foo, Bar: req.params.Bar});
});
app.get('/Page',function(req,res){
res.render('Page.jade', {Foo: req.query.Foo, Bar: req.query.Bar});
});
// more stuff
Page.jade
doctype html
html
head
script var foo = "!{Foo}"; bar = "!{Bar}";
script(src="/Page.js")
// stuff
Page.js
// stuff with foo and bar, such as:
console.log(foo);
console.log(bar);
The thing I don't like about this solution is that it forces me to handle the params and query separately with express (which is almost duplicate code, but not quite close enough to reduce it), pass it to jade, which stores it in a variable for the sole purpose of having a linked javascript file use those variables.
Normally just using query strings I would only have to touch it in Page.js. Is there a way to set up express to effectively interpret the first URL as a query string, like the second URL, so the jade file doesn't have to touch the variables?
Option 1: If you just want to reduce code redundancy, maybe you could save your controllers in an external file, so you will end up with something like this:
controllers/fooBarController.js:
exports.fooBarQueryOrParams = function(req, res) {
res.render('Page.jade', {
Foo: req.params.Foo || req.query.Foo,
Bar: req.params.Bar || req.query.Bar
});
}
You could also add a default value with another || operator if undefined is not valid four you.
server.js:
var fooBarController = require('controllers/fooBarController');
app.get('/Page/:Foo/:Bar', fooBarController.fooBarQueryOrParams);
app.get('/Page', fooBarController.fooBarQueryOrParams);
Option 2: Same thing, but using res.locals, so now there's no need to pass any object to Page.js, all your views will see res.locals properties just by its names, in this case Foo and Bar (not res.locals.Foo and res.locals.Bar).
controllers/fooBarController.js:
exports.fooBarQueryOrParams = function(req, res) {
res.locals.Foo = req.params.Foo || req.query.Foo;
res.locals.Bar = req.params.Bar || req.query.Bar;
res.render('Page.jade');
}
server.js:
var fooBarController = require('controllers/fooBarController');
app.get('/Page/:Foo/:Bar', fooBarController.fooBarQueryOrParams);
app.get('/Page', fooBarController.fooBarQueryOrParams);
Option 3: Always expose everything thought res.locals:
controllers/fooBarController.js:
exports.fooBarQueryOrParams = function(req, res) {
res.render('Page.jade');
}
server.js:
var fooBarController = require('controllers/fooBarController');
app.use(function(req, res, next) {
for (var key in req.params) res.locals[key] = req.params[key];
for (var key in req.query) res.locals[key] = req.query[key];
next();
});
app.get('/Page/:Foo/:Bar', fooBarController.fooBarQueryOrParams);
app.get('/Page', fooBarController.fooBarQueryOrParams);
I would go for the first option as I suppose you don't really need to use Foo and Bar in all your views, so there's no point in using res.locals to expose them to all your views instead of just to the ones that really need them.
You could just set the object properties yourself and continue to the next route etc
app.use('/Page/:Foo/:Bar',function(req, res, next){
for (var key in req.params) {
req.query[key] = req.params[key];
}
next();
});

How use next() properly in routes (node.js)

var s_bookingController = require('s/controllers);
app.get('/dashboard/:page/:param', s_bookingController.index)
app.get('/dashboard/show/:id', s_bookingController.show);
Controllers:
exports.index = function(req, res, next) {
var page = parseInt(req.param("id"));
data = {};
data.page = page;
data.nextPage = page + 1;
data.prevPage = page - 1;
MyModel.find().sort('brand').skip((page-1)*11).limit(11).exec(function(err, result) {
res.render('index', {
data: data,
booking: result,
});
});
};
And
exports.show = function(req, res, next) {
var id = req.param("id");
res.send(id);
};
I'm using this controllers, but there is something wrong with the code of the exports.index, because it's stuck in the code.
If I change the routes to:
app.get('/dashboard/:page', s_bookingController.index)
(Note that I'm take off the second parameter that I was passing)
the show will work, but if I use the second parameter, the show will not run, it will be stuck in the index page.
Why is this? I was wondering if I need use the next();.
Expanding my comment:
You should have the following order of the routes:
app.get('/dashboard/show/:id', s_bookingController.show);
app.get('/dashboard/:page/:param', s_bookingController.index);
Express routing requires that a more specific route should be placed above the more general one.
The /dashboard/show/:id is more specific in this case as /dashboard/:page/:param covers it, so that /dashboard/show is handled by it. When the route is handled next routes are not executed.

How do I get a hold of a Strongloop loopback model?

This is maddening, how do I get a hold of a loopback model so I can programmatically work with it ? I have a Persisted model named "Notification". I can interact with it using the REST explorer. I want to be able to work with it within the server, i.e. Notification.find(...). I execute app.models() and can see it listed. I have done this:
var Notification = app.models.Notification;
and get a big fat "undefined". I have done this:
var Notification = loopback.Notification;
app.model(Notification);
var Notification = app.models.Notification;
and another big fat "undefined".
Please explain all I have to do to get a hold of a model I have defined using:
slc loopback:model
Thanks in advance
You can use ModelCtor.app.models.OtherModelName to access other models from you custom methods.
/** common/models/product.js **/
module.exports = function(Product) {
Product.createRandomName = function(cb) {
var Randomizer = Product.app.models.Randomizer;
Randomizer.createName(cb);
}
// this will not work as `Product.app` is not set yet
var Randomizer = Product.app.models.Randomizer;
}
/** common/models/randomizer.js **/
module.exports = function(Randomizer) {
Randomizer.createName = function(cb) {
process.nextTick(function() {
cb(null, 'random name');
});
};
}
/** server/model-config.js **/
{
"Product": {
"dataSource": "db"
},
"Randomizer": {
"dataSource": null
}
}
I know this post was here a long time ago. But since I got the same question recent days, here's what I figured out with the latest loopback api:
Loopback 2.19.0(the latest for 12th, July)
API, Get the Application object to which the Model is attached.: http://apidocs.strongloop.com/loopback/#model-getapp
You can get the application which your model was attached as following:
ModelX.js
module.exports = function(ModelX) {
//Example of disable the parent 'find' REST api, and creat a remote method called 'findA'
var isStatic = true;
ModelX.disableRemoteMethod('find', isStatic);
ModelX.findA = function (filter, cb) {
//Get the Application object which the model attached to, and we do what ever we want
ModelX.getApp(function(err, app){
if(err) throw err;
//App object returned in the callback
app.models.OtherModel.OtherMethod({}, function(){
if(err) throw err;
//Do whatever you what with the OtherModel.OtherMethod
//This give you the ability to access OtherModel within ModelX.
//...
});
});
}
//Expose the remote method with settings.
ModelX.remoteMethod(
'findA',
{
description: ["Remote method instaed of parent method from the PersistedModel",
"Can help you to impliment your own business logic"],
http:{path: '/finda', verb: 'get'},
accepts: {arg:'filter',
type:'object',
description: 'Filter defining fields, where, include, order, offset, and limit',
http:{source:'query'}},
returns: {type:'array', root:true}
}
);
};
Looks like I'm not doing well with the code block format here...
Also you should be careful about the timing when this 'getApp' get called, it matters because if you call this method very early when initializing the model, something like 'undefined' error will occur.

Categories

Resources