backbone collection change doesn't get triggered when changing model - backbone.js

I'm building a frontend with backbone on top of an API and wonder, why the change event doesn't get triggered. Fetching and saving works, but the template doesn't get rerendered.
Collection
var Clients = Backbone.Collection.extend({
url: API.host + '/clients',
model: Client
});
Model
var Client = Backbone.Model.extend({
urlRoot: API.host + '/clients',
defaults: {
clients: {
number: '',
company: '',
address1: '',
address2: '',
city: '',
zip: '',
country: '',
tax: '',
email: '',
phone: ''
}
}
});
View
var ClientsView = Backbone.View.extend({
id: 'content',
initialize: function() {
// instantiate Collection
this.model = new Clients();
// compile Handlebars template
this.tpl = Handlebars.compile(this.template);
// bind change and reset model events to rerender template
this.model.bind('change', this.render);
this.model.bind('reset', this.render);
},
render: function() {
var self = this;
var obj = this.el;
// get clients and set data to handlebars template
$.when(this.model.fetch()).then(function(response) {
$(obj).html(self.tpl(response));
}, function() {
$(obj).html(self.tpl);
});
return this;
},
events: {
"click #client-save": "save"
},
save: function(e) {
// cache target and parent object for quick form access
var obj = e.target;
var parent = $(obj).closest('#client-modal');
var client = new Client({
clients: {
number: parent.find('[name="number"]').val(),
company: parent.find('[name="company"]').val(),
address1: parent.find('[name="address1"]').val(),
address2: parent.find('[name="address2"]').val(),
city: parent.find('[name="city"]').val(),
zip: parent.find('[name="zip"]').val(),
country: parent.find('[name="country"]').val(),
tax: parent.find('[name="tax"]').val(),
email: parent.find('[name="email"]').val(),
phone: parent.find('[name="phone"]').val(),
web: parent.find('[name="web"]').val()
}
});
client.save();
return false;
}
});

You need pass the context when you bind your functions.
this.model.bind('change', this.render, this)
Try to use another name for your collection instance.
// Example
this.collectionClients = new Clients();
\0/

Related

Adding new Item to collection on 'add' triggers only once

I am pretty new to backbone,so probably it is stupid bug.
When I press send button(#send_email_button) , one email is rendered as it should,
but when i press it again, no more emails added.
the only logs i got is:(after second+ push)
:
console.log('adding to collection');
console.log('about to exit');
in other words it does not even enters add handler in collection.
Can someone explain why and how to fix this?
Many thanks!
EDIT: If i delete 1 email that rendered , and press send again, new email added correctly.
Here relevant code:
$(document).ready(function() {
//email model
var EmailModel = Backbone.Model.extend({
defaults: {
id: '',
email_from: '',
email_recipient: '',
email_subject: '',
email_data: '',
is_email_read: '',
email_date: ''
}
});
//email collection
var email_collection = Backbone.Collection.extend({
model: EmailModel,
url: '/fetch_received_emails'
});
var email_collection = new email_collection();
var EmailView = Backbone.View.extend({
model: new EmailModel(),
tagName:'li',
events: {
"click #email_template_view" : "click_email"
},
initialize: function() {
console.log('initializing email view');
this.template = _.template($('#email_template').html());
console.log('finish initializing email view');
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
click_email: function() {
this.model.is_email_read = true;
$('#toggle_part_email').toggleClass('no_display');
},
});
var CollectionView = Backbone.View.extend({
model: email_collection,
el: $('#emails_class'),
initialize: function() {
console.log('init to collection view');
this.model.fetch();
this.render();
this.model.on('change', this.render, this);
this.model.on('add', this.render, this);
this.model.on('remove', this.render, this);
},
render: function(){
console.log('rendering collection');
var that = this,
i;
that.$el.html('');
emails = this.model.toArray();
for (i in emails){
console.log(' printing emails');
console.log(emails[i]);
var new_email_view = new EmailView( {model : emails[i]});
that.$el.append(new_email_view.render().$el);
}
console.log('about to exit collection view');
return this;
}
});
$('#send_email_button').click(function(event){
// event.preventDefault();
var sending_date= new Date();
sending_date = sending_date.toDateString()
//new email to ajax
console.log('adding to collection');
email_collection.add(new EmailModel({
'email_from':$('#email_from').val(),
'email_recipient' :$('#email_recipient').val(),
'email_subject': $('#email_subject').val(),
'email_data':$('#email_data').val(),
'is_email_read':false,
'email_date': sending_date
}));
console.log('about to exit');
return false;
});
//create singelton for the collection view
var c = new CollectionView();
});
Why don't you try to use the click event as the other one? Inside the collectionView use events again.
events: {
"click #send_email_button" : "AnyNameThatYouWant"
},
AnyNameThatYouWant: function() {
//Do all the things
},
Try this.

Backbone - Nested Model with Model Binder

ModelBinder doesn't seem to work together with nested model( backbone-nested project) ..the changes from model don't get propogated to the nested elements.On changing the input value the span value doesn't change...If NestedModel is replace with DeepModel it works. Again the NestedModel also works if the person.name is removed and Model has just one level(lastName and firstName).
<script type='text/coffeescript'>
$ ->
class MyModel extends Backbone.NestedModel
defaults:
person:
name :
firstName: 'Bob'
lastName: 'Sass'
window.model = new MyModel
FormView = Backbone.View.extend
initialize: ->
#modelBinder = new Backbone.ModelBinder();
#modelBinder.bind(#model,#el)
el: '#frm'
view = new FormView model: model
</script>
<body>
<form method="post" action="/test" id='frm'>
<div id="welcome"> Welcome, <span id='person.name.firstName'></span> <span id='person.name.lastName'></span>
<br><br>
Edit your information:
<input type="text" name="person.name.firstName" value="zz"/>
<input type="text" name="person.name.lastName" value="nn"/></div>
</form>
I ran into the very same problem. I found success using Backbone.ModelBinder in conjunction with backbone-associations. It allowed me to use ModelBinder with my nested models and accomplish what you are describing.
I took the example you posted and created a fiddle Using Backbone.ModelBinder with backbone-associations using your example. Check it out and see if it answers your question.
The JavaScript ends up looking like this:
var Name = Backbone.AssociatedModel.extend({
defaults: { 'firstName': '', 'lastName': '' }
});
var Person = Backbone.AssociatedModel.extend({
defaults: { 'name': null },
// create a relation for our nested model
relations: [{
'type': Backbone.One,
'key': 'name',
'relatedModel': Name
}]
});
var MyModel = Backbone.AssociatedModel.extend({
defaults: { 'person': null },
// create a relation for our nested model
relations: [{
'type': Backbone.One,
'key': 'person',
'relatedModel': Person
}]
});
var FormView = Backbone.View.extend({
el: '#frm',
initialize: function() {
this._modelBinder = new Backbone.ModelBinder();
},
render: function() {
var bindingsHash = {
'person.name.firstName': [
{ 'selector': '#firstName' },
{ 'selector': '[name=firstName]' }
],
'person.name.lastName': [
{ 'selector': '#lastName' },
{ 'selector': '[name=lastName]' }
]
};
this._modelBinder.bind(this.model, this.el, bindingsHash);
},
close: function() {
this._modelBinder.unbind();
}
});
// create the model
var modelInfo = new MyModel({
'person': {
'name': {
'firstName': 'Bob',
'lastName': 'Sass'
}
}
});
// create the view and render
var view = new FormView({ model: modelInfo });
view.render();

Backbone - Accessing Model defaults through Collection in a View

Ok thaht might not be too clear.
I passed a collection to my view. My collection has a Model, and the Model has defaults in an array. When I log the collection from the View it shows no length. But there are 4 defaults in my model. How can I get the default of my model to my view?
The call to view:
var menuLinks = new App.Collections.MenuLinks ;
var newView = new App.Views.Navbar({ collection: menuLinks }) ;
View:
App.Views.Navbar = Backbone.View.extend({
initialize: function(){
console.log(this.collection) ;
//this.render() ;
}
});
COllection:
App.Collections.MenuLinks = Backbone.Collection.extend({
model: App.Models.MenuLinks
});
Model:
App.Models.MenuLinks = Backbone.Model.extend({
//Default menus
defaults:[
{
name: 'Home',
href: ''
},
{ name: 'Trips',
href: '#trips'
},
{ name: 'Login',
href: '#login'
},
{ name: 'LogoutOhYeah',
href: '#logout'
},
]
});
So... you simply want something like:
var collection = this.options.collection, defaults;
defaults = collection.length && collection.models[0].defaults ||{};

how to listen to array submodel's value change

I have a model define like below.
var A = Backbone.Model.extends({
initialize: function(){
this.on('change:users.name',this.onUserNameChanged)
},
onUserNameChanged: function(){ alert('name changed')
});
var a = new A ({
id:1,
name:'test',
users:[
{
id:2,
name:'u1'
},
{
id:3,
name:'u4'
}
]
})
I want add event on the each user name change in Model define.
I have no idea to do this.It's seems hard to me.
It seems that you can't get change events by manipulating Backbone model array members as is described here:
backbone-js-set-model-array-property
Your best option is to set the whole array and listen to a change:users event or even better - come up with a different model that has a Backbone collection for users that will get the event when manipulated:
var A = Backbone.Model.extend({
initialize: function (options) {
_.extend(this, options);
}
});
var Users = Backbone.Collection.extend({
initialize: function () {
this.on('change:name', function () {
alert('name changed');
});
}
});
var a = new A({
id: 1,
name: 'test',
users: new Users([{
id: 2,
name: 'u1'
}, {
id: 3,
name: 'u4'
}])
});
a.users.get('2').set('name', 'u2');

Appending data to same collection after every pagination fetch

I am trying to populate instagram images using backbone,
I have basically 3 models as follows,
User model store all the user info related to instagram
App.Models.User = Backbone.Model.extend({
defaults: {
id: '',
access_token: '',
userid: '',
username: '',
full_name: '',
profile_picture: ''
},
urlRoot: "/api/user/",
initurl: function() {
return "https://api.instagram.com/v1/users/"+this.get('userid')+"/media/recent/?access_token=" + this.get('access_token');
},
initialize: function() {
this.set('id', $('#domdump .id').val());
this.fetch({
success: function(model) {
var photos = new App.Collections.Ig_photos([],{
url: model.initurl()
});
}
});
}
});
A model to store the next url for pagination
App.Models.Ig_next_url = Backbone.Model.extend({
defaults: {
next_url: ''
},
next_url:function(){
return this.get('next_url');
}
});
A model for the photo
App.Models.Ig_photo = Backbone.Model.extend({});
A collection for the multiple photo
App.Collections.Ig_photos = Backbone.Collection.extend({
model: App.Models.Ig_photo,
initialize: function(model, options) {
this.url = options.url;
this.nextSet();
},
sync: sync_jsonp,
parse: function( response ) {
if(response.pagination && response.pagination.next_url && response.pagination.next_url != this.url){
var next_url = new App.Models.Ig_next_url({ next_url: response.pagination.next_url });
this.url = next_url.next_url();
}
return response.data;
},
nextSet: function(){
this.fetch({
success: function(photos){
var ig_photos_views = new App.Views.Ig_photos_view({ collection: photos});
console.log(photos);
}
});
}
});
Also i have some views that does the render with a load more button that calls the nextset of the collection.
What i was trying to achieve is the photos get appended to the collection upon nextset() and the collection get updated with pervious data + new data but right now its getting overwritten.
Also is it okay to instantiate new collection from the modelfetch ?
You shouldn't need to make a new view. You should instead listen to the "add" event being triggered on the collection and render new items accordingly.
nextSet: function(){
this.fetch({add : true}); // The add option appends to the collection
}
This option is detailed in the very good documentation.

Resources