Sunny
2005-Jun-28 05:15 UTC
[Rails-spinoffs] Thomas script.aculo.us (important bug and add ons)
Sorry. Posted from wrong email originally. Moderator, please ignore other post. Thx. To Thomas Fuchs, First of all, thanks for your amazing scripts on script.aculo.us. You''ve got quite some talent. I knew I was in for a treat when I read your goals (mainly about simplicity). My goals exactly. Keep it simple is great when you''re trying to integrate 100 different things. I wrote an additional Effect (didn''t take very long at all) that you might want to add called "Glow" which gradually changes the background color to give a glow like effect. Included in it is a very basic Color class which might be worth expanding in the future. For now it just does what I need so its very lightweight. Anyways, onto the very important bug. The Drag and Drop component, unfortunately, can''t be used for a long time due to it causing a memory leak in Microsoft Internet Explorer. This was a shame to me since I''m building a web app that will be accessed quite a bit. For information on what causes these leaks, here are some of the best resources: New Resource direct from Microsoft http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/d nwebgen/ie_leak_patterns.asp Old but good resource http://www.bazon.net/mishoo/articles.epl?art_id=824 You can confirm this by going to your home page or your drag and drop page on scriptaculous and hitting refresh a number of times with your task manager open. You''ll see your memory requirement continuing to rise. Eventually you run out of memory and things start going bad (slowing down, funny behavior, etc.). I had to deal with a lot of memory leaks in my own code as well and it wasn''t really well documented until recently. The Microsoft article is dated June 2005 (very recent) though memory leak info has been around for a while but rarely mentioned. The issue is circular references between DOM objects and code. In firefox and any responsible browser, this is not a problem. MSIE also has no problems with circular references EXCEPT when they span both DOM objects and javascript. Basically a circular reference (you might already know but I wasn''t sure what your expertise in this area was) is when an object refers to a second object which refers back to the first object. When doing garbage collection on memory, both objects have a reference count of 1 and hence aren''t removed. Usually this isn''t the case because the interpreter is smart enough to see that the objects are pointing to each other. But MSIE has problems when objects are referenced between the DOM and the javascript engine. So you aren''t completely holding all the work, here''s an example of code that needs to be looked at from your dragdrop.js file: ----- Sortable = { create: function(element) { var options = { tag: ''li'', // assumes li children, override with tag: ''tagname'' overlap: ''vertical'', // one of ''vertical'', ''horizontal'' constraint: ''vertical'', // one of ''vertical'', ''horizontal'', false containment: $(element), // also takes array of elements (or id''s); or false handle: false, // or a CSS class only: false, hoverclass: null, onChange: function() {}, onUpdate: function() {} }.extend(arguments[1] || {}); $(element).sortable_tag = options.tag; // convenience for serializing $(element).sortable_onChange = options.onChange; Element.cleanWhitespace(element); // fixes Gecko engine Draggables.addObserver(new SortableObserver(element, options.onUpdate)); ---- In the class above, you create a function called onChange() which is a javascript function. Then you create a reference to onchange from an element in the DOM. $(element).sortable_onChange=options.onChange; So what''s happening is this: function/class instance (has a reference to) -> element (has a reference to) -> original function/class instance For whatever reason, MSIE can''t dereference this. Some workaround you can use are: (a) clean up the reference by removing the reference before the page is unloaded by attaching cleanup to the onunload event. Namely, you could set the references to the element to null before page unloads. (b) Don''t store the element in the function/class at all. Instead, just store the "id" of the element. This works pretty well for me actually. In the "prototype" model, you could just $(id) it when you need to use it. If you are using the element many times, create a temporary variable to it that will get cleaned up. var elem=$(id); and if you want to be sure, after using it you can: elem=null; (c) Keep track of all the elements with references and write an automatic cleanup routine again attached to onunload event of the page. This seems like a very good thing to include as part of the "prototype" library. (d) In your case above, you can probably figure out a way to store the onchange functions in some sort of a javascript object so it never needs to sit in the DOM at all. No cleanup necessary then as the element never refers to the javascript instance. Sorry I''m not a huge help. Just ran across this today while trying out some of your code so I''m not intimately familiar with your code yet. Also, a final bug notice on Effect2.Appear(). There is no way to cancel the effect which is important when the same effect is launched on the same element. My workaround was to not use the Appear effect and use the opacity effect directly and then call the cancel method on that. Basically I always kept track of the last effect and then cancel it before running a new one. An easy way to see this bug in action is to click the "Advanced Options" link in the "Visual Effects" section of your site in the sample search box. But instead of clicking it once, click it three times. It will shudder open as three effects try and move it back and forth. Same thing happens with Appear. Here''s my "glow" code if you accept contributions. I love your work here so much that I''d be honored to be able to add something back. If you want to credit me, I''ll be even happier but I''ll accept an uncredited entry as well. :) Could probably come up with a few more effects. With the Color class, it would be pretty easy to extend this to glowing borders, text color, etc. Probably could be done in one effect pretty easily with a "style" option upon which to apply the color. Come to think of it, I''ll probably just write that and send it to you. Probably will only take a few minutes. // a color class for converting colors. // Stores color internally as r,g,b in decimal format (0-255) function Color(color){ this.r=0; this.g=0; this.b=0; if(arguments.length>1){ this.r=arguments[0]; this.g=arguments[1]; this.b=arguments[2]; }else{ this.r=parseInt(color.substr(0,2),16); this.g=parseInt(color.substr(2,2),16); this.b=parseInt(color.substr(4,2),16); } } Color.prototype.toString=function(){ var s=""; s+=this.r.toColorPart().toUpperCase(); s+=this.g.toColorPart().toUpperCase(); s+=this.b.toColorPart().toUpperCase(); return s; } Effect2.Glow=Class.create(); Effect2.Glow.prototype=(new Effect2.Base()).extend({ initialize:function(){ this.element=$(arguments[0]); options={ from:0.0, to:1.0, fromColor:new Color("FFFFFF"), toColor:new Color("FFFFCC") }.extend(arguments[1]||{}); this.start(options); }, update:function(position){ this.setColor(position); }, setColor:function(position){ var r,g,b,color; var o=this.options; r=parseInt((o.toColor.r-o.fromColor.r)*position)+o.fromColor.r; g=parseInt((o.toColor.g-o.fromColor.g)*position)+o.fromColor.g; b=parseInt((o.toColor.b-o.fromColor.b)*position)+o.fromColor.b; color=new Color(r,g,b); this.element.style.backgroundColor=color.toString(); } })
Sunny
2005-Jun-28 06:40 UTC
[Rails-spinoffs] Thomas script.aculo.us (important bug and add ons)
To Thomas Fuchs, First of all, thanks for your amazing scripts on script.aculo.us. You''ve got quite some talent. I knew I was in for a treat when I read your goals (mainly about simplicity). My goals exactly. Keep it simple is great when you''re trying to integrate 100 different things. I wrote an additional Effect (didn''t take very long at all) that you might want to add called "Glow" which gradually changes the background color to give a glow like effect. Included in it is a very basic Color class which might be worth expanding in the future. For now it just does what I need so its very lightweight. Anyways, onto the very important bug. The Drag and Drop component, unfortunately, can''t be used for a long time due to it causing a memory leak in Microsoft Internet Explorer. This was a shame to me since I''m building a web app that will be accessed quite a bit. For information on what causes these leaks, here are some of the best resources: New Resource direct from Microsoft http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/d nwebgen/ie_leak_patterns.asp Old but good resource http://www.bazon.net/mishoo/articles.epl?art_id=824 You can confirm this by going to your home page or your drag and drop page on scriptaculous and hitting refresh a number of times with your task manager open. You''ll see your memory requirement continuing to rise. Eventually you run out of memory and things start going bad (slowing down, funny behavior, etc.). I had to deal with a lot of memory leaks in my own code as well and it wasn''t really well documented until recently. The Microsoft article is dated June 2005 (very recent) though memory leak info has been around for a while but rarely mentioned. The issue is circular references between DOM objects and code. In firefox and any responsible browser, this is not a problem. MSIE also has no problems with circular references EXCEPT when they span both DOM objects and javascript. Basically a circular reference (you might already know but I wasn''t sure what your expertise in this area was) is when an object refers to a second object which refers back to the first object. When doing garbage collection on memory, both objects have a reference count of 1 and hence aren''t removed. Usually this isn''t the case because the interpreter is smart enough to see that the objects are pointing to each other. But MSIE has problems when objects are referenced between the DOM and the javascript engine. So you aren''t completely holding all the work, here''s an example of code that needs to be looked at from your dragdrop.js file: ----- Sortable = { create: function(element) { var options = { tag: ''li'', // assumes li children, override with tag: ''tagname'' overlap: ''vertical'', // one of ''vertical'', ''horizontal'' constraint: ''vertical'', // one of ''vertical'', ''horizontal'', false containment: $(element), // also takes array of elements (or id''s); or false handle: false, // or a CSS class only: false, hoverclass: null, onChange: function() {}, onUpdate: function() {} }.extend(arguments[1] || {}); $(element).sortable_tag = options.tag; // convenience for serializing $(element).sortable_onChange = options.onChange; Element.cleanWhitespace(element); // fixes Gecko engine Draggables.addObserver(new SortableObserver(element, options.onUpdate)); ---- In the class above, you create a function called onChange() which is a javascript function. Then you create a reference to onchange from an element in the DOM. $(element).sortable_onChange=options.onChange; So what''s happening is this: function/class instance (has a reference to) -> element (has a reference to) -> original function/class instance For whatever reason, MSIE can''t dereference this. Some workaround you can use are: (a) clean up the reference by removing the reference before the page is unloaded by attaching cleanup to the onunload event. Namely, you could set the references to the element to null before page unloads. (b) Don''t store the element in the function/class at all. Instead, just store the "id" of the element. This works pretty well for me actually. In the "prototype" model, you could just $(id) it when you need to use it. If you are using the element many times, create a temporary variable to it that will get cleaned up. var elem=$(id); and if you want to be sure, after using it you can: elem=null; (c) Keep track of all the elements with references and write an automatic cleanup routine again attached to onunload event of the page. This seems like a very good thing to include as part of the "prototype" library. (d) In your case above, you can probably figure out a way to store the onchange functions in some sort of a javascript object so it never needs to sit in the DOM at all. No cleanup necessary then as the element never refers to the javascript instance. Sorry I''m not a huge help. Just ran across this today while trying out some of your code so I''m not intimately familiar with your code yet. Also, a final bug notice on Effect2.Appear(). There is no way to cancel the effect which is important when the same effect is launched on the same element. My workaround was to not use the Appear effect and use the opacity effect directly and then call the cancel method on that. Basically I always kept track of the last effect and then cancel it before running a new one. An easy way to see this bug in action is to click the "Advanced Options" link in the "Visual Effects" section of your site in the sample search box. But instead of clicking it once, click it three times. It will shudder open as three effects try and move it back and forth. Same thing happens with Appear. Here''s my "glow" code if you accept contributions. I love your work here so much that I''d be honored to be able to add something back. If you want to credit me, I''ll be even happier but I''ll accept an uncredited entry as well. :) Could probably come up with a few more effects. With the Color class, it would be pretty easy to extend this to glowing borders, text color, etc. Probably could be done in one effect pretty easily with a "style" option upon which to apply the color. Come to think of it, I''ll probably just write that and send it to you. Probably will only take a few minutes. // a color class for converting colors. // Stores color internally as r,g,b in decimal format (0-255) function Color(color){ this.r=0; this.g=0; this.b=0; if(arguments.length>1){ this.r=arguments[0]; this.g=arguments[1]; this.b=arguments[2]; }else{ this.r=parseInt(color.substr(0,2),16); this.g=parseInt(color.substr(2,2),16); this.b=parseInt(color.substr(4,2),16); } } Color.prototype.toString=function(){ var s=""; s+=this.r.toColorPart().toUpperCase(); s+=this.g.toColorPart().toUpperCase(); s+=this.b.toColorPart().toUpperCase(); return s; } Effect2.Glow=Class.create(); Effect2.Glow.prototype=(new Effect2.Base()).extend({ initialize:function(){ this.element=$(arguments[0]); options={ from:0.0, to:1.0, fromColor:new Color("FFFFFF"), toColor:new Color("FFFFCC") }.extend(arguments[1]||{}); this.start(options); }, update:function(position){ this.setColor(position); }, setColor:function(position){ var r,g,b,color; var o=this.options; r=parseInt((o.toColor.r-o.fromColor.r)*position)+o.fromColor.r; g=parseInt((o.toColor.g-o.fromColor.g)*position)+o.fromColor.g; b=parseInt((o.toColor.b-o.fromColor.b)*position)+o.fromColor.b; color=new Color(r,g,b); this.element.style.backgroundColor=color.toString(); } })