I''ve been experimenting with extending Class.create() to do object casting as well as its usual object creation. The idea is to use syntax similar to performing object casts in C++. For example: int i = (int)3.141; Tree t = (Tree)obj; Casting can also be used to do type verification, which Javascript is loose about. Tree t = (Tree)human; //throws exception Tree t = (Tree)pine; //success I thought about extending the function returned by Class.create() that performs different actions depending on whether it is called as a function or as a constructor. car = new MyCar(); car = MyCar(obj); //may succeed or throw an error MyCar(obj).startEngine(); Then I thought, if the cast method was customizable, then implementators can use it to convert one completely different object into another under the guise of a cast. e.g., TabbedPane($("tab_elm").firstChild).activateTab(); The main difference between: TabbedPane($("tab_elm")); and new TabbedPane($("tab_elm")); is that the latter semantically represents creating a new tabbed pane object, while the former represents getting the tabbed pane object from an existing source (possibly a hashtable) when it is provided with a DOM reference. My dual purpose Class.create() implementation is as follows: var Class = { create: function() { return function() { if(this instanceof arguments.callee) { this.initialize.apply(this, arguments); } else { if(arguments.callee.cast) { return arguments.callee.cast.apply(this, arguments); } else { return null; } } } } }; If the implementor decides to support casting, they would then implement their own cast method: var MyCar = Class.create(); MyCar.cast = function(obj) { return (MyCar.prototype.isPrototypeOf(obj)) ? obj : null; }; You may have noticed that I return a null instead of throwing an exception in the code above. The purpose is to aid in debugging. Under some circumstances, exceptions do not keep their line numbers when thrown, but if a user tries to dereference a null, the exception does keep the line number. So if the user calls MyCar(human).startEngine(), the dereferencing will throw an exception and return the line number at the point where it is dereferenced, which happens to be the point of the type cast. What are your thoughts?
I have to admit to a hard time understanding a real-world use case for this. It seems like any situation where the method would fail would be programmer error and need to be fixed, and any situation where it would succeed, it would be unneccessary. Is there something I''m missing here? On 7/26/06, Em Te <em_te-PkbjNfxxIARBDgjK7y7TUQ@public.gmane.org> wrote:> > I''ve been experimenting with extending Class.create() to do object casting > as well as its usual object creation. The idea is to use syntax similar to > performing object casts in C++. For example: > > int i = (int)3.141; > Tree t = (Tree)obj; > > Casting can also be used to do type verification, which Javascript is > loose > about. > > Tree t = (Tree)human; //throws exception > Tree t = (Tree)pine; //success > > I thought about extending the function returned by Class.create() that > performs different actions depending on whether it is called as a function > or as a constructor. > > car = new MyCar(); > car = MyCar(obj); //may succeed or throw an error > MyCar(obj).startEngine(); > > Then I thought, if the cast method was customizable, then implementators > can > use it to convert one completely different object into another under the > guise of a cast. e.g., > > TabbedPane($("tab_elm").firstChild).activateTab(); > > The main difference between: > > TabbedPane($("tab_elm")); > > and > > new TabbedPane($("tab_elm")); > > is that the latter semantically represents creating a new tabbed pane > object, while the former represents getting the tabbed pane object from an > existing source (possibly a hashtable) when it is provided with a DOM > reference. > > My dual purpose Class.create() implementation is as follows: > > var Class = { > create: function() { > return function() { > if(this instanceof arguments.callee) { > this.initialize.apply(this, arguments); > } else { > if(arguments.callee.cast) { > return arguments.callee.cast.apply(this, arguments); > } else { > return null; > } > } > } > } > }; > > If the implementor decides to support casting, they would then implement > their own cast method: > > var MyCar = Class.create(); > MyCar.cast = function(obj) { > return (MyCar.prototype.isPrototypeOf(obj)) ? obj : null; > }; > > You may have noticed that I return a null instead of throwing an exception > in the code above. The purpose is to aid in debugging. Under some > circumstances, exceptions do not keep their line numbers when thrown, but > if > a user tries to dereference a null, the exception does keep the line > number. > So if the user calls MyCar(human).startEngine(), the dereferencing will > throw an exception and return the line number at the point where it is > dereferenced, which happens to be the point of the type cast. > > What are your thoughts? > > > _______________________________________________ > Rails-spinoffs mailing list > Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs >_______________________________________________ Rails-spinoffs mailing list Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs
Yes, it is used to catch programming errors by introducing a little type safety. How many times have you written this in your project: function showMenu(obj) { if(obj instanceof MenuItem) { obj.getNode().style.visibility = "visible"; } else throw new Error("Invalid parameter!"); } Now you can just do this: function showMenu(obj) { MenuItem(obj).getNode().style.visibility = "visible"; } This convention just makes codifying assertions a little more uniform. You know for sure that the parameter is of the correct type because (a) if it isn''t it, will be converted to the correct type; and (b) if it cannot be converted, it will throw an error. This allows you to quickly detect and correct bugs in your code and allows others (whom use your library) to detect bugs in their code. You are right is saying that it will succeed most of the time when programmer errors are fixed. But do you remove error checking code because of that? What if new code is added in the future? Keeping these type checks in the code makes debugging it easier in the future. It also makes it clear to maintainers what parameters the function requires and enforces that requirement in a design-by-contract type of way.>I have to admit to a hard time understanding a real-world use case for >this. It seems like any situation where the method would fail would be >programmer error and need to be fixed, and any situation where it would >succeed, it would be unneccessary. > >Is there something I''m missing here? > >On 7/26/06, Em Te wrote: >> >>I''ve been experimenting with extending Class.create() to do object casting >>as well as its usual object creation. The idea is to use syntax similar to >>performing object casts in C++. For example: >> >>int i = (int)3.141; >>Tree t = (Tree)obj; >> >>Casting can also be used to do type verification, which Javascript is >>loose about. >> >>Tree t = (Tree)human; //throws exception >>Tree t = (Tree)pine; //success >> >>I thought about extending the function returned by Class.create() that >>performs different actions depending on whether it is called as a function >>or as a constructor. >> >>car = new MyCar(); >>car = MyCar(obj); //may succeed or throw an error >>MyCar(obj).startEngine(); >>[truncated]
> Yes, it is used to catch programming errors by introducing a littletype> safety. How many times have you written this in your project: > > function showMenu(obj) { > if(obj instanceof MenuItem) { > obj.getNode().style.visibility = "visible"; > } else throw new Error("Invalid parameter!"); > } > > Now you can just do this: > > function showMenu(obj) { > MenuItem(obj).getNode().style.visibility = "visible"; > } > > This convention just makes codifying assertions a little more uniform.You> know for sure that the parameter is of the correct type because (a) ifit> isn''t it, will be converted to the correct type; and (b) if it cannotbe> converted, it will throw an error. This allows you to quickly detectand> correct bugs in your code and allows others (whom use your library) to > detect bugs in their code.I have to admit I didn''t read the full original email, and the intention wasn''t clear to me. With this one, I now see what you''re getting at, and it seems like a cool idea to me. The only concern I''d have is that the syntax isn''t immediately clear to someone unfamiliar with the code. It might be better to have a ''cast'' class method or something: MenuItem.cast(obj).getNode().style.visibility = ''visible''; Having it be a dual-purpose constructor might confuse people. Or maybe it wouldn''t, I dunno. Just some thoughts. Greg
It seems to me that attempting to enforce types to catch programming errors misses a bit of the point of Javascript. I suppose it would make certain methods of debugging simpler at the cost of greater code complexity, but that benefit seems to be less than overwhelming to me. Perhaps it''s related more to my Javascript style, but I really don''t run into type errors often enough for me to consider this necessary. I can see it being useful for people who primarily work in statically typed languages, though I feel it would be better in the long term to learn language-friendly ways of handling the situation (like common property names, discipline about object ownership, and proper encapsulation). Also, in this example:>function showMenu(obj) { > if(obj instanceof MenuItem) { > obj.getNode().style.visibility = "visible"; > } else throw new Error("Invalid parameter!"); > }it would be better to hide the visibility changes behind a method on the MenuItem object. Objects should always take care of themselves. Exposing a contained element is typically going to bite you later when you don''t know where all your manipulations are occurring. Also, when you call the method, if it doesn''t exist on the object, you''ll get a language error which is essentially the same error your code produces, without doing any extra work. I apologize for turning this into a screed, I spend a lot of time at work trying to teach the zen of object programming. On 7/28/06, Hill, Greg <grhill-W2hqgAdRMsX2eFz/2MeuCQ@public.gmane.org> wrote:> > > Yes, it is used to catch programming errors by introducing a little > type > > safety. How many times have you written this in your project: > > > > function showMenu(obj) { > > if(obj instanceof MenuItem) { > > obj.getNode().style.visibility = "visible"; > > } else throw new Error("Invalid parameter!"); > > } > > > > Now you can just do this: > > > > function showMenu(obj) { > > MenuItem(obj).getNode().style.visibility = "visible"; > > } > > > > This convention just makes codifying assertions a little more uniform. > You > > know for sure that the parameter is of the correct type because (a) if > it > > isn''t it, will be converted to the correct type; and (b) if it cannot > be > > converted, it will throw an error. This allows you to quickly detect > and > > correct bugs in your code and allows others (whom use your library) to > > detect bugs in their code. > > I have to admit I didn''t read the full original email, and the intention > wasn''t clear to me. With this one, I now see what you''re getting at, > and it seems like a cool idea to me. The only concern I''d have is that > the syntax isn''t immediately clear to someone unfamiliar with the code. > It might be better to have a ''cast'' class method or something: > > MenuItem.cast(obj).getNode().style.visibility = ''visible''; > > Having it be a dual-purpose constructor might confuse people. Or maybe > it wouldn''t, I dunno. Just some thoughts. > > Greg > _______________________________________________ > Rails-spinoffs mailing list > Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs >_______________________________________________ Rails-spinoffs mailing list Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs
>It seems to me that attempting to enforce types to catch programming errors >misses a bit of the point of Javascript. I suppose it would make certain >methods of debugging simpler at the cost of greater code complexity, but >that benefit seems to be less than overwhelming to me. > >Perhaps it''s related more to my Javascript style, but I really don''t run >into type errors often enough for me to consider this necessary. I can see >it being useful for people who primarily work in statically typed >languages, >though I feel it would be better in the long term to learn >language-friendly ways of handling the situation (like common property >names, discipline about object ownership, and proper encapsulation).I agree that the dynamic typing nature of Javascript is what makes it useful and suitable for certain tasks. I also agree that not everyone will benefit from type verification as most webpages are simple and Javascript only serves as a complementary technology (like autocomplete) where type verification may be an unnecessary restriction. But with large-scale AJAX applications where code complexity is inevitable and where many parts of the webpage are generated and driven with Javascript, it is precisely this dynamic typing nature which makes debugging harder, and where type verification would be beneficial. For example, in my project people have mistakenly passed the wrong objects around (they didn''t read the docs) and the errors were not detected because these objects used similar property/function names. In a recent GUI example, we have a function which will process a SubMenu object by calling SubMenu.open() and then passing it position offset information. But some people were mistakenly passing it the MenuItem object instead (which contained the SubMenu) which called MenuItem.open() which still opened the menu, but then passed the wrong position information. People didn''t understand why and assumed that because open() was called, they wrote everything correctly (which is generally true in our trial-and-error society). In the end we enforced the parameter with an instanceof check which allowed the programmers to catch errors early.>Also, in this example: > >>function showMenu(obj) { >> if(obj instanceof MenuItem) { >> obj.getNode().style.visibility = "visible"; >> } else throw new Error("Invalid parameter!"); >>} > >it would be better to hide the visibility changes behind a method on the >MenuItem object. Objects should always take care of themselves.Yes, I wasn''t thinking particularly hard when I came up with this example. Perhaps I thought illustrating a few DOM manipulations would give it more web-mojo. Or what if the object was deliberately exposing the node to allow for a "decorator" design pattern in determining how the menu is shown?>... Also, when you call the method, if it doesn''t exist on the object, >you''ll get a language error which is essentially the same error your code >produces, without doing any extra work.With function invocation, yes, but with property assignment, no. If my particular implementation were used, it would catch both. I guess I would have been better off throwing an error instead.>I apologize for turning this into a screed, I spend a lot of time at work >trying to teach the zen of object programming.No, I love these discussions and am continuously learning new things.>On 7/28/06, Hill, Greg <grhill-W2hqgAdRMsX2eFz/2MeuCQ@public.gmane.org> wrote: >> >> > Yes, it is used to catch programming errors by introducing a little >>type >> > safety. How many times have you written this in your project: >> > >> > function showMenu(obj) { >> > if(obj instanceof MenuItem) { >> > obj.getNode().style.visibility = "visible"; >> > } else throw new Error("Invalid parameter!"); >> > } >> > >> > Now you can just do this: >> > >> > function showMenu(obj) { >> > MenuItem(obj).getNode().style.visibility = "visible"; >> > } >> > >> > This convention just makes codifying assertions a little more[truncated]
Prototype is heavily based on ideas borrowed from Ruby (which has many similarities to JavaScript), and thus relies on the idea of DuckTyping. See http://wiki.rubygarden.org/Ruby/page/show/DuckTyping for some info about this. To address the issue of stuff-that-could-go-wrong-more-easily (as compared to statc typing) Ruby programmers usually heavily rely on unit testing (a test to code ration of 1:1 or higher is normal). You''ll find a clone of Ruby''s unit test framework (http://www.ruby- doc.org/stdlib/libdoc/test/unit/rdoc/) in the script.aculo.us distribution. Of course, neither approach is right or wrong. In my experience the time you safe by using a dynamic language compared to static typing easily allows for implementing lots of unit tests (which can do much more than just checking for types), plus your final code is less cluttered and more readable. Best, -Thomas Am 29.07.2006 um 19:06 schrieb Em Te:> I agree that the dynamic typing nature of Javascript is what makes > it useful and suitable for certain tasks. I also agree that not > everyone will benefit from type verification as most webpages are > simple and Javascript only serves as a complementary technology > (like autocomplete) where type verification may be an unnecessary > restriction. But with large-scale AJAX applications where code > complexity is inevitable and where many parts of the webpage are > generated and driven with Javascript, it is precisely this dynamic > typing nature which makes debugging harder, and where type > verification would be beneficial.
However, when creating consumable components or APIs, it''s always good to embed defensive techniques into your code because you may not know which anonymous application space is trying to do what with your stuff, and yes you can tell those 3rd parties to unit test unit test, but in the end you can''t control that. Design by contract is definitely a safe bet. On 7/31/06, Thomas Fuchs <t.fuchs-moWQItti3gBl57MIdRCFDg@public.gmane.org> wrote:> > Prototype is heavily based on ideas borrowed from Ruby (which has > many similarities to JavaScript), and thus relies on the idea of > DuckTyping. > > See http://wiki.rubygarden.org/Ruby/page/show/DuckTyping for some > info about this. > > To address the issue of stuff-that-could-go-wrong-more-easily (as > compared to statc typing) Ruby programmers usually heavily rely on > unit testing (a test to code ration of 1:1 or higher is normal). > > You''ll find a clone of Ruby''s unit test framework (http://www.ruby- > doc.org/stdlib/libdoc/test/unit/rdoc/) in the script.aculo.us > distribution. > > Of course, neither approach is right or wrong. In my experience the > time you safe by using a dynamic language compared to static typing > easily allows for implementing lots of unit tests (which can do much > more than just checking for types), plus your final code is less > cluttered and more readable. > > Best, > -Thomas > > > Am 29.07.2006 um 19:06 schrieb Em Te: > > > I agree that the dynamic typing nature of Javascript is what makes > > it useful and suitable for certain tasks. I also agree that not > > everyone will benefit from type verification as most webpages are > > simple and Javascript only serves as a complementary technology > > (like autocomplete) where type verification may be an unnecessary > > restriction. But with large-scale AJAX applications where code > > complexity is inevitable and where many parts of the webpage are > > generated and driven with Javascript, it is precisely this dynamic > > typing nature which makes debugging harder, and where type > > verification would be beneficial. > > _______________________________________________ > Rails-spinoffs mailing list > Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs >_______________________________________________ Rails-spinoffs mailing list Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs
That''s why you won''t hear the words "components" and "configurable" too often in the Rails community. It''s often much faster to write your own customized stuff instead of trying to configure a prefab component-- because of the speed you can develop with a dynamic language. :) Embedding defensive techniques is good of course, and your unit tests should include throwing strange stuff at your code. Nevertheless, static typing isn''t a catch-all and could even lead to a false sense of security. The problem is that you easily get to the point that you try to "force" the language into something that it just isn''t-- Microsoft tried something like that with their "Atlas" framework (Java-style OO), and IMHO miserably failed. Anyway, this has already been discussed to death (maybe check the rails mailing list archives), and neither way of doing things is the holy grail, plus it depends heavily on personal experience and preference, how things work in the team, maybe corporate policies, phase of the moon, etc. etc. Best, -Thomas Am 31.07.2006 um 15:42 schrieb Ryan Gahl:> However, when creating consumable components or APIs, it''s always > good to embed defensive techniques into your code because you may > not know which anonymous application space is trying to do what > with your stuff, and yes you can tell those 3rd parties to unit > test unit test, but in the end you can''t control that. > > Design by contract is definitely a safe bet. > > On 7/31/06, Thomas Fuchs <t.fuchs-moWQItti3gBl57MIdRCFDg@public.gmane.org> wrote: > Prototype is heavily based on ideas borrowed from Ruby (which has > many similarities to JavaScript), and thus relies on the idea of > DuckTyping. > > See http://wiki.rubygarden.org/Ruby/page/show/DuckTyping for some > info about this. > > To address the issue of stuff-that-could-go-wrong-more-easily (as > compared to statc typing) Ruby programmers usually heavily rely on > unit testing (a test to code ration of 1:1 or higher is normal). > > You''ll find a clone of Ruby''s unit test framework ( http://www.ruby- > doc.org/stdlib/libdoc/test/unit/rdoc/) in the script.aculo.us > distribution. > > Of course, neither approach is right or wrong. In my experience the > time you safe by using a dynamic language compared to static typing > easily allows for implementing lots of unit tests (which can do much > more than just checking for types), plus your final code is less > cluttered and more readable. > > Best, > -Thomas > > > Am 29.07.2006 um 19:06 schrieb Em Te: > > > I agree that the dynamic typing nature of Javascript is what makes > > it useful and suitable for certain tasks. I also agree that not > > everyone will benefit from type verification as most webpages are > > simple and Javascript only serves as a complementary technology > > (like autocomplete) where type verification may be an unnecessary > > restriction. But with large-scale AJAX applications where code > > complexity is inevitable and where many parts of the webpage are > > generated and driven with Javascript, it is precisely this dynamic > > typing nature which makes debugging harder, and where type > > verification would be beneficial. > > _______________________________________________ > Rails-spinoffs mailing list > Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs > > _______________________________________________ > Rails-spinoffs mailing list > Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs-- Thomas Fuchs wollzelle http://www.wollzelle.com questentier on AIM madrobby on irc.freenode.net http://www.fluxiom.com :: online digital asset management http://script.aculo.us :: Web 2.0 JavaScript http://mir.aculo.us :: Where no web developer has gone before _______________________________________________ Rails-spinoffs mailing list Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs
I''m wondering if there is some confusion here. It didn''t seem to me he was advocating static typing in Javascript, just automatic type-checking when you need it. Maybe I misread it. I''m all for dynamic typing. I''m also all for easy, automatic type-constraining when needed (to turn silent failure into loud, obnoxious failure :-)). I thought that was what he was proposing. Greg ________________________________ From: rails-spinoffs-bounces-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org [mailto:rails-spinoffs-bounces-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org] On Behalf Of Thomas Fuchs Sent: Monday, July 31, 2006 7:58 AM To: rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org Subject: Re: [Rails-spinoffs] Extending Class.create() for casting objects That''s why you won''t hear the words "components" and "configurable" too often in the Rails community. It''s often much faster to write your own customized stuff instead of trying to configure a prefab component-- because of the speed you can develop with a dynamic language. :) Embedding defensive techniques is good of course, and your unit tests should include throwing strange stuff at your code. Nevertheless, static typing isn''t a catch-all and could even lead to a false sense of security. The problem is that you easily get to the point that you try to "force" the language into something that it just isn''t-- Microsoft tried something like that with their "Atlas" framework (Java-style OO), and IMHO miserably failed. Anyway, this has already been discussed to death (maybe check the rails mailing list archives), and neither way of doing things is the holy grail, plus it depends heavily on personal experience and preference, how things work in the team, maybe corporate policies, phase of the moon, etc. etc. Best, -Thomas Am 31.07.2006 um 15:42 schrieb Ryan Gahl: However, when creating consumable components or APIs, it''s always good to embed defensive techniques into your code because you may not know which anonymous application space is trying to do what with your stuff, and yes you can tell those 3rd parties to unit test unit test, but in the end you can''t control that. Design by contract is definitely a safe bet. On 7/31/06, Thomas Fuchs <t.fuchs-moWQItti3gBl57MIdRCFDg@public.gmane.org> wrote: Prototype is heavily based on ideas borrowed from Ruby (which has many similarities to JavaScript), and thus relies on the idea of DuckTyping. See http://wiki.rubygarden.org/Ruby/page/show/DuckTyping for some info about this. To address the issue of stuff-that-could-go-wrong-more-easily (as compared to statc typing) Ruby programmers usually heavily rely on unit testing (a test to code ration of 1:1 or higher is normal). You''ll find a clone of Ruby''s unit test framework ( http://www.ruby- <http://www.ruby-> doc.org/stdlib/libdoc/test/unit/rdoc/) in the script.aculo.us distribution. Of course, neither approach is right or wrong. In my experience the time you safe by using a dynamic language compared to static typing easily allows for implementing lots of unit tests (which can do much more than just checking for types), plus your final code is less cluttered and more readable. Best, -Thomas Am 29.07.2006 um 19:06 schrieb Em Te:> I agree that the dynamic typing nature of Javascript is what makes > it useful and suitable for certain tasks. I also agree that not > everyone will benefit from type verification as most webpages are > simple and Javascript only serves as a complementary technology > (like autocomplete) where type verification may be an unnecessary > restriction. But with large-scale AJAX applications where code > complexity is inevitable and where many parts of the webpage are > generated and driven with Javascript, it is precisely this dynamic > typing nature which makes debugging harder, and where type > verification would be beneficial._______________________________________________ Rails-spinoffs mailing list Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs _______________________________________________ Rails-spinoffs mailing list Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs -- Thomas Fuchs wollzelle http://www.wollzelle.com questentier on AIM madrobby on irc.freenode.net http://www.fluxiom.com :: online digital asset management http://script.aculo.us :: Web 2.0 JavaScript http://mir.aculo.us :: Where no web developer has gone before _______________________________________________ Rails-spinoffs mailing list Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs