Pass data into change event callback? - javascript

Using backbone.js - I want to bind an event to any change in the model and the collections/models that are nested inside it.
Right now I do an #bind 'change', () -> when initializing the base model.
How can I pass data on any change, even that of deep nested models? Does the change event carry variables with it? I need things like the model's collection, id, attributes, etc.
Thanks.

Does the change event carry variables with it?
Yes, the change event gets two arguments: First, the model itself; second, the new attribute value. There are several methods available on the model that are aimed specifically at getting information during a change event. See the docs on hasChanged, changedAttributes, previous, and previousAttributes.
So for instance, to access the previous attributes of a model each time it changes, you'd write
#bind 'change', (model) ->
prevAttrs = model.previousAttributes()
...

Related

How to properly notify data changes between siblings in polymer

I would like to have a polymer element with two sub-elements, one that produces data, and the other that performs some action when the data changes (in my case: sending a notification to a server).
To implement this, I wrote a polymer element, namely root, with the following structure (names changed to simplify the discussion):
<producer data={{foo.bar}}></producer>
<consumer data=[[foo]]></consumer>
The producer changes the data using the set('property', 'value') method, so that the root element sees the notifications. The problem is that the consumer element won't notice the changes to foo since they involve a sub-property.
To solve this, I tried using a computed binding as follows:
<producer data={{foo.bar}}></producer>
<consumer data=[[_compute(foo)]]></consumer>
...
_compute: function() {
return this.foo;
}
However this won't cause the consumer to be notified. I think the reason for this is that the returned object is the same reference (only a sub-attribute changed). Currently the workaround I've used is to use the following version of the compute function:
_compute: function() {
return Object.assign({}, this.foo);
}
This works (the consumer element gets notified), however I'm affraid it might not be the most efficient (I'm creating an object at every call of _compute) and/or elegant way. Then my question is: what is the proper way to achieve this behavior in Polymer?
Do you have access to modify the consumer element?
The best way to fix this is to have the consumer element have a multi-property observer that listens for sub-property changes on the data property.
It might look something like this:
Polymer({
is: 'consumer',
properties: {
data: Object
},
observers: ['consumeData(data, data.*)'],
consumeData: function (data) {
//Do whatever you were planning on doing with data here
}
});
The advantage of an approach like this is that your 'consumer' element just 'knows' how to consume the data object when a sub-property on it changes. Because of the lighter weight approach to data binding in Polymer, trying to implement this behavior outside of the 'consumer' element will necessarily be more expensive and more complicated, since it requires either tricking the data binding into thinking the data object is new by supplying it with a new reference to a copy or forgoing the data binding altogether and building an approach on top of events and calling methods on the consumer in response to events. So if at all possible, I would recommend trying the approach above.
Polymer's data binding does not work the same way as some other two-way enabled data binding implementations, like what you might find in AngularJS. Rather than using dirty-checking, which is extremely expensive, Polymer uses an event based 'path notification' approach. When a sub-property on a property changes, a Polymer element which has that property will fire an event to it's immediate children bound to that property, notifying them that the path 'property.subProperty' has changed. In order for consumer to act on those changes, it has to be told to listen to changes along that 'property.subProperty' path. We specify paths in our polymer observers by using the syntax above. In this case, putting data.* in our observer means we want to listen to any path off of data, so that any notified property change on the data property will trigger the observer.
As you have noticed there isn't an elegant way of doing this. The way you got it working is interesting.
An alternative way which I would expect to work would be to fire an event from within the producer element.
this.fire('data', {data: this.foo.bar});
and then have the parent/root element listen for this event and then update the data property of the consumer element.
<producer on-data="handleData"></producer>
<consumer id="consumer"></consumer>
handleData: function(e) {
self.$.consumer.data = e.detail.data;
}
Edit:
You could make a new property that you compute within the producer element. Then you won't have to do a computed function everytime you want to access foo.bar
Producer element
properties: {
foo: {},
bar: {
computed: 'computeBar(foo)'
}
}
Root element:
<produce bar="{{bar}}"></producer>
<consumer data="[[bar]]"></consumer>

How to detect model parameter change event in mithril.js?

I recently started learning mithril.js and I'm wondering how can I make very basic Model -> View one way data binding app.
TestModel = function(data){
this.name = m.prop(data.name)
}
testModel = new TestModel({name: "John"})
code above declare a model and it works perfectly as getter/setter.
but how can I set an event listener for the model event like Backbone's listenTo('model',"change",callbackFunc)?
all sample codes I saw are setting events for actual user actions like click,keyup or onchange.but never listen to actual model value's state directly.
am I missing something or am I understanding how to use mithril.js wrongly?
thanks in advance.
One of the key ideas with Mithril is that changes usually happens after an event:
A user action like onclick or keyup defined in a m() view template
An ajax request made with m.request
Mithril automatically redraws after those, alleviating the need for most listeners.
If you are updating your models through some other method and you need to redraw manually, use m.redraw or m.startComputation / m.endComputation. Thanks to Mithril's DOM diff algorithm, redraws are very cheap so don't be afraid to use them (with some common sense, of course!) Check out the m.redraw documentation for more info.

Backbone and Rivets: don't immediately update a model instance with values from DOM

Two-way binding with Rivets and Backbone is great. Your Model instance gets updated as the user introduces changes in a form and then you just have to issue a save() on it.
The thing is that sometimes that Model instance is being shown somewhere else and you don't want the changes to be noticed until the save() operation has been successfully processed (or not) on server-side. In another words, sometimes is desirable to delay publishing of changes from DOM to the Model instance.
The best solution we've thought of is to bind Rivets not to the Model instance itself but to a plain object representation of it (via model.toJSON()). Rivets then publishes changes to that plain object and you only have to pass it as the 'attributes' parameter to model.save().
Have you found a better solution/pattern/practice? Thanks in advance
I haven't used Rivets per se but one solution to this sort of problem is to use a 'dirty model', ie a copy of the original model that you pass to Rivets and then when you're ready to save it you save it's attributes to the original model. So:
var dirtyModel = new Backbone.Model( originalModel.toJSON() );
then you pass that model to Rivets or whatever else you are using and then you're ready to save it you do:
originalModel.save( dirtyModel.toJSON() );
That way you escape the trap of two-way binding when you don't want it...

Backbone View render function getting called multiple times

I have a view that represents a folder. I have bunch of subviews, that this folder view creates, each representing a unique thumbnail in that folder. It turns out that each one of those subview's render method is getting called multiple times (3). Is there a way to find out how view's render method is called. There are different places which could render a trigger event for e.g., if models metadata is changed. It has become a huge mess and I'm looking for a way to debug backbone view's to know what is exactly triggering render method.
The way that I always debug events is:
view.on('all', function(eventName){
console.log('Name of View: ' + eventName);
});
You could do this on views, models or collections.
example:
http://jsfiddle.net/CoryDanielson/phw4t/6/
I added the request and sync methods manually to simulate how backbone would actually perform. The rendered event is custom -- nothing listens to it. Just to show you how/when it happens.
So as you requested, here's an example of how to override the trigger method. Note that you'll have to override it for all types of classes (Model, View, Collection, Router).
var trigger = Backbone.Model.prototype.trigger;
Backbone.Model.prototype.trigger = Backbone.View.prototype.trigger = Backbone.Collection.prototype.trigger = Backbone.Router.prototype.trigger = function(name) {
trigger.apply(this, arguments);
console.log(this, 'triggered the event', name, '.').
}
You could be more specific by overriding each method individually to add the type of object in the log. But you got the general idea.
You might what to give backbone.debug a try. Should give you some insight into what events are being fired.

Backbone state handling with nested models?

I'd like to manage all hash state attributes (#) in one Backbone model.
StateModel //Pseudo
attributes
layout : string
modelType1 : model
modelType2 : model
This way I could consistently update history entries just by serializing this single model.
HistoryController
StateModel.bind("change", this.updateHistory);
[...]
state = StateModel.toJSON()
[...]
appRouter.navigate('v1' + state, false);
How do I make the HistoryController trigger change when the nested models (in the StateController) change?
And if the hash changes - and I'd like to update my StateModel - how do these changes propagate down to the nested models? (without causing a feedback loop)
Nested models in Backbone can be tricky because the getter and setter methods do not have built-in functionality to operate at depth. However, I have found that the best way to handle this is to store Backbone models inside other Backbone models. In your example, you would instantiate StateModel and then set its modelType1 and 2 to be, say, TypeModel instances. You can then stateModel.get("modelType1").bind("change",this.updateHistory) and stateModel.get("modelType2").bind("change",this.updateHistory). Alternatively, if you are going to be creating a lot of TypeModels, you can put this binding in the initializer function.
Secondly, you can stateModel.get("modelType1").bind("change",stateModelInstance.updateFoo) or whatever method you would like to call when the modelType model changes.
The nice thing about this pattern is that if you need stateModel to change one of the modelType models you can do stateModel.set({modelType1:newModel3}) or something of that ilk. If you have set the binding action in the ininitializer of TypeModel, everything will stay synced up. If you don't want to blow out the nested model on a change, just do stateModel.get("nestedModel1").set({"foo"}:"bar"). This shouldn't cause a feedback loop unless you have bound something to your stateModel change action that changes the nested Model again but I don't know why you would do that.

Categories

Resources