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();
}
})