000 000

Navigation

Skip navigation.

Search

Site navigation

Email conversation

From000 000
ToMe
SubjectHow to traverse the 'ancestry' of descendant objects and functions?
Date19 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!
FromMe
To000 000
SubjectRe: How to traverse the 'ancestry' of descendant objects and functions?
Date20 December 2008 08:53
Attachmentdemo 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/
From000 000
ToMe
SubjectRe: How to traverse the 'ancestry' of descendant objects and functions?
Date22 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!
FromMe
To000 000
SubjectRe: How to traverse the 'ancestry' of descendant objects and functions?
Date27 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
This site was created by Mark "Tarquin" Wilton-Jones.
Don't click this link unless you want to be banned from our site.