Backbone.js Model previous method not working? - javascript

Strangely I noticed that the model previous method is not working the way I thought.. it keeps returning the same value as the get. I think that something is wrong with my code or backbone.js is not upgrading the this._previousAttributes when change event is fired.
model = new Backbone.Model()
model.set({attr1: 123})
alert(model.previous("attr1")) //alert 123 instead of undefined
alert(model.get("attr1"))
model.set({attr1: 312})
alert(model.previous("attr1")) //alert 321 instead of 123
alert(model.get("attr1"))
http://jsfiddle.net/wLKBk/
What am I doing wrong?

The previous method is only useful while a "change" event is happening:
previous model.previous(attribute)
During a "change" event, this method can be used to get the previous value of a changed attribute.
The previous method is only useful inside a "change" event handler; similar things apply to hasChanged, changedAttributes, and previousAttributes.
You're trying to use previous when you're not inside an event handler so you get nonsense. If you want to know what has changed in a model and you need to know outside of "change" event handlers, then you'll have to track it yourself.

Related

Do Javascript Event listeners get destroyed after firing (by default)?

I have an event listener on an object which fires a function when the object changes.
This is the code:
window.parent.document.getElementById('campval').addEventListener("change", getscriptbuttons1());
This works perfectly the first time that the object changes however, all consecutive changes do not trigger the event listener.
Is this the normal behaviour of Javascript? What can I do to rectify this issue?
No, the event listener should get fired every time.
I think this error is due because you are calling the function instead of passing it as a parameter:
getscriptbuttons1 // passes the function
getscriptbuttons1() // calls the function and passes whatever it returns
Did you mean? :
window.parent.document.getElementById('campval').addEventListener("change", getscriptbuttons1);
No, they do not get destroyed. You have to remove them manually. The issue is that you are actually calling the function in the event listener. You need to change it to this: (no parens, don't call it)
window.parent.document.getElementById('campval').addEventListener("change", getscriptbuttons1);

Wrap jquery event handler around hardcoded handler

I have several forms which look like this:
<form id=myForm onsubmit="saveFormData(this);return false">
....
</form>
Upon submission, a custom function is executed and the default action (sending the data through http) is canceled.
But now I need to validate the form and if it validates ok, then trigger the custom function (saveFormData in this case, but it may be different for other forms), otherwise do nothing.
So the final event handler should work like this:
$('#myForm').submit(function(){
if(formValidatesOk(this))
saveFormData() ;
return false ;
}
However, I cant make changes to the HTML code, so I need a general way of redefining the onsubmit event by wrapping the hardcoded handler with a jquery event handler.
The first thing that comes to my mind is something like (untested):
var hardcodedHandler = $('#myForm').prop('onsubmit') ; // save the current handler
$('#myForm').prop('onsubmit',null) ; // remove the current handler
$('#myForm').submit(function(){
if(formValidatesOk(this)) // if the form is valid then...
Function(hardcodedHandler).call(this) ; // trigger the original handler
return false ;
}) ;
But that's not very elegant (to say the least).
Do you know of a better way of doing it?
You can just take the old function from the onsubmit property of the element, you don't need to eval it:
var form = $('#myForm');
var oldHandler = form.prop("onsubmit");
form.removeProp("onsubmit").submit(function(e){
e.preventDefault();
if(formValidatesOk(this))
oldHandler.call(this, e);
});
Demo at jsfiddle.net
Events are unpredictable (in the way they get fired). I think that handers, bounded to them, must not rely on order either. So binding a handler to a event which changes the previous added handler is certainly a (very) bad practice. And indeed, as you suggested, you can never be sure which handler will be triggered first. After all, events should have no notice of each other. One should use events and handlers to avoid high coupling of the code.
The best way to approach this issue (taking your constraints into account) is to decorate your 'old' handler with the new behaviour. Bergi posted a nice example of this.

Call an object as a function in JavaScript

I'm trying to execute JavaScript functions that are called when a event (for example onClick event) is performed on a web page with JavaScript code. I'm getting the function from the event like this :
var attributval = document.getElementsByTagName("a").getAttribute('onClick');
and I'm trying to execute this object (which a JavaScript function in fact) as a function (suppose we have <a onClick = alert('whatever');> on this example, I tried:
var attributval = document.getElementsByTagName("a").getAttribute('onClick');
attributval() = function(){attributval};
attributval();
but it didn't work.
A DOM attribute is not the same as a JavaScript property (even though they can have the same name onclick). You should use
var attributval = document.getElementsByTagName("a")[0].onclick;
to retrieve a function (or null) from the JS object (as opposed to getAttribute(), which will most likely return a toString() for the property).
Now, attributval() = is illegal syntax, as attributval() is not an l-value (you cannot assign to it).
attributval(); will work but without the second line (which is illegal JavaScript) it will invoke the original A element onclick handler (if one is defined) or throw an exception (if the onclick handler is null).
Skip trying to create a function around the function. Just call it:
var attributval = document.getElementsByTagName("a")[0].onclick;
attributval();
try
var attributval = document.getElementsByTagName("a")[0].getAttribute('onClick');
By using get attribute you are returning a string so your only way is to use eval(onclickString) or var fn = new Function(onClickString); fn();
attributval is simply a string, correct? If you trust this code, execute it with eval(attributval) -- however any reference to this won't work.
What you probably want is to manually trigger an event. jQuery makes that easy.
If you want to do more than a click, then Chris McDonald's answer at Is it possible to trigger a link's (or any element's) click event through JavaScript? seems to fit the bill, although you might need to heed the third comment.
I thought I'd add a short answer on how to work with events using jQuery, since it seems relevant.
// Select the link using it's ID field (assuming it has one)
var myLink = $('a#myLink')
// Add a click event to the link
myLink.on('click', function(e) {
console.log("I've been clicked!");
});
// Trigger the click event manually. This would result in the above
// function being run. Interestingly, this will not cause the browser
// to follow the link like a real click would
myLink.trigger('click');
// Remove the click event (this removes ALL click events)
myLink.off('click');
// Add a click event to the link that only runs once, then removes itself
myLink.one('click', function() {
alert("I'll only bother you once!");
});
// Add a click event that you can identify from other click events.
// This means that you can trigger it or remove it without bothering other
// click events
myLink.on('click.myClick', function() {
alert("This click event has been identified as 'myClick'");
});
// Now you can trigger it without triggering other click events
myLink.trigger('click.myClick');
// And remove it, also with no harm coming to other click events
myLink.off('click.myClick');
Hope this helps

Why does my Backbone model hasChanged() always return false?

Given the following snippet:
var m = new Backbone.Model({
name: 'joshua'
});
m.set('name', 'something else');
If I now call m.hasChanged() or m.hasChanged('name') I get false. Why? My understanding is that both of these should return true.
m.changedAttributes() also returns false.
Here is a fiddle that illustrates what I'm doing, and expecting: http://jsfiddle.net/9cvVv/88/
EDIT: It seems that unless you pass { silent: true; } to the set() method then it will fire the change event on your model which clears out the changedAttributes(), etc. Essentially these properties only track changes since the last time the change event was triggered.
I read that in the documentation but didn't really understand it at first.
This doesn't seem very useful to me. I would appreciate any explanation of why this works the way it does and the best way to achieve the result I want. (Passing around {silent: true; } and giving up usage of the change event seems like a waste.)
Unless you pass { silent: true; } to the set() method then it will fire the change event on your model which clears out the changedAttributes(), etc. Essentially these properties only track changes since the last time the change event was triggered.
So the answer is to call this instead:
m.set('name', 'something else', {silent: true})
This post is premised on the previous behavior of older versions of Backbone. hasChanged does now (as of 0.9.10) always returns true after set(..) is called. The silent flag no longer has any effect.
This is confusingly masked in the jsfiddle linked in the question which uses a CDN-hosted copy of backbone.js which always uses the latest version. Here's some updated jsfiddles showing the change in behavior:
jsfiddle using Backbone 0.9.2
jsfiddle using Backbone 0.9.10
Model.set() takes an attributes hash as the first argument. Try m.set({'name': 'something else'});. Doing m.set('name', 'something') doesn't set 'name', so it never triggers the change event and your call to hasChanged() returns false. You can always inspect the current value of the attributes hash by logging out m.attributes - though accessing and manipulating it directly isn't recommended as none of the change events will fire that way.

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/

Categories

Resources