Email conversation
From | 000 000 |
To | Me |
Subject | How to traverse the 'ancestry' of descendant objects and functions? |
Date | 19 December 2008 19:31 |
Hello!
Sorry to use you like some kind of tech support thingy, but I'm trying
to organise better the way I extend the DOM with custom functions and
am stuck on a referencing problem.
I can add custom functions to elements using:
HTMLElement.prototype.showWidth=function() { alert(this.offsetWidth) }
... after which it's easy enough to do something like:
document.getElementById('theID').showWidth()
...to get the desired result. However, I would like to use a wrapper
object to hold a collection of custom functions (one of the benefits
of which is that I don't need to worry about name clashes as much),
and my problem is referencing the element itself from inside those
functions. The keyword 'this' refers to the wrapper object when used
by those functions, and I haven't yet been able to work out a way to
create a reference to the element that contains the wrapper. I've
tried things like 'this.this' or creating a reference within the
wrapper, hoping I could then user 'this.pointer', but that hasn't
worked either.
I'm optimistic that this is possible because what I'm trying to achive
is already done by some native functions, but as they're written into
the source code they can probably do whatever magic the programmers
want them to do.
So, any ideas how I could do something like:
HTMLElement.prototype.wrapper={
showTop: function() { alert(this.offsetHeight) },
tagName: function() { alert(this.nodeName) },
makeRed: function() { this.style.color='#f00' }
}
...and then use:
document.getElementsByTagName('p')[0].wrapper.tagName()
...and 'this' from inside tagName() would point to the element? What I
need is something similar to parentNode that works on objects as well
as DOM element nodes.
Many thanks!
From | Me |
To | 000 000 |
Subject | Re: How to traverse the 'ancestry' of descendant objects and functions? |
Date | 20 December 2008 08:53 |
Attachment | demo of both approaches |
> HTMLElement.prototype.wrapper={
> showTop: function() { alert(this.offsetHeight) },
> tagName: function() { alert(this.nodeName) },
> makeRed: function() { this.style.color='#f00' }
> }
> document.getElementsByTagName('p')[0].wrapper.tagName()
It's rare that I get such an intriguing question. I have to admit, it
took me a few minutes to get a clean solution to this problem, but it's
nice to exercise the brain sometimes ;)
The problem is a limitation of JavaScript. Objects (like 'wrapper' in
your example) simply have no way to know what object they are a child
of, since they can be a child of multiple objects at any given time, and
there is no 'main' parent. So you need some way to get a reference to an
object in place of wrapper, that knows which object it was attached to.
In JavaScript, only one type of thing can know what it is attached to,
and that is the code running in a method call. So you will need to call
a method instead of referencing a child object. That method can store
the 'current' object (replacing 'this' in your example), then return an
object that has methods like your 'showTop', which act on the 'current'
object.
The easiest way to store the current object is using function scope,
since that avoids all the problems of storing a global variable. I assume
you already know how scope works (also known as closures), but just in case,
here's some more info:
http://www.howtocreate.co.uk/tutorials/javascript/functions#varscope
http://www.howtocreate.co.uk/referencedvariables.html#scope
Rather than show you an answer based solely on DOM (especially since your
use of HTMLElement counts out IE), I will show an answer based on basic
JavaScript without DOM. This is the setup:
function SpecialObject(newid) {
this.id = newid;
}
var myobject = new SpecialObject('MY_UNIQUE_ID');
For your use, imagine that SpecialObject is HTMLElement, and myobject is
a reference to your paragraph. I will create a method called 'alpha' to
replace your object 'wrapper', though obviously, naming is up to you.
SpecialObject.prototype.alpha = function () {
var storedThis = this;
return {
foo: function () { alert( 'Object ID is ' + storedThis.id ); },
bar: function () { ... etc ... }
};
};
The call goes like this:
myobject.alpha().foo();
This syntax is not quite as clean as your original request, and yes, the
DOM does succeed in doing something similar. The way it does it is to
use a getter. In the handful of browsers that support __defineGetter__
(Opera 9.5+, Mozilla/Firefox, Safari 3+), you can replace that method
call with a property that behaves like you want, which will replicate
what the DOM does. However, that really limits its use to very
controlled circumstances, where browser support is not a problem:
SpecialObject.prototype.__defineGetter__('beta',function () {
var storedThis = this;
return {
foo: function () { alert( 'Object ID is ' + storedThis.id ); },
bar: function () { ... etc ... }
};
});
myobject.beta.foo();
[Ed. Note it is now possible to do something similar in IE 8 final as well,
using a significantly different syntax (Object.defineProperty). However,
this does not work with all object types, and apparently cannot be used
with the objects created in the demo.]
I have attached a demo of both these variations, but I recommend you
only use the method call version, since the getter version introduces an
incompatibility with no gain in functionality.
Mark 'Tarquin' Wilton-Jones - author of http://www.howtocreate.co.uk/
From | 000 000 |
To | Me |
Subject | Re: How to traverse the 'ancestry' of descendant objects and functions? |
Date | 22 December 2008 14:42 |
> I will create a method called 'alpha' to
> replace your object 'wrapper', though obviously, naming is up to you.
Ah, I never thought of using a method to create and return a whole object,
thanks! However, am I right in thinking this would have much higher
overheads than a simple wrapper object, and therefore be a lot slower in any
browser?
> especially since your use of HTMLElement counts out IE
Yes I don't think it's possible to extend the DOM in IE even using any
proprietary syntax, is it? For cross-browser stuff I either try to
replicate/overwrite native functions or create conventional methods and pass
a reference to the element as an argument.
Thanks again!
From | Me |
To | 000 000 |
Subject | Re: How to traverse the 'ancestry' of descendant objects and functions? |
Date | 27 December 2008 08:57 |
>> The call goes like this:
>> myobject.alpha().foo();
>>
> am I right in thinking this would have much higher
> overheads than a simple wrapper object
There is a little more overhead, but only in so much as it stores the
new scope variable every time it is called, then garbage collects it
after it stops being referenced. However, the browser's optimiser still
sees only one copy of the inner object and methods, so it can optimise
it quite well, and should not be such a massive overhead as it first
seems. You'd have to test it in your scripts to see if the difference is
too significant.
>> especially since your use of HTMLElement counts out IE
>
> Yes I don't think it's possible to extend the DOM in IE even using any
> proprietary syntax, is it?
Not that I know of. You can't even extend the generic 'Object' instead of
'HTMLElement', since elements do not inherit from it. IE 8 does have
HTMLBodyElement but not HTMLElement. Most hackarounds I have seen for this
issue involve looping through all elements and adding the properties
manually if window.HTMLElement does not exist.
[Ed. Note that Object.defineProperty does now allow some more flexibility,
though not as many interfaces are exposed for prototyping, compared with
other browsers.]
Tarquin