How does model event binding work in backbone.js? - backbone.js

I got a line in the codebase that I inherited and it goes like this:
App.Models.Configuration = Backbone.Model.extend({
initialize: function(){
this.bind('change', function(config, options){
this.save_previous_state();
// ---- 8< ---- snip
})
},
// ---- 8< ---- snip
})
My question is, the anonymous function that gets called: function(config, options){} - what are the objects being passed to it, the config and options?

In the Backbone docs, at the bottom is the catalog of events.
The Model's change event is passed the model, and a hash of options:
So, in your case, config = the changed model = itself.
Inside that event handler, you can access a few fun things like the attributes that changed:
http://backbonejs.org/#Model-changedAttributes
The previous state of the attributes before the change:
http://backbonejs.org/#Model-previousAttributes

Related

Can I save changed attributes of model after several set

I call set method multiple times and change several attributes. Then I want to send the changed data to the server with {patch: true}.
I can use model.save(attrs, {patch: true});, but I do not know attrs. I can't use model.toJSON() (unneeded fields) or model.changedAttributes() (only last set) to obtain attrs.
How can I do this?
According to changedAttributes:
Optionally, an external attributes hash can be passed in, returning the attributes in that hash which differ from the model.
So you could try caching the state of model using toJSON before you start modifying. Once your modifications are done, pass the new state to changedAttributes method to retrieve changed attributes hash and then send a patch request. Something like
var oldAttrs = model.toJSON();
// ...do modifications here
var changedAttrs = model.changedAttributes(oldAttrs);
dataTosend = model.pick(_.keys(changedAttrs));
model.save(dataTosend, {patch: true});
Bind a listener for model
If you are setting value in view, listener should be like (better write it in initialize function)
this.listenTo(this.model, "change", this.onModelValueChange);
And your listener function
onModelValueChange: function(model, args) {
model.save(args.changed, {patch: true});
}
While TJ has the right answer, I have a better way to achieve what he suggest.
Instead of making a raw clone of the attributes, I prefer to keep a master copy of the model at the start of the app or the initialize of the view.
this.master = this.model.clone();
// ... then changes are made to this.model
When ready to save, use the master model to compare the attributes and retrieve the changes directly.
var changes = this.master.changedAttributes(this.model.attributes);
if (changes !== false) this.model.save(changes, { patch: true });
With this, you can skip the dataTosend = model.pick(_.keys(changedAttrs)) altogether since changes already is an object of all the differences with the initial state of the master model.
If it's a view that is re-used after the model save:
var View = Backbone.View.extend({
initialize: function() {
this.updateMaster();
},
saveChanges: function() {
var changes = this.master.changedAttributes(this.model.attributes);
if (changes !== false) {
this.model.save(changes, {
patch: true,
context: true,
success: this.updateMaster
});
}
},
updateMaster: function() {
this.master = this.model.clone();
},
});

Extjs 4 MVC Declare a variable to use in a whole controller

In my controller I want to use certain variables in several places.
For example I got a form with few fields (combo / textfield) and I want to use a link to them in a various places of my controller code. How can / should I declare such variable?
Usually I use:
refs: [
{
ref: 'myCombo',
selector: 'myPanel combo[name=myCombo]'
},
{
ref: 'myTextfield',
selector: 'myPanel textfield[name=myTextfield]'
}
]
But is it ok to use getMyCombo() / getMyTextfield() every time I have to work with this fields in my controller?
The "refs" feature of the controller is really just generating getter functions for you by using Ext.ComponentQuery with the provided CSS selector. The way you're using them is one way you can make use of the system, though you can also use refs to instantiate (for example) views for the controller using their configured alias or xtype. In your example, you're saving yourself the hassle of re-writing some long-ish ComponentQuery calls.
The 'autoCreate' option, although not documented, is great for this type of thing if for example you wanted to always instantiate a new instance of a certain object every time the controller is activated, you could do so in the init() function.
The answer posted here demonstrates using refs to create new instances and further explains the functionality of autoCreate and forceCreate options.
If you want to use an object or some variable throughout your controller, just set a property on the controller, most suitably in the init method...
Ext.define('App.controller.Messaging', {
/** Include models, stores, views, etc... */
refs: [{
ref: 'messageBox', // creates magic method "getMessageBox"
xtype: 'my-messagebox', // in the class file: alias: 'widget.my-messagebox'
selector: '', // could be itemId, name, etc. Same rules as a CSS selector
autoCreate: true // automatically create when "getMessageBox()" is called
}],
/** I always initialize controllers as-needed, passing the application object */
init: function(app){
var me = this;
// Initialize whatever you need, maybe set up some controller properties
this.eventBus = app.getEventBus();
this.user = app.getActiveUser();
// I prevent listeners from being established twice like this..
if(this.inited === true)
return;
this.inited = true; // nothing past this line will be executed twice
// Observe view events
this.control();
// Listen for application events
app.on({
'getMessages': {fn: me.showMessageBox, scope: me}
});
// Specific controller events
this.on({
'someEvent': {fn: me.someFunction, scope: me}
});
},
// A function using controller properties defined in the init() method
someFunction = function(){
var me = this; // controller
// Lets show the active user
console.warn("Active User", me.user);
// And then fire an event, passing this controller
me.eventBus.fireEvent('somethingHappened', me);
},
// Invoked on the application event "getMessages"
showMessageBox: function(sessionId){
var me = this; // controller
/** ... Load the messages for the provided sessionId ... */
// Then create an instance of the message box widget as property on the controller
me.messageBox = me.getMessageBox({
/** pass config to the view here if needed */
});
}
});

Is it possible to trigger an event when initializing a model?

In a backbone model, is it possible to trigger an event in the initialize function, for a nested view? I based my current code off this example: https://stackoverflow.com/a/8523075/2345124 and have updated it for backbone 1.0.0. Here is my initialize function, for a Model:
var Edit = Backbone.Model.extend({
initialize: function() {
this.trigger('marquee:add');
this.on('change', function(){
this.trigger('marquee:add');
});
}
...
}
I'm trying to call a method renderMarquee when the model is initialized:
var EditRow = Backbone.View.extend({
initialize: function() {
this.listenTo(this.model, "change", this.render); // works
this.listenTo(this.model, "marquee:add", this.renderMarquee); // only called when changed, but not when initially created
...
}
renderMarquee IS called when the model is changed, but not when it is initialized. 'change' events work as expected (this.render is called). Any thoughts?
Thanks!
I am currently facing a similar problem. I needed to trigger the change event in the initialize method of my model.
I looked into the backbone code which revealed why this is not happening:
var Model = Backbone.Model = function(attributes, options) {
...
this.set(attrs, options);
this.changed = {};
this.initialize.apply(this, arguments);
};
the set is executed before the initialize and this.change is emptied setting the model state to "nothing has changed".
In order to overwrite behavior this I added the following code to my initialize method.
initialize: function(attributes, options) {
...
this.changed = attributes;
this.trigger('change');
for (attr_name in attributes) {
this.trigger('change:' + attr_name);
}
},
I trigger all change events manually, this is important for me since inheriting models may bind to change or change:attrxy. But this is not enough, because if I just trigger the events the changedAttributes() method would return false therefore I also set this.changed to the current attributes.
This doesn't make a lot of sense because you are initializing the model somewhere prior to doing the view.listenTo call. Unfortunately, you don't really have a choice in that matter.
You are probably going to want to move the event handling to a Backbone.Collection which already has built in events you can listen on for adding/removing.

rerender Backbone views without losing references to dom

I have the following problem with backbone and I'd like to know what strategy is the more appropriated
I have a select control, implemented as a Backbone view, that initially loads with a single option saying "loading options". So I load an array with only one element and I render the view.
The options will be loaded from a collection, so I fire a fetch collection.
Then I initialize a component that is in charge of displaying in line errors for every field. So I save a reference of the dom element of the combo.
When the fetch operation is finally ready, I rerender the control with all the options loaded from the collection.
To render the view I user something like this:
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
}
pretty standard backbone stuff
the problem is that after rendering the view for the second time the reference of the dom is no longer valid,
perhaps this case is a bit strange, but I can think of lots of cases in which I have to re-render a view without losing their doms references (a combo that depends on another combo, for example)
So I wonder what is the best approach to re-render a view without losing all the references to the dom elements inside the view...
The purpose of Backbone.View is to encapsulate the access to a certain DOM subtree to a single, well-defined class. It's a poor Backbone practice to pass around references to DOM elements, those should be considered internal implementation details of the view.
Instead you should have your views communicate directly, or indirectly via a mediator.
Direct communication might look something like:
var ViewA = Backbone.View.extend({
getSelectedValue: function() {
return this.$(".combo").val()
}
});
var ViewB = Backbone.View.extend({
initialize: function(options) {
this.viewA = options.viewA;
},
doSomething: function() {
var val = this.viewA.getSelectedValue();
}
});
var a = new ViewA();
var b = new ViewB({viewA:a});
And indirect, using the root Backbone object as a mediator:
var ViewA = Backbone.View.extend({
events: {
"change .combo" : "selectedValueChanged"
},
selectedValueChanged: function() {
//publish
Backbone.trigger('ViewA:changed', this.$('.combo').val());
}
});
var ViewB = Backbone.View.extend({
initialize: function(options) {
//subscribe
this.listenTo(Backbone, 'ViewA:changed', this.doSomething);
},
doSomething: function(val) {
//handle
}
});
var a = new ViewA();
var b = new ViewB();
The above is very generic, of course, but the point I'm trying to illustrate here is that you shouldn't have to worry whether the DOM elements are swapped, because no other view should be aware of the element's existence. If you define interfaces between views (either via method calls or mediated message passing), your application will be more maintainable and less brittle.

Backbone and delayed destroy event

Again some simple problem with backbone. Instead of long description here is some sample code:
var Group = Backbone.Model.extend({
defaults: {
Product: new Products()
},
initialize: function(data){
var _this = this;
var products = new Products(data.Product);
this.set('Product', products);
this.get('Product').each(function(product){
product.on('destroy', function(){
_this.trigger('change');
}, _this);
});
this.bind('change', this.update, this);
},
update: function(){
console.info("something changed");
console.log(this.get('Product').toJSON());
},
});
So group model contains Product-collection, which obviously contains products. In initialize i am trying to make sure that the update method of the group is called example when product is changed and destroyed. All seems to be working nicely, events get called, attributes look great but it fails when I call destroy method in product model. In update I try to print the contents of product collection and what I get is products BEFORE remove is done. If I call this debug line after 500ms timeout, the contents are ok. Product is removed etc.
So according to my understanding the destroy event of product is called and then passed to group before actual removal from collection is done. What am I doing wrong?
Backbone handles the removal of a destroyed model in a collection by listening to the destroy event on the models: see the source code for Backbone.Model - destroy and Backbone.Collection - _onModelEvent.
The order in which the handlers will be executed is not guaranteed, you will have to use something else. For example, listen to the destroyevent on the collection which will fire after the model is actually removed:
initialize: function(data){
var _this = this;
var products = new Products(data.Product);
this.set('Product', products);
this.get('Product').on("destroy", this.update, this);
this.bind('change', this.update, this);
},
Check this Fiddle http://jsfiddle.net/NUtmt/ for a complete example.

Resources