Save and restore "onclick" action on jQuery objects - javascript

I want to disable a whole bunch of objects on the page, and then re-enable them later. Since some of them are tags rather than buttons, I disable them by removing their onclick attr. I've tried to store the old handler in a .data(), but unfortunately when I attempt to restore them with $(obj).attr('onclick',$(obj).data('onclick')), it calls the function rather than restoring it to the attribute. And if I try to store it in a different attribute instead of a data, it doesn't store the function, it stores the return value for the function.
Is there any way to accomplish this without re-writing every tag and every onclick handler on my page?
if( doEnable) {
$(obj).attr('href', $(obj).data('href'));
$(obj).attr('onclick', $(obj).data('onclick'));
$(obj).removeClass(EIS.config.classes.disabled);
$(obj).show();
}
else {
// Save the things you're going to remove
$(obj).data('onclick', $(obj).attr('onclick'));
$(obj).data('href', $(obj).attr('href'));
$(obj).prop("href", null);
$(obj).prop("onclick", null);
$(obj).addClass(EIS.config.classes.disabled);
$(obj).show();
}
By the way, this code seems to work fine in Chrome and Firefox, but only sometimes in IE8 and never in IE6. Unfortunately the client tests first in IE6.

$(obj).attr('onclick', ...
is ambiguous, has results that differ in different versions of jQuery and different browsers. It probably doesn't do what you want. You should avoid using attr on event handlers.
The problem is the disconnect between the onclick attribute and the onclick property. jQuery has tried to brush the difference between an attribute and a property under the carpet in the past, using attr to access both, but they're quite different. This was changed in jQuery 1.6, and partially reverted in 1.6.1, to widespread controversy, confusion and incompatibility.
For many properties, the values of an attribute and the corresponding DOM property are the same; for others, including all properties that aren't strings, they aren't. Event handlers certainly aren't: the property is a Function object, whereas the string attribute might be (a) the original string of the onclick="..." attribute in the HTML, (b) nothing (if the onclick was assigned from script to be a Function object) or (c) unavailable (in older IE).
To access the event handler Function property, use prop() in jQuery 1.6:
$(obj).data('onclick', $(obj).prop('onclick'));
...
$(obj).prop('onclick', $(obj).data('onclick'));
or just use plain old JavaScript which is actually simpler and more readable; jQuery wins you nothing here.
obj._onclick= obj.onclick;
...
obj.onclick= obj._onclick;
Either way this is not going to reliably ‘disable’ elements since they can (and very likely will, if you're using jQuery) have other event listeners registered on them, using addEventListener/attachEvent rather than the old-school event handler interfaces.

It looks like saving a function via .data() works just fine:
var f1 = function() { console.log('invoked'); };
$('a').data('func', f1)
var f2 = $('a').data('func'); // 'invoked' is not printed
f1 === f2 // true
so how are you storing the function via .data? if you're doing something like
a = $('a');
a.data('onclick', a.click()); // click handler is invoked here
then you're actually invoking the click handler(s) prematurely, and storing the return value with .data().
--edit--
it appears that .attr(function) invokes the passed function. This is a feature of jQuery. I'd suggest using jQuery's .click() method to attach the function as a click handler.
a = $('a');
a.each(function() {
this.data('onclick', handler_fn);
this.bind('click', handler_fn);
});
// later
a.each(function() {
this.unbind('click');
});
// even later
a.each(function() {
this.bind('click', this.data('onclick'));
});

What about binding the event in jQuery instead of setting the onclick attribute?
$(obj).click($(obj).data('onclick'));
Can we see the code that you use to set the data attribute?

Related

Is attaching an onchange object a closure?

I have searched prior SO posts here, here and here, and couldn't an answer that made sense to me. This should be a basic question, but I'm not understanding the posts I find. They don't seem to address using a this parameter.
I want to programatically add an input with an onchange event, such that the final result is this:
<input type="button" onchange="handleButtonOnChange(this)">ClickMe</input>
I am working on a project that is using an embedded IE6 browser inside a old Delphi application, so I have to have a solution that is IE6 compatible (yes, IE6 is horrible, but there are reasons I am stuck with it for now).
My initial attempt was this:
var DaySelect = document.createElement("select");
DaySelect.id = ParentID+"-day";
DaySelect.disabled = true;
MonthSelect.onchange="handleDayChange(this);" //<--- not correct
Parent.appendChild(DaySelect);
I then read that the .onchange should be assigned an object, not a string, and one should use this instead:
MonthSelect.onchange=handleDayChange; //<--- '(this)' removed
But it seem to me that this will result in this element (notice the missing this parameter)
<input type="button" onchange="handleButtonOnChange">ClickMe</input>
If I use the line below, instead, won't this make a closure, and the 'this' will refer to the event at the time the object is assigned to the .onchange property, instead of being the event at the time of the change event?
//Does the line below make a closure?
MonthSelect.onchange=handleDayChange(this); //<-- What does 'this' refer to?
I'm a relatively new web programmer, but long time Delphi programmer. Closures still make my head hurt. I appreciate any help in this.
Also, I read here about using addEventListener and the problems with older versions of IE, and the last post on the page provides a work around. But I don't understand how it works.
EDIT -- And what about passing other parameters? It seems that many event handlers will need to have parameters specific for the attached element. It seems that it is just not possible to add a listener with any parameters.
A simple closure if you are creating the elements in JS as you show:
var DaySelect = document.createElement("select");
DaySelect.id = ParentID+"-day";
DaySelect.disabled = true;
MonthSelect.onchange=function(){handleDayChange(DaySelect);};
Parent.appendChild(DaySelect);
Since the function is created inside the scope that you create the element in, the same variables will be available to it.
EDIT:
Additional parameters can be passed with this method, for example, the anonymous function we create and attach as the handler will still have the event object sent to it:
function(e){handleDayChange(DaySelect, e);};
In the event object you will have access to the event target, but in your example the event target and "this" are not the same element, so there would be no way for the handler to know about the DaySelect element.
jQuery makes a lot of event handling much simpler which is one of the reasons many people use it, it also normalizes it's methods between various browsers so you don't have to write multiple versions of the same code (in most cases)

Is there actually a good reason for jQuery to manipulate 'this' keyword in event handlers?

Given the following, common scenario:
console.log(this); // window or any parent object
$('.selector').on('click', function(event) {
console.log(this); // clicked DOM element
});
var myFunc = function() {
console.log(this); // window or parent object
}
Since version 1.3 jQuery adds the event.currentTarget when binding event handlers for which counts event.currentTarget === this, so is there actually a good reason to manipulate this and switch context? Doesn't this behaviour generally go against the unspoken rule of "don't change keyword values" (like undefined = 'not defined')?
This "feature" of jQuery makes a lot of OOP less efficient and awkward imho, when we need to either cache the original this in a variable like self or use helpers like jQuery.proxy to reassign context to event handlers.
My question: is this just a relic of early jQuery implementations kept alive or is there an actual benefit which I cannot see (except maybe the slightly more convenient way than accessing event.currentTarget to get the element...)?
Let's say you've got an object with some methods on it:
var object = {
click: function() {
alert(this.property);
},
property: "Hello World"
}
You can call object.click() and, as you'd expect, you'll get "Hello World" in the alert.
You'd like to be able to use that "click" function as an event handler:
$("button").on("click", object.click);
However you discover that that doesn't work, because jQuery invokes the "click" function with this set to the DOM node for the clicked button. This is irritating. It's also inevitable because of the semantics of JavaScript function calls.
When you call the "click" function by way of a property reference, the language arranges for this to refer to that object. That's why object.click() works. However, when you fetch the reference to the function and pass it across a function boundary (as in the call to .on()), that relationship is lost. All that the jQuery method gets is a plain unadorned function that has absolutely no inherent relationship to the original object involved in its definition.
Thus, jQuery really has only two choices. The first is that it could make explicit the fact that the function is unconnected by arranging for this to be undefined. That wouldn't be very useful however. The other choice is to pick something interesting for this, and that's what the library does. Note that the native DOM level 0 event dispatch mechanism does the same thing.
The reason is that jQuery wants to mimic how regular event handlers (ones created without jQuery or any other library) works. In regular event handlers the value of this refers to the DOM node that triggers the event if there is one.
One could in fact consider that this is an example of jQuery not manipulating built-in behavior.

What is the difference of using addEventListener?

What is the main difference between of using this...
document.addEventListener('mousedown', function() {
// code
}, false);
...and this?
document.onmousedown = function() {
// code
}
Will there be any different result or any cause?
onclick is a property, like the onclick attribute can be placed in HTML. It has best browser support, however, it is primitive, as reassigning it overwrites the first (like any object property).
addEventListener(), as the name suggests, allows you to register multiple callbacks for an element and event type. This allows you to have multiple mousedown events for the same element. Before IE9, IE had their own attachEvent() which is similar (you must specify the on part too with attachEvent()).

Trying to write a Javascript class to handle dynamically adding more data to my HTML. Need some guidance

Here's what I'm aiming to achieve:
HTML
<fieldset id="addmore">
<p>blah</p>
<a class="remove">remove me</a>
</fieldset>
<a class="add">add more fieldsets</a>
Javascript
var addmore = new AddMore($('fieldset'));
addmore.buildCache(/*this will pull the innerHTML of the fieldset*/);
// bind the buttons
addmore.bind('add', $('a.add'));
addmore.bind('remove', $('a.remove'));
I've found myself having a lot more 'addmore' stuff in my HTML lately so I've been trying to build a class that will do all the leg work for me that I can just reuse in all my projects. The above code will, hopefully, be all I have to add each time and then the rest is done for me.
I've been winging this thing so, off the top of my head, here's what the class has to do:
Apply the jQuery bindings to the supplied 'button' objects so we can add/remove fieldsets
When a new fieldset is added, we have to recall the bind function so the new fieldset's 'a.add' button will work (I've found jQuery's .live() function to be buggy, for whatever reason, and try to avoid it)
It will hopefully do this with no memory leaks :}
Javascript Class
/*
Class to handle adding more data to the form array
Initialise the class by passing in the elements you want to add more of
Then bind 'add' and 'remove' buttons to the functions and the class will do the rest
*/
/*
Pass the jQuery object you want to 'addmore' of
Ex: var x = new AddMore($('fieldset.addmore'));
*/
function AddMore($element)
{
if (!$element || typeof($element) != 'object')
throw 'Constructor requires a jQuery object';
this.element = $element; // this is a jQuery object
this.cache = null;
}
/*
Supply clean HTML to this function and it will be cached
since the cached data will be used when 'adding more', you'll want the inputs to be emptied,
selects to have their first option selected and any other data removed you don't want readded to the page
*/
AddMore.prototype.buildCache = function(fieldset)
{
if (!fieldset)
throw 'No data supplied to cache';
this.cache = fieldset;
}
/*
use this to create the initial bindings rather than jQuery
the reason? I find .live() to be buggy. it doesn't always work. this usually means having to use a standard .bind()
and then re-bind when we add in the new set
that's what this class helps with. when it adds in the new data, it rebinds for you. nice and easy.
*/
AddMore.prototype.bind = function(type, $button)
{
if (!type || !$button && (type != 'add' && type != 'remove'))
throw 'Invalid paramaters';
// don't reapply the bindings to old elements...
if ($button.hasClass('addmore-binded'))
return;
// jQuery overwrites 'this' within it's scope
var _this = this;
if (type == 'add')
{
$button.bind('click', function()
{
_this.element.after(_this.cache);
});
}
}
I was going to have the .bind() method (in my class) call itself upon adding the new fieldset to reapply the binding but lost confidence with efficiency (speed/memory).
How should I tackle this? Do you have any pointers? Can you recommend improvements?
Thanks for the help.
In the most simplest form, you can do something like this:
var html = '{put html to add each time here}';
$('.add').click(function() {
$(html).insertAfter($('fieldset').last());
return false;
});
$('.remove').live('click', function() {
$(this).parent().remove();
return false;
});
You may need to tweak it based on your exact needs, but this should accomplish what you described in your example.
Update: sorry, remove should use the live method.
For creation of the new DOM elements, allow the specification/parameters to be any of the following:
simple HTML as a string (like the example above),
a function returning either a DOM element or HTML text. You can skip bind() or live() issues by adding
the onclick element when creating the HTML/element in the function. Although doing it in the AddMore() scope would be more tedious
if it's not a DOM element that gets returned.
inputs to a helper/factory method (maybe a template and name/value pairs) - postpone this unless you know enough patterns already.
Option #1 seems almost useless, but #3 might be hard unless you have extra time now.
Notes:
You might want to use $(theNewDomElement).insertBefore(_this.button); rather than _this.element.after(theNewDomElement); so that new items are append to the end of the list.
Actually, for insertBefore() you might just use this rather than _this.button since presumably the button (or anchor) is this, but then that limits the functionality - just make it some sort of DOM element (or a jQuery object that equates to one).
In most cases, I'd presume your DOM elements represent data that you'll want to save/send/transmit to your server, so provide a before-removal function, too. Even allow the function to skip removal -- like after a confirm().
Good luck.

Are jQuery events automatically unbound upon destruction of what they where bound to?

If I have the following code in two functions of an object:
add: function()
{
// create trip.
var trip = new Trip();
// add the trip using its id.
this.trips[trip.id] = trip;
},
remove: function(tripId)
{
// remove trip.
delete this.trips[tripId];
}
NOTE: The constructor for the Trip object binds a bunch of custom jQuery event handlers to itself.
Will the event handlers bound to the Trip object be automatically destroyed/cleaned up when the Trip object is deleted?
Would the same occur for a DOM node if it was removed and had event handlers bound to it?
Also I read that objects are not cleaned up by the garbage collector until all references to them no longer exist, so do the event handlers bound to the object by itself count as references and prevent the object from being cleaned up, even when I am no longer referencing it?
The event will not be deleted as jQuery maintains a central repository of all bound event handlers, and does know if or when you deleted an associated object using delete. Try this little test to confirm. (jQuery 1.4.2 only)
jsfiddle link:
// 1. a regular JS object
var root = {};
// Since we can't delete anything created with var (except on Chrome),
// we use an object property here. An array value works just as well,
// which is already the case in your example.
root.o = {};
// 2. at this point, jQuery creates an internal property
// jQuery<UNIQ_ID>, for example jQuery1277242840125 inside object o
$(root.o).bind("myEvent", function() { alert("here"); });
// 3. get the *internal* index jQuery assigned this object:
// there's only 1 property, so we just enumerate and fetch it.
var internalIndex;
for(var prop in root.o) {
internalIndex = root.o[prop];
}
// 4. delete the object
delete root.o;
// 5. query jQuery internal cache with the internal index from step 3
console.log(jQuery.cache[internalIndex].events);
​
Step 5 should log an array of all event types that were associated with the ex-o object, including "myEvent", and it's associated handler, so no the bound events handlers will not delete automatically. Here's what I see get logged (stripped out irrelevant properties):
▾ Object
▾ myEvent: Array (1)
▾ 0: Object
▸ handler: function () { alert("here"); }
namespace: ""
type: "myEvent"
length: 1
The object deletion, however, is not affected, and that will be deleted as expected. However, it is a hole in the wall kind of a situation since there is associated data somewhere in jQuery's cache that will remain there.
It seems that although you can bind events to plain JavaScript objects, you cannot unbind them. It appears jQuery assumes the object is a DOM node when unbinding, and throws the following error:
Uncaught TypeError: Object #<an Object> has no method 'removeEventListener'
Even the part about being able to bind events to objects is, I believe, undocumented. So you have to be a little careful on this, as the following will not work when trying to clean up the event handler references for that object:
$(object).remove()
$(object).unbind(..)
As a workaround, when cleaning up the Trip object, you can explicitly call removeData to do the job.
$(object).removeData();
As I've already mentioned, it's getting knee-deep with jQuery's internals, so you might want to look at an alternative solution, or be wary that library upgrades can easily break your code, which is not very unlikely.
As far as I know, you can only bind event handlers to nodes, or, in special cases, the window, document, etc. For DOM nodes, the event handlers will be removed. Even if they weren't, they wouldn't be able to be triggered anyway. Deleting the object will remove the event handlers associated with it. The event handlers should not prevent the object from being garbage collected.
Would the same occur for a dom node if
it was removed and had event handlers
bound to it?
this.handlerClick = function () { ... };
$(this.testDomNode).bind('click', this.handlerClick);
this.testDomNode.parentNode.removeChild(this.testDomNode);
Using the above code and testing with FireQuery in FireFox removing the dom node does not unbind the handler from the event,
it seems you have to explicitly unbind the handler before removing the dom node as follows:
$(this.testDomNode).unbind('click', this.handlerClick);
this.testDomNode.parentNode.removeChild(this.testDomNode);

Categories