Backbone-relational change event not firing? - backbone.js

I'm stumped on this. I have a simple backbone-relational model:
window.Site = Backbone.RelationalModel.extend({
idAttribute: "_id",
// These are the relations to the user model.
relations: [{
type: Backbone.HasMany,
key: 'users',
relatedModel: 'window.User'
}],
});
And my user model (which is related to the site model) looks like this:
//Site User model
// -------------
window.User = Backbone.RelationalModel.extend({
});
The user model is intentionally kept dumb, as I'm still prototyping.
The JSON I receive from the server to hydrate the Site and users looks something like this:
{
_id: foo,
users: [
username: bar,
password: fizz
]}
What I'm stumped on is the listeners. The events that are on the SiteView (which renders my SiteCollection) look like this:
initialize: function() {
//basic bindings
this.model.bind('change', this.setSave, this);
this.model.bind('destroy', this.remove, this);
// bindings to sub-models
this.model.bind('add:users', this.setDetailsView, this);
this.model.bind('remove:users', this.setSave, this);
this.model.bind('change:users', this.setSave, this);
The add:users and remove:users events fire fine, but the change:users event does not. In the DetailsView, which renders my Users models, I also have some simple event bindings:
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
But, for some reason, the change:users event in the SiteView does not fire, while the change event in the DetailsView does.
Could this be because:
The change event on the Users models is bound twice in two different views?
The Users model is not bidirectional?

To me it looks like the update: event does not work exactly as you would anticipate. From my understanding, it only fires, if the key, i.e. the related model changes, but not if you change its attributes. This means, only if you switch from one user to the other, it would fire, not if you change the attribute of an existing user. This is my understanding of line 711 of the source code.
What might work is something along those lines:
this.model.get('users').each(function(user){
user.bind(change,this.render,this);
},this);

Better solution :
this.model.bind('relational:change:users', this.setSave, this);

Related

How can I listenTo a NestedModel change on it's collection?

I'm using NestedModel change plugin to support nested model changes into my models.
I tried this into my View where I got a reference to a collection.
....
initialize: function(){
this.listenTo(this.collection, 'change', foo);
},
foo: function(model){
console.log("Some model changed")
}
By doing FooModel.set({"fooPropertie.BarPropertie": "fooBarValue"});
I would expect to trigger a collection change event.
Am i doing something wrong?
It is hard to define the trouble since you do not provide the way this.collection was defined. But I see at least one thing to tune: this.listenTo(this.collection, 'change', this.foo);. Add this. before foo call. Currently in your context foo is undefined.

Marionette ItemView how to re-render model on change

I'm using Handlebars template engine.
so, I have Model:
Backbone.Model.extend({
urlRoot: Config.urls.getClient,
defaults: {
contract:"",
contractDate:"",
companyTitle:"",
contacts:[],
tariff: new Tariff(),
tariffs: [],
remain:0,
licenses:0,
edo:""
},
initialize:function(){
this.fetch();
}
});
then Marionette ItemView:
Marionette.ItemView.extend({
template : templates.client,
initialize: function () {
this.model.on('change', this.render, this);
},
onRender: function () {
console.log(this.model.toJSON());
}
});
and then I call everything as:
new View({
model : new Model({id:id})
})
and, it's immediately render a view for me and this is cool.
But after the model fetched data it's trigger "change", so I see in console serialised model twice, and I see for first time empty model and then filled one.
But, the view is NOT updated.
How I can fix it?
P.S. I understand, that I can call a render method on fetch done callback. But I also need it for further actions, when user will change model.
In the View, You can use following code
modelEvents: {
'change': 'render'
}
instead of
initialize: function () {
this.model.on('change', this.render, this);
},
onRender: function () {
console.log(this.model.toJSON());
}
Actually, Backbone and Marionette are smart enough to do it.
Problem was in template and data as I found it another question. So, I re-checked everything and got result.

Backbone: Update not calling callback function

I'm trying to add the functionality to my app so I can update my database and then update the DOM. The database gets updated fine, but the DOM doesn't. Here is part of my view:
App.Views.Table = Backbone.View.extend({
tagName: 'span',
initialize: function(options) {
this.model.on('change', this.render, this);
this.model.on('update', this.remove, this);
this.template = this.options.template;
this.url = this.options.url;
},
events: {
'click .verify': 'verify',
'click .spam': 'spam',
'click .duplicate': 'duplicate'
},
verify: function(e) {
id = e.currentTarget.id;
table = new App.Models.Table({ id: id });
table.urlRoot = this.url;
table.fetch();
table.toJSON();
table.set('verified', 1);
table.save();
},
spam: function(e) {
...
},
duplicate: function(e) {
...
},
remove: function() {
this.$el.remove();
console.log('hello');
},
retrieveTemplate: function(model) {
return _.template($('#' + this.template).html(), model);
},
render: function() {
//console.log(this);
this.$el.html(this.retrieveTemplate(this.model.toJSON()));
return this;
}
});
As I understand it, this.model.on('update', this.remove, this); should call my remove function when save completes. But the callback isn't firing because I'm not getting the console.log and my DOM isn't being updated. What am I doing wrong? I followed a tutorial, but everything works fine in the tutorial.
There is no update event. I think you mean sync
http://backbonejs.org/#Events-catalog
"sync" (model, resp, options) — when a model has been successfully synced with the server.
Also a helpful hit for debugging that I found was using the all event to see what events are getting triggered.
Edit:
After some debugging, the goal of the verify() function was to save the verified attribute to the model. To do that we needed to change verify() to
this.model.set('verified', 1);
this.model.save();
instead of creating a new App.Model.Table and setting it to the table variable. Doing table .save() was saving the new table model, instead of the old model, this.model. That's why the event handlers attached to this.model were net getting triggered.
There are no "create" or "update" events in Backbone.js. That's why your remove() callback isn't firing.
See http://backbonejs.org/#Events-catalog for a catalog of the possible events in Backbone.
UPDATE:
After looking at your code more closely, the answer is clear. They are different models:
initialize: function(options) {
this.model.on('change', this.render, this);
this.model.on('sync', this.remove, this);
and
table = new App.Models.Table({ id: id });
...
table.save();
The events that occur on your table object are not going to trigger event handlers that are bound to a totally different model (this.model).
Why create another model (table) when you already had a model? (this.model) ?
* UPDATE *
I don't really understand what you're trying to do, but perhaps try this:
table = new App.Models.Table({ id: id });
table.on('sync', this.remove, this);

Backbone.Collection.Create not triggering "add" in view

Hopefully this is an easy question. I'm trying to learn backbone and i'm stuck on a really simple thing. the render on the view never gets called when I update the collection by using the create method. I thought this should happen without explicitly calling render. I'm not loading anything dynamic, it's all in the dom before this script fires. The click event works just fine and I can add new models to the collection, but the render in the view never fires.
$(function(){
window.QuizMe = {};
// create a model for our quizzes
QuizMe.Quiz = Backbone.Model.extend({
// override post for now
"sync": function (){return true},
});
QuizMe._QuizCollection = Backbone.Collection.extend({
model: QuizMe.Quiz,
});
QuizMe.QuizCollection = new QuizMe._QuizCollection
QuizMe.QuizView = Backbone.View.extend({
el:$('#QuizMeApp'),
template: _.template($('#quizList').html()),
events: {
"click #addQuiz" : "addQuizDialog",
},
initialize: function() {
// is this right?
_.bindAll(this,"render","addQuizDialog")
this.model.bind('add', this.render, this);
},
addQuizDialog: function(event){
console.log('addQuizDialog called')
QuizMe.QuizCollection.create({display:"this is a display2",description:"this is a succinct description"});
},
render: function() {
console.log("render called")
},
});
QuizMe.App = new QuizMe.QuizView({model:QuizMe.Quiz})
});
Your problem is that you're binding to the model:
this.model.bind('add', this.render, this);
but you're adding to a collection:
QuizMe.QuizCollection.create({
display: "this is a display2",
description: "this is a succinct description"
});
A view will usually have an associated collection or model but not both. If you want your QuizView to list the known quizzes then:
You should probably call it QuizListView or something similar.
Create a new QuizView that displays a single quiz; this view would have a model.
Rework your QuizListView to work with a collection.
You should end up with something like this:
QuizMe.QuizListView = Backbone.View.extend({
// ...
initialize: function() {
// You don't need to bind event handlers anymore, newer
// Backbones use the right context by themselves.
_.bindAll(this, 'render');
this.collection.bind('add', this.render);
},
addQuizDialog: function(event) {
this.collection.create({
display: "this is a display2",
description: "this is a succinct description"
});
},
render: function() {
console.log("render called")
// And some stuff in here to add QuizView instances to this.$el
return this; // Your render() should always do this.
}
});
QuizMe.App = new QuizMe.QuizView({ collection: QuizMe.QuizCollection });
And watch that trailing comma after render, older IEs get upset about that and cause difficult to trace bugs.
I'd give you a quick demo but http://jsfiddle.net/ is down at the moment. When it comes back, you can start with http://jsfiddle.net/ambiguous/RRXnK/ to play around, that fiddle has all the appropriate Backbone stuff (jQuery, Backbone, and Underscore) already set up.

Backbone.js: View with Reference to Model and Collection ... smell?

I have a view for individual Customer models that takes a customer and collection of customers on construction. The requirement of the customer model makes sense -- it is a view for a customer. The requirement of the collection ... I'm not sure if that is a smell or not and would love feedback!
The reason it needs the collection is so that a button in the view can remove the customer from the customers collection and that the view can also listen to collection remove events to see if it has to be unrendered (it was successfully removed from the collection).
var CustomerView = Backbone.View.extend({
events: {
'click button.delete': 'remove'
},
initialize: function() {
_.bindAll(this, 'render', 'unrender', 'remove', 'removed');
this.model.bind('change', this.render);
this.collection.bind('remove', this.removed);
}
// render / unrender removed for brevity
remove: function () {
this.collection.remove(this.model);
},
removed: function (customer) {
if (this.model === customer) {
this.unrender();
}
}
});
Here is how the view is created:
var CustomersView = Backbone.View.extend({
initialize: function () {
_.bindAll(this, 'appendCustomer');
this.model.customers.bind('add', this.appendCustomer);
},
appendCustomer: function (customer) {
var customerView = new CustomerView({
model: customer,
collection: this.model.customers
});
$(this.el).append(customerView.render().el);
}
});
I was thinking I could somehow wire up the CustomerView in this appendCustomer method instead of simply giving the customers collection to the CustomerView wholesale.
Thoughts? Thanks!
If you are adding these models through the collection, you may want to investigate using the model's collection property. This would take the place of the customers association that you plan to give it.
Given the above, you may only need to pass the model into the view. And if you need to access the collection in the template or otherwise, its just 'this.model.collection'.
Also, you can then simply call the 'destroy' method on the model and it will be removed from the collection. You would then use the same event binding strategy you have now except with this built-in collection.
You just have to think in events instead of explicit method calls.
In this example I'm using a custom event.
// simplified code
var CustomerView = Backbone.View.extend({
events: {
'click button.delete': 'remove'
},
initialize: function() {
this.model.bind( "app:remove", this.removed );
},
remove: function () {
this.model.trigger( "app:remove" );
},
removed: function (customer) {
this.unrender();
}
});
// The collection it self removes the model
var CustomersCollection = Backbone.Collection.extend({
initialize: function(){
this.on( "app:remove", this.remove )
}
});

Resources