Backbone.js single collection item view - javascript

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 }
})

Related

Best practice for fetchING a unique Model in a View using BackBone?

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.

How do I pass references of views/models to other views/models/routes in backbone.js

So I'm really new to backbone.js and I'm trying to understand the basic concept when it comes to getting parts(views/models/routes) to interact with other parts.
Here's an example. I have a 'screen' model object being rendered by a 'singleScreen' View to the page. I also have a 'sidebar' model and view being rendered. When i click on a link in the sidebar i want it to render a different screen model object and alter some of the HTML in a separate html div (the heading) according to the 'name' attribute i gave my screen model.
So first question, should all of the code for re-rendering a different view and changing the heading html be done in the routes.js file? It seems like that file would get pretty big pretty fast. If so how do i get a reference to the model object i want to render in the routes.js file so i can access things like myModel.name (which is instantiated in the app.js file)?
Do I handle the browser history and rendering of my view as separate things and add code for 'link is clicked, render this' functionality in my app.js file (the file where I instantiate all my objects)? If that's the case how does my app know what to render if a user tries to go directly to a view by typing in the URL, rather than clicking?
OR, and this is the most likely scenario as far as i can tell,
Do i use the initialize functions of my models/views to trigger/listenTo an event for when a link is clicked(or backbone.history() is changed?) and call render?
I've messed around with all 3 approaches but couldn't understand how to pass references of objects to other parts of the app, without just making those objects global variables (which feels so wrong).
For the last scenario, I messed around with events but everywhere I've read says you have to include a reference to the object that it's listening too, which seems to defeat the whole purpose of setting up an events object and listening for an event rather than just querying the state of a variable of that object...
eg.
this.listenTo(sidebarModel , "change:selected", this.render());
How do i pass a reference to sidebarModel object to the singleScreen view for it to know what it's meant to be listening to.
I'm not really looking for a coded answer, more so just an understanding of best practices and how things are meant to be done.I feel like I'm close but I know i'm missing/not understanding something which is why I'm not able to figure this out myself, so a little enlightening on the whole topic would be greatly appreciated. Thanks.
First, you need to understand the role of each Backbone classes. Reading on MVC first might help.
Model
The model isn't necessary when rendering a view. Its role is to handle the data, which can be local, or from an API. All the functions that affect the data should be in the model, and a model shouldn't have anything related to rendering.
There are always exception, where you could use a model to handle data related only to rendering, but you'll know when you find such case.
A simple example is a book:
// The Book model class
var Book = Backbone.Model.extend({
idAttribute: 'code',
urlRoot: '/api/book'
});
// a Book instance
var solaris = new Book({ code: "1083-lem-solaris" });
Fetching from an API would call:
// GET http://example.com/api/book/1083-lem-solaris
solaris.fetch(); // this is async
When fetching, the API returns the JSON encoded data.
{
"code": "1083-lem-solaris",
"title": "Test title",
}
The attributes are merged with the existing attributes, adding the ones that are not there yet, and overwriting the values of the ones already there.
Collection
A collection's role is to manage an array of models, which, again, can be local or fetched from an API. It should contain only functions related to managing the collection.
var Library = Backbone.Collection.extend({
model: Book,
url: '/api/book'
});
var myLibrary = new Library();
// just adds an existing book to our array
myLibrary.add(solaris);
You can fetch a collection to get an array of existing books from an API:
myLibrary.fetch();
The API should return:
[
{ "code": "blah-code-123", "title": "Test title" },
{ "code": "other-code-321", "title": "Other book title" }
]
Using the collection to create a new book and sync with the API:
var myNewBook = myLibrary.create({ title: "my new book" });
This will send a POST request with the attributes and the API should return:
{ "code": "new-code-123", "title": "my new book" },
View
The view handles its root DOM element. It should handle events from its DOM. It's best used to wrap small component and build bigger components from smaller components.
Put links directly in the templates, in the href of an anchor tag. There's no need to use events for that.
link`
Here's how I render (simplified) a list.
// book list item
var BookView = Backbone.View.extend({
tagName: 'li',
template: _.template('<%= title %>'),
initialize: function() {
// when the model is removed from the collection, remove the view.
this.listenTo(this.model, 'remove', this.remove);
},
render: function() {
this.$el.empty().append(this.template(this.model.toJSON()));
return this;
}
});
// book list
var LibraryView = Backbone.View.extend({
template: '<button type="button" class="lib-button">button</button><ul class="list"></ul>',
events: {
'click .lib-button': 'onLibButtonClick'
},
initialize: function(options) {
this.listenTo(this.collection, 'add', this.renderBook);
},
render: function() {
// this is a little more optimised than 'html()'
this.$el.empty().append(this.template);
// caching the list ul jQuery object
this.$list = this.$('.list');
this.collection.each(this.renderBook, this);
return this; // Chaining view calls, good convention http://backbonejs.org/#View-render
},
addItem: function(model) {
// Passing the model to the Book view
var view = new BookView({
model: model
});
this.$list.append(view.render().el);
},
onLibButtonClick: function() {
// whatever
}
});
Router
The router handle routes and should be as simple as possible to avoid it getting too big too fast. It can fetch collections and models, or the view can handle that, it's a matter of pattern at that point.
var LibraryRouter = Backbone.Router.extend({
routes: {
'*index': 'index',
'book/:code': 'bookRoute',
},
index: function() {
var library = new LibraryView({
el: 'body',
// hard-coded collection as a simple example
collection: new Library([
{ "code": "blah-code-123", "title": "Test title" },
{ "code": "other-code-321", "title": "Other book title" }
])
});
library.render();
},
bookRoute: function(code) {
var model = new Book({ code: code });
// here, I assume an API is available and I fetch the data
model.fetch({
success: function() {
var view = new BookPageView({
el: 'body',
model: model
});
view.render();
}
});
}
});
Events
Everything in Backbone has the Events mixin, even the global Backbone object. So every class can use listenTo to bind callbacks to events.
It would be very long to go in depth for everything in Backbone, so ask questions and I'll try to extend my answer.

consuming RESTful api with backbone

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.

Backbone.js & Marionette.js - Cannot access views from main app.js file

I cant seem to access my Views from my main app file. I'm new to backbone and marionette and I'v read through the documents but cannot find why its not working. Any help or advise?
app.js
window.App = new Marionette.Application();
App.addRegions({
gameCriteria: "#game-criteria"
});
App.myRegion.show(myView);
});
myView.js
App.module("Views", function(Views, App, Backbone, Marionette, $, _) {
var GameCriteriaTab = Backbone.Marionette.ItemView.extend({
template: JST["game-criteria-tab"],
regions: {
rulesRegion: "#rules-region"
},
onShow: function() {
this.rulesRegion.show(RulesView);
}
});
});
did you instatiate your view before calling show ? i am waiting for a code similar to that :
App.myRegion.show(new GameCriteriaTab ({
model: new GameCriteriaModel()
}));
can you provide us with the error in chrome console ?
Not sure what you are trying to do here. I'm assuming that you want to create an app called 'windowApp', and you are trying to display a view called 'GameCriteriaTab' inside a region called 'gameCriteria'. I'm refactoring your code to the following:-
windowApp = new Backbone.Marionette.Application();
windowApp.addRegions({
gameCriteria: "#game-criteria"
});
windowApp.GameCriteriaTab = Marionette.ItemView.extend({ //Defining the view
template: " <include your template Id or className here> "
});
windowApp.on("start",function(){
var myView = new windowApp.GameCriteriaTab(); //You need to create an instance of the view if you want to render it in the page
windowApp.gameCriteria.show(myView); //Showing the view in the specified region
});
windowApp.start(); //This will run the marionette application
You cannot define any regions inside ItemView. Are you trying to create a nested view? In that case, a LayoutView would suit your needs, and you can add regions inside it. Just change the ItemView to a LayoutView then.

Trouble fetching from multiple models in backbone

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();
}

Categories