I'm new to Nodejs, Express and Leveldb.
I created db using level and want to pass parameter.
exports.index = function(req, res) {
var models_array = [];
db.models.createValueStream()
.on('data', function (data) {
console.log(data.name);
models_array.push(data.name);
console.log(models_array); // 1st
});
console.log(models_array); //2nd
res.render('home', {
title: 'Home',
models:models_array
});
};
This is my code but 2nd console.log(models_array) is returning null because they are running asynchronously.
Even 1st console.log is returning what I expected.
How can make this work properly?
So that I can pass proper data to template.
I found myself. I can use .once()
var models_array = [];
db.models.createValueStream()
.on('data', function (data) {
models_array.push(data.name);
}).once('end', function() {
res.render('home', {
title: 'Home',
models:models_array
});
});
Related
So I'm basically trying to do a "foreach" loop for my ejs frontend but I can't seem to res.render the snapshot in the server.js. I can't seem to get the variable into the res.render(). I have no problem retrieving the data from firebase FYI.
I've already tried various methods such as moving it in the ref.on etc
app.get('/', function (req, res) {
var ref = database.ref('Courses');
// var ref = firebase.database().ref("users");
ref.on("value", function (snapshot) {
snapshot.forEach(function (childSnapshot) {
var childData = childSnapshot.val();
});
});
res.render('pages/index', {
ChildData: childData
});
});
Data is loaded from Firebase asynchronously, since it may take some time. Instead of waiting for the data to come back from the server, the main code of your app continues straight away. Then when the data is available, your callback is called with that data.
This means that any code that needs the data from the database needs to be inside that callback.
app.get('/', function(req, res) {
var ref = database.ref('Courses');
ref.once("value", function (snapshot) {
var childData;
snapshot.forEach(function (childSnapshot) {
childData = childSnapshot.val();
});
res.render('pages/index', {
ChildData:childData
});
});
});
The main changes:
I moved the res.render into the callback, so that it runs after the data is available.
I declare the var childData before the loop, so that it's also available to the code outside of the loop.
I use once instead of on, so that the data is only loaded once.
Use this
app.get('/', function(req, res) {
var ref = database.ref('Courses');
// var ref = firebase.database().ref("users");
ref.on("value", function (snapshot) {
snapshot.map(function (childSnapshot) {
var childData = childSnapshot.val();
});
res.render('pages/index', {
ChildData:childData
});
});
});
Move the response send method inside the callback of ref.on and change forEach to map becuase map is synchronous so it will complete the iterations first and will send the data in response
I have a big problem.
I want to iterate over collection a result set and for each set i want to find one result.
This looks like this:
router.get('/', function(req, res) {
var floors = [];
var rooms = [];
req.db.collection('floors').find().sort({_id: 1}).forEach(function(floor) {
floors.push(floor);
});
req.db.collection('rooms').find().sort({_id: 1}).forEach(function(room) {
req.db.collection('floors').findOne({_id: new ObjectID(room.floorId)}, function(error, floor) {
room.floor = floor;
rooms.push(room);
});
});
res.render('rooms', { floors: floors, rooms: rooms });
});
The Problem is that the page will be rendered before the iteration is complete.
I tried to use async and promises, but i didn't get it to run.
Basically you have to wait until all your queries are done before sending the rendering result. Unfortunately you don't use promises so this will get a bit messy.
It appears that you are using the native client and according to the docs there is a second callback that gets called when all iterations are done
http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#forEach
router.get('/', function(req, res, next) {
var floors = [];
var rooms = [];
function done(err){
if(err) {
return next(err);
}
res.render('rooms', { floors: floors, rooms: rooms });
}
function getRooms(err){
if(err){
return next(err);
}
req.db.collection('rooms').find().sort({_id: 1}).forEach(function(room) {
// you already have all the floors, no need to hit the db again
floors.find(floor => floor._id === room.floorId); // not sure about this 100% as _id could be an object
}, done);
}
req.db.collection('floors').find().sort({_id: 1}).forEach(function(floor) {
floors.push(floor);
}, getRooms);
});
to be noted that this request will get quite heavy when your db grows.
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
});
I use nodejs and express for my js server.
I need a way to fetch the number of models inside a collection and send it to Backbone.
The express side is rather straight forward:
db.books.count(function (err, booksNr) {
if (err) return;
console.log(booksNr)
res.json(booksNr);
});
But how do i send this over to backbone?
My first thought was to bing the booksNr to a collection, but I can't make it work.
Here is how I fetch a collection from Backbone:
books.fetch(
{
data: {
'fetchType' : 'list',
'currentPage': 1,
'perPage': 3
},
success: function(books) {
console.log(books);
var books = books.models;
var template = _.template(listBooksTpl, {
books : books,
});
setTimeout(function () {
that.$el.html(template);
$(that.preLoader).hide();
that.$el.show('slow');
}, 500);
},
error: function() {
console.log("BookList error!");
}
}
);
Here is how I send the collection data to Backbone:
exports.books.paginated = function (req, res) {
var currentPage = parseInt(req.query.currentPage - 1),
perPage = parseInt(req.query.perPage),
skip = parseInt(currentPage * perPage);
db.books.find({}).limit(perPage).skip(skip, function (err, books) {
if (err) return;
res.send(books);
});
};
Any ideas?
It is quite simple, you need an api for retrieving books, in express.js:
app.get('/api/books', function(req, res){
var query = createQueryFromReq(req);
BookModel.find(query).exec(function(err, books) {
res.send(books);
});
});
Then you need a Books Backbone.Collection in your client side, notice that it does the parsing automatically, you don't need to add your own success parser:
var Book = Backbone.Model.extend({});
var Books = Backbone.Collection.extend({
model:Book,
url: "/api/books",
});
// Some main.js
var myBooks = new Books();
myBooks.fetch({
data: {/* your data */},
reset: true // Trigger reset in backbone 1.0+
});
Now, you should listen to "reset" event fired on myBooks and act upon it
After Comments:
The need is to be able to paginate from the server, I would recommend using https://github.com/backbone-paginator/backbone.paginator
if you want to use your own solution, you could be using this approach, on the Server side supply the extra information:
app.get('/api/books', function(req, res){
var limit = req.params.limit;
var offset = req.params.offset;
var bookCount = getBookCount(); // how many books we have
BookModel.find(query).skip(offset).limit(limit).exec(function(err, books) {
res.send({
limit: limit,
offset: offset,
total: bookCount,
books:books,
});
});
});
On the Client side you should use the parse function on Books:
var Books = Backbone.Collection.extend({
model:Book,
url: "/api/books",
parse: function(response){
this.total = response.total;
this.offset = this.offset + this.books.length; // I would test this line :) it might need a -1 also here
return response.books; // only using the books part of the response to create the models
}
});
Now you need to keep track on your limit and offset, and in the first time you fetch use data give it offset zero:
myBooks.fetch({
data: {offset:0, limit:10},
});
Another alternative it to have this Collection part of a Paginator Model, (but you can just use backbone.paginator for this)
Upon more research, here is how I did it...
Is this a good way to go?
Is it a good practice to fet a model without an ID?
In Backbone, I instantiate a model without any id:
// Random model - get total number of books.
book = new Book();
// Get the total number of books.
book.fetch({
data : { 'fetchType' : 'booksNr' },
success : function (booksNr) {
that.booksNr = booksNr.get('booksNr');
},
error : function () {
console.log("Error! Failed to get the total number of books.");
}
});
In express, I look for the query string: fetchType and do the following:
exports.books.paginated = function (req, res) {
var fetchType = req.query.fetchType;
if (fetchType == 'booksNr') {
db.books.count(function (err, booksNr) {
if (err) return;
res.json({ 'booksNr' : booksNr });
});
}
};
My js is as follows,
/*
* GET home page.
*/
var MongoClient = require('mongodb');
exports.index = function(req, res){
var studentObj = {};
MongoClient.connect("mongodb://localhost:27017/MY_DB_TEST", function(err, db) {
if(!err) {
console.log("We are connected");
var collection = db.collection('test');
var stream = collection.find().stream();
stream.on("data", function(item){
studentObj = item;
});
}
});
res.render('index', { title: 'Express', obj: studentObj });
};
I try to render this page with jade, my syntax for that is,
h1 Name: #{obj.name}
My JSON: (fetched from mongodb)
{
name: "Ron",
age: 24
}
This does not work, it only works if I keep my studentObj as a global variable i.e after the require statement.
Also, the obj.name is not rendered on first request, only for 2nd request onwards do I get the name property.
Why are these issues happening? am I missing something extremely trivial?
You're trying to treat async code as synchronous. Don't do that.
Here's your code:
// Declare local var
var studentObj = {};
// fire off an async request
MongoClient.connect("mongodb://localhost:27017/MY_DB_TEST", function(err, db) {
// render, not waiting for data to arrive from server
res.render('index', { title: 'Express', obj: studentObj });
This also explains the behaviour with global var. At first render it's still empty, but before second render the data request comes back and inits the global var. Solution is: render only after you got the data.
stream.on("data", function(item){
res.render('index', { title: 'Express', obj: item });
});
Replace:
studentObj = item;
With:
res.render('index', { title: 'Express', obj: item });
And remove the second res.render.
The rendering doesn't wait for mongodb to come back -- that's why you don't have the data available.