Prevent single attribute from firing this.bind("change") - javascript

In Backbone.js, I have a model I am binding a change event to, but I want to prevent this from happening on specific attribute changes. For example, I want it to fire for every single time model.set() is called, except when calling model.set({arbitraryName: value}).
Here's what I have:
this.bind("change", function() {
this.update();
});
But I have no clue how to determine what is being set--any ideas?
EDIT
It looks like I can call
model.set({arbitraryName: value}, {silent: true})
to prevent the change event from firing (which works for what I need), but what if I have something bound like:
this.bind("change:arbitraryName", functionName)

You can consider using hasChanged in the event handler?
var self = this;
this.bind("change", function() {
if(!self.hasChanged("someAttribute")){
self.update();
}
});
I'm not sure I understand your question completely. Please notice the difference of the above, and the below.
this.bind("change:someAttribute", function(){
self.update();
});
The first one will fire update on any change where someAttribute remains constant. The second one will fire update on any change to someAttribute.
Hope this helps.

Related

Remove Event Listener In an Object Literal Lost this reference

Currently working on a small project using an OLOO style approach.
Problem found here
So the issue I am facing is that I have an event handler.
eventHandler: function() {
console.log('Hit');
testSelector.removeEventListener('click', this.eventHandler, false);
}
Now what happens is that I want this to be removed after the first click. However this does not seem to work as I expected. I am binding the object this reference yet there seems to be something that I am missing in what is actually going on here. Would anyone be able to clarify what is actually happening or where I went wrong?
I'm not an expert in OLOO, but I can see two issues in your example:
the this inside an eventListener callback handler refers to the node so you need to take care you're referencing the same this in both methods ( add/removeEventListener )
removeEventListener won't work if the listener parameter isn't the same as addEventListener, and when you use bind you're actually creating a new function (so you have to keep track of that to)
in code:
var testSelector = document.querySelector('.test');
var object = {
init: function() {
this.ref = this.eventHandler.bind(this)
testSelector.addEventListener('click', this.ref, false);
},
eventHandler: function() {
console.log('Hit');
testSelector.removeEventListener('click', this.ref, false);
}
}
object.init();
https://jsbin.com/hejenuraba/1/edit?js,console,output
I got it to work in my environment as follows:
var testSelector;
var eventHandler = function(){
console.log('Hit');
testSelector.removeEventListener('click', eventHandler, false);
}
$(document).ready(function(){
testSelector = this.getElementById('btn');
testSelector.addEventListener('click',eventHandler);
});
You code looks fine but you may want to cross check the following:
In your line of code:
testSelector.removeEventListener('click', this.eventHandler, false);
Make sure you have the references to testSelector and eventHandler

Animating changes to an observable value

I'd like to animate a change to an individual ko.observable in the most MVVM/Knockoutesque way. I can handle the animation and view updates on my own:
function ViewModel() {
var self = this;
self.value = ko.observable("start value");
$("button").on("click", function () {
$("#text").animate(animateOutProperties,
{
complete: function () {
self.value($("#value").val());
$("#text").animate(animateInProperties);
}
});
});
}
The above works exactly as I want it to
However, the above does not take full advantage of two way data binding since I'm actually listening to an event and changes on the value itself. There's almost no point in using data-bind: text since I can just use jQuery to update the text at this point.
Using something like self.value.subscribe to listen to changes in the value would make more sense to me and I could use other bindings to update the value -- however, as far as I can tell there is no way to get both the old and new values at the same time.
I want to use something like beforeRemove and afterAdd, but those only work for adding and removing observableArray elements.
Is there a way to handle the above animation that fits better with the MVVM/two way data binding philosophy?

Is there anyway to bind events with "if" dependence in Backbone?

I wanna know if there is anyway to depend event binding with "if" in Backbone.
For example, if i have user profile model and i want to bind "Send Message" button event only if the attribute "acceptMsgs" sets true.
My current solution is to check it in the event firing, if there is better way, pls correct me.
I'm not sure if it's a better way to do it, but you can use a function that returns a hash for the event hash (and of course in the function you can check for some condition).
For example something along the lines of
myView = Backbone.Views.extend({
events: function () {
if (someCondition) {
return { "#someButton click" : "nameOfFunction"}
}
}
//the rest of your view
});
Alternatively you can forgo the event hash and bind your events in the initialize method, for example
initialize: function (options) {
if (someCondition) {
this.$el.on("click", "#someButton", nameOfFunction);
}
}

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/

nested object events

I'm working on a validation project and I currently have it set up where my inputs are listed as objects. I currently have this code to setup and run the events:
setup method and functions used
function setup(obj) {
obj.getElement().onfocus = function() {startVal(obj)}
obj.getElement().onblur = function() {endVal(obj)}
}
function startVal(obj) {
obj.getElement().onkeyup = validate(obj)
}
function endVal(obj) {
obj.getElement().onkeyup = ""
}
Take note to how I have it where the onkeyup event should set when the object is receives focus, However when I activate the input it acts like I tagged the validate() function directly to the onfocus and it only validates when I initially focus the input.
edit the reason I have it set up this way is so that I don't have every single one of my form elements validating each time I launch an onkeyup event(which would be a lot since forms usually involve a decent amount of typing). I got it to work by simply attaching the validate() function to the onkeyup event. I just would prefer limit it this way so the there's no unnecessary processing.
Can you not set events with other events or is there something more specific that I'm doing wrong?
Any help is appreciated!
Here is some additional information that might help:
getElement Method
function getElement() {
return document.getElementById(this.id)
}
setEvents function
function setEvents() {
firstName.setup(firstName)
}
You are calling validate directly. Unless it is returning a function, it won't work (maybe you should have read my other answer more thoroughly ;)). I think you want:
obj.getElement().onkeyup = function() {validate(obj)};
And as I stated in my comment, there is no reason to add or remove the event handler on focus. The keyup event is only raised if the element receives input, so not when other elements receive input.

Categories

Resources