Skip to main content
gamedev.net
🔒 Locked

JavaScript - DOM Prototypes in Mozilla

Started by kloffy Sep 11, 2006 at 6:18 PM 6 replies 3.4k views
Original Post
kloffy
kloffy
I'm not quite sure if this is the right place for asking such a question, but I'll give it a shot. I've been experimenting with DOM Prototypes in Mozilla and here is what I wanted to do:
HTMLElement.prototype.getAttribute.call(element, 'id');
It gives me this exception:
[Exception... "Illegal operation on WrappedNative prototype object" nsresult: "0x8057000c (NS_ERROR_XPC_BAD_OP_ON_WN_PROTO)"
location: "JS frame :: file:///[PATH REMOVED]/test.html :: onclick :: line 1" data: no]
Has anyone tried that before? Any idea why I'm getting this exception? Here's an article about DOM Prototypes in Mozilla if you want to read more about them.
kloffy
kloffy
Ok, before this thread disappears forever, I'll give it one more bump.

I'll elaborate a little more on what I'm trying to do. Basically I want to write a callback function that gets an object and a member function, and invokes the member function using the object. Here's an example:
function Person(name){	this.name = name;}Person.prototype.who = function(){	alert(this.name);}var invoke = function(obj, func, argument){	return func.call(obj, argument);}// Examplevar joe = new Person('Joe');var jim = new Person('Jim');invoke(joe, Person.prototype.who);invoke(jim, Person.prototype.who);

Sofar everything works just like expected. However, as soon a I try to use the invoke funktion with a HTMLElement it fails. giving me the exception above.

// Examplevar div = document.createElement('div');div.setAttribute('id', 'Div');// Worksid = div.getAttribute('id');alert(id);// Throws Exceptionid = invoke(div, Element.prototype.getAttribute, 'id');alert(id);


[Edited by - kloffy on September 12, 2006 12:28:57 PM]
MrAccident
MrAccident
Well, I can't say I've tried to do this specifically, though I have run across this exception before.

Reading the article, I can't say I understand exactly what's going in any great depth, having never looked around in Mozilla's innards. Just some vague ideas. [smile]

For what it's worth, using HTMLDivElement's prototype instead of the Element prototype works just fine:

// Worksid = invoke(div, HTMLDivElement.prototype.getAttribute, 'id');alert(id);// Trying to use the prototype from any other Element class results// in the same exception. This is probably related to the problem?// Fails (unless div is really an anchor ;-)id = invoke(div, HTMLAnchorElement.prototype.getAttribute, 'id');


I did find what appears to be a (somewhat clumsy) workaround: add your own function to Element.prototype that wraps getAttribute, like so:

Element.prototype.getFoo = function(attr){   return this.getAttribtue(attr);}// This works just fineid = invoke(div, Element.prototype.getFoo, 'id');alert(id);


I'm not sure why this works, but using the call() method of the function object directly does not work...

In any case, why do you need to use this invoke function to call a function on an object? If you have the object in hand, it seems to me you could just call the function directly... I guess a little more information about exactly what you're trying to do might help.
Excors
Excors
var div = document.createElement('div');div.setAttribute('id', 'Div');var div2 = document.createElement('div');div2.setAttribute('id', 'Div2');var span = document.createElement('span');span.setAttribute('id', 'Span');invoke(div, div.getAttribute, 'id'); // worksinvoke(div, div2.getAttribute, 'id'); // worksinvoke(div, HTMLDivElement.prototype.getAttribute, 'id'); // worksinvoke(div, span.getAttribute, 'id'); // doesn't workinvoke(div, HTMLSpanElement.prototype.getAttribute, 'id'); // doesn't workinvoke(div, HTMLElement.prototype.getAttribute, 'id'); // doesn't work
I think the most obvious example is trying to invoke HTMLSpanElement.prototype.getAttribute on the div object - that's a method from one class, being used on an object of a different class, and so it makes sense that it doesn't work. (These are class/method in the C++ sense because that's how Mozilla's DOM is implemented, and the JS wrapper has to work within those limits.)

Invoking methods from the same class - retrieved via another div element or HTMLDivElement.prototype - does work as expected, whatever object it comes from.

The example you gave is invoking methods from a class on an object of a conceptual subclass (the spec says "interface HTMLDivElement : HTMLElement", but I don't know if Mozilla's implementation actually uses inheritance like that) - that's something where you could expect it to do something sensible, but the implementation just doesn't do that. It could be considered a bug in Mozilla - I can't find any relevant reports on Bugzilla, so it could perhaps be reported there to see if people agree it's something that ought to be made to work (even if it's too much work to be worthwhile actually implementing), but in the meantime you'll have to work around it by using HTMLDivElement.prototype.getAttribute or some_div_object.getAttribute instead.


(Opera is different: The spec says span should actually be an HTMLElement, which is how Opera does it, though Mozilla has an HTMLSpanElement instead; Opera also lets you invoke HTMLAnythingElement.prototype.getAttribute on any HTMLOtherElement object, though it gives an error (WRONG_THIS_ERROR) if you try invoking it on some other object like a number.)
kloffy
kloffy
Thanks for the tip, I haven't even tried the HTMLDivElement prototype. The reason why I haven't is that it doesn't help me with what I was trying to do. But at least I have a better understanding where this exception comes from.

Ok, so basically here is what I'm trying to accomplish (I'm doing it mainly out of curiosity and lazyness.):

I'm sure you've seen the popular custom &#106avascript function $ before:
function $() {	var elements = new Array();	for (var i = 0; i < arguments.length; i++) {		var element = arguments;		if (typeof element == 'string')			element = document.getElementById(element);		if (arguments.length == 1)			return element;		elements.push(element);	}	return elements;}

It's basically a shorthand for document.getElementById, and it can retrieve multiple elements at once. Now, I wanted to write a function that invokes a function on all the elements returned.
function invoke(elements, func, args) {	var results = new Array();	for (var i = 0; i < elements.length; i++) {		results.push(func.apply(elements, args));	}	return results;}// Hypothetical Example (Doesn't work)invoke($('element1', 'element2'), Element.prototype.setAttribute,  new Array('class', 'invisible'));

Edit: Used func.apply instead of func.call, because apply accepts an Array of arguments.

[Edited by - kloffy on September 12, 2006 2:28:06 PM]
MrAccident
MrAccident
Why not just do something like this?

// Modify invoke() to simply call a named function on the passed elements:function invoke(elements, func, arg1, arg2) {	var results = new Array();	for (var i = [0]; i < elements.length; i++) {		results.push(elements[func] (arg1, arg2));	}	return results;}


Then you can just do this:
invoke($('element1', 'element2', 'element3'), 'setAttribute', 'class', 'invisible');
This invoke() function will work for any objects, so long as they all have a callable property with the specified name. You could add a check to invoke() to make sure the 'func' property isn't undefined before calling it, but if you're only going to use it with objects returned from getElementById, you'll be safe.
Excors
Excors
Very similar to that, but having a variable number of arguments:
function invoke(elements, func, args) {    var results = [];    for (var i = 0; i < elements.length; i++) {        results.push(elements[func].apply(elements, args));    }    return results;}


Or to keep the original invoke function instead:
invoke($('element1', 'element2'),    function() { return this.setAttribute.apply(this, arguments) },    ['class', 'invisible']);


Or using JS 1.6's forEach (only in Firefox 1.5+, but could be implemented in Array.prototype for other browsers):
$('element1', 'element2').forEach(function(o) { o.setAttribute('class', 'invisible') })
kloffy
kloffy
Those are some good alternatives, thanks! I liked the version using the prototype from an aesthetical point of view, but I think I'll settle for something like this:
function invoke(elements, func) {	var args = Array.prototype.slice.apply(arguments).slice(2);	var results = new Array();	for (var i = [0]; i < elements.length; i++) {		results.push(elements[func].apply(elements, args));	}	return results;}// Exampleinvoke($('element1', 'element2'),'setAttribute', 'class', 'invisible');

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.