PDA

View Full Version : Javascript extensions



mschwartz
9 Jul 2009, 5:55 AM
I think this is a quite interesting subject and issue due to namespace collisions.

If you include something besides ext-base.js (e.g. jQuery, etc.), any of those might muck with the builtin Javascript type prototypes. I'm talking about Array, Object, Number, String, etc.

I note in the 3.0 API docs (and checked the source, too) that Ext extends the Array prototypes to add indexOf() and remove(). In theory, jQuery might someday (if it doesn't now) add its own indexOf() and/or remove() methods that do something vital to jQuery's functionality. Ext would be breaking that functionality.

So maybe Ext should chain to that function or only apply its prototype if there isn't an existing one. Or perhaps we developers should selectively be able to pick and choose which extensions to the base prototypes get installed.

It may not be a problem now, but we're really in the infancy of where Javascript is headed and it'd be good to plan ahead.

All that aside, why not do a more complete/better job of providing additions to the base functionality? I suggest:

ARRAY
contains(val)
union(otherarray)
intersection(otherarray)
clone()
toJson() -> calls Ext.encode
toXML() -> hrm...
dump(html:boolean)
-> returns var_dump() (see php) type string representing the object as boolean or plain text
each(fn(item))
Array subset(fn(item))

DATE
getTimeSpan(otherDate)
-> returns something like "10 minutes from now" or "10 minutes ago"
toUTC()

NUMBER
format(format_string)
toLocaleString() -> returns "10,000" or whatever those funky european formats are
...and perhaps all the Math.* functions: abs(), floor(), ceil(), etc.

OBJECT
clone()
toJson()
toXML()
dump(html:boolean)
merge(otherobject)

STRING
isURL()
isDate()
Date toDate()
isHexColor()
toHexColor() -> converts "#ff00ff" to "rgb(255,0,255)" (the leading # is optional)
fromHexColor() -> rgb(255,0,255) => "ff00ff"
isAlphanumeric()
isAlpha()
isNumeric()
isEmail()
capitalize()
capitalizeWords()
stripTags()
addslashes()
head(n)
tail(n)
md5()
toBase64()
fromBase64()
startsWith(str)
endsWith(str)
contains(str)
nlbr() -> replaces \n or \r\n or \r with <br/>
Object fromJson()

jay@moduscreate.com
9 Jul 2009, 5:59 AM
dump(html:boolean) is silly, especially when you have debugging tools.

mschwartz
9 Jul 2009, 6:04 AM
dump(html:boolean) is silly, especially when you have debugging tools.

Not always guaranteed to have debugging tools. Consider server-side or a variety of lesser known/used browsers.

I'd add to my list:
String.cleans() -> converts < to &lt; and other sanitizations to make some HTML you might render safe[r] from XSS type attacks.

jay@moduscreate.com
9 Jul 2009, 6:16 AM
when do you use var_dump for (production) apps, except for debugging?

jay@moduscreate.com
9 Jul 2009, 6:17 AM
i would add Array.prototype.each

mschwartz
9 Jul 2009, 6:21 AM
when do you use var_dump for (production) apps, except for debugging?

I actually do use it in this commercial App I've been working on. There's a set of troubleshooting tools that the end user can use to help us figure out issues their facing; one of these tools does var_dump() of certain objects rendered in a popup window or tab.

Likewise you might post the var_dump() to the server and have it email tech dept. with info about the error, including the var_dump() output.

jay@moduscreate.com
9 Jul 2009, 6:27 AM
Array.each ==



Ext.apply(Array.prototype, {
each : function(fn, scope) {
for(var i = 0, len = this.length; i < len; i++){
if(fn.call(scope || this[i], this[i], i, this) === false){
return i;
};
}

}
});

var myArray = ['Toyota', 'Honda', 'Nissan'] ; // no american love!

myArray.each(function(element) {
console.info(element)
});

mschwartz
9 Jul 2009, 6:36 AM
I added each() and subset() to Array in the first post.

subset() returns an array of items where the callback returns true

it should probably take an optional 2nd parameter, clone:boolean, so it can return an array of cloned items.

jay@moduscreate.com
9 Jul 2009, 6:38 AM
you'd need scope - don't forget scope :)

dj
9 Jul 2009, 6:41 AM
Many JavaScript engines have Array.prototype.indexOf built-in, Array.prototype.remove is - IMHO - quite constant in its meaning. I.e. if there will be a built-in Array.prototype.remove it will have the same semantics as the one that Ext provides.

Both, indexOf and remove, will only be applied to the Array prototype if there are not already present. (Via Ext.applyIf)

Adding functions to the Object prototype is evil, because it will break code like this:


var obj = { a:'a', b:'b' };
for (var i in obj) alert(i);
It's not how one should write such a for-loop but nevertheless many do write it without checking hasOwnProperty.


IMHO adding stuff to the built-in prototypes makes it difficult to use Ext concurrently with other frameworks and thus should be avoided.

mschwartz
9 Jul 2009, 6:44 AM
scope... good!

This brings me to another observation.

Why should any subroutine ever take arguments other than something resembling a config object?

If the function prototype looks like:

each: function(fn, scope, clone) {

scope is optional, as is clone. If you only want to pass fn and clone, you have to call it something like:

array.each(myfn, undefined, true);

Whereas with a config object style pattern:



each : function(config) {
if (config.fn) {
for(var i = 0, len = this.length; i < len; i++){
if(config.fn.call(config.scope || this[i], this[i], i, this) === false){
return i;
};
}
}
}
called:

array.each({ fn: function(...) {...}, clone: true });

Reminds me of vararg/tags style functions in C for openlook and other toolkits.

Of course, the code should really look like this:


each : function(config) {
if (config.fn) {
for(var i = 0, len = this.length; i < len; i++){
if(config.fn.call(config.scope || this[i], { val1: this[i], index: i, val2: this, clone: config.clone}) === false){
return i;
};
}
}
}

jay@moduscreate.com
9 Jul 2009, 6:45 AM
i personally only use Ext, so some of these work for me.

jay@moduscreate.com
9 Jul 2009, 6:48 AM
Why should any subroutine ever take arguments other than something resembling a config object?


Simply put, creating an object for three references is wasteful. Why carve out portion of memory for an object if the property list is so small?

The purpose of using objects as arguments serves a few purposes, one of which doesn't make sense.

- They allow for unordered list of properties - makes sense, but is irrelevant for a method that accepts two or 3 arguments
- They allow for any number of properties or references to be passed as a single argument. - for two or three properties (fn, scope, clone), this is not really relevant.

mschwartz
9 Jul 2009, 6:49 AM
Many JavaScript engines have Array.prototype.indexOf built-in, Array.prototype.remove is - IMHO - quite constant in its meaning. I.e. if there will be a built-in Array.prototype.remove it will have the same semantics as the one that Ext provides.

Both, indexOf and remove, will only be applied to the Array prototype if there are not already present. (Via Ext.applyIf)

Adding functions to the Object prototype is evil, because it will break code like this:


var obj = { a:'a', b:'b' };
for (var i in obj) alert(i);
It's not how one should write such a for-loop but nevertheless many do write it without checking hasOwnProperty.


IMHO adding stuff to the built-in prototypes makes it difficult to use Ext concurrently with other frameworks and thus should be avoided.



remove: function(item) {
Ext.ComponentMgr.unregister(item);
... // do rest of remove functionality
}

jay@moduscreate.com
9 Jul 2009, 6:50 AM
btw, referencing obj.property, vs property is much slower.

jay@moduscreate.com
9 Jul 2009, 6:51 AM
remove: function(item) {
Ext.ComponentMgr.unregister(item);
... // do rest of remove functionality
}



ComponentMgr is irrelevant when you're talking about modifying the prototypes of the primitives of the language :)

mschwartz
9 Jul 2009, 6:55 AM
ComponentMgr is irrelevant when you're talking about modifying the prototypes of the primitives of the language :)

We might think so, but Resig might not. The beauty of jQuery is that he figured out to cache DOM lookups so 2nd thru nth lookups on the same things are much faster than walking the DOM again. Seems like a trick in remove() someone might pull someday.

Obviously, my example is contrived, but Ext.ComponentMgr.unregister() is smart enough to do nothing if what's being removed isn't registered...

mschwartz
9 Jul 2009, 6:58 AM
btw, referencing obj.property, vs property is much slower.

Agreed, so maybe just the optional arguments should be config-style objects.

I'm not sure what JS does under the hood, but it may be doing obj.property anyway. At least something like:

function foo(a,b,c) {

=>

a = arguments.a;
b = arguments.b;
c = arguments.c;

hendricd
9 Jul 2009, 7:27 AM
Array.each ==

Need to be careful here! Gecko already supports the each prototype on Arrays, but not with the Ext-style exit behavior:



Ext.applyIf(Array.prototype, {
each : function(fn, scope) {
for(var i = 0, len = this.length; i < len; i++){
if(fn.call(scope || this, this[I], i, this) === false){[I]return i;
};
}

}
});

The Ext.applyIf strategy is used throughout the Ext.core when asserting prototype extensions.

ext-basex, in fact, adds the following primitives (if not already defined) for mostly IE's benefit since most modern browser already support these:

String:
forEach , trim , trimRight, trimLeft

Array:
forEach, map, some, every, include, filter, compact, flatten, indexOf, lastIndexOf, unique, grep, first, last, clear, atRandom

Function:
forEach (iterate custom class members, non-prototype members)

and adds clone support to all primitives.

It adds a global forEach as well, that iterates anything (similar to Ext 3's new Ext.iterate).

jay@moduscreate.com
9 Jul 2009, 7:32 AM
Need to be careful here! Gecko already supports the each prototype on Arrays, but not with the Ext-style exit behavior:


Of course, this was just an example.
Btw, Fx 3.0.11 does not have Array.each

X=[1,2,3];


X.each()

hendricd
9 Jul 2009, 7:37 AM
Of course, this was just an example.
Btw, Fx 3.0.11 does not have Array.each

X=[1,2,3];


X.each()

No, but it does have [1,2,3].forEach(fn, scope);

jay@moduscreate.com
9 Jul 2009, 7:40 AM
[1,2,3].forEach();

TypeError: [1, 2, 3].forEach is not a function

maybe this is a firebug thing.

http://www.petdoctors.co.uk/upload/image/thumb_300_27_cat-hiding.jpg