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();
});
Related
I am trying to add paging using express-paginate module. But i am getting limit parameter in url like this: http://example.com:3010/feeds?page=2&limit=10.
But i don't want to use limit in url. How i can remove limit from url?
Below is my pug file code.
if paginate.hasPreviousPages || paginate.hasNextPages(pageCount)
.navigation.well-sm#pagination
ul.pager
if paginate.hasPreviousPages
li.previous
a(href=paginate.href(true)).prev
i.fa.fa-arrow-circle-left
| Previous
if pages
each page in pages
a.btn.btn-default(href=page.url)= page.number
if paginate.hasNextPages(pageCount)
li.next
a(href=paginate.href()).next
| Next
i.fa.fa-arrow-circle-right`
I think you can add this by creating a simple middleware function, e.g.
const app = express();
const DEFAULT_PAGE_COUNT = 10;
// Intercept all calls and add a default page count.
app.use(function(req, res, next) {
if (!("limit" in req.query)) {
req.query.limit = DEFAULT_PAGE_COUNT;
}
next();
});
And actually, I believe the module supplies this middleware function, e.g.
// keep this before all routes that will use pagination
// paginate.middleware(limit, maxLimit)
const paginate = require('express-paginate');
app.use(paginate.middleware(10, 50));
I am using following packages for multi-languages solutions.
var i18next = require('i18next');
var i18nFsBackend = require('i18next-node-fs-backend');
var i18nMiddleware = require('i18next-express-middleware');
Since I am using handlebar as my nodejs template engine, that's I can not use i18next t('key') directly in the HTML.
so I created a handlebar helper like following
```javascript
var i18next = require('i18next');
handlebars.registerHelper('t', function(i18n_key) {
console.log(i18next.language)// always undefined, so i18next.t(i18n_key) always return default translation.
var result = i18next.t(i18n_key);
return new handlebars.SafeString(result);
});
```
However, the problem was the function is unable to detect language changed
My Workaround
app.js
```javascript
var i18nextInitCallback = function(error, t){
handlebars.registerHelper('t', function(i18n_key) {
if(app.locals.language !== i18next.language){
i18next.changeLanguage(app.locals.language);
}
var result = i18next.t(i18n_key);
return new handlebars.SafeString(result);
});
};
```
route
```javascript
router.use(function(req, res, next){
res.locals.lng = req.language;
res.app.locals.language = req.language;
next();
});
```
as you can see that on Route I assign res.app.locals.language = req.language;
and then in the handlebar helper function, I use app.locals.language to get the current language and use i18next.changeLanguage() to change the language.
and it worked.
I would like to know if I am doing it right or not?
or if there is a better solution
Using the handle function of the middleware:
app.use(middleware.handle(i18next, {
// options
}));
res.language gets already set for you and a t function fixed to user language of that request.
see: https://github.com/i18next/i18next-express-middleware/blob/master/src/index.js#L48
check out the handlebars sample: https://github.com/i18next/i18next-express-middleware/tree/master/examples/basic-handlebars
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.
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");
});
I am using expressjs and i18n-node (https://github.com/mashpie/i18n-node). It's working, except for elegant linking. What I have now is this:
app.all(/^\/(\w{2}\/)+(\w*)?/, function(req, res, next) {
var lang = req.params[0];
var type = req.params[1];
req.url = req.url.replace(lang, "");
if(type !== 'javascript' && type !== 'img' && type !== 'css') {
i18n.setLocale(lang.slice(0, 2));
}
next();
});
(I want /foo as well as /en/foo to work). If the language is not specified in the url, the headers are checked, and if not, it defaults to English. By the way, my solution seems less than ideal (I have to make manual checks to see if it is not static content), so if any of you have a better solution I am all ears.
Now, my real problem is making links to internal content. If a user is here: "/en/foo", a "/bar"-link should actually be "/en/bar". I am using jade (for no particular reason other than that it was the default, again open to suggestions..)
I tried adding a helper function to Jade:
app.helpers({
__i: i18n.__
,__n: i18n.__n
,link_to: function(link, text) {
//TODO: how to get request here?
// this should be defined to the absolute base path
var baseUrl = "/";
// only append locale if it is part of the existing url!
var locale = i18n.getLocale();
return '' + text + '';
}
});
.. but it has many problems:
How do I get the base url? So for instance, if the site is located at "http://www.example.com/foo/bar", and I link to "testLink", link_to will generate "http://www.example.com/foo/bar/testLink", instead of "/testLink" or "http://www.example.com/testLink" etc..
How do I determine if the url contains a localization parameter or not? I don't want to append a localization-parameter to the link if it is unnecessary.
The function itself is not great; In Jade it is called like this: := link_to("testUrl", "some link description"). I would rather do something like this:
link_to(href="someUrl")
div some more jade code
(minor) The HTML is written directly in the JS
What is the preferred way to do this? I've been searching and searching, but unable to find a good answer..
This is actually also a general question; i.e how do you link to other internal content, like the link_to in Rails. Simply doing a(href="someAction") is not good enough, as you want it to generate absolute URLs so that the pretty URLs do not break static content links.
Thanks!
I paste code from an own project with i18n, I use a class for dicto and another class to search language by IP:
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({
secret: "sessid",
key: 'uwsid',
store: sessionStore
}));
app.use(function (req,res,next) {
if (req.session.uid) {
req.lang = req.session.uid.lang;
next();
} else if (req.cookies.lang) {
req.lang = req.cookies.lang;
next();
} else {
var alang = typeof req.headers['accept-language'] != "undefined" ? req.headers['accept-language'].substr(0,2) : null;
var ipinfows = ipinfo.getInstance();
ipinfows.getInfo(req.connection.remoteAddress, function (err,data) {
if (err) {
req.lang = alang;
res.cookie('lang', alang);
} else if (data && data.error) {
req.lang = alang;
res.cookie('lang', alang);
} else {
console.log("seteando");
req.lang = data.lang.toLowerCase();
for (i in countryLangs) {
if (countryLangs[i].indexOf(data.lang) != -1) {
req.lang = i;
}
}
if (alang != req.lang) {
req.langdifference = alang;
}
res.cookie('lang', req.lang);
}
next();
});
}
});
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
Before of the routing app.use(app.router); you can define callbacks, in this case I search the lang and define it in a cookie. After I add an dynamicHelper to include the Dicto object into the template:
app.dynamicHelpers({
i18n: function (req,res) {
return new i18n({lang: req.lang});
}
});
With the lang define before that routing. (saving in req.lang), And I can use the i18n halper now from template (with jade):
form.uniForm(action="/account",method="post")
fieldset.inlineLabels
.ctrlHolder
label(for="nickname") #{**i18n.getText('user:nick')**}:
input(type="text",name="nickname",value=everyauth.user.nick)
p.formHint
i18n object now is the same defined on the dynamic helper.
As far as I understand it, express will only call the helper function once (When the template is compiled...)
In the express guide, http://expressjs.com/guide.html, check out dynamicHelpers which should be more useful for your situation (dynamicHelpers provide request and response objects).