I have some code that is repeated among my routes, and I always pass to the view some resulting variables from that repeated code.
Example:
/* GET /events/list */
router.get('/events/list', function(req, res) {
var blah = someFunctionThatAnalyzesUserCookie();
res.render('places/list', { title: 'Places', blah: blah });
});
/* GET /events/details */
router.get('/events/details', function(req, res) {
var blah = someFunctionThatAnalyzesUserCookie();
res.render('places/details', { title: 'Some place', blah: blah });
});
How can I refactor this?
I need to pass blah to all of my views and I want their logics clean, like this:
/* GET /events/list */
router.get('/events/list', function(req, res) {
res.render('places/list', { title: 'Places' });
});
/* GET /events/details */
router.get('/events/details', function(req, res) {
res.render('places/details', { title: 'Some place' });
});
I googled for a while but only found how to execute something in every request, like this:
app.use(function(req, res, next) {
doSomethingInEveryRequest();
next();
});
But I can't figure out how to pass a variable to the view in every request.
Also I read about app.locals, but I guess that is more suited for constants. My passed variable depends on the user's cookies so is a... variable.
Can you try something like bellow:-
var renderPage = function(path,fileName,title){
router.get(path,function(req,res){
var blah = someFunctionThatAnalyzesUserCookie();
res.render(fileName,{title:title, blah:blah});
});
}
renderPage('/events/list','events/list','Places');
renderPage('/events/details','events/details','Some Place');
I think you can use Express's middleware called "Cookie-Parser".
Here is a link to its documenation: https://github.com/expressjs/cookie-parser/blob/master/README.md
Related
I'm having a really difficult time trying to understand how to pass called data to my view. I'm requiring the Mozscape node module in a little application i'm trying to develop but I don't quite understand how I'm to pass the information into my view. I would imagine that I need to create the object in my router and call the function in my view. Mozscapes returns an object which I would pass into my view and then use Handlebars to iterate through the data?
Here's the code from my router below
//pull in express
const express = require('express');
//declare router as variable for index view
const index = express.Router();
//mozscape for seo data
var Mozscape = require('mozscape').Mozscape;
//mozscape expects accessid and secretid
//only allows for 1 call per 10 seconds with free plan
//need to add to env file later
var moz = new Mozscape('mozscape-6d6ab44235', '846aca62a6db69661c81b784115e8a9');
//dummy data
var business =
{
name: 'Party Corner',
address: '123 Main Street, Red Bank, NJ',
hours: 'Monday through Friday 9 - 5',
website: 'http://www.partycorner.com/',
category: 'Party Supplies',
imgUrl: 'https://scontent.fewr1-3.fna.fbcdn.net/v/t31.0-8/14361226_10154547117788288_58127422291546970_o.jpg?oh=126b8240a8ac27dfb06b57ed51f4320e&oe=5A5A8FCC'
};
//middleware to process data for view
var businessSeo;
businessSeo = function (req, res, next) {
req.businessSeo = moz.urlMetrics(business.website, ['page_authority', 'links', 'domain_authority'], function (err, res) {
if (err) {
console.log(err);
return;
}
console.log('requesting data from moz');
console.log(res);
return res;
});
};
index.use(businessSeo);
//logging the data
console.log(businessSeo);
//use declare router for http methods
//get request for index with request, response, next params
index.get('/', function (req, res, next) {
//render response from server using index view from declared path in app.js
res.render('index', {
//declare {{ title }} used in main template extended as default template
title: "Business Directory",
//use business as object keys and values.. |key val|
business: business,
body:
{
description: 'This is a business for the business directory, how neat is that?'
},
mozData: businessSeo
})
});
module.exports = index;
I'm just trying to log the object in the front end right now and it returns that moz is not defined. I imagine I would need to move my business object into it's own variable (later quired response) and then put the function right into my router and access the business website from there?
Expected output:
Object {pda: 24.123844872381643, uid: 187, upa: 33.43142060578742}
function storeData(param) {
businessSeo = param;
}
//middleware not needed to process data for view
businessSeo = moz.urlMetrics(business.website, ['page_authority', 'links', 'domain_authority'], function (err, res) {
if (err) {
console.log(err);
return;
}
console.log('requesting data from moz');
//console.log(res); sent data to the variable
storeData(res);
});
//use declare router for http methods
//get request for index with request, response, next params
index.get('/', function (req, res, next) {
//data has arrived!
console.log(businessSeo);
Is there a way to dynamically remove a path from express so that the endpoint will return 404. For example:
app.get('./test', handle);
Later I would like to do something like:
app.get('./test').remove();
I would put a test in your handler rather than removing the path, something like this:
var handle = function(req, res) {
if (yourCondition) {
res.status(404);
res.send('This path has been removed');
}
}
I'm working on a webserver, which will load various modules for various 'web applications' on the same domain.
In the following example,
createServer(function (req, res) { // crude request routing example
var path = url.parse(req.url).pathname
if(path.match(/^\/cats(?:\/|$)/))
require('./cats.js')(req, res)
else if(path.match(/^\/dogs(?:\/|$)/))
require('./dogs.js')(req, res)
else
require('./404.js')(req, res)
})
the if statement checks for a matching context path, and routes it to the appropriate module.
Now the module cats.js looks something like this:
module.exports = function (req, res) {
var path = url.parse(req.url).pathname
// HOW can I get context path and path info? What variables should I use?
if(req.path == '/cats/add') {
//....
} else if(req.path == '/cats/delete') {
//....
} else
// 404
}
The problem is, down the road I will be updating the context path, so instead of /cats, users will have to go to /somethingelse to get to cats.js
For this reason, cats.js should not be checking for /cats/add, but should instead look for $CONTEXT/add.
What is a good way to pass the 'context path' and the 'path info' to cats.js? I'm thinking about making something up and tacking it onto the req object, but surely this is a common problem and there is a generally accepted correct solution?
You could have cat.js export a function which takes a path arg and uses it to create the request handler on the fly:
module.exports = function(context){
return function (req, res) {
var path = url.parse(req.url).pathname
if(req.path == '/'+context+'/add') {
//....
}
//....
}
}
As others mentioned, Express.JS has this capabilities built in and would be a generally accepted solution, other than that you're free to roll however you want.
I have the following code where I am using the splice function to pass only the first 10 /JSON objects to the JADE template.
app.get('/index', function(req, res) {
new models.Condos()
.query('orderBy', 'age', 'asc')
.fetch()
.then(function(names) {
var name = names.splice(0,10);
res.render('index', {
names: name.toJSON()
});
});
});
};
Is there any way where i could restrict the query itself to return only the first 10 records instead of splicing the array to do that (use the offset and limit parameters) ?
You can write a knex query to achieve this it will look something like:
app.get('/index', function(req, res) {
knex.select('*')
.from('condos')
.limit(10)
.then(function(names) {
res.render(names);
});
});
You will also need to require knex in your router file.
What I was looking for was something more along these lines.
app.get('/index', function(req, res) {
new models.Condos()
.query('orderBy', 'age', 'asc')
.query('limit','10')
.fetch()
.then(function(names) {
var name = names.splice(0,10);
res.render('index', {
names: name.toJSON()
});
});
});
};
You can use the Pagination plugin from Bookshelf.
models.Condos.fetchPage( {page:1, pageSize:10} )
Note: for the first page if the page size is 10, you can leave out the params and just
models.Condos.fetchPage()
Every time I update the database with a new menu item, I'm trying to get the routing to update with one more route. Here's my sad little ugly attempt:
Here in app.js, I check the menu database and shazaam...routes are made on the fly at startup. Cool!:
// in app.js //
var attachDB = function(req, res, next) {
req.contentdb = db.content;
req.menudb = db.menu;
req.app = app; // this is the express() app itself
req.page = PageController;
next();
};
db.menu.find({}, function (err, menuitems){
for(var i=0; record = menuitems[i]; i++) {
var menuitem = record.menuitem;
app.all('/' + menuitem, attachDB, function(req, res, next) {
console.log('req from app all route: ',req)
PageController.run(menuitem, req, res, next);
});
}
http.createServer(app).listen(config.port, function() {
console.log(
'\nExpress server listening on port ' + config.port
);
});
});
Not real elegant but it's a proof of concept. Now here's the problem: When I save a new menu item in my Admin.js file, the database get's updated, the router seems to get updated but something about the request just blows up after clicking on a menu link with a dynamically created route
Many things in the request seem to be missing and I feel like there is something fundamental I don't understand about routing, callbacks or perhaps this is just the wrong solution. Here's what the function responsible for creating a new menu item and creating a new route in my Admin.js file looks like:
// in Admin.js //
menuItem: function(req, res, callback) {
var returnMenuForm = function() {
res.render('admin-menuitem', {}, function(err, html) {
callback(html);
});
};
var reqMenudb = req.menudb,
reqContentdb = req.contentdb,
reqApp = req.app,
reqPage = req.page;
if(req.body && req.body.menuitemsubmitted && req.body.menuitemsubmitted === 'yes') {
var data = { menuitem: req.body.menuitem };
menuModel.insert( data, function(err) {
if (err) {
console.log('Whoa there...',err.message);
returnMenuForm();
} else {
// data is inserted....great. PROBLEM...the routes have not been updated!!! Attempt that mimics what I do in app.js here...
reqApp.all('/' + data.menuitem, function(req, res, next) {
// the 2 db references below are set with the right values here
req.contentdb = reqContentdb;
req.menudb = reqMenudb;
next();
}, function(req, res, next) {
reqPage.run(data.menuitem, req, res, next);
});
returnMenuForm();
}
});
} else {
returnMenuForm();
}
},
Saving the data in the admin section works fine. If you console log app.routes, it even shows a new route which is pretty cool. However after refreshing the page and clicking the link where the new route should be working, I get an undefined error.
The admin passes data to my Page controller:
// in PageController.js //
module.exports = BaseController.extend({
name: "Page",
content: null,
run: function(type, req, res, next) {
model.setDB(req.contentdb); /* <-- problem here, req.contentdb is undefined which causes me problems when talking to the Page model */
var self = this;
this.getContent(type, function() {
var v = new View(res, 'inner');
self.navMenu(req, res, function(navMenuMarkup){
self.content.menunav = navMenuMarkup;
v.render(self.content);
});
});
},
getContent: function(type, callback) {
var self = this;
this.content = {}
model.getlist(function(records) {
if(records.length > 0) {
self.content = records[0];
}
callback();
}, { type: type });
}
Lastly, the point of error is here in the model
// in Model.js //
module.exports = function() {
return {
setDB: function(db) {
this.db = db;
},
getlist: function(callback, query) {
this.db.find(query || {}, function (err, doc) { callback(doc) });
},
And here at last, the 'this' in the getlist method above is undefined and causes the page to bomb out.
If I restart the server, everything works again due to my dynamic loader in app.js. But isn't there some way to reload the routes after a database is updated?? My technique here does not work and it's ugly to be passing the main app over to a controller as I'm doing here.
I would suggest two changes:
Move this menu attachment thing to a separate module.
While you're at it, do some caching.
Proof of concept menu db function, made async with setTimeout, you'll replace it with actuall db calls.
// menuitems is cached here in this module. You can make an initial load from db instead.
var menuitems = [];
// getting them is simple, always just get the current array. We'll use that.
var getMenuItems = function() {
return menuitems;
}
// this executes when we have already inserted - calls the callback
var addMenuItemHandler = function(newItem, callback) {
// validate that it's not empty or that it does not match any of the existing ones
menuitems.push(newItem);
// remember, push item to local array only after it's added to db without errors
callback();
}
// this one accepts a request to add a new menuitem
var addMenuItem = function(req, res) {
var newItem = req.query.newitem;
// it will do db insert, or setTimeout in my case
setTimeout(function(newItem){
// we also close our request in a callback
addMenuItemHandler(newItem, function(){
res.end('Added.');
});
}, 2000);
};
module.exports = {
addMenuItem: addMenuItem,
getMenuItems: getMenuItems
}
So now you have a module menuhandler.js. Let's construct it and use it in our app.
var menuHandler = require('./menuhandler');
var app = express();
// config, insert middleware etc here
// first, capture your static routes - the ones before the dynamic ones.
app.get('/addmenuitem', menuHandler.addMenuItem);
app.get('/someotherstaticroute', function(req, res) {
var menu = menuHandler.getMenuItems();
res.render('someview', {menu: menu});
});
// now capture everything in your menus.
app.get('/:routename', function(req, res){
// get current items and check if requested route is in there.
var menuitems = menuHandler.getMenuItems();
if(menuitems.indexOf(req.params.routename) !== -1) {
res.render('myview', {menu: menuitems});
} else {
// if we missed the route, render some default page or whatever.
}
});
app.get('/', function(req, res) {
// ...
});
Now you don't go to db if there were no new updates (since menuitems array is always up to date) so your initial view is rendered faster (for that 1 db call, anyway).
Edit: oh, I just now saw your Model.js. The problem there is that this refers to the object you have returned:
{
setDB: function(db) {
this.db = db;
},
getlist: function(callback, query) {
this.db.find(query || {}, function (err, doc) { callback(doc) });
}
}
So, no db by default. And since you attach something to the app in the initial pageload, you do get something.
But in your current update function, you attach stuff to the new app (reqApp = req.app), so now you're not talking to the original app, but another instance of it. And I think that your subsequent requests (after the update) get the scope all mixed up so lose the touch with the actual latest data.
In your code when you start your server it reads from the menu db and creates your routes. When your menu changes, you do not re-read from db again.
I suggest you do something like the following
app.all('*', function(req, res) {
//read from your menu db and do the the route management yourself
});