Avoiding real work I decided to try and clean up my html and remove my inline onclick handlers for Ajax.Updater calls. Here''s the inline method I''ve been using: http://hank.org/demos/ajax-inline.html Now, here''s using <script> sections to apply the behavior. http://hank.org/demos/ajax.html (firefox only) Since the links (tabs) are inside the <div> that is updated by the ajax request I have to find a way to re-apply the event handlers to those links after the update. I''m currently using evalScripts and the ajax response includes a <script> section (see http://hank.org/demos/recent). I''m wondering if there''s other approaches people are using. One idea would be to save the list of element ids that need the behavior, and use onComplete to re-observe those elements. The other issue, of course, is how to make this work in more than just Firefox. Anyone see why the problem is? -- Bill Moseley moseley-kv6DSSSScSo@public.gmane.org
Bill Moseley wrote:> Avoiding real work I decided to try and clean up my html and remove my > inline onclick handlers for Ajax.Updater calls. Here''s the inline > method I''ve been using: > > http://hank.org/demos/ajax-inline.html > > > Now, here''s using <script> sections to apply the behavior. > > http://hank.org/demos/ajax.html (firefox only) > > Since the links (tabs) are inside the <div> that is updated by the > ajax request I have to find a way to re-apply the event handlers > to those links after the update. > > I''m currently using evalScripts and the ajax response includes a > <script> section (see http://hank.org/demos/recent). > > I''m wondering if there''s other approaches people are using. One idea > would be to save the list of element ids that need the behavior, and > use onComplete to re-observe those elements.Look at Behaviour.js. If you use that, then you can just call Behaviour.apply() to reapply the effects. -- Michael Peters Developer Plus Three, LP
Michael Peters wrote:> Look at Behaviour.js. If you use that, then you can just call > Behaviour.apply() to reapply the effects.Sorry, forgot the link - http://bennolan.com/behaviour/ -- Michael Peters Developer Plus Three, LP
But... Behavior does not address the problem of disposing of the already created event handlers (which are very often closures, therefore big potential for memory leaks) It''s much better to start programming your "widgets" as autonomous objects, and keep handles to all your event handlers. Bill, your development pattern sounds like it''s getting close to this, and you''re at the point where you need to start thinking about garbage collection, especially if you want your applications to run smoothly in all browsers. Every time you send a request that updates the elements in your div, if you don''t first clean up the event handlers that were setup in the previous elements... you leave your application with a memory leak potential, big time. Your design pattern should start looking more like this... 1. MyControl (div) loads for the first time. The DOM elements associated with MyControl have unique IDs that are prepended with MyControl''s identifier. This will allow you to start loading in multiple controls of the same type more easily... 2. After the HTML is loaded, MyControl sets up its own DOM event handlers (autonomous widget) in a method called something like controlLoaded() or setup() or whatever you fancy. When MyControl does this, the event handlers are stored in member variables, and so are the DOM element references -- this allows for proper cleanup before the next server request to update the widget. Something like this... this.someLink = $(MyControl.id + "_someLink"); this.someLink_ClickHandler this.someLinkClicked.bindAsEventListener(this); Event.observe(this.someLink, "click", this.someLink_ClickHandler); 3. Some application level event occurs which triggers MyControl to send a request (by itself, remember it''s an autonomous widget) to the server to be refreshed. Before it sends the request, MyControl is responsible for cleaning up the resources (memory) that it has a hold of. This means getting rid of the event handlers for all of it''s DOM elements (which will be overwritten with new ones). Something like this... Event.stopObserving(this.someLink, "click", this.someLink_ClickHandler); this.someLink_ClickHandler = null; this.someLink = null; 4. The request comes back from the server, resulting in a call to update the innerHTML property of MyControl''s container div. After that call, the MyControl.controlLoaded() or setup() (or whatever you fancied in step 2) is called again, thus repeating the cycle... This was obviously just a very high level description of a design pattern, but I hope you get the idea. When you start developing truly robust, full blown applications in an AJAX context, your controls will need to be autonomous (unless you really want to have 1 huge script on your page that has to worry about wiring up all the new DOM elements that come and go), and they will need to clean themselves up. Garbage collection is important, and especially when dealing with self updating widgets that (most likely) have event handlers which are closures... Before I realized this, my applications would easily take IE to its knees (and most likely FF... keep in mind memory leaks are NOT, I repeat NOT, an IE only concern) The information transmitted in this electronic mail is intended only for the person or entity to which it is addressed and may contain confidential, proprietary, and/or privileged material. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon, this information by persons or entities other than the intended recipient is prohibited. If you received this in error, please contact the sender and delete the material from all computers.
On Fri, Mar 31, 2006 at 10:22:56AM -0600, Ryan Gahl wrote:> But... Behavior does not address the problem of disposing of the already > created event handlers (which are very often closures, therefore big > potential for memory leaks)Which is why I''ve been moving away from Behavior. Although, I have not looked at the new version yet.> Every time you send a request that updates the elements in your div, if > you don''t first clean up the event handlers that were setup in the > previous elements... you leave your application with a memory leak > potential, big time.Sure seems like something javascript should deal with. Closures are tough, but perhaps some kind of "weak reference" support could solve that. If an element is destroyed sure seems like javascript should figure out how to free up memory. Do these memory leaks persist after a full page reload? The horrors.> Your design pattern should start looking more like this...Thanks very much for this. I suspect it will take me a few passes to understand it. Do you happen to have a small working example? I admit to not fully understanding the object model in javascript, yet. I still find "this" confusing. Sometimes it''s the element, sometimes it''s the object (instance?).> 1. MyControl (div) loads for the first time. The DOM elements associated > with MyControl have unique IDs that are prepended with MyControl''s > identifier. This will allow you to start loading in multiple controls of > the same type more easily...By scanning the DOM looking for the prefix on the ids?> 2. After the HTML is loaded, MyControl sets up its own DOM event > handlers (autonomous widget) in a method called something like > controlLoaded() or setup() or whatever you fancy. When MyControl does > this, the event handlers are stored in member variables, and so are the > DOM element references -- this allows for proper cleanup before the next > server request to update the widget. Something like this... > > this.someLink = $(MyControl.id + "_someLink"); > this.someLink_ClickHandler > this.someLinkClicked.bindAsEventListener(this); > Event.observe(this.someLink, "click", this.someLink_ClickHandler);bindAsEventListener() means that when the event fires you get the *instance* of the object in "this", correct? Is there any worry that bindAsEventListener(), as a closure, will also leak?> 3. Some application level event occurs which triggers MyControl to send > a request (by itself, remember it''s an autonomous widget) to the server > to be refreshed. Before it sends the request, MyControl is responsible > for cleaning up the resources (memory) that it has a hold of. This means > getting rid of the event handlers for all of it''s DOM elements (which > will be overwritten with new ones). Something like this... > > Event.stopObserving(this.someLink, "click", this.someLink_ClickHandler); > this.someLink_ClickHandler = null; > this.someLink = null;Here''s where I get lost. The event fires and then I free up the event bound to that element. Where''s the list of other events to free up before starting the ajax call? Thanks very much for the help, -- Bill Moseley moseley-kv6DSSSScSo@public.gmane.org
> Sure seems like something javascript should deal with. Closures are > tough, but perhaps some kind of "weak reference" support could solve > that. If an element is destroyed sure seems like javascript should > figure out how to free up memory.> Do these memory leaks persist after a full page reload? The horrors.Even a full page reload COULD persist these leaks, yes. But prototype does handle that by wiring up an "onunload" event handler that purges all remaining DOM handlers. Our problem comes into play because we are starting to make applications where a full page reload may not occur for an arbitrarily long period of time...> Thanks very much for this. I suspect it will take me a few passes to > understand it. Do you happen to have a small working example?I don''t have anything available example-wise yet that''s in the public domain... But myself, Marco Jaeger (whose Dialogs you may have seen talked about here recently), Jerod Venema (who also helped Marco and I with the Dialogs), and Joseph Potenza (all members of this list) are currently working on something that should bring this pattern to light in a big way (although progress is somewhat slow due to all of our life obligations at the moment).> By scanning the DOM looking for the prefix on the ids?Exactly. How you achieve that (uniquely IDing your DOM elements) from your server code I guess will depend on what platform you use... .NET handles it all for me, but you may have to figure it out for yourself if you use RoR or PHP. But getting this part right means you can easily load in an N number of widgets of the same type, knowing full well there will be no collisions of DOM element IDs when you try to wire up all the events.> bindAsEventListener() means that when the event fires you get the > *instance* of the object in "this", correct?Yes, that''s exactly what it''s for. bind() and bindAsEventListener() are both for that purpose, but the latter also gives you access to the "event" object.> Is there any worry that bindAsEventListener(), as a closure, will also > leak?No, closures in and of themselves are fine. The problem comes in when you have a reference to a DOM element within your closure, creating a circular reference (mainly an IE problem, but again if you think about this in a general manner you''ll be safe on all browsers, as FF has it''s leaks as well). Check out the following link for an in depth explanation: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechC ol/dnwebgen/ie_leak_patterns.asp> Here''s where I get lost. The event fires and then I free up the > event bound to that element. Where''s the list of other events to > free up before starting the ajax call?This is why you should always keep handles to your event handlers, and making autonomous widgets is so important (each widget knows which events it has tied to which elements, and therefore knows how to clean itself up). I always wire up all my DOM event handlers in 1 method (called _attachEvents() in my case), and right before making the call to the server I call my _detachEvents() method. When I say that, I mean in each widget... each class has it''s own _attach/_detachEvents() methods... In _attachEvents, I know explicitly which elements I need to attach to which event handlers, and I store all the handlers in member variables of the widget... thus in the _detachEvents() method cleanup is a snap. Like so... //NOTE: This is obviously a pseudo-class just meant to illustrate the attaching and detaching of event handlers... also notice the difference in bind() and bindAsEventListener(), and the fact that I define a dispose() method, which is where _detachEvents() is called from (this way you can put other disposal related code in the same place -- like un-registering any Draggables that the widget might have registered using the DOM elements that are about to be annihilated) MyWidget = Class.create() MyWidget.prototype { initialize: function(id) { this.id = id; this._attachEvents(); }, _attachEvents: function() { //someElement1 this.someElement1 = $(this.id + "_someElement1"); this.someElement1_ClickListener this.someElement1_Clicked.bind(); Event.observe(this.someElement1, "click", this.someElement1_ClickListener); //someElement2 this.someElement2 = $(this.id + "_someElement2"); this.someElement2_ClickListener this.someElement2_Clicked.bindAsEventListener(); Event.observe(this.someElement2, "click", this.someElement2_ClickListener); }, _detachEvents: function() { //someElement1 Event.stopObserving(this.someElement1, "click", this.someElement1_ClickListener); this.someElement1 = null; this.someElement1_ClickListener = null; //someElement2 Event. stopObserving(this.someElement2, "click", this.someElement2_ClickListener); this.someElement2 = null; this.someElement2_ClickListener = null; }, someElement1_Clicked: function() { alert(this.id + "_someElement1 was clicked"); }, someElement2_Clicked: function(evt) { //notice the evt arg in this function... I have access the "event" object if I need it because I used bindAsEventListener... alert(this.id + "_someElement2 was clicked"); }, dispose: function() { this._detachEvents(); //insert other disposal code here (like unregistering Draggables.. etc..) } }; The information transmitted in this electronic mail is intended only for the person or entity to which it is addressed and may contain confidential, proprietary, and/or privileged material. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon, this information by persons or entities other than the intended recipient is prohibited. If you received this in error, please contact the sender and delete the material from all computers.
On Fri, Mar 31, 2006 at 02:13:11PM -0600, Ryan Gahl wrote:> > Here''s where I get lost. The event fires and then I free up the > > event bound to that element. Where''s the list of other events to > > free up before starting the ajax call? > > This is why you should always keep handles to your event handlers, and > making autonomous widgets is so important (each widget knows which > events it has tied to which elements, and therefore knows how to clean > itself up).Ah, I see the confusion. With your code example I now see what you are saying. My current setup is not that organized, I guess. I think I''m trying to accomplish something in a more generic way. For example, I generate a table with a template. That table might include links in the headers to sort by each column. I have a template macro that creates those links, and the macro has a flag to indicate if it should also generate the code for an ajax request. The IDs are generated on the fly, so I don''t know them ahead of time, and I don''t know which or how many links will have events until the page is generated. So, that doesn''t lend itself to defining the object ahead of time. Maybe I need to rethink the design in a way that does. Maybe I could walk the DOM and look for events inside of the <div> that''s about to be updated and remove those before making the ajax call. Thanks for the help. -- Bill Moseley moseley-kv6DSSSScSo@public.gmane.org
> For example, I generate a table with a template. That table might > include links in the headers to sort by each column. I have a template > macro that creates those links, and the macro has a flag to indicate > if it should also generate the code for an ajax request. The IDs are > generated on the fly, so I don''t know them ahead of time, and I don''t > know which or how many links will have events until the page is > generated.In that case, there has to still be a point when you are wiring up the events that you have a chance to store information about those DOM elements in some scope-wide variable (or array or what-have-you). Like "this.DOMElements.push(el);" and "this.DOMEventHandlers.push(handler);"... and then in the dispose phase, iterate those collections to get rid of the event handlers. The information transmitted in this electronic mail is intended only for the person or entity to which it is addressed and may contain confidential, proprietary, and/or privileged material. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon, this information by persons or entities other than the intended recipient is prohibited. If you received this in error, please contact the sender and delete the material from all computers.