Backbone, after chaging url in my collection, fetch is looping. - loops

I'm pretty new with Backbone and I'm struggling with Backbone.Collection :
I have a list of products in different categories (shoes, shirts, pants, ...). The idea is when an user clicks on a category, I'm updating URL in my Collection (ex : datas/shoes.json becomes datas/shirts.json) and do a collection.fetch() in order to render my new list.
It's actually working but I don't know why it's continually triggering "add", "remove" events. This is my code, let me know if you see something strange :
define([
"backbone",
],
function(Backbone)
{
var ProductsView = Backbone.View.extend({
el: "#products",
initialize:function(){
_.bindAll(this,"addItem","removeAll");
this.populate();
},
populate:function(){
this.collection = new ProductCollection();
this.listenTo(this.collection, 'add', this.addItem);
this.listenTo(this.collection, 'remove', this.removeAll);
this.collection.fetch();
},
addItem(todo){
var view = new ProductItemView({model: todo});
this.$el.append(view.render().el);
},
removeAll:function(){
this.$el.children().remove();
this.collection.url = "datas/shoes.json";
this.collection.fetch();
},
});
return ProductsView;
});
This is my collection
define([
"backbone",
"models/modules/products/ProductModel"
],
function(Backbone, ProductModel)
{
var ProductsCollection = Backbone.Collection.extend({
model : ProductModel,
url : "datas/shirts.json",
parse: function(response){
return response.products;
},
});
return ProductsCollection;
});
Thanks for your help guys !!
Let me know if it's not clear, I'll try to clarify that.

it's definitely helpful to give up, go to bed, in order to take a step back.
So I've read the documentation and found reset method :
remove:function(todo){
this.$el.children().remove();
this.collection.reset();
this.collection.url = "datas/shoes.json";
this.collection.fetch();
},

Related

backbone .bind('change', _.bind(this.render, this)) not registering

I have a measure model, that is made up of two collections, a beats collections, and a measureRep[resentation] collection. Each collection is made of beat models and representation models respectively.
Whenever a measureRep[resentation] collection changes (by addition or subtraction of a representation model), I want the measureView (which has the measure model, and therefore the measureRep[resentation] collection) to re-render itself using the render function().
I am adding a new model in another View by the following function:
var representationModel = new RepresentationModel({representationType: newRepType});
StageCollection.get(cid).get('measures').models[0].get('measureRepresentations').add(representationModel);
I can see that before and after the addition that the measure and its measureRep collection are getting added correctly, however, the bind call on the measureView is not registering the change and calling the render function. I even put the bind on the model, to show that the backend is getting updated, however, it doesn't respond. This leads me to believe that the View and the models are decoupled, but that doesn't make sense, since it originally renders from the model. Here are the relevant files:
measureView.js [View]:
define([...], function(...){
return Backbone.View.extend({
initialize: function(options){
if (options) {
for (var key in options) {
this[key] = options[key];
}
this.el = '#measure-container-'+options.parent.cid;
}
window.css = this.collectionOfRepresentations; //I use these to attach it to the window to verify that the are getting updated correctly
window.csd = this.model; // Same
_.bindAll(this, 'render'); // I have optionally included this and it hasn't helped
this.collectionOfRepresentations.bind('change', _.bind(this.render, this));
this.render();
},
render: function(){
// Make a template for the measure and append the MeasureTemplate
var measureTemplateParameters = { ... };
var compiledMeasureTemplate = _.template( MeasureTemplate, measureTemplateParameters );
// If we are adding a rep, clear the current reps, then add the template
$(this.el).html('');
$(this.el).append( compiledMeasureTemplate )
// for each rep in the measuresCollection
_.each(this.collectionOfRepresentations.models, function(rep, repIndex) {
var measureRepViewParamaters = { ... };
new MeasureRepView(measureRepViewParamaters);
}, this);
return this;
},
...
});
});
measure.js [Model]:
define([ ... ], function(_, Backbone, BeatsCollection, RepresentationsCollection) {
var MeasureModel = Backbone.Model.extend({
defaults: {
beats: BeatsCollection,
measureRepresentations: RepresentationsCollection
},
initialize: function(){
var logg = function() { console.log('changed'); };
this.measureRepresentations.bind('change', logg);
this.bind('change', logg);
}
});
return MeasureModel;
});
representations.js [Collection]:
define([ ... ], function($, _, Backbone, RepresentationModel){
var RepresentationsCollection = Backbone.Collection.extend({
model: RepresentationModel,
initialize: function(){
}
});
return RepresentationsCollection;
});
I have also tried registering the bind on the measure model, and not its child collection, but neither work.
_.bindAll(this, 'render');
this.model.bind('change', _.bind(this.render, this));
see: https://stackoverflow.com/a/8175141/1449799
In order to detect additions of models to a collection, you need to listen for the add event (not the change event, which will fire when a model in the collection is changed http://documentcloud.github.io/backbone/#Events-catalog ).
so try:
this.measureRepresentations.bind('add', logg);

Backbone call a model method from the view

I have a backbone app with require where I want to add a collection inside a collection with a method inside model.
I have tried to insert the method in the collection but I can't add elements.
I'd want to make a collection of app when I click an element outside the app I want add inside my app other app in a collection.
This is my app:
Model:
define(['backbone', 'collections/element'],function(Backbone, ElementCollection){
var DesignModel = Backbone.Model.extend({
initialize:function(){
console.log('Initialized Design model');
_.defaults(this, {
elements: new ElementCollection()
});
},
addElement: function(elements, options) {
return this.elements.add(elements, options);
}
});
return DesignModel;
});
Collection:
define(['backbone', 'models/design'], function(Backbone, DesignModel){
var designCollection = Backbone.Collection.extend({
model: DesignModel,
});
return designCollection;
});
View
define(['jquery' , 'backbone', 'models/design', 'collections/design', 'views/element'],
function($, Backbone, DesignModel, DesignCollection, ElementView){
var DesignView = Backbone.View.extend({
el:$('#page'),
initialize: function(){
console.log('initialize DesignView');
this.collection = new DesignCollection();
var here = this;
$('#insert-dynamic-element').click(function(){
var element = new ElementView();
here.collection.models.addElement(element);
});
},
render: function(){
}
})
return DesignView;
});
I have tried to call the function addElement in this way:
here.collection.models.addElement(element);
and
here.collection.addElement(element);
But always with error that Object has no method addElement
How can I solve this? I want to call the method addElement from the view to add an app inside another app in a collection.
Thanks
The safest way to call the method is to add the method to the collection instead of the Model. Currently the method is available on the Model instance .
So this.collection.models.addElement will not cut it
Collection
define(['backbone', 'models/design'], function(Backbone, DesignModel){
var designCollection = Backbone.Collection.extend({
model: DesignModel,
addElement: function(elements, options) {
return this.add(elements, options);
}
});
return designCollection;
});
View
define(['jquery' , 'backbone', 'models/design', 'collections/design', 'views/element'],
function($, Backbone, DesignModel, DesignCollection, ElementView){
var DesignView = Backbone.View.extend({
el:$('#page'),
initialize: function(){
console.log('initialize DesignView');
this.collection = new DesignCollection();
var here = this;
$('#insert-dynamic-element').click(function(){
var element = new ElementView();
here.collection.addElement(element);
});
},
render: function(){
}
})
return DesignView;
});
If you do not want to move the method from the current model. Then you might have to call a specific model using the index
here.collection.at(0).addElement(element);
But there might be a case when there are no model in the collection and this might lead to a error condition..
here.collection.at(0) && here.collection.at(0).addElement(element);
Well, you need to get a specific model, not the array of them. This seems like an error since you'll be picking a specific model essentially arbitrarily (unless you application has semantics that support this), but this would work:
here.collection.at(0).addElement(element);

Backbone boilerplate: "this.model is undefined"

I'm a backbone newbie, so I'm sort of fumbling on getting an app set up. I'm using the backbone-boilerplate (https://github.com/tbranyen/backbone-boilerplate) and github-viewer (https://github.com/tbranyen/github-viewer) as a reference, though when running I seem to be getting a "this.model is undefined".
Here is my current router.js:
define([
// Application.
"app",
//Modules
"modules/homepage"
],
function (app, Homepage) {
"use strict";
// Defining the application router, you can attach sub routers here.
var Router = Backbone.Router.extend({
initialize: function(){
var collections = {
homepage: new Homepage.Collection()
};
_.extend(this, collections);
app.useLayout("main-frame").setViews({
".homepage": new Homepage.Views.Index(collections)
}).render();
},
routes:{
"":"index"
},
index: function () {
this.reset();
this.homepage.fetch();
},
// Shortcut for building a url.
go: function() {
return this.navigate(_.toArray(arguments).join("/"), true);
},
reset: function() {
// Reset collections to initial state.
if (this.homepage.length) {
this.homepage.reset();
}
// Reset active model.
app.active = false;
}
});
return Router;
}
);
And my homepage.js module:
define([
"app"
],
function(app){
"use strict";
var Homepage = app.module();
Homepage.Model = Backbone.Model.extend({
defaults: function(){
return {
homepage: {}
};
}
});
Homepage.Collection = Backbone.Collection.extend({
model: Homepage.Model,
cache: true,
url: '/app/json/test.json',
initialize: function(models, options){
if (options) {
this.homepage = options.homepage;
}
}
});
Homepage.Views.Index = Backbone.View.extend({
template: "homepage",
el: '#mainContent',
render: function(){
var tmpl = _.template(this.template);
$(this.el).html(tmpl(this.model.toJSON()));
return this;
},
initialize: function(){
this.listenTo(this.options.homepage, {
"reset": function(){
this.render();
},
"fetch": function() {
$(this.el).html("Loading...");
}
});
}
});
return Homepage;
});
Thanks in advance for the help!
Update: After much googling (you should see how many tabs I have open), I think I made a little bit of headway, but still no luck. I updated my router to have the following:
app.useLayout("main-frame").setViews({
".homepage": new Homepage.Views.Index()
}).render();
I made a number of modifications to my homepage.js module to now look like this:
define([
"app",
["localStorage"]
],
function(app){
"use strict";
var Homepage = app.module();
Homepage.Model = Backbone.Model.extend({
defaults: function(){
return {
homepage: {}
};
}
});
Homepage.Collection = Backbone.Collection.extend({
//localStorage: new Backbone.LocalStorage("Homepage.Collection"),
refreshFromServer: function() {
return Backbone.ajaxSync('read', this).done( function(data){
console.log(data);
//save the data somehow?
});
},
model: Homepage.Model,
cache: true,
url: '/app/json/test.json',
initialize: function(options){
if (options) {
this.homepage = options.homepage;
}else{
//this.refreshFromServer();
}
}
});
Homepage.Views.Index = Backbone.View.extend({
template: "homepage",
el: '#mainContent',
initialize: function(){
var self = this;
this.collection = new Homepage.Collection();
this.collection.fetch().done( function(){
self.render();
});
},
render: function(){
var data = this.collection;
if (typeof(data) === "undefined") {
$(this.el).html("Loading...");
} else {
$(this.el).html(_.template(this.template, data.toJSON()));
}
return this;
}
});
return Homepage;
});
As you can see, I have localStorage code but commented out for now because I just want to get everything working first. The ultimate goal is to have an initial call that loads data from a JSON file, then continues afterwards using localStorage. The app will later submit data after the user does a number of interactions with my app.
I am getting the main view to load, though the homepage module isn't populating the #mainContent container in the main view.
I did all of the googling that I could but frustrated that it's just not sinking in for me. Thanks again for looking at this and any feedback is appreciated!
I think your class hierarchy is a bit wonky here. Your instance of Homepage.Collection is actually assigning a homepage property out of options, for instance. Then you pass an instance of Homepage.Collection into Homepage.Views.Index as the homepage option... It's a bit hard to follow.
That said, it seems to me your problem is simply that you aren't supply a model option when you construct your Homepage.Views.Index:
new Homepage.Views.Index(collections)
collections doesn't have a model property, and thus I don't see how this.model.toJSON() later on in the view can have a model to access. Basically, you seem to want Homepage.Views.Index to handle a collection of models, not just one. So you probably need a loop in your render function that goes over this.collection (and you should change your construction of the view to have a collection option instead of homepage option).
If I'm missing something here or I'm unclear it's because of this data model oddness I mentioned earlier. Feel free to clarify how you've got it reasoned out and we can try again :)
This example code you have is a little bit confusing to me, but I think the problem lies in the following two lines of code:
".homepage": new Homepage.Views.Index(collections)
$(this.el).html(tmpl(this.model.toJSON()));
It looks like you pass a collection to the view, but in the view you use this.model, hence the error "this.model is undefined", since it is indeed undefined.
If you aren't in any rush, may I suggest that you start over. It seems you are trying too much too quickly. I see that you have backbone, requirejs (or some other module loader), and the boilerplate, which is a lot to take in for someone new to backbone. Trust me, I know, because I am relatively new, too. Maybe start with some hello world stuff and slowly work your way up. Otherwise, hacking your way through bits of code from various projects can get confusing.

backbone.js each function not receiving the models

I am trying to receive a json data and append to element. all are work fine up to i use the static assignments. while i start to fetch the data from server side, or using fetch nothing work for me.. something wrong with my fech process, any can help me to correct my fetch process and update my code.(instead of simply placing the correct code)..
my JSON(sample):
nameing = [
{name:'student4'},
{name:'student5'},
{name:'student6'}
]
Backbone code:
(function($){
var list = {};
list.model = Backbone.Model.extend({
defaults:{
name:'need the name'
}
});
list.collect = Backbone.Collection.extend({
model:list.model,
url : 'data/names.json', //this is correct path.
initialize:function(){
this.fetch();
}
});
list.view = Backbone.View.extend({
initialize:function(){
this.collection = new list.collect();
this.collection.on("reset", this.render, this);
},
render:function(){
_.each(this.collection.models, function(data){
console.log(data); // i am not get any model here... any one correct my code?
})
}
});
var newView = new list.view();
})(jQuery)
thanks in advance.
Your JSON is not valid. Wiki
[
{"name":"student4"},
{"name":"student5"},
{"name":"student6"}
]

backbone data view not showing

I have been having a few issue with backbone so decided to do a very simple tutorial.
After getting this working I tried to simplify it but now cannot get it working.
I think the problem is around returning the view to the screen..
here is the code
var Theater = {
Models: {},
Collections: {},
Views: {},
Templates:{}
}
Theater.Models.Movie = Backbone.Model.extend({})
Theater.Collections.Movies = Backbone.Collection.extend({
model: Theater.Models.Movie,
url: "scripts/data/movies.json",
initialize: function(){
console.log("Movies initialize")
}
});
Theater.Templates.movies = _.template($("#tmplt-Movie").html())
Theater.Views.Movies = Backbone.View.extend({
el: $("#mainContainer"),
template: Theater.Templates.movies,
//collection: new Theater.Collections.Movies(), //Not needed
initialize: function () {
_.bindAll(this, "render");
this.collection.bind("reset", this.render, this);
},
render: function () {
$(this.el).append(this.template(this.collection.toJSON())) ;
}
})
Theater.Router = Backbone.Router.extend({
routes: {
"": "defaultRoute"
},
defaultRoute: function () {
Theater.movies = new Theater.Collections.Movies()
new Theater.Views.Movies({ collection: Theater.movies });
Theater.movies.fetch();
}
})
var appRouter = new Theater.Router();
Backbone.history.start();
and here is the very basic html
<div id="mainContainer"></div>
<script type="text/template" id="tmplt-Movie">
<div><%=name %> </div>
</script>
thanks
this.collection.toJSON()) converts collection into a json, so trying to access name on it in the template won't give you anything.
You can write your render method like this:
render : function() {
var _view = this;
this.collection.each(function(model) {
$(_view.el).append(_view.template(model.toJSON())); // assuming model has 'name' attribute which is accessed in the template code
});
}
This should work.
You have an incorrect template
template: Theater.Templates.movies,
In the render function use
var template = _.template( $("#tmplt-Movie").html(), this.collection.toJSON() );
this.$el.html( template );
Try that. If it fails. Try some console to log to check that fetch is being called, the collection is being populated and that render is being called. If render is being called then it just a matter of correcting a small mistake that will probably be related to dom selection.
It seems that you want to provide a collection to the template, and that the template should loop through the collection and present the values. You can provide a collection to a template, but that’s probably not the best way.
The primary problem seems that you are using a colleciton where you should be using an model. In the render function you are passing a collection to the template. The template should take Models Json.
This is where sub views can be used. So you would want a primary view that takes a collection and that primary view will call a subview that will accept a model.
I did provide an example on jsFiddle.net. It’s somewhat of an hack. Instead of passing a colleciton into the template, I passed an individual item from the collection. This will only render 1 model. Since Routing can be confusing, I went ahead and removed it.
Example on jsFiddle.net. I sometime have problems with IE and jsFiddle.net. I recommend using the Chrome Browser.
this.$el.append(this.template(this.collection.at(0).toJSON()));
Just this month I did started creating more simple tutorials on Backbone.js. This list of tutorial is located at the bottom of this page:
More Simple Backbone.js Examples
Hopefully soon, I will have the time create a simple tutorial on rendering colletion.
Here's the complete code
<div id="mainContainer"></div>
var Theater = {
Models: {},
Collections: {},
Views: {},
Templates: {}
};
Theater.Models.Movie = Backbone.Model.extend({});
Theater.Collections.Movies = Backbone.Collection.extend({
model: Theater.Models.Movie,
//url: "scripts/data/movies.json",
initialize: function() {
console.log("Movies initialize")
}
});
Theater.Templates.movies = _.template($("#tmplt-Movie").html());
Theater.Views.Movies = Backbone.View.extend({
el: $("#mainContainer"),
template: Theater.Templates.movies,
//collection: new Theater.Collections.Movies(), //Not needed
initialize: function() {
_.bindAll(this, "render");
this.collection.bind("reset", this.render, this);
},
render: function() {
this.$el.append(this.template(this.collection.at(0).toJSON()));
}
});
var movies = new Theater.Collections.Movies();
var movieView = new Theater.Views.Movies({ collection: movies });
var myMovies =
[{
"Id": "BVwi1",
"Name": "Bag It",
"AverageRating": 4.6,
"ReleaseYear": 2010,
"Url": "http://www.netflix.com/Movie/Bag_It/70153545",
"Rating": "NR"
},
{
"Id": "BW1Ss",
"Name": "Lost Boy: The Next Chapter",
"AverageRating": 4.6,
"ReleaseYear": 2009,
"Url": "http://www.netflix.com/Movie/Lost_Boy_The_Next_Chapter/70171826",
"Rating": "NR"
}];
movies.reset(myMovies);
I hope this help.

Resources