Prototype's use of global variables

15 views
Skip to first unread message

Larry

unread,
Aug 1, 2008, 2:24:18 PM8/1/08
to Prototype: Core
Hi, I posted this on the general discussion group. Since this alias
seems to go to the experts, I hope that it is ok that I am also
posting it here:

My company uses Prototype and allows certain third-party javascript
inclusions.

Unfortunately, we found a conflict where both Prototype.js and
javascript from one of our third-parties have a global variable
conflict. Both are using the same global variable: Hash.

We have informed the third-party about the global variable conflict
with Prototype and they have agreed to change their naming. The fix
will be available at a future date.

Now, we need a fix today. We don't want to exclude the third party
code while they are making their fix. So, it looks like the only
option is manually changing the Prototype.js..

It seems to me that the obvious solution is to temporarily change the
following lines of code in Prototype.js:

Change:

var Hash = function(obj) {

To something like:

Prototype.Hash = function(obj) {

And then to change all reference from:

Hash

To:

Prototype.Hash

Is this the recommended approach? Is there another simpler, safer
approach than this? We will be changing prototype.js back to the
original code as soon as the fix gets implemented by the third party.

Also, on this note, it seems to me that Prototype should minimize its
use of global variables. From my view, Prototype is awesome code so I
am a bit surprised that it is not following the recommendations of
people like Douglas Crockford:
http://yuiblog.com./blog/2006/06/01/global-domination/

At any rate, if there is a justification for keeping Hash a global
variable, I would be very glad to better understand the decision.
Hash, for example, is such a common term that there will likely be
future third-parties that make the same mistake.

Are there are plans for moving away from global variables?

Thanks very much,

-Larry

kangax

unread,
Aug 1, 2008, 6:14:21 PM8/1/08
to Prototype: Core
On Aug 1, 2:24 pm, Larry <larry.free...@gmail.com> wrote:

> [snip]
> Is this the recommended approach?  Is there another simpler, safer
> approach than this?  We will be changing prototype.js back to the
> original code as soon as the fix gets implemented by the third party.

I would recommend exactly this "solution" as the easiest/safest way to
"fix" the collision.

>
> Also, on this note, it seems to me that Prototype should minimize its
> use of global variables.  From my view, Prototype is awesome code so I

Agreed.
Certain methods/objects are to be deprecated in the near future (e.g.
Array#reduce). Others have already been deprecated and are only kept
for back-compat (e.g. Position.*).

> am a bit surprised that it is not following the recommendations of
> people like Douglas Crockford:http://yuiblog.com./blog/2006/06/01/global-domination/
>
> At any rate, if there is a justification for keeping Hash a global

I believe most of the methods that have corresponding "short helpers"
could be moved out of the global without much pain - ObjectRange ($R),
Hash ($H), etc. Those shorthands are the ones that are used most and
are less likely to collide with 3rd party code.

> variable, I would be very glad to better understand the decision.
> Hash, for example, is such a common term that there will likely be
> future third-parties that make the same mistake.
>
> Are there are plans for moving away from global variables?

--
kangax

Ken Snyder

unread,
Aug 2, 2008, 11:30:46 PM8/2/08
to prototy...@googlegroups.com
On Fri, Aug 1, 2008 at 12:24 PM, Larry <larry....@gmail.com> wrote:

...Unfortunately, we found a conflict where both Prototype.js and

javascript from one of our third-parties have a global variable
conflict.  Both are using the same global variable: Hash...
Larry, your post gave me an idea that I'm not sure is good or crazy. What if the Prototype Library were modified to have a Prototype.noConflict() function similar to jQuery.noConflict()? See my brainstorm: http://pastie.org/246403
It would allow resolving conflicts like your case. It is not as involved or as useful as John David Dalton's ProtoSafe (http://code.google.com/p/protosafe/), but it is simple and easy to use.
For example, if you include your 3rd-party library before prototype.js, simply call Prototype.noConflict('Hash') to give control back to the first library. If you include your 3rd-party library after prototype.js, no action is needed. In both cases, you can access Prototype's Hash object using "Prototype.Hash".
What do others think of this approach?
- Ken Snyder

T.J. Crowder

unread,
Aug 3, 2008, 8:25:23 AM8/3/08
to Prototype: Core
Hi Ken,

I quite liked my post-processing idea:
http://groups.google.com/group/prototype-core/browse_thread/thread/d38f2123aa64eb0e/ac612b72cc060943

Never did hear back from Tobie about the Caja thing, but I'd rather
not have the dependency...
--
T.J. Crowder
tj / crowder software / com

On Aug 3, 4:30 am, "Ken Snyder" <kendsny...@gmail.com> wrote:

Ben Laurie

unread,
Aug 3, 2008, 11:17:47 AM8/3/08
to prototy...@googlegroups.com
On Sun, Aug 3, 2008 at 1:25 PM, T.J. Crowder <t...@crowdersoftware.com> wrote:
>
> Hi Ken,
>
> I quite liked my post-processing idea:
> http://groups.google.com/group/prototype-core/browse_thread/thread/d38f2123aa64eb0e/ac612b72cc060943
>
> Never did hear back from Tobie about the Caja thing, but I'd rather
> not have the dependency...

On the Caja front: supporting Prototype in Caja turned out to be
harder than we expected - but we have a plan, and this time we really
do expect to have it working in the near future :-)

Cheers,

Ben (Caja tech lead).

Tobie Langel

unread,
Aug 3, 2008, 1:05:08 PM8/3/08
to Prototype: Core
T.J. There's a Caja branch :)

On Aug 3, 5:17 pm, "Ben Laurie" <b...@google.com> wrote:
> On Sun, Aug 3, 2008 at 1:25 PM, T.J. Crowder <t...@crowdersoftware.com> wrote:
>
> > Hi Ken,
>
> > I quite liked my post-processing idea:
> >http://groups.google.com/group/prototype-core/browse_thread/thread/d3...

Larry Freeman

unread,
Aug 3, 2008, 1:50:33 PM8/3/08
to prototy...@googlegroups.com
Ken,

Thanks very much for the reply!  I think that your idea sounds very good.

For our situation, this would work fine. 

It is nice that that something like protosafe is also available if the conflicts are harder to isolate.

-Larry
--
If you like number theory, check out:
http://fermatslasttheorem.blogspot.com

Want something way simpler than blogging, check out HubPages.Com:
http://hubpages.com/_yf9r1u5aiqlc/hub/MoviesThatIveSeen

T.J. Crowder

unread,
Aug 4, 2008, 9:43:37 AM8/4/08
to Prototype: Core
@Ben & Tobie:

Cool!!

That said, though, I'd still like something without the dependency/
complication...

-- T.J. :-)

Dan Dorman

unread,
Aug 4, 2008, 11:03:44 AM8/4/08
to prototy...@googlegroups.com
On Sat, Aug 2, 2008 at 9:30 PM, Ken Snyder <kends...@gmail.com> wrote:
> Larry, your post gave me an idea that I'm not sure is good or crazy. What if
> the Prototype Library were modified to have a Prototype.noConflict()
> function similar to jQuery.noConflict()?
> [snip]

> What do others think of this approach?

That's pretty clever, Ken. The only potential drawback I can think of
is that it depends upon Prototype being loaded last, but that's hardly
a big deal.

:Dan Dorman

Ken Snyder

unread,
Aug 4, 2008, 2:47:00 PM8/4/08
to prototy...@googlegroups.com
No, in my example, Prototype can be loaded at any point. My pastie was
trying to say that all of the internals of Prototype would have to be
changed to reference the Prototype namespace (e.g. "Prototype.$" instead
of "$").

If Prototype is loaded first, (in the example of the Hash situation) you
just have to make sure you're own code references the Hash you want to
use--Prototype.Hash or that 3rd-party Hash.

The effort to convert all of Prototype's internal references (e.g. find
and replace "Selector" with "Prototype.Selector", "Template" with
"Prototype.Template", etc.) would be a matter of minutes, would add
maybe two or three kilobytes, and could be quickly certified through
Prototype's own unit tests.

I think the true request on this subject is that the Prototype Group
make an official release of /some/ version of Prototype that resolves
namespace conflicts. If it is the Caja branch that Tobie mentioned, that
would be awesome. It would also be a powerful bonus if a namespaced
Prototype release was 100% backwards compatible.

- Ken Snyder

John-David Dalton

unread,
Aug 6, 2008, 12:50:39 PM8/6/08
to Prototype: Core
Ken I dig your noConflict() snippet :),

I support moving everything to Prototype namespace.
Prototype can be wrapped in a closure so internally we could use
shortcut variables.

I think we can have back-compat by creating a method which dumps the
methods back on the global scope.

Prototype.expose = (function() {
var all = Prototype.$w('$ $$ $A $H $F $R $w Ajax Class Element
Enumerable '+
'Event Form Insertion ObjectRange Node PeriodicalExecuter Position
Template TimedObserver Try');

function exposeNative(namespace) {
//extra logic for Element, Event, and Node
}

return function(namespaces) {

if (!namespaces) {
namespaces = all;
} else if (Object.isString(namespaces)) {
namespaces = this.$w(namespaces);
}

var groups = namespaces.partition(function(n) {
return /^(Element|Event|Node)$/.test(n)
});

// expose native namespaces
groups.first().each(exposeNative, null);

// expose regular namspaces
groups.last().each(namespace) {
this[namespace] = Prototype[namespace];
}, null);
};
})();

We could even have specific methods for exposing just the utility
methods to the global namespace:
Prototype.expose(); // everything is dumped to the global namespace
Prototype.expose('$ $$ $A $H $F $R $w'); // only the utility methods
are exposed

http://pastie.org/248569

- JDD

Ken Snyder

unread,
Aug 6, 2008, 3:03:18 PM8/6/08
to prototy...@googlegroups.com
John-David Dalton wrote:
> Ken I dig your noConflict() snippet :),
>
> I support moving everything to Prototype namespace.
> Prototype can be wrapped in a closure so internally we could use
> shortcut variables.
>
> I think we can have back-compat by creating a method which dumps the
> methods back on the global scope.
>
> ...

> We could even have specific methods for exposing just the utility
> methods to the global namespace:
> Prototype.expose(); // everything is dumped to the global namespace
> Prototype.expose('$ $$ $A $H $F $R $w'); // only the utility methods
> are exposed
>
> http://pastie.org/248569
>
> - JDD
I like that. Tell us more about your thinking regarding native objects.
Are you meaning that static properties and methods are changed (e.g.
internally using Prototype.Element.Methods instead of Element.Methods)?
Or do you mean that prototyped methods would both be namespaced (e.g.
internally using String.prototype.$camelize instead of
String.prototype.camelize)? Or dropping the internal use of native
object and native object prototypes entirely?

In theory, Prototype could namespace all objects /and prototypes/ and
use a noConflict() or expose() concept to provide backwards
compatibility. My only concern is the increased file size and parse time
of prototype.js.

Along those lines, what is the advantage to using expose() instead of
noConflict()? Maybe expose() is faster in the backwards-compatible case?

- Ken

John-David Dalton

unread,
Aug 6, 2008, 5:08:02 PM8/6/08
to Prototype: Core
@Ken
noConflict() is the opposite of expose().

Your solution starts with a namespaced Prototype, backups the global
scoped conflicting objects,
and paves the existing global objects with Prototype objects.
noConflict can be used to restore the original, while the Prototype
internals use the namespaced objects.

Mine starts with a namespaced Prototype and allows the dev to
selectively expose objects to the global scope.
So it starts off not conflicting and then the dev chooses to expose
methods to potential conflicts.
(whereas yours starts off populating the global namespace)

I like your idea of backing-up potential conflicts though,
I think expose could do that as well so things could be undone.

noConflict() = undo
expose() = override

File size isnt a concern if we wrap Prototype's gutts in a closure and
create shorter
alias variables for the longer namespaces.

BTW looks like i missed Field, $break, $continue (wasn't that
removed?), good catch :)
I don't know if there is an advantage of one approach over the other.
A form of both methods could be used in Prototype :)

- JDD

- JDD

John-David Dalton

unread,
Aug 6, 2008, 5:13:59 PM8/6/08
to Prototype: Core
Meh hehe I missed Selector and Hash objects too,
total pseudo code warning should have been attached to my pastie :D

kangax

unread,
Aug 6, 2008, 11:08:44 PM8/6/08
to Prototype: Core
On Aug 6, 5:08 pm, John-David Dalton <John.David.Dal...@gmail.com>
wrote:
On Aug 6, 5:13 pm, John-David Dalton <John.David.Dal...@gmail.com>
wrote:
> Meh hehe I missed Selector and Hash objects too,
> total pseudo code warning should have been attached to my pastie :D

I actually find base2.js approach quite elegant. Each "module" defines
a string property which when eval'ed "imports" (i.e. aliases) module's
methods into the current scope. Prototype can use the same approach
(for namespacing/sandboxing previously-global, non-native objects):

/*
The following "injection" is just an example.
Global variables should not be declared in the first place.
It's also impossible to remove them with `delete` (if "var" keyword
was used)
*/

$w('$A $R $$').each(function(fn) {
Prototype[fn] = this[fn];
this[fn] = undefined;
}, null);

Prototype.imports = 'var $A = Prototype.$A, $R = Prototype.$R, $$ =
Prototype.$$;';

/* Sandbox which allows to use any of the functions/objects just like
before */

(function(){
eval(Prototype.imports);
$R(1,10).toArray(); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
})();

/* Outer scope is "clean" */

$A // undefined
$R // undefined

Element.prototype and Event.prototype, on the other hand, could be
replaced with wrappers.

--
kangax

T.J. Crowder

unread,
Aug 9, 2008, 4:15:07 AM8/9/08
to Prototype: Core
> I actually find base2.js approach quite elegant. Each "module" defines
> a string property which when eval'ed "imports" (i.e. aliases) module's
> methods into the current scope.

Now that's much more elegant than my "nsprototype.js" suggestion.
Perhaps take it a step further: A function that accepts a namespace
and returns the string to eval:

eval(Prototype.makeImports('P'));
P.$R(1,10).toArray(); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

No args would mean no namespace:

eval(Prototype.makeImports());
$R(1,10).toArray(); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

We'd have to figure out what to do with the extensions to native
objects, though. One approach would be to simply make the extension
explicit:

// I want to use Prototype's extensions to Element
// objects and Arrays
Prototype.extendElements();
Prototype.extendArrays();

function blarg()
{
var d;
var title;

// This works, I've told Prototype to extend Elements
d = Prototype.$('mydiv');
d.observe('click', function() { /* ... */ });

// This fails, I haven't told Prototype to extend Strings
title = $F('theTitleField');
title = title.camelize();
}
</script>

And again the "extendXYZ" functions could accept an optional prefix,
a'la my nsprototype suggestion from a while back, so:

// No prefix:
Prototype.extendElements();
$('mydiv').observe(...);

// Prefix:
Prototype.extendElements('p$');
$('mydiv').p$observe(...);

Unfortunately I don't see a way to scope extensions to a block
(indeed, what would that even mean?) like the imports stuff.

And of course finally, if you want the old behavior, two lines of
code:

eval(Prototype.makeImports());
Prototype.extendAll();

--
T.J. Crowder
tj / crowder software / com

kangax

unread,
Aug 10, 2008, 10:10:27 AM8/10/08
to Prototype: Core
On Aug 9, 4:15 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> > I actually find base2.js approach quite elegant. Each "module" defines
> > a string property which when eval'ed "imports" (i.e. aliases) module's
> > methods into the current scope.
>
> Now that's much more elegant than my "nsprototype.js" suggestion.
> Perhaps take it a step further: A function that accepts a namespace
> and returns the string to eval:
>
> eval(Prototype.makeImports('P'));
> P.$R(1,10).toArray(); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>
> No args would mean no namespace:
>
> eval(Prototype.makeImports());
> $R(1,10).toArray(); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

I see what you mean, but `eval`ing into an object seems like an
overkill : )
"eval(makeImports('P'))" would be identical to:

function makeImports(o) {
o = window[o] || (window[o] = { });
$w('$A $R $ $$ ... ').each(function(name){
o[name] = Prototype[name];
})
}

makeImports('P'); // <= no need to `eval`
P.$$ == Prototype.$$ // true

`eval` is useful when declaring variables (rather than assigning
properties). `eval` would not be needed if JavaScript allowed us to
somehow declare variables via bracket notation (and identifier string)
or let us assign properties of a Variable Object directly.

>
> We'd have to figure out what to do with the extensions to native
> objects, though. One approach would be to simply make the extension
> explicit:
>
> // I want to use Prototype's extensions to Element
> // objects and Arrays
> Prototype.extendElements();
> Prototype.extendArrays();

This looks good.
I'm still in favor of wrappers for elements and events, though.

>
> function blarg()
> {
> var d;
> var title;
>
> // This works, I've told Prototype to extend Elements
> d = Prototype.$('mydiv');
> d.observe('click', function() { /* ... */ });
>
> // This fails, I haven't told Prototype to extend Strings
> title = $F('theTitleField');
> title = title.camelize();
> }
> </script>
>
> And again the "extendXYZ" functions could accept an optional prefix,
> a'la my nsprototype suggestion from a while back, so:
>
> // No prefix:
> Prototype.extendElements();
> $('mydiv').observe(...);
>
> // Prefix:
> Prototype.extendElements('p$');
> $('mydiv').p$observe(...);
>
> Unfortunately I don't see a way to scope extensions to a block
> (indeed, what would that even mean?) like the imports stuff.

Maybe that's because DOM doesn't have scopes or "blocks" : )
Extending Element.prototype/Event.prototype (which only leads to
problems) affects all instances of elements/events throughout the
document.

>
> And of course finally, if you want the old behavior, two lines of
> code:
>
> eval(Prototype.makeImports());
> Prototype.extendAll();

--
kangax

T.J. Crowder

unread,
Aug 10, 2008, 1:11:03 PM8/10/08
to Prototype: Core
> I see what you mean, but `eval`ing into an object seems like an
> overkill : )
> "eval(makeImports('P'))" would be identical to:
>
> function makeImports(o) {
> o = window[o] || (window[o] = { });
> $w('$A $R $ $$ ... ').each(function(name){
> o[name] = Prototype[name];
> })
>
> }

Good refinement in principle, but it's not quite the same thing --
that wouldn't limit the "import" to the containing scope, it would
make it global. (Easily fixed, though, by having the caller pass in
the namespace object and defaulting to window -- as in your code above
-- if they don't.) The cool thing about the base2.js approach, in my
view, is that you can limit it to a given scope.

But but but: Never mind about the namespace parameter for imports. I
don't know what I was thinking. We're starting _out_ with namespaced
version, right? Prototype.$, etc. So if I want an alias for it
(either globally or within a given scope), wow, that's hard:

var P = Prototype;

Doh! And I don't see any need to support simple prefixes (as opposed
to namespace objects, e.g. "p$()" [no dot] as an alias for "Prototype.$
()").

> I'm still in favor of wrappers for elements and events, though.

Sorry, missed that bit of your earlier post. When you say wrappers,
do you mean stop extending the native objects entirely? Only in a
compatibility mode, or generally? On the one hand it's *so*
convenient to have them, on the other hand I can see avoiding the
issues around it. The thing is, though, there are going to be some
(even many!) who will not want to give them up, so are we going to
support both via some kind of config call?

I ask because in all of this I worry about bloat. One of the things I
like about Prototype is that it's so small. That's why I liked the
base2.js approach so much when you mentioned it: I *think* it allows
us to easily keep Prototype itself quite small (e.g., not endlessly
repeating the namespace in the code) whilst still namespacing it
(unless the user does the import), e.g.:
* * * *
// Check if we're already defined
if (!window.Prototype)
{
// No, define us
Prototype = {};
(function(){
// Define each "global" in two parts:
// 1. The namespaced function (complete with pdoc)
// 2. Our local alias for brevity
// If this syntax doesn't work for pdoc, put the local
// alias after the main def...or enhance pdoc ;-)
var $ = Prototype.$ = function() {
/* ... */
};
var $A = Prototype.$A = function() {
/* ... */
};
var $R = Prototype.$R = function() {
/* ... */
};
var Class = Prototype.Class = {
/* ... */
};
var Hash = Prototype.Hash = Class.create(/* ... */);
/* ...repeat for all global symbols...*/

// Build imports string for base2.js-like imports
Prototype.imports = (function(){
var rv;

rv = '';
Object.keys(Prototype).each(function(name){
rv += ', ' + name + ' = Prototype.' + name;
});
rv = 'var' + rv.substring(1) + ';';
return rv;
})();
})();
}
* * * *
At least that's the simple approach that comes to mind. Doing the
namespace has had minimal impact on the download size because very
little of the Prototype code itself has to use the namespace. (And we
could make those few places even smaller if we wanted to, by passing
the Prototype namespace object as a parameter to the big closure and
using a shorter name -- like "NS" -- for the param, but I doubt it
would save that much and it could be confusing.) Although we could
also achieve that with a "with" block, I'm very reluctant to introduce
one for the usual reasons.

So is there a similarly low-impact way that we could have the code
support wrappers vs. extensions depending on the user? The big
stumbling block I see is allowing the Prototype code itself to
continue to assume extended objects rather than having to do wrapping
each time...
--
T.J. Crowder
tj / crowder software / com

John-David Dalton

unread,
Aug 10, 2008, 1:13:45 PM8/10/08
to Prototype: Core
@Kangax
> Maybe that's because DOM doesn't have scopes or "blocks" : )
> Extending Element.prototype/Event.prototype (which only leads to
> problems) affects all instances of elements/events throughout the
> document.

That is only the case in browsers that support the Element and Event
host object.
IE would not get those on the unextedned elements or the previously
extended ones when you add methods to the Element.Methods.

I think most are suggesting wrappers for Element, Event, Node and what
not, but when you want back compat we might dump it back to the
Element, Event, Node host objects.
Though I think a wrapper for Element would have minimal impact as most
usage is
var foo = $('elementID'); // returns a wrapper instead of dom node;
foo.nodeType// nodeType prop of wrapper can be set to 1;
foo.select(...) //seems the same as the non wrapped

things like foo.value and foo.style would fail to work.
But if devs used foo.getValue() or foo.getStyle then they would be
fine.

OK I am for some patching being made.
Lets make one to namespace Prototype and then sep ones for the
backcompat, Event, Element, Node wrappers.

:D

- JDD

kangax

unread,
Aug 10, 2008, 11:58:19 PM8/10/08
to Prototype: Core
On Aug 10, 1:11 pm, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> > I see what you mean, but `eval`ing into an object seems like an
> > overkill : )
> > "eval(makeImports('P'))" would be identical to:
>
> > function makeImports(o) {
> >   o = window[o] || (window[o] = { });
> >   $w('$A $R $ $$ ... ').each(function(name){
> >     o[name] = Prototype[name];
> >   })
>
> > }
>
> Good refinement in principle, but it's not quite the same thing --
> that wouldn't limit the "import" to the containing scope, it would
> make it global.  (Easily fixed, though, by having the caller pass in
> the namespace object and defaulting to window -- as in your code above
> -- if they don't.)  The cool thing about the base2.js approach, in my
> view, is that you can limit it to a given scope.

Yep. It's very convenient to create a separate scope to operate in and
just "dump" all the "familiar" methods/objects into it (without
pollution of global object).

>
> But but but:  Never mind about the namespace parameter for imports.  I
> don't know what I was thinking.  We're starting _out_ with namespaced
> version, right?  Prototype.$, etc.  So if I want an alias for it
> (either globally or within a given scope), wow, that's hard:
>
>    var P = Prototype;
>
> Doh!  And I don't see any need to support simple prefixes (as opposed
> to namespace objects, e.g. "p$()" [no dot] as an alias for "Prototype.$
> ()").
>
> > I'm still in favor of wrappers for elements and events, though.
>
> Sorry, missed that bit of your earlier post.  When you say wrappers,
> do you mean stop extending the native objects entirely?  Only in a
> compatibility mode, or generally?  On the one hand it's *so*
> convenient to have them, on the other hand I can see avoiding the
> issues around it.  The thing is, though, there are going to be some
> (even many!) who will not want to give them up, so are we going to
> support both via some kind of config call?

When I say "wrappers" I mean stop extending the host objects entirely
(at least not implicitly). I remember at least few issues related to
host objects extensions. I don't think convenience is worth the
trouble. Moreover, IE, with its lack of Element.prototype inheritance,
already forces us to "extend" elements manually.

The idea is actually simple:

function ElementWrapper(element) {
this.element = element;
};

ElementWrapper.prototype = {
show: function(){ this.element.style.display = ''; },
hide: function(){ this.element.style.display = 'none'; },
...
};

function $(el) {
if (typeof el == 'string') {
el = document.getElementById(el);
}
return new ElementWrapper(el);
};

$(someElement); // returns wrapper
$(someElement).element; // reference to an actual element
$(someElement).show(); // method that modifies an actual element

etc.

Same approach could be used for Event extension. I believe that
convenience level when using wrappers is almost the same.

The tricky part is "taking care of" native objects : )
Array.prototype, String.prototype, etc.

Two obvious ways to solve this issue seem to be:

// 1) wrappers
(function(){
eval(Prototype.imports);
$A([1,2,3,4,5]).invoke('toString');
$S('someString').strip();
})();

// 2) namespaced methods
Prototype.lang.invoke([1,2,3], 'toString');
Prototype.lang.strip('someString');

I think the latter one is uglier. The former one is just not as
convenient as it is now.

Thoughts?

--
kangax

Ken Snyder

unread,
Aug 11, 2008, 12:23:04 AM8/11/08
to prototy...@googlegroups.com
On Sun, Aug 10, 2008 at 9:58 PM, kangax <kan...@gmail.com> wrote:

...
The tricky part is "taking care of" native objects : )
Array.prototype, String.prototype, etc.

Two obvious ways to solve this issue seem to be:

// 1) wrappers
(function(){
 eval(Prototype.imports);
 $A([1,2,3,4,5]).invoke('toString');
 $S('someString').strip();
})();

// 2) namespaced methods
Prototype.lang.invoke([1,2,3], 'toString');
Prototype.lang.strip('someString');

I think the latter one is uglier. The former one is just not as
convenient as it is now.

Thoughts?

--
kangax
It sounds like we are merely brainstorming, but I just want to clarify. Are we talking about changing prototype.js or creating a namespaced version of prototype.js?
Just stating the obvious: Prototype's philosopy is reflected in it's name--taking advantage of the power of prototyping. It would be a shame to move away from prototyping just to gain "wider" appeal.
I'm all for making the internals less prone to breaking from naming collisions, but I would be heartbroken to see prototype.js kill Element.prototype die or deprecate much of the API. I'm thinking you all agree, but I just want to clarify.
- Ken Snyder

kangax

unread,
Aug 11, 2008, 12:42:10 AM8/11/08
to Prototype: Core
Ken,
I'm not sure what you mean by a "wider appeal".
I think we need to deprecate element extension for the reason of
creating a more robust code base.

As a user, I don't want to remember that Element#wrap can not be used
on textarea elements in IE (http://dev.rubyonrails.org/ticket/10189),
that Opera throws error when invoking Element#replace on certain form
controls (http://dev.rubyonrails.org/ticket/9261), that Element#select
cannot be safely used on form elements (http://dev.rubyonrails.org/
ticket/11251) and that I might eventually run into problems with
extending iframes (http://dev.rubyonrails.org/ticket/10991)

As a core developer, I don't want to deal with another compatibility
issue with some non-standard DOM elements (e.g. applets/objects or
those implemented via IE's COM interface) - elements that might throw
errors by just trying to look-up their properties (not even talking
about declaring custom methods on them). Elements that might be
introduced in later versions of popular (or not) browsers whose
behavior could vary greatly.

Extending host objects is surely convenient.
The idea was tempting at first, but seems to be "shaky" as we move
forward.
I just doubt that such strategy will lead to a "robust" future : )

--
kangax

Jeff Watkins

unread,
Aug 11, 2008, 11:24:51 AM8/11/08
to prototy...@googlegroups.com
Ken, I think this is the most important issue Prototype should
address. One additional question that needs to be answered is: just
how prevalent is the multi-library use case in the real world?

From an academic standpoint, solving hard problems is fun. But from a
practical, makes-a-real-difference standpoint, I simply fail to see
collision avoidance as a major issue.

Jeff Watkins

unread,
Aug 11, 2008, 11:35:23 AM8/11/08
to prototy...@googlegroups.com
On 10 Aug, 2008, at 9:42 PM, kangax wrote:
I'm not sure what you mean by a "wider appeal".
I think we need to deprecate element extension for the reason of
creating a more robust code base.

I suspect that by "wider appeal" the goal is to enable the inclusion of multiple libraries on a single page. This is the concept that I find suspect. I just don't believe this is really an important issue.

On the other hand, I'd completely agree that modifying the native DOM prototypes is a fragile system. Cool? No doubt! And a very clever idea, to be sure. But given that browsers are once again innovating in the DOM space, we may be looking at increasing instability as new node types arise (I'm specifically thinking of some of the new HTML5 elements).

I'm kind of on the fence as regards modifying some of the global objects like Array, String, and RegExp. The fact is, the APIs of these classes has been relatively stagnant for the last 5 years. It's especially nice to add forward compatibility with advanced JavaScript, like adding Array#forEach for browsers that don't support it.

Ken Snyder

unread,
Aug 11, 2008, 11:42:31 AM8/11/08
to prototy...@googlegroups.com
Jeff Watkins wrote:
> Ken, I think this is the most important issue Prototype should
> address. One additional question that needs to be answered is: just
> how prevalent is the multi-library use case in the real world?
>
> From an academic standpoint, solving hard problems is fun. But from a
> practical, makes-a-real-difference standpoint, I simply fail to see
> collision avoidance as a major issue.
>
@Jeff, when you say "this" which issue do you mean?

I completely agree with your comment. When I said "wider" appeal, I
meant appeal to multi-library use and collision avoidance.

@kangax, the issues with Element#wrap, Element#replace, and
Element#select seem to be based on a short-sighted naming decision. For
example, if we used names like Element#wrapNode, Element#replaceNode,
Element#selectNodes there would be no problem.

Your point about iframes, non-standard DOM elements, and new elements is
well taken. Needing to manipulate those types of elements is arguably an
edge-case, but I agree that it demonstrates how fragile it is to extend
Element.prototype.

But what are the arguments for avoiding modifying prototypes on
Function, Array, and String? Is there more to it than trying to avoid
naming collisions?

- Ken

Andrew Kaspick

unread,
Aug 11, 2008, 12:12:35 PM8/11/08
to prototy...@googlegroups.com
In a Rails app of mine, I needed to use a an effect that was supplied
by jquery. There are conflicts between jquery and prototype, but
jquery provides a noconflict method which allowed me to keep all of my
prototype code as is while allowing me to make use of the specific
jquery effect that I was wanting to use.

It's not much of an issue if all the other libraries provide
"noconflict" options, but prototype should really play nice too and
provide it's own version otherwise it makes prototype look like the
bully in the playground. :)

Jeff Watkins

unread,
Aug 11, 2008, 12:44:52 PM8/11/08
to prototy...@googlegroups.com
On 11 Aug, 2008, at 8:42 AM, Ken Snyder wrote:
@Jeff, when you say "this" which issue do you mean?

I meant the multi-library issue: I think a lot of what's been driving the discussion of name spaces and whatnot is a desire to satisfy a very vocal minority of developers who would like to include multiple JS core libraries. I don't think this represents a major pain point for Prototype's primary developer community. So by addressing "this" issue, I meant the Prototype development team needs to determine whether multi-library support is important before they waste a tremendous amount of time solving a problem that may not need to be solved.

But what are the arguments for avoiding modifying prototypes on 
Function, Array, and String? Is there more to it than trying to avoid 
naming collisions?

My personal pet peeve is the collision between Prototype's Array extensions and the JS 1.7 & 1.8 Array methods. I explicitly have code to delete Prototype's implementation of indexOf, lastIndexOf, forEach, etc. I'd much rather use the native language features than library features. I think adding methods to the prototypes of native objects opens Prototype up to two issues:

1) Possible collisions with future language features – these problems need to be fixed as soon as possible whether the new language features are better than what Prototype offers or not. Prototype shouldn't be causing confusion on core language functionality.

2) Collisions with other libraries – these problems should be prioritised by the core development team and addressed according to some simple rules. I'd propose that collisions with other core libraries (jQuery, MooTools, and similar) are a non-issue and should be ignored.


On 11 Aug, 2008, at 9:12 AM, Andrew Kaspick wrote:
In a Rails app of mine, I needed to use a an effect that was supplied
by jquery.

No, you wanted to use an animation just like one in jQuery. So instead of rolling up your sleeves and porting the animation to Prototype and improving the overall ecosystem, you increased the download and page rendering time of your visitors by adding jQuery (and possibly some plug ins) to the page.

We're all on tight deadlines, including the Prototype development team. It seems very selfish that we should expect them to jump through hoops because we're not willing to write a bit more code.

It's not much of an issue if all the other libraries provide
"noconflict" options, but prototype should really play nice too and
provide it's own version otherwise it makes prototype look like the
bully in the playground. :)

I don't think it makes Prototype seem like a bully. I think with a little messaging, it makes Prototype seem very focussed and in tune with their core constituency: those developers who use Prototype. Personally, I feel that providing a noconflict mode smacks of desperation. To keep with the playground metaphor, it's like the kid who do anything just so he'll get picked to play baseball at recess.

Yes, I know jQuery didn't implement noconflict because they were desperate for users. But it might be a bit along the lines of "Hey, try it. You'll like it. It even plays nice with your library."


--
Jeff Watkins
UI Engineering Manager
Online Apple Store

We're looking for a top-notch JS developer. Interested?

kangax

unread,
Aug 11, 2008, 1:02:59 PM8/11/08
to Prototype: Core
On Aug 11, 11:42 am, Ken Snyder <kendsny...@gmail.com> wrote:
The only argument is "playing well with others" and future
compatibility. I don't find this issue as important as the "element
extension" one. It does make us more polite, makes integration with
other libs smoother and leads to a wider appeal. It doesn't stop the
library progress as the issue with element extensions does.

When I say future compatibility, I mean things like native
Array#reduce (introduced in JS1.8) conflicting with methods defined by
prototype. Introducing different naming would only lead to more
verbosity, and compatibility problems.

--
kangax

Nick Stakenburg

unread,
Aug 11, 2008, 1:03:41 PM8/11/08
to Prototype: Core
> I'd propose that collisions with other core
libraries (jQuery, MooTools, and similar) are a non-issue and should
be ignored.

I agree. I'm not feeling noConflict alternatives for Prototype. Using
multiple frameworks is bad practice to begin with, a waste of time, so
let's not go there.

Andrew Kaspick

unread,
Aug 11, 2008, 1:11:34 PM8/11/08
to prototy...@googlegroups.com
> On 11 Aug, 2008, at 9:12 AM, Andrew Kaspick wrote:
> In a Rails app of mine, I needed to use a an effect that was supplied
> by jquery.
>
> No, you wanted to use an animation just like one in jQuery. So instead of
> rolling up your sleeves and porting the animation to Prototype and improving
> the overall ecosystem, you increased the download and page rendering time of
> your visitors by adding jQuery (and possibly some plug ins) to the page.

I hardly increased my download and page rendering time. I was able to
swap out scripty and use a subset of jquery in it's place. Pretty
much the same amount (if not less) to download. For not seeing my
final solution, you seem to know a lot about it. :)

I always try to contribute back to the "ecosystem" when possible, but
jquery's noconflict option made things easier for me in the end. I've
only integrated two JS libs the once, so it's not a common case, but
it's a case for me regardless.

Ken Snyder

unread,
Aug 11, 2008, 4:25:37 PM8/11/08
to prototy...@googlegroups.com
kangax wrote:
> The only argument is "playing well with others" and future
> compatibility. I don't find this issue as important as the "element
> extension" one. It does make us more polite, makes integration with
> other libs smoother and leads to a wider appeal. It doesn't stop the
> library progress as the issue with element extensions does.
>
> When I say future compatibility, I mean things like native
> Array#reduce (introduced in JS1.8) conflicting with methods defined by
> prototype. Introducing different naming would only lead to more
> verbosity, and compatibility problems.
>
Hmm. If we are going to fundamentally change the library (e.g. require
$S() or $A() to access Prototype string methods) why not just use a "$"
prefix instead? For example, Array#$each, String#$strip,
Function#$bind. I think such an approach was suggested many months ago,
but I don't recall. Some advantages of a dollar-prefix approach:

1. prototype.js can be made backwards compatible with a few loops
2. Current code can be ported to the "2.0" version with a
find-and-replace based approach

I'm sure there are other approaches besides the Decorator-pattern and
the dollar-prefix approach. And I agree with Jeff: I think the very
first and most important steps are for the Prototype team to define the
goals for moving forward and choose an approach to meet those goals.
Whether defining the goals and approaches is collaborative isn't as
important to me--but here are my top goals:

- Robust and future proof. Avoid some of the "fragile" practices we
discussed on this thread. Create hooks in all parts of the API to allow
additional extensions (e.g. Element.addMethods, Class.addMethods and I
am dying to see Event.addMethods).
- File size / Modularity. Simplifying and giving options for modularity
by reducing the interdependence between modules. Many times I've wished
I could use the Element and Selector code without including the rest of
the library.
- Backwards compatibility / easy porting. If Prototype fundamentally
changes, it will basically be a fork because so many apps rely on
Prototype 1.6 that it will probably never go away. It is certainly nice
to be able to use new features right away but have time to update code
that uses deprecated features.


Once we define the goals and approach, building a roadmap with specific
features is intuitive.

- Ken

Mislav Marohnić

unread,
Aug 11, 2008, 4:35:38 PM8/11/08
to prototy...@googlegroups.com
Hello, I'd like to turn attention to an old post of mine (Dec 2006) titled "Namespace Prototype in 3 easy steps":

I re-visited that approach by creating a patch for master:

It's very slim and again shows how it's relatively easy to create a custom build of Prototype that's namespaced. Of course, this solution doesn't change augmentation of native prototypes and extensions of Object, Element and other classes, but is certainly a leap towards safety when using it in a combination with a different library.

kangax

unread,
Aug 11, 2008, 7:49:09 PM8/11/08
to Prototype: Core
On Aug 11, 4:25 pm, Ken Snyder <kendsny...@gmail.com> wrote:
> kangax wrote:
> > The only argument is "playing well with others" and future
> > compatibility. I don't find this issue as important as the "element
> > extension" one. It does make us more polite, makes integration with
> > other libs smoother and leads to a wider appeal. It doesn't stop the
> > library progress as the issue with element extensions does.
>
> > When I say future compatibility, I mean things like native
> > Array#reduce (introduced in JS1.8) conflicting with methods defined by
> > prototype. Introducing different naming would only lead to more
> > verbosity, and compatibility problems.
>
> Hmm. If we are going to fundamentally change the library (e.g. require
> $S() or $A() to access Prototype string methods) why not just use a "$"
> prefix instead? For example, Array#$each, String#$strip,
> Function#$bind.  I think such an approach was suggested many months ago,
> but I don't recall.  Some advantages of a dollar-prefix approach:
>
> 1. prototype.js can be made backwards compatible with a few loops
> 2. Current code can be ported to the "2.0" version with a
> find-and-replace based approach

Ken,
I'm not sure I understand how we get the above mentioned benefits by
changing String#foo to String#$foo.
Could you explain?

>
> I'm sure there are other approaches besides the Decorator-pattern and
> the dollar-prefix approach.  And I agree with Jeff: I think the very
> first and most important steps are for the Prototype team to define the
> goals for moving forward and choose an approach to meet those goals.  

Absolutely.

> Whether defining the goals and approaches is collaborative isn't as
> important to me--but here are my top goals:
>
> - Robust and future proof. Avoid some of the "fragile" practices we
> discussed on this thread. Create hooks in all parts of the API to allow
> additional extensions (e.g. Element.addMethods, Class.addMethods and I
> am dying to see Event.addMethods).

I am planning on starting a fork to replace element extensions with
wrappers.
This change itself should be quite painless. Most of the syntax would
stay the same. The only difference would be in the way you access
native host object methods (e.g. nodeType, tabIndex, checked, etc.).
These would need to be accessed either via specific wrapper-methods or
directly via an element reference (<wrapper>.element.<property>)

> - File size / Modularity. Simplifying and giving options for modularity
> by reducing the interdependence between modules. Many times I've wished
> I could use the Element and Selector code without including the rest of
> the library.

I'm not sure "core" has such plans.
On the other hand, modularization gives much more "space" for
extension (no need to decide whether certain functionality "fits the
core" - if it's useful, it goes in the separate module)
This would also (most likely) ensure higher quality of code (comparing
to 3rd party scripts).

> - Backwards compatibility / easy porting. If Prototype fundamentally
> changes, it will basically be a fork because so many apps rely on
> Prototype 1.6 that it will probably never go away.  It is certainly nice
> to be able to use new features right away but have time to update code
> that uses deprecated features.

Yep.
Compatibility definitely needs to be preserved.

>
> Once we define the goals and approach, building a roadmap with specific
> features is intuitive.

Cheers,

--
kangax

kangax

unread,
Aug 11, 2008, 8:30:48 PM8/11/08
to Prototype: Core
On Aug 11, 4:35 pm, "Mislav Marohnić" <mislav.maroh...@gmail.com>
wrote:
Mislav,
what's the reason for `eval`?

--
kangax

Mislav Marohnić

unread,
Aug 12, 2008, 4:06:14 AM8/12/08
to prototy...@googlegroups.com
2008/8/12 kangax <kan...@gmail.com>


Mislav,
what's the reason for `eval`?

It's a hack to get a reference to a local variable from a string. Is there a better way to do this loop?

kangax

unread,
Aug 12, 2008, 7:22:51 AM8/12/08
to Prototype: Core
On Aug 12, 4:06 am, "Mislav Marohnić" <mislav.maroh...@gmail.com>
wrote:
I think this should do it:

$w("Class Abstract Try PeriodicalExecuter Template $break Enumerable
$A $w "+
"$H Hash ObjectRange $R Ajax $ Selector $$ Form Field $F
Event").each(function(name) {
Prototype[name] = this[name];
}, null);

--
kangax

T.J. Crowder

unread,
Aug 12, 2008, 9:00:22 AM8/12/08
to Prototype: Core
> ...why not just use a "$"
> prefix instead? For example, Array#$each, String#$strip,
> Function#$bind. I think such an approach was suggested many months ago,
> but I don't recall.

You might be thinking of my "ending the conflict" thread:
http://groups.google.com/group/prototype-core/browse_thread/thread/d38f2123aa64eb0e/ac612b72cc060943

Essentially I suggested a post-build process taking prototype.js and
creating nsprototype.js which prefixed all of the methods added to
host objects (and created a namespace object for the other stuff). We
can do it that way or write the individual modules in a namespaced way
and have a post-process that globalizes them, whichever is easier.

If we can do a namespaced version that can be imported, without bloat,
I'd be in favor of that instead. If not, though, I can't imagine a
perl-head having that much trouble with the post-process, um, process.

My suggestion (which I very much doubt was original) was meant to be
minimal-impact and so it wouldn't address some of the issues kangax
has raised with the non-namespaced/non-prefixed version. And speaking
as (still) a relative outsider, I think he has some very good points.

On the other theme, I'm not sure the collision thing is as much of a
non-issue as some in this thread seem to believe. I think a lot of
web sites / apps, more and more, are becoming something people
assemble from bits and glue together just enough to make them
functional, and so I suspect we'll see more of this rather than less.
I'm not saying it's a good thing; in fact, I think in many cases you
see people putting things together with little or no regard for proper
engineering, little regard for human factors considerations, and a
shocking degree of ignorance of what they're really doing and how it
really works. My automatic reaction to that sort of thing is usually
quite elitist; I find myself wanting to say "Look, I'm sorry, but you
don't know what you're doing, go away and come back when you've taken
the time and put in the effort to learn enough to have a clue." But
that elitism doesn't usually serve me well and I try to moderate it,
not least becomes sometimes *I'm* the one speaking through his
hat. :-) In any case, I doubt it will change the reality I see
emerging, which is a democratization of this stuff. And at the end of
the day (I'll say it) that probably IS a Good Thing in the wider
context -- I just wish people would bother to educate themselves
before spewing things out.
--
T.J. Crowder
tj / crowder software / com

John-David Dalton

unread,
Aug 12, 2008, 10:37:55 AM8/12/08
to Prototype: Core
@Mislav:
Your post was the inspiration for ProtoSafe which rewrites Prototype
wrapped in an anon-closure and makes local variables out of things :)
Your patch may be one way to namespace much of Prototype's helpers
without changing much of the core.

@People who don't get why playing well with others is important:
Sure playing well with other libraries is a bonus (but don't get
fixated on that as the only reason).
Playing well with any third party code is important. The for-in loop
issue is a major cause of pain
in this department. You can't add Prototype to certain bbs systems or
other web products because
some third party code uses for-in on Arrays. Devs are saying "Why risk
it?" and opting to use other frameworks
that don't have issues with DOM element methods or for-in loops. I
mean Prototype throws so many
road blocks in the way of development it actually repels users. I and
others who use
Prototype a lot can be forgiving of its roadblocks because we are
aware of them and can work around them.
But why should a developer have to worry about it. With other
smaller, more feature packed frameworks, you
can simply drop them in and start coding without the fear of borking
your entire site.

Other smaller frameworks have better dimensions coding, quirksmode
support, no naming conflicts (or a noConflict() solution), no for-in
loop issues.
When a dev is faced with that decision it’s pretty clear they will
choose the other framework, or convert to it later.
At this point the gimmick of extending native prototypes has no real
benefit when compared to other frameworks.

- JDD

Ken Snyder

unread,
Aug 12, 2008, 12:59:54 PM8/12/08
to prototy...@googlegroups.com
I agree with John-David that compatibility with for-in loops is
desirable--arguably an edge case, but certainly good. I mocked up an
approach a la kangax and JDD that addresses many of the goals that were
brought up: It uses namespacing, the expose concept, avoids prototyping
and uses the methodize concept to drop functions into prototypes if
desired. http://pastie.org/pastes/251691

pastie.org seems to be having some trouble right now, so here is an
alternate version: http://kendsnyder.com/sandbox/prototype/namespace1.php

Example usage:
|// static call within Prototype namespace
Prototype.$S.strip(' test1 '); // "test1"
// instance call within Prototype namespace
Prototype.$S(' test2 ').strip(); // "test2"

// dump Prototype objects to global namespace
Prototype.expose();
// static call in global namespace
$S.strip(' test3 '); // "test3"
// instance call within Prototype namespace
$S(' test4 ').strip(); // "test4"

// copy instance methods to String.prototype using "$" prefix
Prototype.addPrototypes('$');
' test5 '.$strip(); // "test5"

// copy instance methods to String.prototype using no prefix
Prototype.addPrototypes('');
' test6 '.strip(); // "test6"|


kangax wrote:
> ...


>> Hmm. If we are going to fundamentally change the library (e.g. require
>> $S() or $A() to access Prototype string methods) why not just use a "$"
>> prefix instead? For example, Array#$each, String#$strip,
>> Function#$bind. I think such an approach was suggested many months ago,
>> but I don't recall. Some advantages of a dollar-prefix approach:
>>
>> 1. prototype.js can be made backwards compatible with a few loops
>> 2. Current code can be ported to the "2.0" version with a
>> find-and-replace based approach
>>
> Ken,
> I'm not sure I understand how we get the above mentioned benefits by
> changing String#foo to String#$foo.
> Could you explain?
>

I mean getting those benefits doing something like the following:

1. Backwards compatibility
// define dollar prefix functions
var ArrayMethods = {
foo: function() {
doSomething(this);
}
};
// copy methods to Array.prototype with a dollar prefix
for (var method in ArrayMethods) {
Array.prototype['$' + method] = ArrayMethods[method];
};
// copy methods to Array.prototype without the prefix
// (intended for compatibility with "deprecated" API)
for (var method in ArrayMethods {
Array.prototype[method] = ArrayMethods[method];
};


2. Find-and-replace code--To convert deprecated code, use a text editor
to replace ".foo(" with ".$foo(" in existing applications.

The dollar-prefix doesn't solve all the problems with prototyping, but
it avoids naming conflicts with other code.


Anyhow, kangax, thanks for clarifying. What you say makes a lot of sense.

- Ken Snyder


John-David Dalton

unread,
Aug 12, 2008, 2:08:58 PM8/12/08
to Prototype: Core
@Ken I haven't had a chance to look at your code yet, but instead of
pastie try
"gist" -> http://gist.github.com/

Tobie Langel

unread,
Aug 12, 2008, 2:14:47 PM8/12/08
to Prototype: Core
Prototype.$S(' test2 ').strip();

... You can't be serious!

Mislav Marohnić

unread,
Aug 12, 2008, 2:24:36 PM8/12/08
to prototy...@googlegroups.com
On Tue, Aug 12, 2008 at 18:59, Ken Snyder <kends...@gmail.com> wrote:

pastie.org seems to be having some trouble right now, so here is an
alternate version: http://kendsnyder.com/sandbox/prototype/namespace1.php

I like this, but would rather stay away from prefixing everything with "$". It makes code rather ugly, especially in the case of "$S" because they are similar characters.

I would rather use Prototype.String.strip() and make people define their own shortcuts. 

Diego Perini

unread,
Aug 12, 2008, 2:27:34 PM8/12/08
to Prototype: Core
Ken,
I was trying to achieve the same but in a different way.

Load prototype in an embedded iframe, then use it from there.
Unfortunately Prototype is not context sensitive...

I mean selectors doesn't work on child iframe documents,
and there are quirks in using events cross-documents,
even if the documents are local to the same server.

Here is a simple snippet I wrote to achieve that separation
of scope (but I should patch some $() methods in all those
libraries at that time):

http://javascript.nwbox.com/JsCOPE/

There, I am trying to load three different libraries, each in a
separate iframe and then be able to use $() methods of them
all on the main document container. I had some success....

Even the loading is very fast since it is parallelized in three
different scopes. Would like your opinion,
is it worth a retake ?

--
Diego Perini

John-David Dalton

unread,
Aug 12, 2008, 3:02:48 PM8/12/08
to Prototype: Core
@Diego this patch is related to your issues. It's not as clever as the
one mentioned on the jQuery list (doesn't let you specify the document
context),
but does allow to perform operations on iframes with minor code
adjustments.

Diego Perini

unread,
Aug 12, 2008, 3:24:11 PM8/12/08
to Prototype: Core
JDD,
maybe having a "setDefaultContext()" method in Prototype
will be as much useful as the clever parameter in jQuery !!!

I am not a fan of having multiple libraries loaded, it is a
very bad programming decision under every aspect, but
I vote for each library being as clean as possible and let
the developers decide what to do with those tools without
risking to break everything else around.

--
Diego Perini


On 12 Ago, 21:02, John-David Dalton <John.David.Dal...@gmail.com>
wrote:

Ken Snyder

unread,
Aug 12, 2008, 4:08:05 PM8/12/08
to prototy...@googlegroups.com
Tobie Langel wrote:
> Prototype.$S(' test2 ').strip();
>
> ... You can't be serious!
>
I know that use is impractical. :D

It was intended to illustrate that it is /possible/ to define all the
prototype internals as static methods, use namespacing and expose
static, instance and prototype versions as desired. It would avoid
global naming conflicts if needed, avoid prototype naming conflicts if
needed, and allow optional backwards compatibility.

It is not a solution--just a brainstorm that might give others some ideas.

@Mislav Your suggestions about dropping the "$" prefix, using
"Prototype.String" instead of "Prototype.$String", and self-serve
shortcuts are great.

@JDD Here is a copy of the code on gist: http://gist.github.com/5141

- Ken

John-David Dalton

unread,
Aug 12, 2008, 4:26:39 PM8/12/08
to Prototype: Core
@ Diego hehe I must of forgot to paste the link:
http://dev.rubyonrails.org/ticket/11475

@Ken I dig Mislav/Juriy namespacing approach more.
I think that variable aliases to them inside the closure for internal
use is
a very good idea though.
(function() {
var $ = Protototype.$;
.//core gutts
...)()

- JDD

T.J. Crowder

unread,
Aug 12, 2008, 5:00:10 PM8/12/08
to Prototype: Core
Hi all,

Lots of good stuff here. It seems like we're starting to wander
around a bit. Let's see if we can agree on what it is we're trying to
achieve with this brainstorming session.

Desired outcomes (*potentially*, feel free to shoot me down):

1. Namespacing Prototype with the option of importing it into a
lexical block, for legacy support (older code just needs a line or two
added to it) and for convenience in specific situations.

2. Not extending DOM element prototypes and other tricky object
prototypes by default; but offering an option to do so (e.g.,
"Prototype.extendElement();" -- for legacy support, and for situations
where the author isn't worried about the potential issues, which is
frequently).

3. Not extending other host object prototypes (Array, String) by
default; but offering an option to do so (for legacy support, and for
judicious use by people who know what they're doing).

4. When extending, try to avoid naming conflicts in a non-ugly way.

Keeping in mind:

A. We don't want to bloat Prototype.

B. We want to make it fairly easy for legacy projects to use the
updated lib, e.g., minimal changes _required_.

Does that sum it up?
--
T.J. Crowder
tj / crowder software / com

On Aug 12, 9:26 pm, John-David Dalton <John.David.Dal...@gmail.com>
wrote:

John-David Dalton

unread,
Aug 12, 2008, 11:47:13 PM8/12/08
to Prototype: Core

@T.J. sounds good to me.

kangax

unread,
Aug 13, 2008, 12:46:57 AM8/13/08
to Prototype: Core
On Aug 12, 12:59 pm, Ken Snyder <kendsny...@gmail.com> wrote:
> I agree with John-David that compatibility with for-in loops is
> desirable--arguably an edge case, but certainly good. I mocked up an
> approach a la kangax and JDD that addresses many of the goals that were
> brought up:  It uses namespacing, the expose concept, avoids prototyping
> and uses the methodize concept to drop functions into prototypes if
> desired.  http://pastie.org/pastes/251691

Somewhat irrelevant to this discussion, but I would vote for
structuring prototype in this way:


// make sure to operate within a dedicated scope
(function(){

// get actual `global` object
// (rather than relying on browser-specific `window`)
var __global__ = this;

// return early if global Prototype is already defined
if (typeof __global__.Prototype != 'undefined') return;

// define global Prototype
__global__.Prototype = {

// namespace certain sets of methods
dom: { /* ... */ },
lang: { /* ... */ },
event: { /* ... */ },
// ...
}
})();

--
kangax

T.J. Crowder

unread,
Aug 13, 2008, 6:45:03 AM8/13/08
to Prototype: Core
@kangax:

Good point about not relying on window, just to make life a bit easier
in other environments. (Some parts of Prototype will still be browser
specific.)

So updating my earlier model in line with that (on pastie here:
http://pastie.org/252135):
* * * *
(function(){
var __global__;
var Prototype;

// Get the global object; will be 'window' on browsers
__global__ = this;
if (__global__.Prototype)
{
// Already defined; done!
return;
}

// Define it locally for brevity
Prototype = {};

// Define each Prototype "global" in two parts:
// 1. The namespaced function (complete with pdoc)
// 2. Our local alias for brevity
// If this syntax doesn't work for pdoc, put the local
// alias after the main def...or enhance pdoc ;-)
var $ = Prototype.$ = function() {
/* ... */
};
var $A = Prototype.$A = function() {
/* ... */
};
var $R = Prototype.$R = function() {
/* ... */
};
var Class = Prototype.Class = {
/* ... */
};
var Hash = Prototype.Hash = Class.create(/* ... */);
/* ...repeat for all global symbols...*/

// Build imports string for base2.js-like imports
Prototype.imports = (function(){
var rv;

rv = '';
Object.keys(Prototype).each(function(name){
rv += ', ' + name + ' = Prototype.' + name;
});
rv = 'var' + rv.substring(1) + ';';
return rv;
})();

// Save globally
__global__.Prototype = Prototype;
})();
* * * *
--
T.J. Crowder
tj / crowder software / com

T.J. Crowder

unread,
Aug 13, 2008, 8:01:55 AM8/13/08
to Prototype: Core
> Does that sum it up?

If so, perhaps:

i) Namespace Prototype and use a base2.js-like import mechanism (see
earlier in this thread), whilst using shortcut locals in Prototype's
own code for brevity.

ii) Provide static methods for things as with Event and Element
currently, e.g. "Prototype.String.strip(' test ')", although
presumably people will rarely use that full form, as it's ridiculously
verbose. They'll alias things (e.g., "$P = Prototype;" and/or "$PS =
Prototype.String;") and/or use extensions and/or wrappers.

iii) Offer "extendXYZ" methods for extending the prototypes of various
objects, including of course "extendAll". Have this accept an
optional prefix. E.g.: "Prototype.extendString()" gives you
"'s'.strip()"; "Prototype.extendString('$')" gives you "'s'.$strip()".

iv) Offer methods that extend instances (which would presumably be
used for (iii) above, since prototypes are just instances), e.g., "var
x = $S(' text '); s.strip()". Ideally, offer a factory for them
that allows an optional prefix. E.g.:

// In one's page init code:
var blarg;
blarg = Prototype.createExtender(Prototype.StringExtender, '$');

// Usage:
var x = blarg(' test ');
x.$strip();

v) Provide wrappers for unextended objects. (? see below)

Three concerns (at least):

Firstly, I worry about wrappers -- or more specifically, having both
the wrapper and extension concepts in the same library. This is just
because of the potential for confusion, e.g., having a function that
extends an instance vs. a function that wraps an instance.

Secondly, I worry that unifying the underlying code across the
statics, extensions, and wrappers could make writing the Prototype
code itself awkward, particularly wrt the wrappers -- the methodize()
solution probably works for the others. Trivial cases like
String.strip aren't difficult, but cases where Prototype itself makes
extensive use of its own nifty stuff -- Element.replace, perhaps -- it
could get awkward. Or perhaps not, maybe I worry too much.

I reserve my third concern for below.

But *using* the resulting library would be quite easy indeed, and
wonderfully flexible. Your own code can import Prototype and enable
prototype extensions if that works for your project; people writing
libs on top of Prototype would be well-served by only importing into
their own scope and extending instances rather than prototypes or
sticking with statics or wrappers; people writing libs who want to use
extended instances can do that with their own prefix if they like to
minimize the odds of conflict.

A spattering of usage scenarios; this is by no means comprehensive:

1. Import w/instance extension
* * * *
<script src='prototype.js'></script>
<script>
eval(Prototype.imports);
$(document).observe('dom:loaded', initFunction);
function initFunction()
{
var h;
h = new Hash();
...
}
...
</script>
* * * *

1.1. Import w/prototypical extensions
* * * *
<script src='prototype.js'></script>
<script>
eval(Prototype.imports);
Prototype.extendAll();
document.observe('dom:loaded', initFunction);
function initFunction()
{
var h;
h = new Hash();
...
}
...
</script>
* * * *

2. Namespaced
* * * *
<script src='prototype.js'></script>
<script>
Prototype.Event.observe(document, 'dom:loaded', initFunction);
function initFunction()
{
var h;
h = new Prototype.Hash();
...
}
...
</script>
* * * *

2.1. Namespaced, Aliased
* * * *
<script src='prototype.js'></script>
<script>
var $P = Prototype;
var $PE = $P.Event;
$PE.observe(document, 'dom:loaded', initFunction);
function initFunction()
{
var h;
h = new $P.Hash();
...
}
...
</script>
* * * *

3. Wrappers
* * * *
<script src='prototype.js'></script>
<script>
Prototype.Element.wrap(document).observe('dom:loaded', initFunction);
function initFunction()
{
var h;
h = new Prototype.Hash();
...
}
...
</script>
* * * *

3.1. Aliased Wrappers
* * * *
<script src='prototype.js'></script>
<script>
var $ewrap = Prototype.Element.wrap;
$ewrap(document).observe('dom:loaded', initFunction);
function initFunction()
{
var h;
h = new Prototype.Hash();
...
}
...
</script>
* * * *

5. Instance Extenders
* * * *
<script src='prototype.js'></script>
<script>
Prototype.$(document).observe('dom:loaded', initFunction);
function initFunction()
{
var h;
h = new Prototype.Hash();
...
}
...
</script>
* * * *

Etc. etc. etc.

Third concern: I *love* power, and I *love* flexibility. And this
sort of thing provides power and flexibility. But it also means that
newbies have a panoply of choices when using the library, which can be
confusing.

So with all of that in mind, do we really need wrappers? Is there a
problem, per se, with instance extension? Or is it "just" that we've
had naming conflicts (wrap, replace, select) that could be addressed
by user-selectable prefixes? Kangax?

</ramble>
--
T.J. Crowder
tj / crowder software / com

kangax

unread,
Aug 13, 2008, 5:51:44 PM8/13/08
to Prototype: Core
On Aug 13, 8:01 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
[snip]
> So with all of that in mind, do we really need wrappers?  Is there a
> problem, per se, with instance extension?  Or is it "just" that we've
> had naming conflicts (wrap, replace, select) that could be addressed
> by user-selectable prefixes?  Kangax?

Well,
I strongly believe that we DO need wrappers : )
Some core members share the same opinion.

The cons:

1) More verbose in a limited number of cases (e.g. as I mentioned
before - accessing certain "native" methods of an element).


The pros:

1) No conflicts:
Wrappers are plain objects created from the scratch. They have a
predefined set of methods/properties and so behave exactly as
specified. Such reliability can hardly be accomplished by extending
host objects : )

2) More flexibility:
Wrappers could be created via "classes" and mixins. By combining
mixins it is possible to create very task-specific constructors with
no bloat.

3) Faster:
Wrapper methods can be shared via a prototype chain. This brings
certain browsers (which do not implement Element.prototype - e.g. IE)
to a much higher level of efficiency. In essence, elements don't need
to carry properties directly, but can "delegate" to a [[Prototype]].

As an example of speed benefits, here's a simple mock-up of an element
wrapper and a test to compare its performance vs. current approach:

(function(){
Prototype.ElementWrapper = Class.create({
initialize: function(element) {
this.element = element;
},
show: function() {
this.element.style.display = '';
return this;
},
hide: function() {
this.element.style.display = 'none';
return this;
}
});

this.$W = function(element) {
if (typeof element == 'string') {
element = document.getElementById(element);
}
return new Prototype.ElementWrapper(element);
};
})();

var el = document.body;
var wrapper = $W(el);

console.time(1);
for (var i=0;i<10000;i++) wrapper.show();
console.timeEnd(1);

console.time(2);
for (var i=0;i<10000;i++) el.show();
console.timeEnd(2);

/*
1: 96ms
2: 300ms
*/

IE, being sensitive to `$` (or rather Element.extend) should show even
better results : )


Best,

--
kangax

T.J. Crowder

unread,
Aug 14, 2008, 4:22:07 AM8/14/08
to Prototype: Core
@kangax:

Sounds good to me (and I might have been playing devil's advocate a
bit :-) ).

@all:

On wrappers, one suggestion I'd have is to have the same property of
every wrapper used to refer to the wrapped item, e.g.,
"myWrappedElement.$raw" refers to the DOM element that was wrapped,
"myWrappedFunction.$raw" refers to the wrapped function, etc. That
way I don't have to remember that the property for the wrapped element
is 'element' but the property for the wrapped function is 'fnc' and
the property for a wrapped string is 'str'; and of course there can be
one generic unwrap method that works with all wrapped objects:

function unwrap(obj)
{
return (obj.$raw || obj);
}

On a similar note, to help code be compatible with both wrapper
objects and extended objects, having extended objects carry the same
property (which simply refers back to the object, e.g. extendedObject.
$raw === extendedObject) might be helpful. Then I can write code like
"el.setStyle(...); if (el.$raw.tagName == 'xxxx') ..." without
worrying about whether el is a wrapped element or an extended
element. Naturally, if you *know* you're working with extended
objects (e.g., your own project), you can drop the $raw bit, but if
you're writing code that may not know, best to be safe.

(I'm not suggesting "$raw" is the best name for this property. :-) We
will need to pick something that is unlikely to appear as a native
property or method in future DOMs and JavaScript versions, though,
since we can't practically allow users to namespace or prefix it,
unlike other things.)

FWIW.
--
T.J. Crowder
tj / crowder software / com

T.J. Crowder

unread,
Aug 14, 2008, 9:54:23 AM8/14/08
to Prototype: Core
@kangax:

In terms of wrappers, when using Prototype in wrapper-mode (if you
will), are you thinking it won't extend anything? Not Strings or
Functions, for instance? This is just to inform my thinking as I look
at the source.

Thanks,

-- T.J. :-)

On Aug 13, 10:51 pm, kangax <kan...@gmail.com> wrote:

kangax

unread,
Aug 14, 2008, 5:48:57 PM8/14/08
to Prototype: Core
On Aug 14, 9:54 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> @kangax:
>
> In terms of wrappers, when using Prototype in wrapper-mode (if you
> will), are you thinking it won't extend anything?  Not Strings or
> Functions, for instance?  This is just to inform my thinking as I look
> at the source.

I was thinking of Element and Event wrappers for now. These solve most
urgent issue - smooth integration (compatibility) with environment
(rather than compatibility with 3rd party code). On the other hand,
native objects' wrappers seem to allow for a more pleasant code:

// procedural-style
(function(){
var findAll = Prototype.enum.findAll;
// or Prototype.enum.importAll();
findAll([1,2,3,4,5], function(v){ return v % 2 }); // [1,3,5]
})();

vs.

// wrapper-style
$A([1,2,3,4,5]).findAll(function(v){ return v % 2 }); [1,3,5]

And can be defined with the same pattern:

(function(){
Prototype.ArrayWrapper = Class.create({
initialize: function(array) {
this.array = array;
},
find: function(iterator, context) {
for (var i=0, l=this.array.length; i<l; i++) {
if (iterator.call(context || iterator, this.array[i], i,
this.array))
return this.array[i];
}
}
});
this.$A = function(o) {
return new Prototype.ArrayWrapper(o);
};
})();

$A([1,2,3,4,5]).find(function(v){ return v > 3 }); // 4

--
kangax

T.J. Crowder

unread,
Aug 15, 2008, 7:40:24 AM8/15/08
to Prototype: Core
Hiya,

On the one hand, not extending anything (by default) is clean and
clear conceptually; it's easy to explain to people. On the other
hand, this question was prompted in part by this line of code in
Element.replace:

content.evalScripts.bind(content).defer();

At that point in that method, 'content' would probably already be a
WrappedString, but I'm looking at the function stuff. Would bind()
return a WrappedFunction, or a Function? I tend to assume Function
since we mostly assign these to event handlers. That being the case,
a statics-only version of this becomes something like this:

Function.defer(Function.bind(content.evalScripts, content));

...and a wrappers-only version looks something like this:


WrapFunction(WrapFunction(content.evalScripts).bind(content)).defer();

...neither of which is nearly as clear (to me) as the version above
where Functions are extended.

So although not extending anything is clean and clear in concept, it
makes for some code that is neither clean nor clear. :-)

So do we then take a pragmatic approach and say "We extend things we
haven't had much trouble as a result of extending" and "we don't
extend other things by default (although you can enable it in your
code) because we've had trouble with it."? FWIW, that seems a
reasonable approach to me.

If so, what are the lists? You mentioned not extending Event and
Element, but what about Array? The "for..in" thing is a constant
background hum. I don't *think* I've heard about problems extending
Function.prototype, Number.prototype, String.prototype, String (we add
some statics), or Object (more statics; we don't extend
Object.prototype!!)...
--
T.J. Crowder
tj / crowder software / com

kangax

unread,
Aug 15, 2008, 8:08:39 AM8/15/08
to Prototype: Core
On Aug 15, 7:40 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> Hiya,
>
> On the one hand, not extending anything (by default) is clean and
> clear conceptually; it's easy to explain to people.  On the other
> hand, this question was prompted in part by this line of code in
> Element.replace:
>
>     content.evalScripts.bind(content).defer();
>
> At that point in that method, 'content' would probably already be a
> WrappedString, but I'm looking at the function stuff.  Would bind()
> return a WrappedFunction, or a Function?  I tend to assume Function
> since we mostly assign these to event handlers.  That being the case,
> a statics-only version of this becomes something like this:
>
>     Function.defer(Function.bind(content.evalScripts, content));
>
> ...and a wrappers-only version looks something like this:
>
> WrapFunction(WrapFunction(content.evalScripts).bind(content)).defer();
>
> ...neither of which is nearly as clear (to me) as the version above
> where Functions are extended.
>
> So although not extending anything is clean and clear in concept, it
> makes for some code that is neither clean nor clear. :-)

Yeah, this looks pretty verbose.
I don't think we need to "wrap" native objects, but I might be wrong.

>
> So do we then take a pragmatic approach and say "We extend things we
> haven't had much trouble as a result of extending" and "we don't
> extend other things by default (although you can enable it in your
> code) because we've had trouble with it."?  FWIW, that seems a
> reasonable approach to me.

It looks like having element/event wrappers is the least painful
change we can start with.

>
> If so, what are the lists?  You mentioned not extending Event and

"extending" that is : )

> Element, but what about Array?  The "for..in" thing is a constant
> background hum.  I don't *think* I've heard about problems extending
> Function.prototype, Number.prototype, String.prototype, String (we add
> some statics), or Object (more statics; we don't extend
> Object.prototype!!)...

There are *some* problems there too - mostly compatibility with newer
language revisions (e.g. Array#reduce) and compatibility with 3rd
party code (e.g. Function#defer defined in ExtJS that acts as our
Function#delay). Generics seem like a safer route, but there could be
problems as well. ES3.1 defines at least a dozen of new Object
generics - Object.getPrototypeOf, Object.getOwnPropertyDescriptor,
Object.getOwnPropertyNames, Object.create, Object.clone,
Object.defineProperty, Object.defineProperties, Object.seal, etc.

--
kangax

dhtml

unread,
Aug 15, 2008, 2:31:49 PM8/15/08
to Prototype: Core


On Aug 10, 8:58 pm, kangax <kan...@gmail.com> wrote:
> On Aug 10, 1:11 pm, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
>
>
>
>
>
> > > I see what you mean, but `eval`ing into an object seems like an
> > > overkill : )

>
> Yep. It's very convenient to create a separate scope to operate in and
> just "dump" all the "familiar" methods/objects into it (without
> pollution of global object).
>

exporting.


>
> When I say "wrappers" I mean stop extending the host objects entirely
> (at least not implicitly). I remember at least few issues related to
> host objects extensions. I don't think convenience is worth the
> trouble. Moreover, IE, with its lack of Element.prototype inheritance,
> already forces us to "extend" elements manually.
>


>
> $(someElement); // returns wrapper
> $(someElement).element; // reference to an actual element
> $(someElement).show(); // method that modifies an actual element
>
> etc.
>
> Same approach could be used for Event extension. I believe that
> convenience level when using wrappers is almost the same.
>

In many cases, inline code is sufficient for things.

function handle(e) {
e = e||event;
var target = e.target || e.srcElement;
...
}

> The tricky part is "taking care of" native objects : )
> Array.prototype, String.prototype, etc.
>

Yeah, clients of the current API can expect [1,2,3].each(fun) to work.

> Two obvious ways to solve this issue seem to be:
>
> // 1) wrappers
> (function(){
>   eval(Prototype.imports);
>   $A([1,2,3,4,5]).invoke('toString');
>   $S('someString').strip();
>
> })();
>
> // 2) namespaced methods
> Prototype.lang.invoke([1,2,3], 'toString');
> Prototype.lang.strip('someString');
>
> I think the latter one is uglier. The former one is just not as
> convenient as it is now.
>
> Thoughts?
>

If Prototype is to deprecate the augmented built-in methods, it should
provide an equal or better alternative that is just as easy to use.
This will provide users the motivation to use the new method, not the
old one.

Here are some rough ideas. I didn't test them and they should
definitely be tested before becoming real code. Just rough ideas for
brainstorming.

define Prototype.Element,
add shortcut $ to the window, if globalExport is true
define Prototype.export function (should probably use hasOwnProperty)
create a store of Elements - Element.instances


/**
* Export methods/properties of s to r.
* @param {Object} s - the supplier of properties.
* @param {Object} r - the receiver of properties.
*/
Prototype.export = function(r,s){
for(var p in s)
r[p] = s[p];
};

(function() {
if(Prototype.globalExport)
Prototype.export(window, {$:$});
Prototype.export(Prototype, {Element: Element});

var instances = { }, c = 0, p = "Prototype";

/**
* @constructor
* @param {HTMLElement} el
*/
function Element(el) {
this.el = el;
this.id = el.id;
this.style = el.style;
}
Element.prototype = {
show : function(){
this.style.display='block';
return this.
}
};

Element.get = $;

/**
* @method Element.get
* @param {String|HTMLElement} el if el is an element
* and has no ID, an ID will be generated automatically.
*/
function $(el) {
if (typeof el == 'string') {
el = document.getElementById(id);
}
else {
el.id = el.tagName + c++;
}
return instances[el.id] || (instances[el.id] = new Element(el));
};
})();

Prototype.Element.get(document.body);

Garrett

> --
> kangax

kangax

unread,
Aug 15, 2008, 4:04:32 PM8/15/08
to Prototype: Core
It's not quite pleasant to manually "normalize" event model
idiosyncrasies. Yes, in some cases inline handlers could be
sufficient, but I think it's a good idea to have a robust "event
wrapper" model. Do you see any possible problems with such approach?

> > The tricky part is "taking care of" native objects : )
> > Array.prototype, String.prototype, etc.
>
> Yeah, clients of the current API can expect [1,2,3].each(fun) to work.

There should be a way to do things the "old" way (e.g. after executing
`Prototype.extendArray' or similar). The main goal is to "not extend"
native/host objects implicitly.
Caching of wrappers looks useful, but wouldn't there be problems if
one needs to extend a newly cloned or manually created (and not yet
inserted into a document) element? I think such elements are allowed
to have id's that might already be present in a document. There could
also be conflicts in a multi-document environment.

>
> };
> })();
>
> Prototype.Element.get(document.body);

Thanks for the feedback.

Best,

--
kangax

dhtml

unread,
Aug 15, 2008, 8:06:50 PM8/15/08
to Prototype: Core
var x = document.createElement("div");
Prototype.Element.get(x);

would create an Element (wrapped) that has a unique id.

> I think such elements are allowed
> to have id's that might already be present in a document. There could
> also be conflicts in a multi-document environment.
>

Two elements with the same ID would be a problem. These could come
from:
1) different frames/windows (mutual problem - handle edge case)
2) the same document via
- invalid markup (client's problem)
- changing innerHTML (mutual problem - provide purging)
- coincidence w/dynamic id generation - an element has id like
"DIV0" (this could be checked first with document.getElementById
(inefficient)). (library problem)
3) A cloned element that is not part of the document yet. (client's
problem - set new ID first)

It should be possible for the client to assign the element an ID prior
to calling get:-

var x = document.createElement("div");
x.id = "x"
Prototype.Element.get(x);

There could be a boolean flag for updating the cache. There could also
be a doc parameter.

function $(el, options) {
var e, doc;
if (typeof el == 'string') {
doc = options && options.doc || document;
el = doc.getElementById(el);
}
else {
if(!el.id)
el.id = el.tagName + c++;
}
var e = instances[el.id];
if(!e || options && options.updateCache)
return instances[el.id] = new Element(el);
return e;
}

// Get the 'a' element in the first iframe.
var doc = document.getElementsByTagName('iframe')[0].contentDocument;
$('a', {doc: doc});

It would be a consideration to allow the client to "destroy" an
Element, removing it from the cache after purging the event handlers.

Garrett

T.J. Crowder

unread,
Aug 16, 2008, 5:50:33 AM8/16/08
to Prototype: Core
@Garrett:

On wrapper caching:

1. Before doing it, I'd recommend doing a mock up, throwing various
test cases at it, and doing performance benchmarks across a wide
variety of target environments (both browsers and operating systems)
to prove that reusing the wrappers provides sufficient performance
benefit to merit the complexity (code, bugs) and memory use. It could
be a case of premature optimization (something I've *never* been
guilty of ;-) ).

2. If wrapper caching *is* written, as a user of the library I'd want
control over whether it's enabled, as the benefits/costs will vary
dramatically (I would think) by usage pattern.

FWIW.
--
T.J. Crowder
tj / crowder software / com

Diego Perini

unread,
Aug 16, 2008, 12:35:52 PM8/16/08
to Prototype: Core
Kangax,
are there some fork already implementing this idea ?

It sound really great in my view, having the capabilities to
transform the way the library work and the target one is
going to work on is a great flexibility you are adding.

It is not about having a multiple-library environment that's
just academic and fun as it has been said, but it is about
encapsulation of code and isolation of scopes and context.

The main problem was introducing "context" sensitivity all
around current code, the mixed in and somehow related
Wrappers and scoped evaluations are another way of
letting other people see a flexible environment they
can grow up their ideas in the way they see fit.

Thank you for sharing these good ideas.

--
Diego Perini

dhtml

unread,
Aug 16, 2008, 3:30:22 PM8/16/08
to Prototype: Core


On Aug 16, 2:50 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> @Garrett:
>
> On wrapper caching:
>
> 1. Before doing it, I'd recommend doing a mock up, throwing various
> test cases at it, and doing performance benchmarks across a wide
> variety of target environments (both browsers and operating systems)
> to prove that reusing the wrappers provides sufficient performance
> benefit to merit the complexity (code, bugs) and memory use.  It could
> be a case of premature optimization (something I've *never* been
> guilty of ;-) ).
>
Constructing a new object will always be more memory intensive than
reusing existing one.

> 2. If wrapper caching *is* written, as a user of the library I'd want
> control over whether it's enabled, as the benefits/costs will vary
> dramatically (I would think) by usage pattern.

What are the use cases for Element?

Caching might be best left to the client.

Keeping Element to the bare minimum, with few instance properties
would be an approach to avoid feature creep and bloat. But it seems
like there are already a lot of features that would need to be
maintained for backwards compatibility.

Garrett

>
> FWIW.
> --
> T.J. Crowder
[snip]
> > > kangax

T.J. Crowder

unread,
Aug 16, 2008, 3:44:59 PM8/16/08
to Prototype: Core
> Constructing a new object will always be more memory intensive than
> reusing existing one.

I don't think so. I think there *might* be argument along those lines
if we knew the client were going to keep a reference to the object
(and so might end up with several distinct wrappers for the same
underlying element), but in the vast majority of use cases I've seen
that's not how the current extended elements are used, and therefore
not how I would expect wrapped elements to be used. (And of course,
if they keep a reference, they can reuse it directly.) Most of the
time, we're only using them briefly for the syntactic sugar, and in
many cases never using them again. Consequently, caching the wrapper
would consume memory that may well be best left for other purposes.
The trade-off comes in speed, not memory use: Is it dramatically
faster to find and reuse the wrapper than to build a new one? I tend
to suspect not, hence my suggestion that caching wrappers *may* be
premature optimization. Now, arguing the other way, if you have an
element you reference several times a second and create a new wrapper
every time, on some JavaScript implementations that do poor garbage
collection, it could be a memory use issue, but that brings us to...

> Caching might be best left to the client.

That's my off-the-cuff feeling, yes. :-) The author knows whether
they'll want to reuse the element (and therefore its wrapper), or if
it's a one-off thing.
--
T.J. Crowder
tj / crowder software / com

John-David Dalton

unread,
Aug 18, 2008, 11:27:07 AM8/18/08
to Prototype: Core

If a cache is to be used it can be uniquely marked via the same
approach as elements in the event system
element._prototypeWrapperId = [arguments.callee.id++];
the id is unique and doesn't transfer if you clone the node.

var instance = Prototype.Element.cache[ element._prototypeWrapperId ];
if (instance) return instance;

This would work well with this patch (allowing Class.create to return
a value).
http://dev.rubyonrails.org/ticket/11481

Some host objects may not allow the addition of custom properties
though, and in
those cases they wouldn't get cached.

I like having a common property for the wrapped object (+1 for the
suggestion):
var el = new Prototype.Element(element);
el.raw, or el.obj, or el.wrapped, or el.source.
I kinda dig el.source

We don't have to worry to much about naming conflicts because this is
a property of the wrapper instance and
not of the Element host object.

I like the "imports" idea. I think adding custom prefixes is a bit
much though.
(or maybe its just the method name that bothers me)

Checking if the object you are dealing with is extended is as easy as
if (element instanceof Prototype.Element)

There has been reports of Prototype loosing the properties assigned to
String static object when dealing with Flash's externalInterface
(flash/js communication)
I think using a wrapper is pretty painless and we can see this via:
var text = $.str(text);
text.strip();

I also like Kangax's idea to use $A for wrapping arrays, it feels like
the natural choice.
var arr = [1,2,'bob'];
$A(arr).each(function(n) { ..});

As a bonus I think we should follow jQuery's lead and allow
$$(selector).hide() or something. If $$ is a selector wrapper, I would
want to be able to do
$$(selector).hide() instead of $$(...).invoke('hide') or instead of $$
('#input')[0].getValue() -> $$(...).getValue();

- JDD





T.J. Crowder

unread,
Aug 18, 2008, 12:37:49 PM8/18/08
to Prototype: Core
> I like the "imports" idea. I think adding custom prefixes is a bit
> much though.
> (or maybe its just the method name that bothers me)

If we're going to continue enhancing some host objects (String,
Functino) and not others (Element, Array), I'd suggest we drop the
idea of custom prefixes anyway since only some of them would be
affected -- and that would be *really* confusing.

I do wonder if a Prototype-wide prefix for functions added to host
objects might be a good idea, though, so we don't have the issue of
name conflicts. Even just throwing a $ on the front would help, e.g.
"handler = myNiftyFunction.$bind(this)". For me it's worth drawing
the distinction between standard stuff and Prototype stuff. That
won't be a popular sentiment, but esp. for newbies making a clear
distinction may be quite helpful. In cases where Prototype is
supplying a missing function because the host environment doesn't
provide it (such as the new Array stuff), obviously we wouldn't prefix
that as the whole point is to not worry about whether the host
environment provides the function.
--
T.J. Crowder
tj / crowder software / com

On Aug 18, 4:27 pm, John-David Dalton <John.David.Dal...@gmail.com>
wrote:
> If a cache is to be used it can be uniquely marked via the same
> approach as elements in the event system
> element._prototypeWrapperId = [arguments.callee.id++];
> the id is unique and doesn't transfer if you clone the node.
>
> var instance = Prototype.Element.cache[ element._prototypeWrapperId ];
> if (instance) return instance;
>
> This would work well with this patch (allowing Class.create to return
> a value).http://dev.rubyonrails.org/ticket/11481

kangax

unread,
Aug 18, 2008, 6:53:59 PM8/18/08
to Prototype: Core
On Aug 18, 11:27 am, John-David Dalton <John.David.Dal...@gmail.com>
wrote:
> If a cache is to be used it can be uniquely marked via the same
> approach as elements in the event system
> element._prototypeWrapperId = [arguments.callee.id++];
> the id is unique and doesn't transfer if you clone the node.
>
> var instance = Prototype.Element.cache[ element._prototypeWrapperId ];
> if (instance) return instance;
>
> This would work well with this patch (allowing Class.create to return
> a value).http://dev.rubyonrails.org/ticket/11481
>
> Some host objects may not allow the addition of custom properties
> though, and in
> those cases they wouldn't get cached.
>
> I like having a common property for the wrapped object (+1 for the
> suggestion):
> var el = new Prototype.Element(element);
> el.raw, or el.obj, or el.wrapped, or el.source.
> I kinda dig el.source

`source` sounds good. I just looked at YUI3 and it seems like they
only expose `_yuid` as a public property of their wrapper
implementation - everything else is accessed via `get`/`set` (which is
great, imo).

I think having Node and NodeList wrappers should be enough from the
start. NodeList could be returned by `$$` and implement some DOM
methods which would operate on the entire collection:

$('foo').show(); // operates on an element
$$('div.foo').show(); // operates on a NodeList

--
kangax

T.J. Crowder

unread,
Aug 19, 2008, 2:34:59 AM8/19/08
to Prototype: Core
> > var el = new Prototype.Element(element);
> > el.raw, or el.obj, or el.wrapped, or el.source.
> > I kinda dig el.source
>
> `source` sounds good.

It's not very important, but I can't say "source" appeals. I think it
will be confusing in discussion. The underlying element is as much a
target as a source, and source is already an overloaded word. When
you call item.update(), the target (not source) of the update is the
underlying element. In terms of it being overloaded, suppose you have
a container element and you use select() to get an 'item' element from
somewhere within it. At that point, it's easy (but wrong) to read
"item.source" as meaning the container, since you got the item "from"
the container.

"raw" has grown on me. It's not overloaded, and it's pretty clear in
conversation: "Q. How do I get the tag name of a wrapped element? A.
From the raw element it's wrapping. Q. How do I get the real nodelist
from a wrapper? A. Use the 'raw' property to get the raw nodelist."
It's also short (we'll be using this a lot). The biggest negative I
see to it is that it's typed entirely with the left hand (if you're a
touch typist).

raw:
* * * *
var wel;

wel = $('mydiv');
if (wel.raw.childNodes.length == 0)
{
wel.update('<p class="note">No content</p>");
}
* * * *

vs. source:
* * * *
var wel;

wel = $('mydiv');
if (wel.source.childNodes.length == 0)
{
wel.update('<p class="note">No content</p>");
}
* * * *

Just for the sake of completeness, there's always the symbolic route,
for instance "_":
* * * *
var wel;

wel = $('mydiv');
if (wel._.childNodes.length == 0)
{
wel.update('<p class="note">No content</p>");
}
* * * *

...but that gets pretty cryptic pretty quickly. Mind you, the code
does look a bit like it's smiling at us. ;-)

All told, I think "raw" makes good sense.
--
T.J. Crowder
tj / crowder software / com

kangax

unread,
Aug 19, 2008, 10:56:37 AM8/19/08
to Prototype: Core
On Aug 19, 2:34 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> > > var el = new Prototype.Element(element);
> > > el.raw, or el.obj, or el.wrapped, or el.source.
> > > I kinda dig el.source
>
> > `source` sounds good.
>
> It's not very important, but I can't say "source" appeals.  I think it
> will be confusing in discussion.  The underlying element is as much a
> target as a source, and source is already an overloaded word.  When
> you call item.update(), the target (not source) of the update is the
> underlying element.  In terms of it being overloaded, suppose you have
> a container element and you use select() to get an 'item' element from
> somewhere within it.  At that point, it's easy (but wrong) to read
> "item.source" as meaning the container, since you got the item "from"
> the container.
>
> "raw" has grown on me.  It's not overloaded, and it's pretty clear in
> conversation: "Q. How do I get the tag name of a wrapped element?   A.
> From the raw element it's wrapping.  Q. How do I get the real nodelist
> from a wrapper?  A. Use the 'raw' property to get the raw nodelist."
> It's also short (we'll be using this a lot).  The biggest negative I
> see to it is that it's typed entirely with the left hand (if you're a
> touch typist).

I was thinking of `node`/`nodeList` or `element`/`elements` but those
wouldn't make for a unified interface. `raw`, `source` and `target`
all sound intuitive, imo, though `row` seems a little weird : )
On the other hand, if we have a common property for accessing "origin"
of both `Node` and `NodeList` wrappers, shouldn't it be of the same
type? Having same-named property, and not knowing (easily) what it is,
doesn't seem convenient:

function doStuff(wrapper){
// exit if not a wrapper
if (!wrapper || (wrapper && !wrapper.raw)) return;
// we can try `length` to make sure it's a NodeList, but what if
some DOMElement exposes length property?
if (wrapper.raw.length) {
// do something with the entire collection
} else { // assume it's a Node
// do something with it
}
};

vs:

function doStuff(wrapper) {
if (!wrapper) return;
if (wrapper.__node) {
// definitely a Node - `__node` property is present and is not
falsy
} else if (wrapper.__nodeList) {
// definitely a NodeList - `__nodeList` property is present and is
not falsy
}
// ignore anything else
}

Thoughts?

--
kangax

T.J. Crowder

unread,
Aug 19, 2008, 11:40:51 AM8/19/08
to Prototype: Core
> On the other hand, if we have a common property for accessing "origin"
> of both `Node` and `NodeList` wrappers, shouldn't it be of the same
> type?

I don't see any reason it does, no. My reason for suggesting a
unified term was mostly for the convenience of users of the API, not
primarily so the same code could be used on multiple wrappers. E.g.,
so users don't have to remember that to get the underlying Element
from a WrappedElement it's "wrapper.element" but to get the underlying
Event from a WrappedEvent it's "wrapper.event" and to get the
underlying NodeList from a WrappedNodeList it's
"wrapper.nodeList" (not "wrapper.list" or "wrapper.nodelist" -- you
see where I'm going with this). Instead, if you have a wrapper, you
know you can get at the raw object via the ".raw" property regardless
of what kind of wrapper it is. Sure, people _can_ remember the
various properties, I'm just saying it's easier to remember one.

Separately, my reason for suggesting something short is that we'll use
it a *lot*, so it should be short as reasonably possible.
--
T.J. Crowder
tj / crowder software / com

John-David Dalton

unread,
Aug 19, 2008, 12:03:48 PM8/19/08
to Prototype: Core
@kangax

Whatever the name is I think it should be used in all wrappers.
This makes it consistent.

If you want to check if a wrapper is of a certain instance.
if( wrapper instanceof Prototype.Node ) {
//wrapper.raw
}
else if (wrapper instanceof Prototype.NodeList) {
//wrapper.raw
}
...

I like using the get/set methods
element.get('tagName');
element.set('disabled', 'disabled');
might blur the line between attribute and property though.

- JDD

John-David Dalton

unread,
Aug 19, 2008, 12:35:31 PM8/19/08
to Prototype: Core
More on the get/set

Maybe get/set are more generalized
Get: would first check the property and then the attribute;
Set: would use the element['somePropOrAttrib'] = value;
instead of setAttribute and let the browser figure it out.

If you want finer grain then you can use
getProperty() and getAttribute,
setProperty() and setAttribute,

Most though would be fine using get/set
and letting Prototype figure it out.

- JDD



Johan McCain

unread,
Aug 19, 2008, 1:09:15 PM8/19/08
to Prototype: Core
Wrapping elements with divs seems like overkill to me. It will break
layouts and give unexpected results when you have some css like:

.myclass div { margin-left: 10px; padding: 5px;}

People will be left wondering why all of a sudden their layouts are
screwed because everything doubles. I'm not saying this is the right
way to do css but it's a valid one, something you'll end up with
wrapping elements. I haven't spend much thought on an alternative to
wrapping elements yet but perhaps there is a better alternative.

--
Johan

kangax

unread,
Aug 19, 2008, 3:18:02 PM8/19/08
to Prototype: Core
On Aug 19, 12:03 pm, John-David Dalton <John.David.Dal...@gmail.com>
wrote:
> @kangax
>
> Whatever the name is I think it should be used in all wrappers.
> This makes it consistent.
>
> If you want to check if a wrapper is of a certain instance.
> if( wrapper instanceof Prototype.Node ) {
> //wrapper.raw}
>
> else if (wrapper instanceof Prototype.NodeList) {
> //wrapper.raw}
>

Good point about `instanceof`. That should work (in a context of one
document).

--
kangax

kangax

unread,
Aug 19, 2008, 3:26:21 PM8/19/08
to Prototype: Core
On Aug 19, 12:35 pm, John-David Dalton <John.David.Dal...@gmail.com>
wrote:
John,
I don't think we should let prototype figure it out. Honestly, I don't
see a reason to mix attributes with properties. There's a
`readAttribute`/`writeAttribute` interface (for attributes), so we
only need the one for properties - `get`/`set` or `getProperty`/
`setProperty`.

I also think making setters support accepting function could make for
a much more flexibility. A function would be called with a current
value of a property (that's being acted upon) and its result would be
the one that is assigned to a property:

$W('myElement').set('innerHTML', function(value){ return value + '.
The end...'; });

--
kangax

John-David Dalton

unread,
Aug 20, 2008, 9:42:05 AM8/20/08
to Prototype: Core
Johan, when we say "wrapper" we don't mean a div in HTML.
We mean instead of extending the native DOM elements
$(element) -> returns an extended dom element
$(element) -> returns an instance of a ElementWrapper class
the class interacts with the element but does not extend it directly.

see kangax's new thread:
http://groups.google.com/group/prototype-core/browse_thread/thread/c05add9fabd5a00c

- JDD
Reply all
Reply to author
Forward
0 new messages