I've made view to listen to model changes. When there is change in model render function will be called and alert window will be prompted. But it is coming twice that means render function is calling twice because of two change events.
WineDetails View
app.WineView = Backbone.View.extend({
template:_.template($('#tpl-wine-details').html()),
initialize:function () {
this.model.bind("change", this.render, this);
},
render:function (eventName) {
if(eventName)alert("changed")
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
events:{
"change input":"change",
"click .save":"saveWine",
"click .delete":"deleteWine"
},
change:function (event) {
var target = event.target;
console.log('changing ' + target.id + ' from: ' + target.defaultValue + ' to: ' + target.value);
// You could change your model on the spot, like this:
// var change = {};
// change[target.name] = target.value;
// this.model.set(change);
},
saveWine:function () {
this.model.set({
name:$('#name').val(),
grapes:$('#grapes').val(),
country:$('#country').val(),
region:$('#region').val(),
year:$('#year').val(),
description:$('#description').val()
});
if (this.model.isNew()) {
var self = this;
app.router.wineList.create(this.model,{wait:true,success:function(){
app.router.navigate('wines/'+self.model.id,false);
}});//add event,request event on collection will be triggered
} else {
this.model.save();//change event,request event on model will be triggered
}
return false;
},
onClose:function()
{
alert("onclose");
this.model.unbind("change",this.render);
}
And its not because of zombie view because i've this following code
Backbone.View.prototype.close=function()
{
alert("closing view "+this);
if(this.beforeClose){
this.beforeClose();
}
this.remove();
this.unbind();
if(this.onClose){
this.onClose();
}
}
please tell me what is wrong in this code. Thank u :)
So, as you didn't provide the information regarding your Model#save call, I'll assume it's the one within your view. I'll also assume the problem doesn't come from zombie views because you're following an outdated method for that. I'll make a guess here about what's probably happening:
this.model.set({
name:$('#name').val(),
grapes:$('#grapes').val(),
country:$('#country').val(),
region:$('#region').val(),
year:$('#year').val(),
description:$('#description').val()
});
// ...
this.model.save();
Ok, the first part (the set method) will trigger a first change event.
The second part, the save method may trigger another change. Another set will indeed be done with the attributes sent back from the server.
Possible solution to a possible problem:
save can be passed attributes, and a wait flag to postpone the use of the set method until the server responds:
this.model.save({
name:$('#name').val(),
grapes:$('#grapes').val(),
country:$('#country').val(),
region:$('#region').val(),
year:$('#year').val(),
description:$('#description').val()
}, {wait: true});
You can also try it by creating always a new instance of your model like :
var wine = new WineModel({
name:$('#name').val(),
grapes:$('#grapes').val(),
country:$('#country').val(),
region:$('#region').val(),
year:$('#year').val(),
description:$('#description').val()
});
And then save it like :
wine.save(null, success: function(model){
// do your call action on call back
},
beforeSend: function() {
// before save
}
error: function(model, errors) {
// on error occurred
});
Related
I have this collection with an over-riden parse method. I want a method in my view to be called when the collection is finished with parse
This collection will be calling sync and so parse only once.
I tried this.collection.on("reset", this.more, this); but that doesn't work.
more: function() {
var users = this.collection.slice( this.index, this.index + this.load_once), that = this;
this.index = this.index + this.load_once;
_.each( users, function( user ){
that.addOne( user );
});
},
addOne: function( user ){
var view = new UserView({model: user});
this.$("#user_list").append(view.render().el);
}
The reset method will be triggered when {reset: true} option is passed to the fetch. You can listen to the add and sync that will fire this method. Also use this.listenTo bind the events in a cleaner manner.
initialize: function() {
... some other code
this.listenTo(this.collection, "add sync", this.more);
this.collection.fetch();
}
In my backbone function, while the name get change the change function not at all triggering.. any one suggest me the right way to get it.. (actually i need to get changed stuff and need to update);
code :
(function($){
var list = {};
list.model = Backbone.Model.extend({
defaults:{
name:'need the name'
},
initialize:function(){
this.bind('change:name', function(model) {
console.log('Model->change()', model);
});
}
});
list.collect = Backbone.Collection.extend({
model:list.model,
url : 'data/names.json',
initialize:function(){
this.fetch({update:true});
this.keepUpdate();
},
keepUpdate:function(){
var that = this;
var updateData = function(){
that.fetch({update:true});
myTimeout = setTimeout(updateData,10000);
}
var myTimeout = setTimeout(updateData,10000);
}
});
list.view = Backbone.View.extend({
initialize:function(){
this.collection = new list.collect();
this.collection.on("update", this.render, this);
this.collection.bind("change:name", function(model, attributes){
console.log(model,attributes,'property changed'); // this is not triggering at all..
});
},
render:function(data){
_.each(this.collection.models, function(data){
//console.log(data.get('name')); it works fine
})
},
updateName:function(){
console.log('updated called');
}
});
var newView = new list.view();
})(jQuery)
Collection.fetch doesn't trigger the change event. You only get the reset event. If you need more granular events, consider calling fetch with the options {update:true}.
that.fetch({update:true});
That will trigger change event for every model that was already in the collection, and add if the model was previously not in the collection.
Try removing keepUpdate from the collection and put a setTimeout in the initialize function of the view at the end. I suggest that fetch is called from the view as well as this.collection.fetch() instead of the collection's initialize function. Makes your code more reusable.
I'm not sure I understand your question. What are you trying to achieve ?
I don't think that fetch accepts {add:true} as a parameter (I just checked the source code and it does not appear anywhere).
When fetch completes, it only triggers a reset event (not an add). You should listen to that if you want to do something when the content of the collection changes. You can also simplify listen to change.
In my backbone function, while the name get change the change function not at all triggering.. any one suggest me the right way to get it.. (actually i need to get changed stuff and need to update);
code :
(function($){
var list = {};
list.model = Backbone.Model.extend({
defaults:{
name:'need the name'
},
initialize:function(){
this.bind('change:name', function(model) {
console.log('Model->change()', model);
});
}
});
list.collect = Backbone.Collection.extend({
model:list.model,
url : 'data/names.json',
initialize:function(){
this.fetch({update:true});
this.keepUpdate();
},
keepUpdate:function(){
var that = this;
var updateData = function(){
that.fetch({update:true});
myTimeout = setTimeout(updateData,10000);
}
var myTimeout = setTimeout(updateData,10000);
}
});
list.view = Backbone.View.extend({
initialize:function(){
this.collection = new list.collect();
this.collection.on("update", this.render, this);
this.collection.bind("change:name", function(model, attributes){
console.log(model,attributes,'property changed'); // this is not triggering at all..
});
},
render:function(data){
_.each(this.collection.models, function(data){
//console.log(data.get('name')); it works fine
})
},
updateName:function(){
console.log('updated called');
}
});
var newView = new list.view();
})(jQuery)
Collection.fetch doesn't trigger the change event. You only get the reset event. If you need more granular events, consider calling fetch with the options {update:true}.
that.fetch({update:true});
That will trigger change event for every model that was already in the collection, and add if the model was previously not in the collection.
Try removing keepUpdate from the collection and put a setTimeout in the initialize function of the view at the end. I suggest that fetch is called from the view as well as this.collection.fetch() instead of the collection's initialize function. Makes your code more reusable.
I'm not sure I understand your question. What are you trying to achieve ?
I don't think that fetch accepts {add:true} as a parameter (I just checked the source code and it does not appear anywhere).
When fetch completes, it only triggers a reset event (not an add). You should listen to that if you want to do something when the content of the collection changes. You can also simplify listen to change.
I have a two views:
1 LeftView (maximized when RightView is minimized & vice versa)
2 RightView (containing)
- collection of
- RightItemView (rendering RightItemModel)
When RightView is maximized and the user clicks a RightItemView, I want to maximize LeftView and display something according to the data from the clicked RightItemView.
What's the proper way to wire them?
I would recommend using the Backbone.Events module:
http://backbonejs.org/#Events
Basically, this line is all it takes to create your event dispatcher:
var dispatcher = _.clone(Backbone.Events);
Then all of your views can trigger/listen for events using the global dispatcher.
So, in RightItemView you would do something like this in the click event:
dispatcher.trigger('rightItemClick', data); // data is whatever you need the LeftView to know
Then, in LeftView's initialize function, you can listen for the event and call your relevant function:
dispatcher.on('rightItemClick', this.maximizeAndDisplayData);
Assuming your LeftView would have a function like so:
maximizeAndDisplayData: function(data) {
// do whatever you need to here
// data is what you passed with the event
}
The solution #jordanj77 mentioned is definitely one of the correct ways to achieve your requirement. Just out of curiosity, I thought of another way to achieve the same effect. Instead of using a separate EventDispatcher to communicate between the two views, why shouldn't we use the underlying model as our EventDispatcher? Let's try to think in those lines.
To start with, add a new boolean attribute to the RightItem model called current and default it to false. Whenever, the user selects the RightItemView, set the model's current attribute to true. This will trigger a change:current event on the model.
var RightItem = Backbone.Model.extend({
defaults: {
current: false,
}
});
var RightItemView = Backbone.View.extend({
events: {
'click li': 'changeCurrent'
}
changeCurrent: function() {
this.model.set('current', true);
}
});
On the other side, the LeftView will be handed a Backbone.Collection of RightItem models during creation time. You would anyways have this instance to supply the RightView isn't it? In its initialize method, the LeftView will listen for change:current event. When the event occurs, LeftView will change the current attribute of the model it is currently displaying to false and start displaying the new model that triggered this event.
var LeftView = Backbone.View.extend({
initialize: function() {
this.collection.on('change:current', this.render, this);
},
render: function(model) {
// Avoid events triggered when resetting model to false
if(model.get('current') === true) {
// Reset the currently displayed model
if (this.model) {
this.model.set('current') = false;
}
// Set the currently selected model to the view
this.model = model;
// Display the view for the current model
}
}
});
var leftView = new LeftView({
// Use the collection that you may have given the RightView anyways
collection: rightItemCollection
});
This way, we get to use the underlying model as the means of communication between the Left and Right Views instead of using an EventDispatcher to broker for us.
The solution given by #Ganeshji inspired me to make a live example
I've created 2 views for this.
var RightView = Backbone.View.extend({
el: $('.right_view'),
template: _.template('<p>Right View</p>'),
renderTemplate: function () {
this.$el.html('');
this.$el.append(this.template());
this.$link = this.$el.append('Item to view').children('#left_view_max');
},
events: {
'click #left_view_max' : 'maxLeftView'
},
maxLeftView: function () {
//triggering the event for the leftView
lView.trigger('displayDataInLeftView', this.$link.attr('title'));
},
initialize: function (options) {
this.renderTemplate();
}
});
var LeftView = Backbone.View.extend({
el: $('.left_view'),
template: _.template('<p>Left View</p>'),
renderTemplate: function () {
this.$el.html('');
this.$el.append(this.template());
},
displayDataInLeftView: function (data) {
this.$el.append('<p>' + data + '</p>');
},
initialize: function (options) {
//set the trigger callback
this.on('displayDataInLeftView', this.displayDataInLeftView, this);
this.renderTemplate();
}
});
var lView = new LeftView();
var rView = new RightView();
Hope this helps.
suppose I have a model and a view ,ths view have two method:one is bind the document mousemove event and the other is unbind method,defalut I give the document mousemove event, once the model's enable value changed I will call the view's unbind method:
window.ConfigModel = Backbone.Model.extend({
defaults: {
'enable':0
},
initialize: function(){
this.bind("change:enable", function () {
var portView2 = new PortView();
portView2.viewOff();
});
},
change:function () {
this.set('enable', 9);
}
})
window.PortView = Backbone.View.extend({
viewOn: function () {
$(document).on('mousemove', function () {
console.log('move')
})
},
viewOff: function () {
$(document).off('mousemove');
}
})
then I put an input on the document to call the model changed:
$('input').click(function () {
var configModel = new ConfigModel();
configModel.change();
})
the boot script is :
var portView1 = new PortView();
portView1.viewOn();
The problem is once I call the click the input button ,the chrome would tell me an error:Maximum call stack size exceeded it seems the change be invoke many times.So what's the problem with my problem ,how can I solve this problem
Backbone models already have a change method:
change model.change()
Manually trigger the "change" event and a "change:attribute" event for each attribute that has changed. If you've been passing {silent: true} to the set function in order to aggregate rapid changes to a model, you'll want to call model.change() when you're all finished.
Presumably something inside Backbone is trying to call configModel.change() and getting your version of change which triggers another change() call inside Backbone which runs your change which ... until the stack blows up.
You should use a different name for your change method.
That said, your code structure is somewhat bizarre. A model listening to events on itself is well and good but a model creating a view is odd:
initialize: function() {
this.bind("change:enable", function () {
var portView2 = new PortView();
portView2.viewOff();
});
}
And instantiating a view simply to call a single method and then throw it away is strange as is creating a new model just to trigger an event.
I think you probably want to have a single ConfigModel instance as part of your application state, say app.config. Then your click handler would talk to that model:
$('input').click(function () {
app.config.enable_level_9(); // or whatever your 'change' gets renamed to
});
Then you'd have some other part of your application (not necessarily a view) that listens for changes to app.config and acts appropriately:
app.viewOn = function() {
$(document).on('mousemove', function() {
console.log('move')
});
};
app.viewOff = function() {
$(document).off('mousemove');
};
app.init = function() {
app.config = new ConfigModel();
app.viewOn();
$('input').click(function () {
app.config.enable_level_9();
});
// ...
};
And then start the application with a single app.init() call:
$(function() {
app.init();
});