Re-enable disabled form elements when user goes 'Back'

K

Kevin Blount

First, let me state that I'm not asking if I can disable a browsers
Back Button <g>

I have a form on my .NET site that, when submitted, takes a few
seconds to get to the next page. As such I've disabled the submit
button on the first page, so users don't re-click it.

The problem I'm facing is that if the user, when they finally reach
the 2nd page, clicks the browsers Back button, then the form submit
button on the first page is still disabled.

I'm looking to see if anyone can suggest a way to either re-enable the
submit button (or rather, always ensure that the button is enable when
that page is being viewed), or suggest another method of how I can
stop people re-submitting a page because the 2st page hasn't
disappeared yet (other than a big HOLD YOUR HORSES message on the
screen).

Thanks
 
D

David Mark

First, let me state that I'm not asking if I can disable a browsers
Back Button <g>

I have a form on my .NET site that, when submitted, takes a few
seconds to get to the next page. As such I've disabled the submit
button on the first page, so users don't re-click it.

Bad move. The server needs to handle dupes anyway.
The problem I'm facing is that if the user, when they finally reach
the 2nd page, clicks the browsers Back button, then the form submit
button on the first page is still disabled.

Congratulations on not breaking fast history navigation. Hard to
believe .NET didn't interfere, but you probably aren't using their
Ajax stuff (or the bundled jQuery!)
I'm looking to see if anyone can suggest a way to either re-enable the
submit button (or rather, always ensure that the button is enable when
that page is being viewed), or suggest another method of how I can
stop people re-submitting a page because the 2st page hasn't
disappeared yet (other than a big HOLD YOUR HORSES message on the
screen).

Just handle that issue on the server.
 
B

Bart Van der Donck

Kevin said:
I have a form on my .NET site that, when submitted, takes a few
seconds to get to the next page. As such I've disabled the submit
button on the first page, so users don't re-click it.

The problem I'm facing is that if the user, when they finally reach
the 2nd page, clicks the browsers Back button, then the form submit
button on the first page is still disabled.

On the first page, do

<body onLoad="document.formName.buttonName.disabled=false;">

Hope this helps,
 
D

David Mark

On the first page, do

<body onLoad="document.formName.buttonName.disabled=false;">

That won't do anything in this case. The button remains disabled
because the browser preserved the previous state of the DOM and
restored it on navigating back (fast history navigation.)
 
B

Bart Van der Donck

David said:
That won't do anything in this case.  The button remains disabled
because the browser preserved the previous state of the DOM and
restored it on navigating back (fast history navigation.)

That appears to be correct in a few browsers, yes (should've
tested...). I don't see problems when the instruction is repeated in
onUnLoad:

<body onLoad="document.formName.buttonName.disabled=false;"
onUnLoad="document.formName.buttonName.disabled=false;">
 
D

David Mark

That appears to be correct in a few browsers, yes (should've
tested...).

It is true in all browsers that have this symptom. If they don't have
the symptom, they don't have the ability to do fast history navigation
(or it has been crippled by an unload listener.)
I don't see problems when the instruction is repeated in
onUnLoad:

<body onLoad="document.formName.buttonName.disabled=false;"
      onUnLoad="document.formName.buttonName.disabled=false;">

There is considerable harm as it unnecessarily breaks fast history
navigation. You would add server hits and everything else associated
with loading a fresh document for the sake of disabling a submit
button (which isn't necessary because validation and duplication
checks must occur on the server anyway.)

It's the standard Web2.0 paradox. Add scripts that make XHR requests
and manipulate the DOM, adding unload listeners as they go (typically
because the authors don't understand what causes memory leaks), in a
belief that server hits will be reduced. In fact, the opposite is
often true as the unload listeners make fast history navigation
impossible, requiring every back or forward action to completely
reload each document (often compounded by documents that use numerous
HTTP requests to load the required scripts.)
 
D

David Mark

"unload" listener is usually added in IE only, to "cleanup" whatever
event registry is being employed.

I know. Because the designers of such "registries" have no clue how
to design something that doesn't leak. Thus the "cleanup" has come to
be seen as a requirement. It definitely is not.
"IE only" is usually determined as

I know. Usually with browser sniffing. See the pattern of futility?
It's in virtually every Web app written in the last few years.
something that has `attachEvent` but lacks `addEventListener`.

Object inferences are still browser sniffing.
This
looks like a safe inference for detecting leaky environment (i.e. IE).

Not in the slightest. It is unknown which past versions of IE leak
(I've heard anything from 6 and under to all of them to just 6, etc.)
Who knows what the new one does? And who knows if IE9 will lose
attachEvent, but re-introduce the leaks.
I think I've also seen JScript's conditional compilation used for this.

Same problems.
Why would someone attach unload listener in other environments?

Because reportedly other agents suffer from similar problems with
circular references.
All
other browsers should receive the benefits of fast history navigation.

Even if that were the case, why on earth should you break this
critical feature for IE users. There are still loads of them out
there and I don't think they are going away. I know some people may
not understand how critical it is to a site's performance, but that's
because everybody has been crippling it for years and it's become like
a Yeti (you hear about it, but never see it.)
What causes memory leaks is unfortunately a consequence of certain
"enhancements" that might be desirable, namely - normalizing context of
event handlers.

Absolutely not. You are confusing the design mistake with the cause.

http://www.jibbering.com/faq/faq_notes/closures.html#clMem

Granted, that is usually the stumbling block for library authors.
Always best to assume they are doing something wrong (especially if I
have indicated as much.) This has been discussed numerous times here
as well.
Setting up event handler in such way that attached
callback is called in a context of correct element, effectively traps
that element into a closure (as desired) but also creates a circular
reference that leads to leakage.

And you can't figure out how to avoid that? I know you can. Stop
trying to explain to me why I am wrong and think for a moment. It
will come to you. If not, the answer is out there and I'm sure you
can find it.
...
function addEvent(element, eventName, callback) {
   ...
   else if (element.attachEvent) {
     element.attachEvent('on' + eventName, function(ev) {
       return callback.call(element, window.event);
     });
   }
   ...

}

Oh God, not that function again. Is this really your example? Wasn't
it written by PPK? Not that there is an argument in who wrote it, but
it is a definite clue you have gone down the wrong path in this case.
IIRC, an alternative to this (which avoids circular reference) is to
employ some kind of a hash table, where an expando attached to an
element acts as a key to that table.

Oh take me now Lord. :) That was Resig's "winning" entry to PPK's
addEvent contest. Do you understand why I am so adamant that people
erase the last few years from their memories and think everything
through from the start. Arguments like this are why (see also:
breaking the back button, faux navigation, CSS queries, browser
sniffing, etc., etc.)
 
D

David Mark

David said:
David Mark wrote:
[...]
I don't see problems when the instruction is repeated in
onUnLoad:
<body onLoad="document.formName.buttonName.disabled=false;"
      onUnLoad="document.formName.buttonName.disabled=false;">
There is considerable harm as it unnecessarily breaks fast history
navigation.  You would add server hits and everything else associated
with loading a fresh document for the sake of disabling a submit
button (which isn't necessary because validation and duplication
checks must occur on the server anyway.)
It's the standard Web2.0 paradox.  Add scripts that make XHR requests
and manipulate the DOM, adding unload listeners as they go (typically
"unload" listener is usually added in IE only, to "cleanup" whatever
event registry is being employed.
I know.  Because the designers of such "registries" have no clue how
to design something that doesn't leak.  Thus the "cleanup" has come to
be seen as a requirement.  It definitely is not.
I know.  Usually with browser sniffing.  See the pattern of futility?
It's in virtually every Web app written in the last few years.
Object inferences are still browser sniffing.

What's with perfectionism? You can't do without careful object
inference. Browser scripting is not a perfect world. `userAgent` is one
side of a spectrum, pure feature detection is on the other. You try to
stay as close to the latter one as possible. It doesn't mean you always can.

You sure can in this case, because you don't need the end-around. The
straight-ahead play is always best.
Looking at MyLib.js:

...
if (queue.length == 1 && version > 7 &&
     activeX && isHostMethod(global, 'attachEvent')) {
   global.attachEvent('onunload', fixIELeaks);
   global.attachEvent('onbeforeunload', fixFlashScript);}

That is from the *Flash* module (the version is the Flash version.)
And as I have stated numerous times, that code is two years old (the
Flash stuff is older still.) I think I've mentioned (though it should
go without saying) that my ideas about browser scripting have evolved
considerably since.
...

Is this not object inference?

Not really. With Flash, all bets are off.
and then there are these comments further down [formatted to fit]

// These two functions are to clean up leaks created by closures
// in user code
// They are attached to the unload event of the window in browsers
// that do not support addEventListener
// IE5+ (or any browser that supports attachEvent, but not
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// addEventListener)
    ^^^^^^^^^^^^^^^^^

See above. Do not fall into the trap of trying to prove me wrong with
years old code. Listen to what I am telling you *now* (and what I
have been saying for at least the last year.)
I know that 6 and 7 leak. I haven't heard of IE8 leaking.

Some people say 7 does not leak. I don't agree though. Don't really
care though as I don't need to know. ;)
You don't know. Just like you don't know whether IE9 will change
"unknown" to something like "unspecified" breaking all of your scripts.

Difference is, this situation is entirely (and easily) avoidable.
It's a quirky and unpredictable environment we are working in. You
should know it better than me.

Clearly I do.
Never heard of that. Which ones were reported as leaking?

FF2 I think. Doesn't matter though. Just avoid it.
I think I have an idea of what the cause is :) Didn't my script
demonstrate circular reference?

Your script demonstrated how to introduce a circular reference, which
is sure to leak. It's a design mistake.
I wasn't proving anyone wrong, neither was I trying to figure anything out;

You are arguing when you should be thinking. Same with others today
(see the faux navigation thread.)
I gave an example of one of the common implementations that triggers
leaks and explained why this implementation is common. It might be
useful to someone who wants to employ similar solution but is not aware
of it being leaky.
Okay.



What's so special about these three trivial lines? How would I know who
else, when and where wrote them?

The addEvent function is an infamously bad example.
Last time I checked your MyLib.js, it did the same thing - attaching
expandos to elements - and IIRC those "unique id's" were also used in
event registry.

Nope. As I have stated before (some time between the time I posted
that code and now), the library creates expandos in some modules. The
query module comes to mind. Expandos are definitely not used to track
listeners. And even if they were, see above.

You should look a bit closer as the answer you seek is in the event
module.
 
D

David Mark

[snip]
Last time I checked your MyLib.js, it did the same thing - attaching
expandos to elements - and IIRC those "unique id's" were also used in
event registry.

I actually looked, but I knew there was no way that was the case.
Perhaps if you had gone in looking for answers rather than ammunition,
you'd have skipped the Flash module and found this comment in the
events module:

API.eventContexts = []; // Keeps normalized event closures clean of
DOM references

Clue found. Granted, there is a red herring in My Library (and it
does not involve expandos either) in that it does have the unload
cleanup. I have remarked here previously that it is certainly
unneeded as all listeners are wrapped, so the calling code is shielded
from inadvertently creating circular references on attaching them.

You should always consider that I have not done any real work on that
code in almost two years. It's not an excuse for making silly
mistakes (like I care at this point), and I do consider the inclusion
of the unload cleanup a silly mistake (and have said so previously),
but a warning not to take anything in there at face value. Same goes
for anybody's code (the name on it doesn't matter.) As for using
perceived problems about somebody's code as an argument, it will never
fly. Most people who steer you clear of problems have run into them
before. That's how they found the answers.

So now you should be able to rewrite the addEvent function. It's been
done to death though. I think there were thousands of entries to that
contest. Unfortunately, most were awful, especially the ultimate
"winner."
 
M

Matt Kruse

And as I have stated numerous times, that code is two years old (the
Flash stuff is older still.)  I think I've mentioned (though it should
go without saying) that my ideas about browser scripting have evolved
considerably since.
...
Do not fall into the trap of trying to prove me wrong with
years old code.

Good advice to try to take yourself, too, whether talking about my
toolbox or the jQuery source or another library or what anyone has
published. It all tends to go stale, and sometimes it takes a while to
get published code up to par with learned better practices. A lot of
the stuff I have published makes me cringe now, but I don't have the
time (or much interest) to go back and re-write it the way I now think
it should be.

Even if you are ahead on the learning curve, it's ridiculous to insult
those who might be a bit behind you but are just now where you have
already been. Perhaps someday you will learn this lesson in humility.

Matt Kruse
 
D

David Mark

David said:
David Mark wrote:
David Mark wrote:
[...]
I don't see problems when the instruction is repeated in
onUnLoad:
<body onLoad="document.formName.buttonName.disabled=false;"
      onUnLoad="document.formName.buttonName.disabled=false;">
There is considerable harm as it unnecessarily breaks fast history
navigation.  You would add server hits and everything else associated
with loading a fresh document for the sake of disabling a submit
button (which isn't necessary because validation and duplication
checks must occur on the server anyway.)
It's the standard Web2.0 paradox.  Add scripts that make XHR requests
and manipulate the DOM, adding unload listeners as they go (typically
"unload" listener is usually added in IE only, to "cleanup" whatever
event registry is being employed.
I know.  Because the designers of such "registries" have no clue how
to design something that doesn't leak.  Thus the "cleanup" has cometo
be seen as a requirement.  It definitely is not.
"IE only" is usually determined as
I know.  Usually with browser sniffing.  See the pattern of futility?
It's in virtually every Web app written in the last few years.
something that has `attachEvent` but lacks `addEventListener`.
Object inferences are still browser sniffing.
What's with perfectionism? You can't do without careful object
inference. Browser scripting is not a perfect world. `userAgent` is one
side of a spectrum, pure feature detection is on the other. You try to
stay as close to the latter one as possible. It doesn't mean you always can.
You sure can in this case, because you don't need the end-around.  The
straight-ahead play is always best.

Of course you can. Invoking event handler in a proper context is just a
convenience.

You're killing me. You can have your convenience without death.
You sure can avoid closing element over and so creating a leak.

Wrong answer.
On the other hand, avoiding object inference *completely* seems
impossible to me. Next time I stumble upon it, I'll ask your advice.
Perhaps you'll offer a more robust solution.

I already did. You need to read more carefully.
I've seen where it was from.

Okay. Wasn't where I would have looked. Perhaps the light was better
in there? :)
So it's OK to use sniff^^^^^ object inference when it comes to flash?

It's not okay to use Flash at all. It will lead you through all sorts
of undesirable hoops (especially for IE.) You lose control with
that. There's a slight parallel with the other case (hint: it is also
a design decision.)
Aren't all bets off with host objects too? :)

Generally speaking. But you have much better control over DOM nodes,
which are far more predictable than Flash movies, which are
implemented as ActiveX controls in IE (known to be the most sensitive
of host objects.)
and then there are these comments further down [formatted to fit]
// These two functions are to clean up leaks created by closures
// in user code
// They are attached to the unload event of the window in browsers
// that do not support addEventListener
// IE5+ (or any browser that supports attachEvent, but not
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// addEventListener)
    ^^^^^^^^^^^^^^^^^
See above.  Do not fall into the trap of trying to prove me wrong with
years old code.  Listen to what I am telling you *now* (and what I
have been saying for at least the last year.)

That's the only public code written by you that I know of.

I don't publicize very much.
And again,
I'm not trying to catch you on the spot or prove you wrong.

I know that. I am exhorting you to use your head on this one. You
still have it wrong.
There was a
discrepancy between what I've seen in your code (and code *recommended
by you* - e.g. Fork) and what you were saying.

Fork has been recommended only because it is currently supported and
well-tested. And that is a very thin layer (which I like.) As for My
Library, that's ancient history; however, much of it stands up today.
It is ironic that unlike virtually every script ever published and my
practically begging anybody on the planet to rip it apart (even
proclaiming there were mistakes to be found.) You are it in the
complaint department. What I am saying (and doing) now, which has
been roughly the same for the last year or so (hard to pin it down),
is what you should pay the most attention to.
If code from MyLib.js is irrelevant and has faulty ideas, than I won't
use it as a reference.

Now who is the perfectionist? What would you use as a reference? See
above.
Does FD dojo branch reflect your latest ideas only?

At the moment, of course not. See my post in the Dojo forums about
what was done on the first pass and what still needs to be addressed.
There are about thousand files and God knows how many installations
out there. The next release must remain compatible with the current
production. By the end of the summer (and in preparation for 1.4),
yes. Of course.
[...]


Your script demonstrated how to introduce a circular reference, which
is sure to leak.  It's a design mistake.

It's a design decision.

A bad one that requires the unload listener. Change it.
Arguing would be me saying that "my way" is correct and/or that your way
is not (or something along those lines). I am not saying either.

What am I arguing about?

I don't want to get into semantics. It's not really what I do here.
Probably because it's the most common way of introducing leaks.

So why would you use it? I know you were illustrating a perceived
problem. I'm telling you the problem is in your own keystrokes (and
is easily avoidable.) Did you look at the clue I gave you?

[snip]
 
D

David Mark

[snip[
I vaguely remember you saying that expandos on elements was a design
mistake in MyLib.js as well. Is that so or am I confusing something?

I missed this one. You remember correctly. IIRC, they occur in
certain types of queries (and little if anything else.) Regardless, I
consider a CSS selector query engine to be the ultimate in folly.
Look at how often they cause simple apps to behave wildly different in
even recent browsers. Would gEBI and gEBTN do that? Despite jQuery
and others writing everything around them, they are nothing more than
a curiosity.

[snip]
 
D

David Mark

David said:
I vaguely remember you saying that expandos on elements was a design
mistake in MyLib.js as well. Is that so or am I confusing something?
I missed this one.  You remember correctly.  IIRC, they occur in
certain types of queries (and little if anything else.)  Regardless, I
consider a CSS selector query engine to be the ultimate in folly.
Look at how often they cause simple apps to behave wildly different in
even recent browsers.  Would gEBI and gEBTN do that?  Despite jQuery
and others writing everything around them, they are nothing more than
a curiosity.

`API.attachListener` also results in expando. I see an element has
`uniqueID` property with "ms__id32" value after I pass it to that method.

You are wrong. That has nothing to do with my library (as should be
obvious from the value.)

Try this in any MSHTML DOM:

javascript:window.alert(document.documentElement.uniqueID)
 
D

David Mark

David said:
David Mark wrote:
David Mark wrote:
David Mark wrote:
[...]
I don't see problems when the instruction is repeated in
onUnLoad:
<body onLoad="document.formName.buttonName.disabled=false;"
      onUnLoad="document.formName.buttonName.disabled=false;">
There is considerable harm as it unnecessarily breaks fast history
navigation.  You would add server hits and everything else associated
with loading a fresh document for the sake of disabling a submit
button (which isn't necessary because validation and duplication
checks must occur on the server anyway.)
It's the standard Web2.0 paradox.  Add scripts that make XHR requests
and manipulate the DOM, adding unload listeners as they go (typically
"unload" listener is usually added in IE only, to "cleanup" whatever
event registry is being employed.
I know.  Because the designers of such "registries" have no clue how
to design something that doesn't leak.  Thus the "cleanup" has come to
be seen as a requirement.  It definitely is not.
"IE only" is usually determined as
I know.  Usually with browser sniffing.  See the pattern of futility?
It's in virtually every Web app written in the last few years.
something that has `attachEvent` but lacks `addEventListener`.
Object inferences are still browser sniffing.
What's with perfectionism? You can't do without careful object
inference. Browser scripting is not a perfect world. `userAgent` is one
side of a spectrum, pure feature detection is on the other. You try to
stay as close to the latter one as possible. It doesn't mean you always can.
You sure can in this case, because you don't need the end-around.  The
straight-ahead play is always best.
Of course you can. Invoking event handler in a proper context is just a
convenience.
You're killing me.  You can have your convenience without death.

I think I see where my misunderstanding is coming from. Last time I
tested leaks in IE, no closure was needed to introduce a leak. Passing

Closures don't cause leaks. Circular references do.
an element (to be used as a context) into a separate factory to create a
normalized handler was still leaking.

Then your factory created a circular reference.
I'll take some time off now and will look into your event module more
carefully. Either I always thought wrong, or your approach also leaks.

I don't know what you thought, but my approach does not create any
circular references.
 
D

David Mark

It doesn't, from what I can see.

Rather than associate an id with an element, you simply create a new id
on every single `attachListener` invocation. You avoid circular
references of course and you avoid adding expandos to elements.

More like a handle, not an ID.
For reference, here's the pattern being used (simplified and without
feature testing, please correct me if I missed or skewed something):

var addEvent = (function(){

   var globalContext = this,
       contextId = 0,
       eventContexts = [ ];

   function getNormalizedHandler(contextId, handler) {
     return function() {
       return handler.call(eventContexts[contextId],
         globalContext.event);
     }
   }

   if (window.addEventListener) {
     return function(element, eventName, handler) {
       return element.addEventListener(eventName, handler, false);
     };
   }
   else if (window.attachEvent) {
     return function(element, eventName, handler) {
       eventContexts[contextId++] = element;
       var normalizedHandler = getNormalizedHandler(contextId, handler);
       element.attachEvent('on' + eventName, normalizedHandler);
     };
   }

})();

It's an interesting solution, but has potential of creating a bunch of
"repeating" element references. For example, invoking `attachListener`
with same element, same event name and same handler 100 times will
result in an array of 100 objects, `context` property of which will
reference that same element. If expandos were used, N invocations would
result in 1 reference, not N.

That's 100% correct. So, if you wish to do such things (hard to
imagine), it would be best to adjust the approach (not very hard.)
There should be a more sophisticated store that can be cleaned up on
removing listeners, but then this was just something I threw together
to demonstrate it could be done. Since then, I've found no pressing
need to improve on it as I don't often remove listeners or attach lots
of listeners to the same element.
If you then delete an element (that has an attached event listener
through your wrapper) from the document, you still have 1 or more
references to that element in your array.

Remove it from the document? Yes, that's true and if you did that
with lots of them, it would be a good idea to implement a more
sophisticated store (e.g. one that allows for quick cleanup.)
If you want to delete that
reference at some point (to let removed element be garbage collected)
you need to go through N objects, not 1. This can be costly, but would
all depend on a context of course.

Correct, but the last bit is key. For instance, if the element has an
ID, you could use that in lieu of a handle. That's one possibility.
These are the only downsides I can see in that approach as of now.

That's it in a nutshell. Depending on the context, it may be a little
more work, but the benefits are well worth it.

As an example, I navigated around Ajaxian (what a rag) a bit today and
they could use something like this. Every back or forward action must
hit their server(s) dozens of times. After about five to ten seconds
(on broadband), the browser scrolls to where I was in the first
place. That's not going to be good for anybody, but most high tech
"Web 2.0" sites behave in similar fashion (typically due to the
presence of one or more "Ajax libraries").
 
M

Matt Kruse

   else if (window.attachEvent) {
     return function(element, eventName, handler) {
       eventContexts[contextId++] = element;
       var normalizedHandler = getNormalizedHandler(contextId, handler);
       element.attachEvent('on' + eventName, normalizedHandler);
     };
   }

Why not simply use the 'id' attribute of the element itself (and set
it if it doesn't have one)? That would avoid the potential of
duplicate elements in the eventContexts array, wouldn't it?

Matt Kruse
 
D

David Mark

   else if (window.attachEvent) {
     return function(element, eventName, handler) {
       eventContexts[contextId++] = element;
       var normalizedHandler = getNormalizedHandler(contextId, handler);
       element.attachEvent('on' + eventName, normalizedHandler);
     };
   }

Why not simply use the 'id' attribute of the element itself (and set
it if it doesn't have one)? That would avoid the potential of
duplicate elements in the eventContexts array, wouldn't it?

In the context of a general-purpose library (which is a pretty lousy
context for browser scripting), that would be out of the question.
See why those are virtually always bad news?

In the context more specific scripts, a better idea is to require an
ID on elements passed to the function. It is better to document the
requirement than to silently augment elements. During development of
any substantial project, I would throw an exception in the case of a
missing ID (then I could trace back to the offending code.)

And the event "context" need not be an element, but the ID requirement
works just as well for other objects (provided naming conventions are
in place to avoid collisions.)
 
D

David Mark

Matt said:
   else if (window.attachEvent) {
     return function(element, eventName, handler) {
       eventContexts[contextId++] = element;
       var normalizedHandler = getNormalizedHandler(contextId, handler);
       element.attachEvent('on' + eventName, normalizedHandler);
     };
   }
Why not simply use the 'id' attribute of the element itself (and set
it if it doesn't have one)? That would avoid the potential of
duplicate elements in the eventContexts array, wouldn't it?

It would, just like when using expando.

The problem I see here is that if user swaps ids of 2 "observed"
elements, that swapping will compromise context integrity. Also,
creating an element and attaching a listener to it before assigning an
id to it, will result in a lost context "handle".

Keeping handle private in a closure (or as an expando, like
jQuery/Prototype do) avoids these problems.

It should be pointed out that expandos are very dangerous and should
never be used. It is unsurprising that jQuery/Prototype take such
liberties with DOM nodes. IIRC, jQuery sets either an expando or a
custom attribute (depending on IE version I think) on every node it
touches.

One property in IE (document.expando) cam thwart any attempt to
augment DOM nodes. And, of course, host object properties have been
known to throw exceptions on access, which seems a much less intrusive
operation. IIRC, you can't augment an ActiveX object at all (e.g.
MSXML nodes.)
 
M

maher_rj

Hi,

I don't know what you thought, but my approach does not create any
circular references.

It doesn't, from what I can see.

Rather than associate an id with an element, you simply create a new id
on every single `attachListener` invocation. You avoid circular
references of course and you avoid adding expandos to elements.

For reference, here's the pattern being used (simplified and without
feature testing, please correct me if I missed or skewed something):

var addEvent = (function(){

   var globalContext = this,
       contextId = 0,
       eventContexts = [ ];

   function getNormalizedHandler(contextId, handler) {
     return function() {
       return handler.call(eventContexts[contextId],
         globalContext.event);
     }
   }

   if (window.addEventListener) {
     return function(element, eventName, handler) {
       return element.addEventListener(eventName, handler, false);
     };
   }
   else if (window.attachEvent) {
     return function(element, eventName, handler) {
       eventContexts[contextId++] = element;
       var normalizedHandler = getNormalizedHandler(contextId, handler);
       element.attachEvent('on' + eventName, normalizedHandler);
     };
   }

})();

It's an interesting solution, but has potential of creating a bunch of
"repeating" element references. For example, invoking `attachListener`
with same element, same event name and same handler 100 times will
result in an array of 100 objects, `context` property of which will
reference that same element. If expandos were used, N invocations would
result in 1 reference, not N.

If you then delete an element (that has an attached event listener
through your wrapper) from the document, you still have 1 or more
references to that element in your array. If you want to delete that
reference at some point (to let removed element be garbage collected)
you need to go through N objects, not 1. This can be costly, but would
all depend on a context of course.

These are the only downsides I can see in that approach as of now.

I'm looking over the many addEvent implementations and trying to
distill something that's cross-browser functional and without memeory
leaks (if not "best practice") and was wondering if David had a
matching removeEvent, or is the optimized-back-button friendly
behaviour meant to leave all handlers ready for a swift return?

Is there still no universally applauded (and publicly available) add/
remove event mechanism?

Personally, I use applets and will stick an onunload event in there
even if it does nothing just to disable the disasterously "clever"
behaviour of the back-button on some browsers. IMHO if you want an
optimized return to a previous page then use tabs and leave the page
up. "Wow, look how quick that was!" :)

Cheers Richard Maher
 
D

David Mark

Hi,

It doesn't, from what I can see.
Rather than associate an id with an element, you simply create a new id
on every single `attachListener` invocation. You avoid circular
references of course and you avoid adding expandos to elements.
For reference, here's the pattern being used (simplified and without
feature testing, please correct me if I missed or skewed something):
var addEvent = (function(){
   var globalContext = this,
       contextId = 0,
       eventContexts = [ ];
   function getNormalizedHandler(contextId, handler) {
     return function() {
       return handler.call(eventContexts[contextId],
         globalContext.event);
     }
   }
   if (window.addEventListener) {
     return function(element, eventName, handler) {
       return element.addEventListener(eventName, handler, false);
     };
   }
   else if (window.attachEvent) {
     return function(element, eventName, handler) {
       eventContexts[contextId++] = element;
       var normalizedHandler = getNormalizedHandler(contextId, handler);
       element.attachEvent('on' + eventName, normalizedHandler);
     };
   }

It's an interesting solution, but has potential of creating a bunch of
"repeating" element references. For example, invoking `attachListener`
with same element, same event name and same handler 100 times will
result in an array of 100 objects, `context` property of which will
reference that same element. If expandos were used, N invocations would
result in 1 reference, not N.
If you then delete an element (that has an attached event listener
through your wrapper) from the document, you still have 1 or more
references to that element in your array. If you want to delete that
reference at some point (to let removed element be garbage collected)
you need to go through N objects, not 1. This can be costly, but would
all depend on a context of course.
These are the only downsides I can see in that approach as of now.

I'm looking over the many addEvent implementations and trying to
distill something that's cross-browser functional and without memeory
leaks (if not "best practice") and was wondering if David had a
matching removeEvent, or is the optimized-back-button friendly
behaviour meant to leave all handlers ready for a swift return?

Of course there is a removeEvent equivalent (detachListener in My
Library IIRC).
Is there still no universally applauded (and publicly available) add/
remove event mechanism?

Mechanism for what? Your thinking is too broad here.
Personally, I use applets and will stick an onunload event in there

Java applets on the client side?!
even if it does nothing just to disable the disasterously "clever"
behaviour of the back-button on some browsers.

That's completely silly.
IMHO if you want an
optimized return to a previous page then use tabs and leave the page
up. "Wow, look how quick that was!" :)

What do tabs (other windows) have to do with it? It is about back/
forward navigation, which is the primary navigation for Web browsers.
You can careen from one end of your history to the other without
waiting (or bothering the server). Your Internet connection could
have gone down in the interim and browsing still works. Open a new
window and start a new history? How is that helping anyone? :)

If you say don't like that because you want to track user activity (to
echo an "argument" I heard recently), rotate banners. The typical
"clever" tracking script blocks content on navigation to load a 1x1
blank GIF. Sounds backwards doesn't it?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top