views:

309

answers:

6

Can someone explain to me the importance of using function wrappers (ie: when assigning onclick functionality)? I know I should, but I don't fully understand why...

Example: It's my understanding that this:

$(callingImg)[0].setAttribute("onclick", "function(){removeChildren(this);}");

is better than this:

$(callingImg)[0].setAttribute("onclick", "removeChildren(this)");
A: 

So you can reuse the functions.

Maybe explain in a little more detail why you are unclear, I'd be happy to help explain.

Joseph Silvashy
+1  A: 

They're generally used to limit variable scope and guaranteeing that you won't pollute your surrounding scope(s).

Håvard S
+3  A: 

One of the most common uses of function wrappers is to maintain or lock in that function's context. For example, if you have some object and you want to use one of it's method's as the onclick handler for a given element:

someElement.onclick = someObject.someMethod;

If someObject.someMethod makes any references to this, instead of this pointing to someObject it will instead point to someElement because the context has changed. By wrapping the someObject.someMethod

someElement.onclick = function() { someObject.someMethod() };

you are still executing someMethod as a method of someObject rather than as a method of someElement.

However, if the event handler method never makes any references to this, then a wrapper isn't needed.

From the example code that you posted, if you just did

$(callingImg)[0].setAttribute("onclick", removeChildren(this));

removeChildren(this) would be executed immediately and it's return value would be assigned as the onclick handler.


Here's some sample code to illustrate what's happening

var FakeElement = function() {
    this.name    = "FakeElement";
    this.onclick = function() {};
};

var FakeEventHandler = function() {
    this.name = "FakeHandlerObject";
    this.clickHandler = function() {
        console.log("`this` = ", this.name);
    };
};

var e = new FakeElement(); 
var h = new FakeEventHandler();

// Normal usage, `this` points to instance of `h`
console.info("h.clickHandler();");
h.clickHandler();

// Context of this is changed to `e` instead of `h`
console.info("e.onclick = h.clickHandler;");
e.onclick = h.clickHandler;
e.onclick();

// Wrapped to maintain proper context of `this` within `h`
console.info("e.onclick = function() { h.clickHandler(); };");
e.onclick = function() { h.clickHandler(); };
e.onclick();

// Executed immediately and returns `null` causing an error in `e.onclick();`
console.info("e.onclick = h.clickHandler();");
e.onclick = h.clickHandler();
e.onclick();

Output:

h.clickHandler();
`this` = FakeHandlerObject

e.onclick = h.clickHandler;
`this` = FakeElement

e.onclick = function() { h.clickHandler(); };
`this` = FakeHandlerObject

e.onclick = h.clickHandler();
`this` = FakeHandlerObject


As a side note, it looks like you're using jQuery. If that's the case, you can simply do

$($(callingImg)[0]).click(function() {
    removeChildren(this);
});

better still, if there is always one callingImg or you want to apply the same click handler to every callingImg, you can just do

$(callingImg).click(function() {
    removeChildren(this);
});
Justin Johnson
Thanks, very helpful.
danwoods
now very, very helpful! :)
danwoods
Haha. You're welcome mate
Justin Johnson
In your opinion, would it be better to use $($(callingImg)[0]).click(function() { removeChildren(this); }); or $(callingImg)[0].addEventListener("click", function() { removeChildren(this); }, false); ?
danwoods
I guess the first one uses slightly more JQuery?
danwoods
@danwoods: Basically, they both do the same thing. The first one uses a jQuery method and the second uses a native JavaScript method. If you're already using jQuery, use the first—it will deal with cross-browser-messing-around etc....
Steve Harrison
@danwoods I agree with @Steve Harrison. If you're going to use the library, make sure you at least use it for the best parts about it which are, in this case, DOM element selection and cross-browser compatible event handling. For example, `addEventListener` doesn't work in IE which instead uses `attachEvent` which also implemented and behaves differently. I highly suggest you stick with the jQuery provided event interface. If for some reason you don't want this, then you'd be best off with just doing `$(callingImg)[0].onclick = function() { ... };`, but again, I don't suggest this.
Justin Johnson
A: 

Actually it should be

$(callingImg)[0].setAttribute("onclick", function(){removeChildren(this);});

and

$(callingImg)[0].setAttribute("onclick", "removeChildren(this)");

First is anonymous function defined inline and will be called after event fires, second is string which needs to be eval'ed, which is slower and evil.

MBO
I've been trying to do it the way you've posted (with the function wrapper) but I keep getting a error that says my onclick function looks like: "function () {\n" any ideas why that might be? I'd rather ddo it the most proper way...
danwoods
It's not *necessarily* evil in this context, just slow and unnecessary.
Justin Johnson
Thanks_________
danwoods
The reason this isn't working is because you're using `setAttribute`. If you're using `setAttribute`, then the second parameter should be a string. Instead, use `addEventListener` (https://developer.mozilla.org/en/DOM/element.addEventListener): `$(callingImg)[0].addEventListener("click", function() { removeChildren(this); }, false);`
Steve Harrison
@Steve Harrison `addEventListener` isn't implemented in IE which opens a whole other bag of worms.
Justin Johnson
A: 

Some of the uses for function wrappers in this case is so you can do multiple statements within a single event or so you have more control of the argument you're passing in.

If you have no required arguments, you can simply put the name of the function to be called in the event binding. Otherwise, you can put an inline function(){} into that space to be executed. I see no reason to put it as a string in the attributes of the elements the way you're doing it.

w/jQuery

$( callingImg ).click( aFunctionPointer );
// or if you need arguments
$( callingImg ).click( function( ){ aFunction( someArg ); } );

w/Prototype

Event.observe( callingImg, 'click', aFunctionPointer );
// or if you need arguments
Event.observe( callingImg, 'click', function( ){ aFunction( someArg ); } );

DON'T mix and match these ways, like this

$( callingImg ).click( aFunction( ) ); // DON'T DO IT

as it will execute aFunction( ) and will use whatever the return value is (unless of course you're returning a function, but I doubt it) as the click event.

Dan Beam
+1  A: 
$(callingImg)[0].setAttribute("onclick", "function(){removeChildren(this);}");

That makes no sense. It says: on click, define a function, and do nothing with it.

$(callingImg)[0].setAttribute("onclick", "removeChildren(this)");

This will work except on IE. Don't use setAttribute on an HTML document, ever. There are many bugs in setAttribute in IE, affecting various attributes including all event handlers. And in general setAttribute has no advantage over the shorter, easier-to-read DOM Level 1 HTML properties.

Also it's defining code from a string, something which is widely considered bad practice. It's slower, prone to security problems if you start slinging variables into the string, and may break in the future should Content Security Practice.

More usually, you'd use a function literal:

$(callingImg)[0].onclick= function() {
    removeChildren(this);
};

However that $ looks like you're using jQuery, in which case you should use jQuery's own event handling stuff:

$(callingImg).click(function() {
    removeChildren(this);
});
bobince
Interesting note about `setAttribute`. I usually opt for the more direct approach when setting properties (as you showed in your 3rd code block) so I haven't encountered any of these problems. I have however seen the necessity for `getAttribute`, particularly with the `rel` attribute. I've found that typically `someAnchorNode.rel` returns `null` (unless explicitly set using the method just stated) whereas `someAnchorNode.getAttribute("rel")` returns the expected value. Any thoughts on the cross-browser issues (if any) of `getAttribute`?
Justin Johnson
I have no difficulty reading `link.rel` or `a.rel` in IE6–8, Firefox, Opera, Chrome or Safari... test case? The basic issue with get/setAttribute is that IE before version 8 treats `o.getAttribute(x)` as the same thing as `o[x]`, so for attributes whose DOM property name is different to the HTML attribute name, or attributes whose DOM properties are of a different type to `String` (which includes all event handlers which are `Function`), IE will give you the wrong results.
bobince
You know, while trying to recreate this I couldn't come up with anything. Then I remembered that sometimes I just don't care about standards and I want to embed data in an element without polluting the `class` attribute ... so I'll pollute `rel` instead. It's in these cases, when using `rel` as an attribute on elements that don't support it in the HTML4/XHTML standard, that this behavior is seen (which will however be supported in HTML5). Test case: http://jsbin.com/imina/
Justin Johnson
Ah OK, you would have to use getAttribute for attributes that simply don't exist in HTML, whether or not they do exist as attributes on a different element. There's no reason to use `rel` here... if you're using your own arbitrary attributes (and it's questionable whether that's a good idea) you should use unambiguous names like `data-mymadeupthing`.
bobince
I completely agree, there is no reason to use rel here. It's not necessarily coddling, but I find that when working with designers and expecting them to populate some attribute with relevant data for some progressive enhancement do-dad, sticking with familiar attribute names makes things easier for them and it's less work that I have to check over. Ultimately, you're probably right about avoiding the naming conflict, but it has severed me well so far.
Justin Johnson