Make Backbone.js Model change "partially" silent? - javascript

When my "chartModel" changes I want to update the "globalModel".
chartModel.bind("change", updateGlobalModel);
updateGlobalModel(){
globalModel.set(obj)
}
And vice versa, I want my chartModel to update when the globalModel changes.
globalModel.bind("change", updateChartModel);
updateChartModel(){
chartModel.set(obj)
}
This results in a feedback loop when setting the globalModel. I could prevent this by setting {silent:true}.
But here comes the problem. I have another Model that is dependent on the change event:
globalModel.bind("change", updateOtherModel);
How can I alert this model of the change but not the former one (to avoid the feedback loop)?
UPDATE:
For now, I decided to generate a specific ID for each set call:
set : function(attrs, options) {
if(!("setID" in attrs)){
attrs.setID = myApp.utils.uniqueID(); //newDate.getTime();
}
Backbone.Model.prototype.set.call(this, attrs, options);
},
This way, I can always generate a "setID" attribute from anywhere in my application. If the setID is still the same when fetching something from the model, I know there could be risk for a feedback loop.

Better late than never..
The easiest way to do this is by using a flag. For example, when setting something in globalModel, you could also change a property on the model to indicate that you've changed something. You can then verify the value of this flag in updateChartModel. For example:
chartModel.bind("change", updateGlobalModel);
function updateGlobalModel() {
if (!flag) {
globalModel.set(obj);
flag = true;
}
}
Probably very similar to what you've ended up doing with your setID. As an aside, underscore has a uniqueId function built in.
Another thing that you can do, which is much cleaner, is to pass an option with your sets calls.
chartModel.set(obj, { notify : false });
Yes, you can pass any options you want, you're not just limited to { silent : true }. See this discussion on github for more. Then, you check for the existence of this property where you handle change events like so:
function updateGlobalModel(model, options){
// explicitly check for false since it will otherwise be undefined and falsy
// you could reverse it.. but I find this simpler
if (options.notify !== false) {
globalModel.set(obj)
}
}
and in your third (and other models), you can just forego this check.
The final option is of course to look at your design. If these two models are so closely related that they must be kept in sync with each other, maybe it makes sense to merge their functionality. Alternatively, you could split the common functionality out. This all depends heavily on your particular situation.

My knowledge is limited, so maybe I shouldn't be answering, but I would try to pass a reference to chartModel when it's created that refers to the "other" model that you want updated. Then trigger an event on updateChartModel() and make sure your "other" model is bound on that event.
The question I have is: does the silent object mute all events? Or just model related ones? This obviously wouldn't work if all events are muted.

Related

How to animate unchanged ng-repeat with AngularJS

I have a template that looks like this:
<p ng-repeat="item in myobj.items" class="toAnimate">{{item}}</p>
and I would like to use the animate module do a jQueryUI addClass/removeClass animation on the element using the JavaScript method described in the docs:
ngModule.animation('.toAnimate', function() {
return {
enter: function(element) {
element.addClass('pulse').removeClass('pulse', 2000);
}
};
});
This works beautifully, but the problem is that, since I want to use the p.toAnimate element to display status messages, it will not change the content according to angular.
To break it down a little further, say I have a name field. When I click Save the message Name was saved successfully. is displayed. Now if I modify the name and click save again, assuming the save was successful, the message should be re-displayed to give the user feedback of the newly edited name. The pulse does not happen, however, because the items in myobj.items didn't technically change.
I realize that I could remove the item after a period of time (and that is probably the route I will take to implement the real solution), but I'm still interested to see if this sort of thing can be done using AngularJS.
What I want to do is register with angular that the message should be treated as new even though it is not. Is there any way to do this?
A fiddle to go along with this: http://jsfiddle.net/Jw3AT/
UPDATE
There is a problem with the $scope.$$phase approach in my answer, so I'm still looking for the "right" way to do this. Basically, $scope.$$phase is always returning $digest, which causes the conditional to fail. Removing the conditional gives the correct result in the interface, but throws a $rootScope:inprog.
One solution I found is to add a $apply in the middle of the controller function:
$scope.updateThingy = function () {
$scope.myobj.items = [];
if (!$scope.$$phase) {
$scope.$apply();
}
$scope.myobj.items = ['Your name was updated.'];
};
Updated fiddle: http://jsfiddle.net/744Rv/
May not be the best way, but it's an answer.

How do I bind a function to a change in an object's data member in Javascript?

I'm working on a project in JavaScript where we're building a Greasemonkey plugin to an organizational site we're using in our office. We're having trouble getting our changes to stay rendered, since we can't simply inject our changes into the existing render function.
As a result, we need to find every event where rendering happens and inject our own render function there. However, there are some events that we can see happening, but we can't hook into them. What I'd like to know is how to bind a function to an object's data member, so that the function is called whenever that member changes. One of our team members seemed to think it was possible, but the method he told us to use didn't seem to work.
What we tried was something along the lines of
window.Controller.bind("change:idBoardCurrent", OMGITWORKED);
where idBoardCurrent is a member of window.Controller and OMGITWORKED is the function we'd like to be called when window.Controller.idBoardCurrent is changed.
I'm not very familiar with JavaScript or data binding, so I have no idea if this is right or wrong, or what is correct or incorrect about it. If someone could point out what to change in this snippet, or if they could suggest another way to go about this, I would be very appreciative.
You can use Object.defineProperty to define a setter and getter for the Objects property
Object.defineProperty(window.Controller,"idBoardCurrent",{
get : function() { return this.val; },
set : function(value) {this.val = value;OMGITWORKED(value); }
});
function OMGITWORKED(param) {
console.log("idBoardCurrent has been Changed to " + param);
}
window.Controller.idBoardCurrent = "Test";
window.Controller.idBoardCurrent = "Test2";
console.log(window.Controller.idBoardCurrent)
Edit: changed the code according to the contexts object
JSBin
As this is specifically Firefox, you can use the mutation events it provides. But note the caveats on them from that page:
The W3C specification for them was never widely implemented and is now deprecated
Using DOM mutation events "significantly degrades" the performance of DOM modifications
If you're able to restrict yourselves to Firefox 14 and higher, you can use the new mutation observers stuff instead.
This is, when I am not totally wrong, more a question of javascript.
I found some information about that topic
Listening for variable changes in JavaScript or jQuery
jQuery trigger on variable change
Javascript Track Variable Change
Sorry when I didn't understand the topic.
All the best

How to limit the number change events when multiple attributes are set?

I have noticed that when multiple attributes of a Backbone model are set like so
model.set({
att1:val1,
att2:val2
});
two change events are triggered. I was wrongly assuming that only one change event would be triggered after all the attributes had been set.
This might not seem like a problem, but it is when a function is bound to att1 that also uses the value of att2. In other words, when you do this
model.bind('change:att1', func1);
...
func1 = function() {
var att2 = model.get('att2');
}
the variable att2 will be set to the old value of the model's attribute att2.
The question is how to prevent this in an elegant manner. Of course, one option is to set att2 before setting att1 or to bind to att2 (instead of att1), but it seems that this is only a viable option in simple situations. The latter option also assumes that the attributes are set in the order in which they are listed in the set method (which is the case I think).
I have run into this issue several times hence my question. The issue is that it took me some time to realize what was actually happening.
On a final note, just like you can pass {silent:true} as an option of the set method, it would be nice to have an option {group:true} (or something like that) indicating that the change events should only be fired after all the attributes have been set.
In more complex situations i'd go for custom events.
instead of binding to a change:att1 or change:att2 i'd look for a specific custom event, that you trigger after you have set all attributes you wanted to change on the model.
model.set({
att1:val1,
att2:val2
});
model.trigger('contact:updated'); // you can chose your custom event name yourself
model.bind('contact:updated', func1);
...
func1 = function() {
var att2 = model.get('att2');
}
downside on this idea is you have to add a new line of code everywhere you want to trigger the event. if this happens alot you might like to change or override the model.set() to do it for you, but then you're already changing backbone code, don't know how you feel about that.
EDIT
after looking into the sourcecode of backbone, i noticed the change event is triggered right after the change:attribute triggers. (proven by the snippit below)
// Fire `change:attribute` events.
for (var attr in changes) {
if (!options.silent) this.trigger('change:' + attr, this, changes[attr], options);
}
// Fire the `"change"` event, if the model has been changed.
if (!alreadyChanging) {
if (!options.silent && this._changed) this.change(options);
this._changing = false;
}
while the this.change(options); refers to this:
change: function(options) {
this.trigger('change', this, options);
this._previousAttributes = _.clone(this.attributes);
this._changed = false;
},
so if you would be binding to the change event instead of the specific change:argument event, you will arrive at a callback function after both (or all) attributes are changed.
the only downside is, it will trigger on ANY change, even if you change a third or fourth attribute. you need to calculate that in...
small example of how it works on jsfiddle http://jsfiddle.net/saelfaer/qm8xY/

How can I tell if a model has not been changed in Backbone.js?

This may be a result of misuse of the component, though I don't think so.
I have an issue where a View updates a model in Backbone JS and calls the model's Set method so that it may verify it's input.
In theory there are two results to such an action: Error and Change.
Both events work as prescribed.
But in fact there is a third event: No change.
That is, if the input has not been changed at all, I can't tell after calling Set because no error will be thrown but nor will a change event, as nothing has actually changed- but I still want to know about such a case.
Is there a way for me to do this?
The reason is that there is an action I want performed only if no error occurs, but there is no way for me to know (without a change event) that the model has attempted to set the new values and ended with no result as it all happens asynchronously.
Thanks!
Every Backbone model has a hasChanged method:
hasChanged model.hasChanged([attribute])
Has the model changed since the last "change" event? If an attribute is passed, returns true if that specific attribute has changed.
Perhaps you can use that to check your third possibility.
BTW, the callbacks aren't asynchronous. The error and changed callbacks are triggered and return before set returns:
set : function(attrs, options) {
//...
// Run validation.
if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
//...
// Update attributes.
for (var attr in attrs) {
var val = attrs[attr];
if (!_.isEqual(now[attr], val)) {
now[attr] = val;
delete escaped[attr];
this._changed = true;
if (!options.silent) this.trigger('change:' + attr, this, val, options);
}
}
The _performValidation call triggers the error callbacks, the this.trigger calls will call the per-attribute callbacks.
In this case, you may need to dance around Model.set() a little bit to get where you want. If you are using this functionality, then you should have defined a validate() method on your model.
http://documentcloud.github.com/backbone/#Model-validate
So you can call this method directly...
// something happens and we need to update the model to "newvalues"
if (model.validate(newvalues)) {
model.trigger('error')
} else {
model.trigger('change')
}
model.set(newvalues)
That way you will always at least get 'change' or 'error' out of it, even if it's the same. You will also still get the existing events from set.

javascript property change event

I need to fire an event every time a property is updated/changed in order to keep dom elements in sync with the property values on the model (Im using john resig's simple inheritance http://ejohn.org/blog/simple-javascript-inheritance/). Is this possible to do in a cross-browser way? It seems to me that if I could wrap whatever function js uses to set properties and make it fire an event, that it could work, Im just not sure how to do that.
JavaScript doesn't use a function to set properties. They're just variables, and setting them doesn't require any elaborate wrappers.
You could use a function to set the property, though — the same sort of a getter/setter arrangement you might use in a language that supported private data in classes. In that way your function could easily run other functions that have been registered as callbacks. Using jQuery you can even handle those as events.
$(yourObject).bind('some-event-you-made-up', function() {
// This code will run whenever some-event-you-made-up is triggered on yourObject
});
// ...
$(yourObject).trigger('some-event-you-made-up');
Maybe you already solved your problem with jQuery bind/trigger, but I wanted to tell that I'm building a Change Tracking and (in top of that) Entity Modeling Javascript Framework, named "tent" that solves the problem you exposed, without requiring any special syntax on object manipulation, its open source and hosted at:
https://github.com/benjamine/tent
It's documented with JSDoc and unit tested with js-test-driver.
you can use the change tracking module this way:
var myobject = { name: 'john', age: 34 };
// add a change handler that shows changes on alert dialogs
tent.changes.bind(myobject, function(change) {
alert('myobject property '+change.data.propertyName+' changed!');
});
myobject.name = 'charly'; // gets notified on an alert dialog
it works with Array changes too (adds, deletes).
Further you can use "Entity" Contexts to keep a changesets of all detected changes (ADDED, DELETED, MODIFIED items) grouped on collections, cascade adds and deletes, keep reverse properties synced, track 1-to-1, 1-to-N and N-to-M relationships, etc.
Object defineProperty/defineProperties does the trick.
Here goes a simple code. I have built some data binding frameworks based on that, and it can get really complex, but for exercising its like this:
var oScope = {
$privateScope:{},
notify:function(sPropertyPath){
console.log(sPropertyPath,"changed");
}
};
Object.defineProperties(oScope,{
myPropertyA:{
get:function(){
return oScope.$privateScope.myPropertyA
},
set:function(oValue){
oScope.$privateScope.myPropertyA = oValue;
oScope.notify("myPropertyA");
}
}
});
oScope.myPropertyA = "Some Value";
//console will log: myPropertyA changed
You could try Javascript Property Events (jpe.js)
I encountered a similar issue, and ended up writing an overload function for Object.defineProperty that adds event handlers to the properties. It also provides type checking (js-base-types) and stores its value internally, preventing unwanted changes.
Sample of normal defineProperty:
Object.defineProperty(document, "property", {
get:function(){return myProperty},
set:function(value){myProperty = value},
})
var myProperty = false;
Sample of property with onchange event:
Object.defineProperty(document, "property", {
default:false,
get:function(){},
set:function(value){},
onchange:function(event){console.info(event)}
})

Categories

Resources