How to launch exports.function in Node.Js - javascript

In the routes folder of a Node.Js app I have a file entries.js which has the following function:
exports.form = function(req, res){
res.render('post', { title: 'Post' });
};
Is it actually possible to launch something like this from another exports function in the same file, such as:
exports.something = function(req, res){
this.form(req.res);
};
Where this.form refers to exports.form function in the same file.
Thank you!

The value of this by default will point to the object whose member the function is. In this case, both functions are members of the same object, thus using this.form should work as expected.
The exceptions to this rule are when the function in question is used together with bind, call, or apply.

Related

passing a function through res.render() in express.js and accessing that from a pug file

i am new in express.js. i used pug for templating. I need to pass a function through res.render() and that will accessible from pug file. Is it possible? If possible then how? please help me.
app.js
passingMethod= (info) => {
console.log(info);
}
app.get('/',(req, res)=>{
res.render('first.pug', {methods: {passingMethod: this.passingMethod}})
});
and first.pug
script.
var onBtnClick= (event) => {
var methods = !{JSON.stringify(methods)}
console.log(methods)
//want to accessing passingMethod function from here
}
.fb-area
.fb
This is good?
button#good(onclick='onBtnClick(event)') Good
button#bad Bad
Depends on the type of function you are passing. Provided your function returns a value, this will be easy. Assign your function as an object to a variable and pass the variable to the res.render() method like this:
//Within your Route or Route-controller
var someVariable = someFunction()
res.render('templateName', {someVariable})
Then Call your variable in pug template

Question about res.render and calling methods in the EJS file

So I created a practice function on another file, which I imported into my server.js file as I am just playing around with node and testing what works and what doesn't.
I am using express as a framework and when I pass the function in the res.render object and give the function a parameter in the res.render scope, it works no problem.
app.get('/projects', (req, res) => {
res.render('projects', {
pageTitle: "Projects Page",
allCaps: practice.screamIt("this will be all caps")
});
});
"This will be all caps" is outputed to the screen when I call it in my projects.ejs file like so <h1><%-allCaps%></h1>
What I am trying to do is to pass the parameter in the ejs file by calling <%allCaps('capitalize this string')%> But it doesn't seem to work.
When I create a function directly in the server.js file using app.locals like so...
app.locals.addSum = (a,b) => {
return a + b
};
I have no issues passing parameters to said function from my ejs file...
<h1><%-addSum(10, 15)%></h1>
Any ideas on how I can achieve calling my other function from the EJS file? Or is it only possible using app.locals.
EDIT found how to do it:
Hi Guys i just passed the require statement through the object like so
const practice = require('./public/functions/practice.js')
app.get('/projects', (req, res) => {
res.render('projects', {
pageTitle: "Projects Page",
practice: practice
});
});
And called it in the ejs file like so
<h1><%-practice.screamIt('capitilize this')%></h1>
Issue is solved!
EDIT found how to do it:
Hi Guys i just passed the require statement through the object like so
const practice = require('./public/functions/practice.js')
app.get('/projects', (req, res) => {
res.render('projects', {
pageTitle: "Projects Page",
practice: practice
});
});
And called it in the ejs file like so
<h1><%-practice.screamIt('capitilize this')%></h1>
Issue is solved!

Why is "this" undefined in this class method?

I've tried to search over what seems to be the entire internet, but I'm still vexed by a problem with a JS class I'm writing for a Micro Service (still in learning a bit).
So, I try to call a class method on an instantiated object, and according to my knowledge and my (faulty I presume) unit tests it should work.
Well, I'll start with the error I receive:
GET /api/users 500 2.863 ms - 2649
TypeError: Cannot read property 'repository' of undefined
at list (C:\Users\<user>\Documents\Programming\node\kaguwa-ngn\kaguwa-user-service\controllers\user-controller.js:20:9)
at Layer.handle [as handle_request] (C:\Users\<user>\Documents\Programming\node\kaguwa-ngn\kaguwa-user-service\node_modules\express\lib\router\layer.js:95:5)
at next (C:\Users\<user>\Documents\Programming\node\kaguwa-ngn\kaguwa-user-service\node_modules\express\lib\router\route.js:137:13)
(And a lot more).
The code calling code:
user-controller.js
'use strict';
var utils = require('./utils');
class UserController {
constructor(repository) {
this.repository = repository || {};
}
/**
*
* Lists all users.
*
* #param {object} req
* #param {object} res
*/
list(req, res) {
this.repository.list(function (err, users) {
if (err) return res.status(500).json(utils.createError(500));
if (Object.keys(users).length !== 0) {
res.json(users);
} else {
res.status(404).json(utils.createNotFound('user', true));
}
});
}
// more code
}
module.exports = UserController
Caller of controller
user-api.js
'use strict';
var express = require('express');
var UserController = require('../controllers/user-controller');
var router = express.Router();
module.exports = function (options) {
var userController = new UserController(options.repository);
router.get('/users', userController.list);
// Mode code
return router;
};
I really jave no idea on why this undefined in UserController.
Any help would be greatly appriciated.
When you do this:
router.get('/users', userController.list);
what gets passed to your router is just a reference to the .list method. The userController instance gets lost. This is not unique to routers - this is a generic property of how things are passed in Javascript. To understand further, what you are essentially doing is this:
let list = userController.list;
// at this point the list variable has no connection at all to userController
router.get('/users', list);
And, in Javascript's strict mode, when you call a regular function without any object reference such as calling list() above, then this will be undefined inside the function. That is what is happening in your example. To fix it, you need to make sure that your method is called with a proper object reference as in userController.list(...) so that the interpreter sets the this value appropriately.
There are multiple ways to solve this problem:
Make your own function wrapper
router.get('/users', function(req, res) {
userController.list(req, res);
});
This works in any version of Javascript.
Using .bind() to make a wrapper for you that calls it with the right object
router.get('/users', userController.list.bind(userController));
This works in ES5+ or with a .bind() polyfill.
Use an ES6 arrow function shortcut
router.get('/users', (...args) => userController.list(...args));
This works in ES6+
Personally, I prefer the .bind() implementation because I think it's just simpler and more declarative/clearer than any of the others and the ES6 "shortcut" isn't really shorter.
router.get() won't call your class the way you think it will. You are giving it a reference to a function which it will call in the context of the router.get meaning, it won't be in the context of your userController.
You can fix this by doing:
router.get('/users', function(){userController.list(...arguments)});
In other words, don't have express use userController's reference to list, have express use a closure that will have userController call list with the given arguments.

Mongoose TypeError on a model's findOne method

The following TypeError cropped up in some old code.
TypeError: Object #<Object> has no method 'findOne'
The Model that was affected recently had two new static methods defined and those methods referenced external models. After backing out the new static methods, I was able to determine the root cause to be the require statements of the external models. The pattern looks like the following:
var UserModel = require('./user');
var GroupSchema = new Schema({
name: String,
users: [{ type : Schema.ObjectId, ref: 'UserModel'}],
});
GroupSchema.statics.findSomeUsers = function(group, callback) {
this.find({name : session_user._id}, function(err, groups) {
UserModel.find({_id : {$in : group.users}}, function(err,patients) {
// do magic
});
});
};
module.exports = mongoose.model('GroupModel', GroupSchema);
There is a code fragment in the application that calls GroupModel.findOne({name:'gogo'}) that leads to the TypeError. when I remove the require statement for the UserModel in the GroupSchema, app code works again.
Why does Javascript start to think findOne() is an instance method with the addition of the require statement?
It's known node.js issue. It means that you have looping require somewhere in your code and node.js forbids it.
The right way to do it is by using mongoose.model method. So, instead of UserModel variable you shall use mongoose.model('UserModel'). So, when findSomeUsers will be called mondoose will fetch UserModel and invoke its find method.
GroupSchema.statics.findSomeUsers = function(group, callback) {
this.find({name : session_user._id}, function(err, groups) {
mongoose.model('UserModel').find({_id : {$in : group.users}}, function(err,patients) {
// do magic
});
});
};

Express.js View "globals"

I'm using Express.js (on Node.js) and I know that you can render a view with custom data via the "locals" parameter. (res.render("template", { locals: { foo: "bar" } });)
Is there any way to have "globals"? (ie. data that's accessible to every view)
I saw view options, but that isn't recursive, so it replaces the locals I set if I use any locals with my template.
This is my use case: I want to make it so that CSS/JS files can be added on a per-page basis, and that is part of my main layout. The problem is, if I don't explicitly set those arrays on every render, I get an undefined error, so in my template I always have to do the typeof css !== "undefined" dance. Additionally, I have other select box option lists that I don't want to have to explicitly add to each of my forms.
It's worth noting for those who may have come across this question since the release of Express 3, that the method 'dynamicHelpers' no longer exists.
Instead you can use the app.locals function, which acts as an object you can store values or functions in, and then makes them available to views. For example:-
// In your app.js etc.
app.locals.title = "My App";
app.locals({
version: 3,
somefunction: function() {
return "function result";
}
});
// Then in your templates (shown here using a jade template)
=title
=version
=somefunction()
// Will output
My App
3
function result
If you need access to the request object to pull information from, you can write a simple middle-ware function and use the app.settings variable.
For example, if you are using connect-flash to provide messages to your users, you might do something like this:
app.use(function(req, res, next) {
app.set('error', req.flash('error'));
next();
});
Which would give you access to the error message with =settings.error in your template.
These topics are covered here, albeit slightly briefly: http://expressjs.com/api.html#app.locals
Update: Express 4
app.locals is now a simple JavaScript Object, so every property has to be set one by one.
app.locals.version = 3;
app.locals.somefunction = function() {
return "function result";
}
res.locals provides the exact same functionality, except it should be used for request-specific data rather than application-wide data. A user object or settings is a common use case.
res.locals.user = req.isAuthenticated() ? req.user : null;
res.locals.userSettings = {
backgroundColor: 'fff'
}
There is a way to have "global" variables for views, using dynamic view helpers.
From the Express.js guide:
app.dynamicHelpers(obj)
Registers dynamic view helpers.
Dynamic view helpers are simply
functions which accept req, res, and
are evaluated against the Server
instance before a view is rendered.
The return value of this function
becomes the local variable it is
associated with.
app.dynamicHelpers({ session:
function(req, res){
return req.session; } });
All views would now have session
available so that session data can be
accessed via session.name etc:
You can find a real example on how to use them here: https://github.com/alessioalex/Nodetuts/tree/master/express_samples (node app.js to start the app)
A real-world example of using view options as the author mentioned:
var app = express.createServer();
app.configure(function() {
app.set('views', path.join(__dirname, '..', 'views'));
app.set('view engine', 'jade');
app.set('view options', {
assetVersion: 1
});
And then in my layout.jade (base template for the app in my case):
link(rel='stylesheet', href='/static/css/' + assetVersion + '/style.css')
script(src='/static/js/' + assetVersion + '/script.js')
With this little trick, I only have to update the assetVersion variable one place to make sure that my assets aren’t cached in Varnish or other places.
I wound up looking into the source code, and I've actually found that this is now possible in never versions of Express. (so far, only available through GitHub)
The simplest way to accomplish this is to create a variable that represents the default set of locals for your views. Then create a function that accepts an object, merges it with the locals, and returns the merged object.
I also pass ALL my locals inside a container object i.e. {locals:{g:{prop:val}}} so in my views I can refernce g.prop which will just return null when it isn't set, instead of throwing an undefined error.
function default_page_vars(custom_vars){
var vars = {
footer: true,
host: req.headers.host.split(':')[0],
config: this.config
};
if(custom_vars){
for(var k in custom_vars){
vars[k] = custom_vars[k];
}
}
return {
g:vars
};
}
//within your handler
response.render(view, {
locals: default_page_vars(other_locals)
});
This is a buried response, but I finally got it to work.
1) This is an example around the module connect-flash
2) Add a piece of middleware in server.js/app.js to add req to locals. This allows the template to call request.flash() whenever it needs. Without this, flash() gets consumed on each request/redirect defeating the purpose.
var app = module.exports = express()
, flash=require('connect-flash');
app.configure(function(){
...
app.use(express.session({ secret: "shhh" }));
// Start Router
app.use(flash());
app.use(function(req, res, next) {
res.locals.request = req;
next();
});
app.use(app.router);
});
3) Setup your route as normal (this is coffeescript, but nothing special)
app.get '/home', (req, res) ->
req.flash "info", "this"
res.render "#{__dirname}/views/index"
4) Call request.flash() when you want the messages. They are consumed on each call, so don't console.log them or they'll be gone :-)
!!!
html
head
title= config.appTitle
include partials/_styles
body
include partials/_scripts
#header
a(href="/logout") Logout CURRENTUSER
h2= config.appTitle
#messages
- var flash = request.flash()
each flashType in ['info','warn','error']
if flash[flashType]
p.flash(class=flashType)
= flash[flashType]
block content
h1 content here
Express 4
You can access local variables in templates rendered within the application.
So, if you want to use any locals in your template => assuming you have a template engine npm installed to your node/express application.
First, you need to set the express locals objects with your custom variables in your app.js file, you can use an object if multiple values are needed (our case in this post)
/**
* Set locals object
*/
app.locals.layoutData = {
site: {
title: 'MyWebSiteTitle',
},
metaTag: {
charset: 'UTF-8',
description: 'MyDescription',
keywords: 'keyword-1,keyword-2,...',
author: 'MyName',
viewport: 'width=device-width, initial-scale=1.0'
}
};
Then, to access the values in the template file layout.pug (in the case of PUG template engine for instance)
doctype html
html
head
//title
title #{locals.layoutData.site.title}
//Describe metadata
meta(charset=layoutData.metaTag.charset)
meta(name='description', content=locals.layoutData.metaTag.description)
meta(name='keywords', content=locals.layoutData.metaTag.keywords)
meta(name='author', content=locals.layoutData.metaTag.author)
meta(name='viewport', content=locals.layoutData.metaTag.viewport)
body
block content
br
hr
footer
p All rights reserved © 2018 | #{locals.layoutData.site.title}
Tested with
"dependencies": {
"express": "^4.16.3",
"pug": "^2.0.3"
}

Categories