I'm working on an app in node.js using backbone and am having trouble understanding how to go about pulling in data from two models that are related to one another. In this case I have a model Users and a model Comments, and on the user view I want to show some of the user data as well as a list of the user's comments. I've tried doing a multiple fetch statement (not sure if this is the right direction), but it's only returning the data in an object array and not under the attributes object that backbone requires.
Here's the function from the backbone router that I'm trying to work with:
showUser: function(id) {
var user = new User({id: id});
var comments = new Comments({userId: id});
$.when(user.fetch(), comments.fetch())
.done(function(userdata, commentdata) {
window.showUserView = new showUserView({
model: userdata,
data: commentdata
});
});
What is the preferred method of pulling data from multiple models / collections in backbone?
There is many ways to accomplish this, but the best one (in my point of view) is :
Split your view into two views (UserView & UserCommentsView), in each view add this method :
initialize : function() {
this.model.bind('change', 'render');
}
After that, change your router as :
showUser: function(id) {
var user = new User({id: id});
var comments = new Comments({userId: id});
new UserView({ model: user });
new UserCommentsView({ model: comments });
user.fetch();
comments.fetch();
}
Related
I have the following scenario:
Item Grid > Select one Item from Grid > Open a view with Item Detail.
Which is the best practice for initialize the ItemDetail Model and store it in variable?
Store in some global var and reset every ItemDetail fetch?
Store in some ItemDetailView variable?
Snippet:
app.ItemDetailView = Backbone.View.extend({
initialize: function(id){
app.ItemDetailInstance = new ItemDetail(id);
//OR
this.itemDetail = new ItemDetail(id);
//fetch
}
}
Do you use a Backbone collection in order to fetch all your Items?
Problem with Backbone is there is no really good practices since you're totally free about your architecture. But I think that you should fetch model before creating the ItemDetailView.
If you use a collection:
app.listItemsView = Backbone.View.extend({
initialize: function() {
// You fetch the collection once
// This collection contain all items models
this.collection = collection.fetch();
},
onItemClick: function(id) {
// Get the id or cid of a specific item
var model = this.collection.get(id);
// Model object already contain data of the model
var view = new ItemDetailsView({model: model});
}
})
This is a basic use of collection and models.
If you can fetch a global collection that contain all your model, you can only fetch it once, and then play with views.
I have a backbone application and a RESTful api. I used the sample created by Coenraets to understand the architecture of a backbone app, but I decided to setup my own structure and just use the data for testing.
I want to know the best way to return data from the RESTful api. I currently have my app folder structure setup with model, collection, view and service folders. I have a node server running with express that handles the backend and is working fine.
What I want to know is what is the best practice for accessing the restful data api? Should I do that in my service class or in my view class? How do I make this work dynamically using the returned data from my restful api: http://localhost:3000/employees
It seems like there are many ways to do this and for now I just want something that works, but eventually I do want to know what is the best way to do it. Ultimately I want to have a CRUD setup. But I'm not sure where that should be setup. Similar to what is detailed here: http://www.codeproject.com/Articles/797899/BackBone-Tutorial-Part-CRUD-Operations-on-Backbone
My files are as follows:
employeecolletion.js
var Backbone = require('backbone');
var Employee = require('../models/employeemodel.js');
module.exports = Backbone.Collection.extend({
model: Employee,
url:"http://localhost:3000/employees"
});
employeemodel.js
var Backbone = require('backbone');
var EmployeeCollection = require('../collections/employeecollection.js');
module.exports = Backbone.Model.extend({
urlRoot:"http://localhost:3000/employees"
// initialize:function () {
// this.reports = new EmployeeCollection();
// //this.reports.url = this.urlRoot + "/" + 1 + "/reports";
// }
});
employee.js (employee view that binds to my template)
var fs = require('fs');
var base = require('./base.js');
var EmployeeList = require('../collections/employeecollection.js');
var employeeService = require('../services/employeeService.js');
var template = fs.readFileSync('app/templates/employee.mu', { encoding: 'utf8' });
module.exports = base.extend({
el: '.view',
template:template,
collection: employeeService.collection,
initialize: function () {
this.viewModel = {
employee_list: this.collection.toJSON()
//employee_list: this.collection.fetch() --HERE I EXPERIMENTED WITH FETCHING THE DATA
};
this.render();
}
});
employeeservice.js (file in service folder that would ideally return the collection which I would just bind to my template in they employees view file)
var EmployeeCollection = require('../collections/employeecollection.js');
//if wanting to pass in data manually
var employee_list = [
{
id:1,
firstName:"James",
lastName:"King",
fullName:"James King",
managerId:0,
managerName:"",
title:"President and CEO",
department:"Corporate",
cellPhone:"617-000-0001",
officePhone:"781-000-0001",
email:"jking#fakemail.com",
city:"Boston, MA",
pic:"james_king.jpg",
twitterId:"#fakejking",
blog:"http://coenraets.org"
}
];
//HERE I WAS EXPERIMENTING WITH A DIFFERENT SYNTAX TO DO THE FILTERING BY ID
//IN MY SERVICE AND SIMPLY RETURNING THE FINAL DATA I WANT TO MY VIEW CLASS
// var employees = new EmployeeCollection({id: id});
// employees.fetch({
// success: function (data) {
// console.log(data);
// }
// });
module.exports = {
collection: new EmployeeCollection(employee_list)
};
Backbone is meant for RESTful services.
I'll try to explain the basics using some easy to understand terms.
So backbone is based on models and views.
The model is responsible to the data.
That means, that the model is the one who fetches the data from the server and stores it.
In an interactive application, the model should have a url or urlRoot properties which indicate what is the url of the specific resource this model refers to.
For example, if we had a Person resource, and assuming we are consuming a standard RESTfult service, I would expect something similiar to this:
var Person = Backbone.Model.extend({
url : 'http://localhost:3000/api/Person'
});
That actually lets us create new instances of this model and manipulate it.
This url will be used by the model for all CRUD operations related to it.
For example, if we now create a new instance:
var person = new Person();
We now have the following basic CRUD operations:
fetch: this method is executing an async AJAX GET request behind the scenes, and injects the data into the model.
Now, after we fetched the data, we can use it by simply calling get:
person.get('name'); * assuming there's a name property.
save this method is exectuing an async AJAX POST or PUT request behind the scene.
If the model's idAttribute is undefined, it will executed POST, otherwise PUT. The idAttribute is a model property which indicates what is the model's unique id.
A sample usage:
person.set({name : 'Mor'});
person.save();
The abvoe will execute a post request with the name: 'Mor' in the request body.
If for example I fetched the model, and already have an idAttribute assigned, calling the same save method will use the PUT request.
destroy this method will execute a DELETE request behind the scene.
Sample usage: person.destroy();.
Obviously I have just shown you the basic usages, there's a lot more options out there.
A collection is simply a list of models so there's not much to explain, you can read more here: http://backbonejs.org/#Collection
A view is all you see. It is the visual part of the application.
What Backbone lets us do, is to bind views to models and collections.
By that, we can create some dynamic content and visuals.
A basic view would like something like that:
var PersonView = Backbone.View.extend({
el: '.person',
initialize: function(){
this.listenTo(this.model, "change", this.render);
},
render: function(){
this.$el.html("hello :"+this.model.get("name"));
}
});
As you can see, I used listenTo. It is an event listener that calls render each time the model changes.
When I refer to this.model I refer to a model I will pass to the view when I initiate it:
var view = new View({ model : person});
By that, and since I used listenTo, my view is now binded with the person model.
This is basically it.
Obviously, there's a lot more to learn and understand, but this pretty much covers the basics.
Please refer to http://backbonejs.org/ and read some more information.
I've recently started building my first Backbone.js project and am having a difficult time grasping how to handle "individual views" for single items.
If for example, I had a list of todos that were being displayed by a Backbone collection. If I wanted to have the application provide a link and view to an individual todo item, how would I go about that? Would I create a new type of collection and somehow filter the collection down by the id of an individual item? Where would this logic live? Within a router?
Edit
I've since updated my router as such:
var Backbone = require('backbone');
var IndexListing = require('./indexlisting');
var BookView = require('./bookview');
var LibraryCollection = require('./library');
module.exports = Backbone.Router.extend({
routes: {
'': 'allBooks',
'book/:id': 'singleBook'
},
allBooks: function(){
new IndexListing({
el: '#main',
collection: LibraryCollection
}).render();
},
singleBook: function(bookID){
console.log(bookID);
new BookView({
el: '#main',
collection: LibraryCollection.get(bookID)
}).render();
}
});
The bookID is coming straight from my MongoDB ID and returns within the console.log but when I attempt to run this in the browser I get the following error: Uncaught TypeError: undefined is not a function which is referring to the line collection: LibraryCollection.get(bookID)
My LibraryCollection (./library) contains the following:
'use strict';
var Backbone = require('backbone');
var Book = require('./book');
module.exports = Backbone.Collection.extend({
model: Book,
url: '/api/library'
});
Both of my endpoints for GET requests return the following:
/api/library/
[{"_id":"54a38070bdecad02c72a6ff4","name":"Book Name One"},{"_id":"54a38070bdecad02c72a6ff5","name":"Book Name Two"},{"_id":"54a38070bdecad02c72a6ff6","name":"Book Name Three"},{"_id":"54a38070bdecad02c72a6ff7","name":"Book Name Four"}]
/api/library/54a38070bdecad02c72a6ff4
{"_id":"54a38070bdecad02c72a6ff4","name":"Book Name One"}
Edit no 2
I've updated my router for singleBook as follows:
singleBook: function(id){
var lib = new LibraryCollection;
lib.fetch({
success: function(){
book = lib.get(id);
new BookView({
el: '#main',
model: book
}).render();
}
});
}
I really recommend the codeschool backbone courses here for an intro to backbone.
You would have a collection of models (todos). Each todo would have its own view and would show the model data, i.e the todo description. You would probably want to have a wrapper view also known as a collection view that would have each view as a subview, but it might be better to leave that out while you're getting started.
With regards to the router logic, try and think of the functions in a router as controller actions (assuming you are familiar with MVC). There are a number of backbone extensions that create MVC controller-like functionality.
For your question you would create the link in the view when you render it with a template engine. A few engines that come to mind are Underscore, Handlebars and Mustache. A template might look like this:
{{ description }}
In this template we pass in the model and between the curly braces you can see we are trying to pull out the id and description of that model. Out model attributes might look like this:
{
id: 1,
description: "Go shopping"
}
If you want to just get one model you need to make sure that the model has urlRoot property set. Then you need to fetch the model like this:
var singleTodo = new Todo({id: 1});
singleTodo.fetch();
As I said before I would really recommend watching a number of good tutorials on backbone before you get too deep into your app. Good luck!
I guess you mean you're trying to navigate to a page that shows a single todo item? if yes the logic would live in the router
var TodosRouter = Backbone.Router.extend({
routes: {
"todo": "allTodos"
"todo/:todoId": "singleTodo"
},
allTodos: function() {
// assume you have a todo listing view named TodoListing, extended from Backbone.View
// which the view inside has the link to click and navigate to #todo/<todoId>
var view = new TodoListing({
el: $('body'),
collection: YourTodosCollection
}).render();
},
singleTodo: function(todoId) {
// assume you have a todo single view named TodoView, extended from Backbone.View
var view = new TodoView({
el: $('body'),
model: YourTodosCollection.get(todoId)
}).render();
}
});
update based on edit 2
Yes this would works but it will need to fetch the whole collection from the DB and then only get the single model to display, which might cost a lot of the network resource/ browser memory performance.
In my point of view is good to just pass the ID of the book to the BookView, and then we fetch single book from there (provided we have a REST GET call for the single book with ID as param).
var BookView = Backbone.View.extend({
render: function () {...},
initialize: function(option) {
this.model.fetch(); // fetch only single book, with the bookId passed below
//alternative - we do not pass the model option at below, we could parse the bookId from the router's since it will be part of param in URL eg: http://xxxx/book/:bookId, then we can do the below
this.model = new BookModel({id: parsedFromRouter});
this.model.fetch();
}
});
new BookView({
el: '#main',
model: new BookModel({id: bookId})
}).render();
the fetch call should be async, so we might want to show some loading indicator and only render the view when the fetch done
this.model.fetch({
success: function () { //call render }
})
I am looking to retrieve a specific piece of data from Backbone (I'm using the localStorage adapter, not a server). I can see the ID of the items by looking at inspector.
I want a way to click on any item in any view, regardless of the collection it is in, or if it's it's own model, or whatever, and pass it to a details template.
So, <span onclick="router.navigate('details/:id', {trigger: true});">Single Page</span> would be attached in certain places throughout the app. All items would create the URL with their own ID.
Is it possible to agnostically render a template with the data from that single model?
My router:
assetDetail: function () {
var assetId = router.current().params[0];
var assetModel = new app.Models.Show({
id: assetId
});
var assetDetailView = new app.Views.assetDetail({
model: assetModel
});
},
However, when I put console.log( this.model ); in my detail page's render function the data is in the right format, but everything is null (the model defaults).
Thank you for any help.
Update:
As long as I know the exact collection, I can do the following. I'm currently rewriting my app to be like this. However, it still would be great to know if you can do it without knowing the collection (just having the ID)
var assetId = router.current().params[0];
var collection = new app.Collections.Collection({});
collection.fetch();
var themodel = collection.get(assetId);
var assetDetailView = new app.Views.assetDetail({
model: themodel
});
Context: I am building an application that needs several large collections of reference data to operation. I am limited to HTML and Javascript only (including JSON).
Question: How do I bootstrap a collection in Backbone.js where the collection objects are in JSON format on the server and I'm using Javascript only?
This is what I know already:
Backbone.js bootstrapping best practice requires Rails or some other server-side language (http://backbonejs.org/#FAQ-bootstrap).
Most Javascript I/0 operations are asynchronous, such as loading JSON from the server.
Using fetch() to bootstrap data is considered an anti-pattern in Backbone.js. fetch() is also also an asynchronous operation.
This is what I've come up with so far:
ItemList = Backbone.Collection.extend({
model: Item,
url: 'http://localhost:8080/json/items.json'
});
var itemList = new ItemList;
itemList.fetch();
itemList.on('reset', function () { dqApp.trigger('itemList:reset'); });
'dqApp' is my application object. I can display a spinner, and update a loading status while collections are being populated by sending alerts to the application object.
this may be help you : http://ricostacruz.com/backbone-patterns/#bootstrapping_data
The fetch function accepts an options parameter, which can have a success callback:
var itemList = new ItemList;
itemList.fetch({success: function () {
dqApp.trigger('itemList:reset');
}});
One possible solution is to make your view dependent on the status of fetch, so it doesn't get instantiated until your model/collection has finished loading.
Just keep in mind this is a Backbone anti-pattern. Making the view dependent on your collection/model will likely cause UI delays. That's why the recommended method is to bootstrap your data by inlining the json directly in your page.
But this doesn't solve your situation, where you need to bootstrap data on a server-less situation. It's easy to embed json data in your page dynamically with a few lines of Ruby/PHP/etc, but if you're working client-side only, making the view dependent on the model is the way to go.
If you're loading the collection using fetch(), you can use something like:
var Model = Backbone.Model.extend({});
var Collection = Backbone.Collection.extend({
model: MyModel,
url: 'http://localhost:8080/json/items.json'
});
var View = Backbone.View.extend({
//code
});
var myCollection = new Collection();
myCollection.fetch({
success: function () {
console.log('Model finished loading'); }
myView = new View();
});
My preferred way is using ajax (e.g., .getJSON, .ajax) and saving the returned jqXHR object (or XMLHTTPRequest, if you're not using jQuery) to a property in your model. This way you have more granular control, and can use the deferred object response to check for the status of the call before creating your view.
var Model = Backbone.Model.extend({});
var Collection = Backbone.Collection.extend({
model: Model,
status: {},
initialize: function () {
var _thisCollection = this;
this.status = $.getJSON("mydata.json", function (data) {
$.each(data, function(key) {
var m = new Model ( {
"name": data[key].name,
"value": data[key].value,
} );
_thisCollection.add(m);
});
});
}
});
var View = Backbone.View.extend({
console.log( "Creating view...");
//code
});
var myCollection = new Collection();
var myView = {};
myCollection.status
.done(function(){
console.log("Collection successfully loaded. Creating the view");
myView = new View();
})
.fail(function(){
console.log("Error bootstrapping model");
});