Various DOM-related wrappers (Code Worth Recommending Project)

P

Peter Michaux

[snip]


As granularity is increased the amount of code served to the client
that is not necessary for a given page is also increased. This is the
bloat problem that all of the popular libraries seem to have.

Do you mean when granularity is decreased?

I mean bigger grains of sand :)
By increasing granularity,
I mean breaking the code base up into smaller morsels, thereby
reducing the downloading of unused code.

yes.
 
D

David Mark

On Dec 9, 12:45 pm, Peter Michaux <[email protected]> wrote:
I have always tested manually and it is time-consuming.

Very and especially when testing on a wide set of browsers. I think
the code in this repository should be tested on a wider set than the
following which took me days manually.

<URL:http://forkjavascript.org/welcome/browser_support>

[snip]
This automated testing suite should help that. But if only one person
is handling documentation and everybody else is adding and
scrutinizing code, then a bottleneck will always exist.

I think some pacing is needed anyway. In just couple weeks from now we
can have a full Ajax module and that is a substantial component to any
code base.

Given what we've already discussed in the various threads, I would
like to shoot for the following for the first milestone.

* DHTML
setOpacity
* DOM queries
getEBI
getEBTN
getEBCN (getElementsByClassName)

Let me know when you want to start discussing this one. I'll have to
disengage mine from a more generalized XPath wrapper (or perhaps it
would make sense to discuss an XPath wrapper first?)
getEBCS (getElementsByCSSSelector) (a very simple one)

I take it you already have this.
* Ajax
form serialization
XHR
* automated testing
* parsable documentation

This is definitely a must. I've got some ideas for a database schema
for the build process.
We will need to have threads on Ajax and documentation over the next
while when the current threads have died down.

That is sufficient for the first round and enough to reflect on how
the process is going and if things need to be changed.

After that I will certainly make my own build process and start using
the code where I can in my own work.

Be sure to share that. Do you plan to use a database?
I hope so!

I have already learned enough to justify my time invested.

Certainly it has helped me improve a few of my modules.
I'm not so concerned with that and that requires marketing which I
learned I am not interested in doing.

Me either. However, I think the viral spread of a "better mousetrap"
for browser scripting will be inevitable. I recently read a
completely ridiculous article titled "Top Ten JavaScript Functions of
All Time" and noted that despite its virtually useless content, it was
being echoed all over the place on blogs, shared bookmark sites, etc.
I'm interested to see if "Code Worth Recommending" is deemed to be
code worth recommending by many comp.lang.javascript contributors.

Same here, at least for those contributors whose opinions have not
been deemed to be worthless.
 
D

David Mark

So you recognized it was at least moderately paranoid ;-)

Absolutely. That's why I asked Richard what he thought about it. He
didn't seem to consider it excessive, so I kept it.
I try to keep all source files under 1K. Programmers have a very
difficult time agreeing on granularity.

If I did that, I'd have nearly 200 files and far too many dependencies
to document.
 
P

Peter Michaux

Very and especially when testing on a wide set of browsers. I think
the code in this repository should be tested on a wider set than the
following which took me days manually.
I can't keep up on more than is going on right now. I didn't think
This automated testing suite should help that. But if only one person
is handling documentation and everybody else is adding and
scrutinizing code, then a bottleneck will always exist.
I think some pacing is needed anyway. In just couple weeks from now we
can have a full Ajax module and that is a substantial component to any
code base.
Given what we've already discussed in the various threads, I would
like to shoot for the following for the first milestone.
* DHTML
setOpacity
* DOM queries
getEBI
getEBTN
getEBCN (getElementsByClassName)

Let me know when you want to start discussing this one. I'll have to
disengage mine from a more generalized XPath wrapper (or perhaps it
would make sense to discuss an XPath wrapper first?)
getEBCS (getElementsByCSSSelector) (a very simple one)

I take it you already have this.

It is very simple (~30 lines). Since this thread has been about DOM
query functions, I'll post it in this thread shortly.

This is definitely a must. I've got some ideas for a database schema
for the build process.

I believe build process is out of the scope of the project but the
project code should be flexible enough to allow for many build
processes. All along, I've not been sure why you need a database. I'm
probably just going to concatenate files.

Be sure to share that. Do you plan to use a database?

I am definitely not using a database. It just doesn't seem that
complex to me to make a list of files that require concatenation. I
may use a simple templating system.

My build process will add an API that I like with one level of
"namespacing". Since this doesn't affect the correctness of algorithms
I really don't see the chance that group members will agree or that
the discussion will be productive.

[snip]
 
D

David Mark

I have always tested manually and it is time-consuming.
Very and especially when testing on a wide set of browsers. I think
the code in this repository should be tested on a wider set than the
following which took me days manually.
<URL:http://forkjavascript.org/welcome/browser_support>
[snip]
I can't keep up on more than is going on right now. I didn't think
This automated testing suite should help that. But if only one person
is handling documentation and everybody else is adding and
scrutinizing code, then a bottleneck will always exist.
I think some pacing is needed anyway. In just couple weeks from now we
can have a full Ajax module and that is a substantial component to any
code base.
Given what we've already discussed in the various threads, I would
like to shoot for the following for the first milestone.
* DHTML
setOpacity
* DOM queries
getEBI
getEBTN
getEBCN (getElementsByClassName)
Let me know when you want to start discussing this one. I'll have to
disengage mine from a more generalized XPath wrapper (or perhaps it
would make sense to discuss an XPath wrapper first?)
I take it you already have this.

It is very simple (~30 lines). Since this thread has been about DOM
query functions, I'll post it in this thread shortly.
This is definitely a must. I've got some ideas for a database schema
for the build process.

I believe build process is out of the scope of the project but the
project code should be flexible enough to allow for many build
processes. All along, I've not been sure why you need a database. I'm
probably just going to concatenate files.

I was thinking in terms of a generalized, automated build process that
would choose the proper versions of each function, based on
compatibility requirements. Personally, I also just concatenate
files.

[snip]
My build process will add an API that I like with one level of
"namespacing". Since this doesn't affect the correctness of algorithms
I really don't see the chance that group members will agree or that
the discussion will be productive.

Probably not, but I agree that a "flat" namespace is the best way to
go.
 
P

Peter Michaux

I have always tested manually and it is time-consuming.
Very and especially when testing on a wide set of browsers. I think
the code in this repository should be tested on a wider set than the
following which took me days manually.
<URL:http://forkjavascript.org/welcome/browser_support>
[snip]
I can't keep up on more than is going on right now. I didn't think
This automated testing suite should help that. But if only one person
is handling documentation and everybody else is adding and
scrutinizing code, then a bottleneck will always exist.
I think some pacing is needed anyway. In just couple weeks from now we
can have a full Ajax module and that is a substantial component to any
code base.
Given what we've already discussed in the various threads, I would
like to shoot for the following for the first milestone.
* DHTML
setOpacity
* DOM queries
getEBI
getEBTN
getEBCN (getElementsByClassName)
Let me know when you want to start discussing this one. I'll have to
disengage mine from a more generalized XPath wrapper (or perhaps it
would make sense to discuss an XPath wrapper first?)
getEBCS (getElementsByCSSSelector) (a very simple one)
I take it you already have this.
It is very simple (~30 lines). Since this thread has been about DOM
query functions, I'll post it in this thread shortly.
I believe build process is out of the scope of the project but the
project code should be flexible enough to allow for many build
processes. All along, I've not been sure why you need a database. I'm
probably just going to concatenate files.

I was thinking in terms of a generalized, automated build process that
would choose the proper versions of each function, based on
compatibility requirements. Personally, I also just concatenate
files.

If everyone using the library just concatenates then I sure would
spend the time on an elaborate system. I say let the person who wants
the elaborate system build it exactly to his liking.
 
P

Peter Michaux

Below is a very simple getElementsByCssSelector function. I think that
selector functions that can do more are more bulk to download than
necessary. I am not opposed to a fancier one being in the repository
but it will require a huge amount of testing and performance
profiling. If anyone wants to build such a thing I have some code that
can make a fast function. This type of function is a perfect candidate
for the multiple implementations approach.

There is a w3c spec for a css selector like document.getElementById in
the works so this may be a good approach to adopt.

// s is a subset of the CSS simple selector syntax.
// Examples
// div
// #foo
// .bar
// div#foo
// div.bar
// #foo.bar
// .bar#foo
// div#foo.bar
// div.bar#foo
// If a tagName is used it must be first.
// No whitespace tolerated
//
// d is the optional document or element object
//
// always returns an array
//
// If you are simply using getEBCS('#foo') then
// using this function is overkill and
// it is faster to use getEBI('foo')

if (typeof getEBI != 'undefined' &&
typeof getEBTN != 'undefined' &&
// match appears first in ECMAScript in v3 so test
String.prototype.match) {

var getEBCS = function(s, d) {
var m, // regexp matches and temp var in loop
tn, // tagName in s
id, // id in s
cn, // className in s
els, // candidate elements for return
ns, // elements to return
i, // loop index
ilen, // loop limit
el; // temp element variable

m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : '*';

m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;

m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;

// If d.documentElement then know d is a document object.
// Need this because getEBI only takes document object
// as optional second argument but getEBCS takes any
// element or a document as second argument.
els = (id && (!d || d.documentElement)) ?
((el=getEBI(id, d)) ? [el] : []) :
getEBTN(tn, d);

ns = [];
for (i=0, ilen=els.length; i<ilen; ++i) {
el = els;
// Could call an external hasClassName function but in line
// it here for efficiency. There are no cross browser issue
// with checking className.
if ((!cn ||
((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ')>-1)) &&
// If tn != '*' then el necessarily has tagname property.
(tn == '*' || el.tagName.toLowerCase() == tn) &&
(!id || el.id == id)) {
ns[ns.length] = el;
}
}
return ns;
};

}

No checks for toLowerCase and indexOf because they are so old.

If the indexOf trick for checking classname looks weird it is used
because it is much faster than a regexp. Since this is in a loop speed
counts.

getElementsByClassName and getElementsByCssSelector functions are both
sugar on top of what the browser offers. Given the limited scope of
what a getElementsByClassName function can do I think it would be best
to just implement it as one of the getElementsByCssSelector functions.
The expense is just removing a preceeding dot in the call. For
example, getEBCS('.bar') instead of getEBCN('bar'). This also saves
one more function in the repository API and I am all for that!

Here is the getEBCS that can get elements only by class name

if (typeof getEBTN != 'undefined') {

var getEBCS = function(s, d) {
var m, // regexp matches and temp var in loop
i, // loop index
ilen, // loop limit
el, // temp element variable
ns = [], // elements to return
cn = s.substring(1), // className in s
els = getEBTN('*', d); // candidate elements for return

for (i=0, ilen=els.length; i<ilen; ++i) {
el = els;
// Could call an external hasClassName function but in line
// it here for efficiency. There are no cross browser issue
// with checking className.
if ((m = el.className) &&
(' ' + m + ' ').indexOf(' ' + cn + ' ') >- 1) {
ns[ns.length] = el;
}
}
return ns;
};

}
 
T

Thomas 'PointedEars' Lahn

Peter said:
Have you ever seen a host where where document.getElementById is not
callable?

Several existing (and used) UAs do not support the W3C DOM, but I thought I
had made myself clear already. I can also easily create you a fitting UA,
and that does not involve (re)compiling its source code. With client-side
scripting, and especially host objects, all bets are off. The sooner you
realize this, the earlier your client-side scripts will become more
interoperable and less error-prone.
If you are concerned that someone might send a "d" that is not a
document then they will have violated what will be the documentation
saying "d" must be a document.

`d' may be a a reference to a "document" object and still does not need to
provide that method. You assume a forced commonality where none exists.


PointedEars
 
D

David Mark

Below is a very simple getElementsByCssSelector function. I think that
selector functions that can do more are more bulk to download than
necessary. I am not opposed to a fancier one being in the repository

No question. Functions that enable complex CSS selector queries take
up space for no good reason. When you look at the samples provided
with such functions, it is apparent that they will break if the markup
or related styles are changed in the slightest. In other words,
changing a className, changing the order or nesting level of elements,
etc. requires scrutinizing all of the selector queries in associated
scripts. To me, this is completely contrary to the notion of
separation of markup, presentation and behavior. In fact, it tangles
all three of them together.
but it will require a huge amount of testing and performance

No question there either.
profiling. If anyone wants to build such a thing I have some code that
can make a fast function. This type of function is a perfect candidate
for the multiple implementations approach.

I've got to mention that if we are going to allow something like that
in, then an extra gEBI (and getAnElement) function with a couple of
extra lines doesn't seem like it should qualify as an issue.
There is a w3c spec for a css selector like document.getElementById in
the works so this may be a good approach to adopt.

Might as well create a wrapper for it now. But I wonder how complex
the w3c's version will be. If we can't duplicate everything it does,
then the wrapper might confuse people. It might be better to stick
with gEBCN.
// s is a subset of the CSS simple selector syntax.
// Examples
// div
// #foo
// .bar
// div#foo
// div.bar
// #foo.bar
// .bar#foo
// div#foo.bar
// div.bar#foo
// If a tagName is used it must be first.
// No whitespace tolerated
//
// d is the optional document or element object
//
// always returns an array
//
// If you are simply using getEBCS('#foo') then
// using this function is overkill and
// it is faster to use getEBI('foo')

if (typeof getEBI != 'undefined' &&
typeof getEBTN != 'undefined' &&
// match appears first in ECMAScript in v3 so test
String.prototype.match) {

I don't test the JS 1.2 String (or RegExp) methods. I think we can
safely draw the line there.
var getEBCS = function(s, d) {
var m, // regexp matches and temp var in loop
tn, // tagName in s
id, // id in s
cn, // className in s
els, // candidate elements for return
ns, // elements to return
i, // loop index
ilen, // loop limit
el; // temp element variable

m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : '*';

m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;

m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;

// If d.documentElement then know d is a document object.
// Need this because getEBI only takes document object
// as optional second argument but getEBCS takes any
// element or a document as second argument.
els = (id && (!d || d.documentElement)) ?
((el=getEBI(id, d)) ? [el] : []) :
getEBTN(tn, d);

That seems like an iffy test for a document node. It would be more
robust to test if it is not an element node (see the gEBTN wrapper.)
ns = [];
for (i=0, ilen=els.length; i<ilen; ++i) {

Does it not make sense to use while (--i) and reverse the results
after the loop? At least for large numbers of nodes, it would seem
more efficient.
el = els;
// Could call an external hasClassName function but in line
// it here for efficiency. There are no cross browser issue
// with checking className.
if ((!cn ||
((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ')>-1)) &&


Would this be a more efficient test?

// Outside of the loop
re = new RegExp('(^|\\s)' + cn + '(\\s|$)')

re.test(el.className)
// If tn != '*' then el necessarily has tagname property.

Right. It should be noted that if one uses the unfiltered gEBTN
version, then this function may return non-element nodes.
(tn == '*' || el.tagName.toLowerCase() == tn) &&
(!id || el.id == id)) {
ns[ns.length] = el;
}
}
return ns;
};

}

No checks for toLowerCase and indexOf because they are so old.

If the indexOf trick for checking classname looks weird it is used
because it is much faster than a regexp. Since this is in a loop speed
counts.

Is the extra string concatenation really faster than testing with a
RegExp? If so, I need to re-think my use of the test method in some
cases.

Speaking of speed. I have an XPath version of this. It is tangled up
in a general-purpose query function that has a few more options (e.g.
checking for the presence of attributes.) But it wouldn't take too
much effort to untangle it and add it as a branch to this.
getElementsByClassName and getElementsByCssSelector functions are both
sugar on top of what the browser offers. Given the limited scope of
what a getElementsByClassName function can do I think it would be best
to just implement it as one of the getElementsByCssSelector functions.

I think it makes sense to include it as a one-liner that calls gEBCS.
The expense is just removing a preceeding dot in the call. For
example, getEBCS('.bar') instead of getEBCN('bar'). This also saves
one more function in the repository API and I am all for that!

Either way. I just think it is more readable to use
getElementsByClassName. And isn't that an upcoming w3c function as
well?
Here is the getEBCS that can get elements only by class name

I would leave this one out. But I guess if one is only interested in
querying by className, this would provide a slightly smaller
alternative
if (typeof getEBTN != 'undefined') {

var getEBCS = function(s, d) {
var m, // regexp matches and temp var in loop

It is only the latter in this function.
i, // loop index
ilen, // loop limit
el, // temp element variable
ns = [], // elements to return
cn = s.substring(1), // className in s
els = getEBTN('*', d); // candidate elements for return

for (i=0, ilen=els.length; i<ilen; ++i) {
el = els;
// Could call an external hasClassName function but in line
// it here for efficiency. There are no cross browser issue
// with checking className.
if ((m = el.className) &&
(' ' + m + ' ').indexOf(' ' + cn + ' ') >- 1) {
ns[ns.length] = el;
}
}
return ns;
};

}
 
P

Peter Michaux

No question. Functions that enable complex CSS selector queries take
up space for no good reason. When you look at the samples provided
with such functions, it is apparent that they will break if the markup
or related styles are changed in the slightest. In other words,
changing a className, changing the order or nesting level of elements,
etc. requires scrutinizing all of the selector queries in associated
scripts. To me, this is completely contrary to the notion of
separation of markup, presentation and behavior. In fact, it tangles
all three of them together.


No question there either.


I've got to mention that if we are going to allow something like that
in, then an extra gEBI (and getAnElement) function with a couple of
extra lines doesn't seem like it should qualify as an issue.

The issue isn't a few extra lines in gEBI. The issue for me is that
there needs to be a set of guidelines that are followed when deciding
how to code for the repository. A rule like "support IE4 when it is
easy" is an ambiguous rule. I want the guidelines to be as black and
white as possible. I've never seen any library with a set of design
guidelines that can be pointed to when someone says "why didn't you
test for that?" I want a set of such guidelines so there is no need to
deal with those questions by humming and hawing about how I was
feeling about IE4 on a particular day.

I know the IE4 support issue will be a fun reoccurring issue. I don't
mind. I imagine that IE4 support is only an issue for a few members of
comp.lang.javascript and almost no one else in the world. It is easy
enough to maintain your own gEBI and getEBTN. I know there will be
some things I will have to maintain for my particular situation and
they won't go into the repository. That's ok.

Might as well create a wrapper for it now. But I wonder how complex
the w3c's version will be.

I imagine at least full CSS2 support given libraries like jQuery have
a big set of CSS2 support.

If we can't duplicate everything it does,
then the wrapper might confuse people. It might be better to stick
with gEBCN.

We already have wrappers that don't completely match what they are
supposed to wrap. For example, the gEBI wrapper you suggested that
checks the name/id problem but doesn't walk the DOM is somewhat
similar in my mind.

I don't test the JS 1.2 String (or RegExp) methods. I think we can
safely draw the line there.

The tables are turned and now I want to check something you don't :)

Given ECMAScript v3 is the most recent spec I thought we should
probably check anything new in it.

var getEBCS = function(s, d) {
var m, // regexp matches and temp var in loop
tn, // tagName in s
id, // id in s
cn, // className in s
els, // candidate elements for return
ns, // elements to return
i, // loop index
ilen, // loop limit
el; // temp element variable
m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : '*';
m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;
m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;
// If d.documentElement then know d is a document object.
// Need this because getEBI only takes document object
// as optional second argument but getEBCS takes any
// element or a document as second argument.
els = (id && (!d || d.documentElement)) ?
((el=getEBI(id, d)) ? [el] : []) :
getEBTN(tn, d);

That seems like an iffy test for a document node. It would be more
robust to test if it is not an element node (see the gEBTN wrapper.)

Please post a replacement for the above line.

ns = [];
for (i=0, ilen=els.length; i<ilen; ++i) {

Does it not make sense to use while (--i) and reverse the results
after the loop? At least for large numbers of nodes, it would seem
more efficient.

I haven't tested this system. Is it really faster? By how much?

el = els;
// Could call an external hasClassName function but in line
// it here for efficiency. There are no cross browser issue
// with checking className.
if ((!cn ||
((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ')>-1)) &&


Would this be a more efficient test?

// Outside of the loop
re = new RegExp('(^|\\s)' + cn + '(\\s|$)')

re.test(el.className)


Using a regular expression is much slower. I tested this. It is a big
difference.

Right. It should be noted that if one uses the unfiltered gEBTN
version, then this function may return non-element nodes.

But this is not a worry here as gEBTN will always return elements with
the correct tagName if it is sent a tagName (i.e. not sent '*'). I
believe the next line is fine even with the unfiltered gEBTN and
el.tagName.toLowerCase will never throw a "no properties" error.

(tn == '*' || el.tagName.toLowerCase() == tn) &&
(!id || el.id == id)) {
ns[ns.length] = el;
}
}
return ns;
};

No checks for toLowerCase and indexOf because they are so old.
If the indexOf trick for checking classname looks weird it is used
because it is much faster than a regexp. Since this is in a loop speed
counts.

Is the extra string concatenation really faster than testing with a
RegExp? If so, I need to re-think my use of the test method in some
cases.

Much faster. Give it a try.

Where I learned this is from the EXT css selector which is the fastest
of the popular ones. I looked in it and was amazed to see the string
concatenation.

Speaking of speed. I have an XPath version of this. It is tangled up
in a general-purpose query function that has a few more options (e.g.
checking for the presence of attributes.) But it wouldn't take too
much effort to untangle it and add it as a branch to this.

Please do post that! I have never looked into this but want to learn
about.

I think it makes sense to include it as a one-liner that calls gEBCS.

I really don't mind either way (thanks to automated testing! :D). So
you are suggesting the following in two files?

getEBCN = function(cn, d) = {
/* lots of code*/
};

getEBCS = function(s, d) = {
return getEBCN(s.substring(1), d);
};
Either way. I just think it is more readable to use
getElementsByClassName. And isn't that an upcoming w3c function as
well?

I believe it is.

I would leave this one out.

It will just be the "one-liner" above.
But I guess if one is only interested in
querying by className, this would provide a slightly smaller
alternative


if (typeof getEBTN != 'undefined') {
var getEBCS = function(s, d) {
var m, // regexp matches and temp var in loop

It is only the latter in this function.
Indeed.
i, // loop index
ilen, // loop limit
el, // temp element variable
ns = [], // elements to return
cn = s.substring(1), // className in s
els = getEBTN('*', d); // candidate elements for return
for (i=0, ilen=els.length; i<ilen; ++i) {
el = els;
// Could call an external hasClassName function but in line
// it here for efficiency. There are no cross browser issue
// with checking className.
if ((m = el.className) &&
(' ' + m + ' ').indexOf(' ' + cn + ' ') >- 1) {
ns[ns.length] = el;
}
}
return ns;
};

 
P

Peter Michaux

Several existing (and used) UAs do not support the W3C DOM,

I know that. What is your answer to my question? I'll make it clearer

Have you ever seen a host that has document.getElementById where
document.getElementById is not callable?
but I thought I had made myself clear already.

Unfortunately no. Communication takes two people. I'll take half the
responsibility.

I can also easily create you a fitting UA,

What is a "fitting" UA?
and that does not involve (re)compiling its source code.

If I work hard enough I can create a brand new browser that will break
any code that any JavaScript programmer produces. I don't think an
argument like this is helpful however.
With client-side
scripting, and especially host objects, all bets are off.

I see no difference between language features and host features. They
were probably programmed by the same guys in the same cubicles. There
are standards for both the language and the DOM. The two are very
similar situations.

If "all bets are off" then you must test everything but I don't think
that is true. It was a serious question when I asked do you test that
float division works properly? If you do not, which is what I expect
is true, then you recognize that it is ok to draw the line somewhere
where certain features, that have never been known to have a problem,
can be simply assumed to work.
The sooner you
realize this,

I realize this. Do you see that, if I am right that you draw the line
somewhere for feature testing, that different programmers will draw
the line in different places? Can you see that this is a grey area and
it is ok if different people draw the line in different places than
you do? (I'm not asking if you think I draw the line in the right
place.)
the earlier your client-side scripts will become more
interoperable and less error-prone.

Yes. The more testing, the less error-prone. The more testing, the
more buggy code to maintain. The more testing, the more code to
download. There are pros and cons.

I think by now you will agree that for feature testing the line has to
be draw somewhere and different developers will draw the line in
different places. Do you agree?

`d' may be a a reference to a "document" object and still does not need to
provide that method. You assume a forced commonality where none exists.

One more time...

Have you ever seen a host that has document.getElementById where
document.getElementById is not callable?

I don't mean any of the above questions sarcastically.

So far I'm the only one that has posted a set of design guidelines.
I'd be interested to read your design guidelines.
 
D

David Mark

The issue isn't a few extra lines in gEBI. The issue for me is that
there needs to be a set of guidelines that are followed when deciding
how to code for the repository. A rule like "support IE4 when it is
easy" is an ambiguous rule. I want the guidelines to be as black and
white as possible. I've never seen any library with a set of design
guidelines that can be pointed to when someone says "why didn't you
test for that?" I want a set of such guidelines so there is no need to
deal with those questions by humming and hawing about how I was
feeling about IE4 on a particular day.

I know the IE4 support issue will be a fun reoccurring issue. I don't
mind. I imagine that IE4 support is only an issue for a few members of
comp.lang.javascript and almost no one else in the world. It is easy
enough to maintain your own gEBI and getEBTN. I know there will be
some things I will have to maintain for my particular situation and
they won't go into the repository. That's ok.



I imagine at least full CSS2 support given libraries like jQuery have
a big set of CSS2 support.


We already have wrappers that don't completely match what they are
supposed to wrap. For example, the gEBI wrapper you suggested that
checks the name/id problem but doesn't walk the DOM is somewhat
similar in my mind.

Slightly similar. It doesn't completely work around the IE bug (and I
am not convinced it needs to), but it does add some value to the
implementation. The gEBCS wrapper as it stands is probably only
implementing half of what the w3c method will do. I don't think that
is necessarily a bad thing, but it may cause some confusion in that
the two branches will diverge considerably once there is a featured
w3c method. And BTW, it wouldn't hurt to add the test for it and
associated branch now (assuming we are going to wrap it.)

[snip]
The tables are turned and now I want to check something you don't :)

IE4/NN4 support 1.2 and I ignore the consequences of inadequate
feature testing for anything that came before that. That's where I
draw the line. The use of RegExp literals in this project will ensure
that NN <= 4 and IE < 4 won't parse the scripts anyway.

[snip]
Please post a replacement for the above line.

See below.
ns = [];
for (i=0, ilen=els.length; i<ilen; ++i) {
Does it not make sense to use while (--i) and reverse the results
after the loop? At least for large numbers of nodes, it would seem
more efficient.

I haven't tested this system. Is it really faster? By how much?

Depends on the environment and what is happening inside the loop. In
most cases it is relatively faster.

[smip]
Using a regular expression is much slower. I tested this. It is a big
difference.

That surprises me. You could eliminate the extra string concatenation
though.
But this is not a worry here as gEBTN will always return elements with
the correct tagName if it is sent a tagName (i.e. not sent '*').

That's what I meant by "right." I was just noting that the
explanation should perhaps be expanded.

I
believe the next line is fine even with the unfiltered gEBTN and
el.tagName.toLowerCase will never throw a "no properties" error.

Right.
(tn == '*' || el.tagName.toLowerCase() == tn) &&
(!id || el.id == id)) {
ns[ns.length] = el;
}
}
return ns;
};
}
No checks for toLowerCase and indexOf because they are so old.
If the indexOf trick for checking classname looks weird it is used
because it is much faster than a regexp. Since this is in a loop speed
counts.
Is the extra string concatenation really faster than testing with a
RegExp? If so, I need to re-think my use of the test method in some
cases.

Much faster. Give it a try.

Where I learned this is from the EXT css selector which is the fastest
of the popular ones. I looked in it and was amazed to see the string
concatenation.

I amazed to hear that. I suspect I will have to update some of my
functions to reflect this.
Please do post that! I have never looked into this but want to learn
about.

I spliced together a new version of function.
I really don't mind either way (thanks to automated testing! :D). So
you are suggesting the following in two files?

getEBCN = function(cn, d) = {
/* lots of code*/

};

getEBCS = function(s, d) = {
return getEBCN(s.substring(1), d);

};

No, exactly the opposite. Else how could you swap in the version of
gEBCS that does more than just className?

[snip]

I haven't tested this thoroughly.

var queryXPath, resolve;

if (isFeaturedMethod(doc, 'evaluate')) {
resolve = function() { return 'http://www.w3.org/1999/xhtml'; };
queryXPath = function(ancestor, tagName, className) {
var l, r, bSnapshot, docNode = (ancestor.nodeType == 9)?ancestor:
(ancestor.ownerDocument || doc);
var queryResults = [];
var queryString = ['.//', ((xmlParseMode(docNode))?'html:':''),
tagName, ((className)?"[contains(concat(' ', @class, ' '), ' " +
className + " ')]":'')].join('');
r = docNode.evaluate(queryString, ancestor,
(xmlParseMode(docNode))?resolve:null,
global.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
l = r.snapshotLength;
while (l--) { queryResults[l] = r[l]; }
return queryResults;
};
}

var getEBCN;
var getEBCS = (function() {
var m, // regexp matches and temp var in loop
tn, // tagName in s
id, // id in s
cn, // className in s
els, // candidate elements for return
ns, // elements to return
i, // loop index
el; // temp element variable
var get = (function() {
if (typeof queryXPath != 'undefined' && typeof getEBI !=
'undefined') {
return function(d, id, tn, cn) {
if (id) {
ns = [];
el = getEBI(id);
if (el) {
if ((tn == '*' || el.tagName.toLowerCase() == tn) &&
(!cn || ((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ') > -1))) {
ns = [el];
}
}
return ns;
}
else {
return queryXPath(d, tn, cn);
}
};
}
if (typeof getEBI != 'undefined' &&
typeof getEBTN != 'undefined' &&
// match appears first in ECMAScript in v3 so test
String.prototype.match) {

return function(d, id, tn, cn) {
// Need this because getEBI only takes document object
// as optional second argument but getEBCS takes any
// element or a document as second argument.
els = (id && (d.nodeType == 9 || (!d.nodeType && !
d.tagName))) ?
((el=getEBI(id, d)) ? [el] : [])
:
getEBTN(tn, d);
ns = [];
i = els.length;
while (i--) {
el = els;
// Could call an external hasClassName function but in line
// it here for efficiency. There are no cross browser issue
// with checking className.
if ((!cn ||
((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ')>-1)) &&
// If tn != '*' then el necessarily has tagname
property.
(tn == '*' || el.tagName.toLowerCase() == tn) &&
(!id || el.id == id)) {
ns[ns.length] = el;
}
}
return ns.reverse();
};
}
})();

if (get) {
return function(s, d) {
m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : '*';

m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;

m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;

return get(d || doc, id, tn, cn);
};
}
})();

if (getEBCS) {
getEBCN = function(s, d) {
return getEBCS('.' + s, d);
};
}

I was surprised to see that Windows Safari has XPath. So other than
IE, what modern browser doesn't have it?
 
P

Peter Michaux

[snip]

Slightly similar. It doesn't completely work around the IE bug (and I
am not convinced it needs to), but it does add some value to the
implementation. The gEBCS wrapper as it stands is probably only
implementing half of what the w3c method will do. I don't think that
is necessarily a bad thing, but it may cause some confusion in that
the two branches will diverge considerably once there is a featured
w3c method. And BTW, it wouldn't hurt to add the test for it and
associated branch now (assuming we are going to wrap it.)

Wrapping an unstable and/or unimplemented spec seems to be jumping the
gun a bit.
IE4/NN4 support 1.2 and I ignore the consequences of inadequate
feature testing for anything that came before that. That's where I
draw the line. The use of RegExp literals in this project will ensure
that NN <= 4 and IE < 4 won't parse the scripts anyway.

Ok, I'll yield. We will do less testing!

[snip]
Please post a replacement for the above line.

See below.


ns = [];
for (i=0, ilen=els.length; i<ilen; ++i) {
Does it not make sense to use while (--i) and reverse the results
after the loop? At least for large numbers of nodes, it would seem
more efficient.
I haven't tested this system. Is it really faster? By how much?

Depends on the environment and what is happening inside the loop. In
most cases it is relatively faster.

I tested the following in Firefox

// ----------------------

var start = (new Date()).getTime();
var a = [];
var i=100000;
while (i--) {
a=i;
}
a.reverse();
console.log((new Date()).getTime() - start); // ~800

// ----------------------

var start = (new Date()).getTime();
var a = [];
for (var i=0; i<100000; i++) {
a=i;
}
console.log((new Date()).getTime() - start); // ~900

// ----------------------

The first version with reverse must be a little faster because the
memory alloted for the array does not need to be dynamically. Sound
reasonable?

For what it's worth, 13% more time for the forward version when the
array is huge (100000) doesn't really bother me because it would be
very unlikely to deal with an array that big in a browser scripting
task. I don't mind the reverse way but it is a little more confusing
to understand.

That surprises me. You could eliminate the extra string concatenation
though.

If the function is to be optimized a lot more needs to be done than
that. The CSS selector string should be compiled into a JavaScript
string which represents a function and then that function string is
evaluated. This function can be cached to be used later. This sort of
optimization is really important if selectors with several simple
selectors (e.g. div div div) because those tasks are so much more
laborious. For simple selectors it is not so important.

[snip]
Please do post that! I have never looked into this but want to learn
about.

I spliced together a new version of function.
[snip]
I really don't mind either way (thanks to automated testing! :D). So
you are suggesting the following in two files?
getEBCN = function(cn, d) = {
/* lots of code*/

getEBCS = function(s, d) = {
return getEBCN(s.substring(1), d);

No, exactly the opposite.
Ok.

[snip]

I haven't tested this thoroughly.

var queryXPath, resolve;

if (isFeaturedMethod(doc, 'evaluate')) {
resolve = function() { return 'http://www.w3.org/1999/xhtml';};
queryXPath = function(ancestor, tagName, className) {
var l, r, bSnapshot, docNode = (ancestor.nodeType == 9)?ancestor:
(ancestor.ownerDocument || doc);

If ancestor is from a different document and doc is used, then the doc
is not the one that contains ancestor. It seems to me this could be a
big problem if ancestor is in an XHTML document and doc is an HTML
document. Isn't it necessary to walk up the DOM from ancestor to find
it's ownerDocument?

var queryResults = [];
var queryString = ['.//', ((xmlParseMode(docNode))?'html:':''),
tagName, ((className)?"[contains(concat(' ', @class, ' '), ' " +
className + " ')]":'')].join('');

Why use join() instead of just adding the bits together with string
concatenation?
r = docNode.evaluate(queryString, ancestor,
(xmlParseMode(docNode))?resolve:null,
global.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);


Please use a 70 character line width. This is very difficult to read
and time consuming to reformat so it is readable.

l = r.snapshotLength;
while (l--) { queryResults[l] = r[l]; }
return queryResults;
};
}

var getEBCN;
var getEBCS = (function() {
var m, // regexp matches and temp var in loop
tn, // tagName in s
id, // id in s
cn, // className in s
els, // candidate elements for return
ns, // elements to return
i, // loop index
el; // temp element variable
var get = (function() {
if (typeof queryXPath != 'undefined' && typeof getEBI !=
'undefined') {
return function(d, id, tn, cn) {
if (id) {
ns = [];
el = getEBI(id);
if (el) {
if ((tn == '*' || el.tagName.toLowerCase() == tn) &&
(!cn || ((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ') > -1))) {
ns = [el];
}
}
return ns;
}
else {
return queryXPath(d, tn, cn);
}
};
}
if (typeof getEBI != 'undefined' &&
typeof getEBTN != 'undefined' &&
// match appears first in ECMAScript in v3 so test
String.prototype.match) {

return function(d, id, tn, cn) {
// Need this because getEBI only takes document object
// as optional second argument but getEBCS takes any
// element or a document as second argument.
els = (id && (d.nodeType == 9 || (!d.nodeType && !
d.tagName))) ?
((el=getEBI(id, d)) ? [el] : [])
:
getEBTN(tn, d);
ns = [];
i = els.length;
while (i--) {
el = els;
// Could call an external hasClassName function but in line
// it here for efficiency. There are no cross browser issue
// with checking className.
if ((!cn ||
((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ')>-1)) &&
// If tn != '*' then el necessarily has tagname
property.
(tn == '*' || el.tagName.toLowerCase() == tn) &&
(!id || el.id == id)) {
ns[ns.length] = el;
}
}
return ns.reverse();
};
}
})();

if (get) {
return function(s, d) {
m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : '*';

m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;

m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;

return get(d || doc, id, tn, cn);
};
}
})();

if (getEBCS) {
getEBCN = function(s, d) {
return getEBCS('.' + s, d);
};
}


I didn't think the code for using xpath was going to be so big. Given
how big this code is I think speed profiles would be good if this is
to be recommended over the non-xpath version. I think that there won't
be too much difference for simple selectors.
I was surprised to see that Windows Safari has XPath. So other than
IE, what modern browser doesn't have it?

I don't know.

What I would like to know is why in Prototype.js they turn off using
xpath for any browser with "AppleWebKit" in navigator.userAgent. Do
early versions of Safari have a bug in the xpath implementation. If
there is a bug, a feature test would be needed.
 
P

Peter Michaux

On Dec 10, 2:30 pm, Peter Michaux <[email protected]> wrote:
[snip]

Slightly similar. It doesn't completely work around the IE bug (and I
am not convinced it needs to), but it does add some value to the
implementation. The gEBCS wrapper as it stands is probably only
implementing half of what the w3c method will do. I don't think that
is necessarily a bad thing, but it may cause some confusion in that
the two branches will diverge considerably once there is a featured
w3c method. And BTW, it wouldn't hurt to add the test for it and
associated branch now (assuming we are going to wrap it.)

Wrapping an unstable and/or unimplemented spec seems to be jumping the
gun a bit.
IE4/NN4 support 1.2 and I ignore the consequences of inadequate
feature testing for anything that came before that. That's where I
draw the line. The use of RegExp literals in this project will ensure
that NN <= 4 and IE < 4 won't parse the scripts anyway.

Ok, I'll yield. We will do less testing!

[snip]


See below.
ns = [];
for (i=0, ilen=els.length; i<ilen; ++i) {
Does it not make sense to use while (--i) and reverse the results
after the loop? At least for large numbers of nodes, it would seem
more efficient.
I haven't tested this system. Is it really faster? By how much?
Depends on the environment and what is happening inside the loop. In
most cases it is relatively faster.

I tested the following in Firefox

// ----------------------

var start = (new Date()).getTime();
var a = [];
var i=100000;
while (i--) {
a=i;}

a.reverse();
console.log((new Date()).getTime() - start); // ~800

// ----------------------

var start = (new Date()).getTime();
var a = [];
for (var i=0; i<100000; i++) {
a=i;}

console.log((new Date()).getTime() - start); // ~900

// ----------------------

The first version with reverse must be a little faster because the
memory alloted for the array does not need to be dynamically. Sound
reasonable?

For what it's worth, 13% more time for the forward version when the
array is huge (100000) doesn't really bother me because it would be
very unlikely to deal with an array that big in a browser scripting
task. I don't mind the reverse way but it is a little more confusing
to understand.
That surprises me. You could eliminate the extra string concatenation
though.

If the function is to be optimized a lot more needs to be done than
that. The CSS selector string should be compiled into a JavaScript
string which represents a function and then that function string is
evaluated. This function can be cached to be used later. This sort of
optimization is really important if selectors with several simple
selectors (e.g. div div div) because those tasks are so much more
laborious. For simple selectors it is not so important.



For example

if (typeof getEBI != 'undefined' &&
typeof getEBTN != 'undefined') {

var getEBCS;

(function() {

// http://elfz.laacz.lv/beautify/
var compile = function(s) {
var m, // regexp matches
tn, // tagName in s
id, // id in s
cn, // className in s
f; // the function body

m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : null;

m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;

m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;

f = 'var i,els,el,m,ns=[];';
if (id) {
f += 'els=(!d || d.documentElement) ?' +
'((el=getEBI("'+id+'", d)) ? [el] : []) :' +
'getEBTN("'+(tn||'*')+'", d);'
}
else {
f += 'els = getEBTN("'+(tn||'*')+'", d);';
}

f += 'i = els.length;' +
'while (i--) {' +
'el = els;' +
'if (';
if (id) {
f += 'el.id=="'+id+'"';
}
if ((cn||tn) && id) {
f += '&&';
}
if (tn) {
f += 'el.tagName.toLowerCase() == "' + tn + '"';
}
if (cn && tn) {
f += '&&';
}
if (cn) {
f += '((m = el.className) &&' +
'(" "+m+" ").indexOf(" '+cn+' ")>-1)';
}
f += '){' +
'ns[ns.length] = el;' +
'}' +
'}';

f += 'return ns.reverse();';

return new Function('d', f);
}

var cache = {};

getEBCS = function(s, d) {
if (!cache) {
cache = compile(s);
}
return cache(d);
}

})();

}

// -------------------------------

I may have been wrong saying that

ns[ns.length] = el;

is faster than

ns.push(el)

// --------------------------------

If you find out that there is no problem with xpath and safari or that
there is a feature test, perhaps we can compare the two for speed.
 
D

David Mark

On Dec 10, 2:30 pm, Peter Michaux <[email protected]> wrote:
[snip]

Slightly similar. It doesn't completely work around the IE bug (and I
am not convinced it needs to), but it does add some value to the
implementation. The gEBCS wrapper as it stands is probably only
implementing half of what the w3c method will do. I don't think that
is necessarily a bad thing, but it may cause some confusion in that
the two branches will diverge considerably once there is a featured
w3c method. And BTW, it wouldn't hurt to add the test for it and
associated branch now (assuming we are going to wrap it.)

Wrapping an unstable and/or unimplemented spec seems to be jumping the
gun a bit.

I think it is unimplemented at the moment. I guess it may be unstable
when it is implemented in Beta versions. So that makes sense.
IE4/NN4 support 1.2 and I ignore the consequences of inadequate
feature testing for anything that came before that. That's where I
draw the line. The use of RegExp literals in this project will ensure
that NN <= 4 and IE < 4 won't parse the scripts anyway.

Ok, I'll yield. We will do less testing!

[snip]


See below.
ns = [];
for (i=0, ilen=els.length; i<ilen; ++i) {
Does it not make sense to use while (--i) and reverse the results
after the loop? At least for large numbers of nodes, it would seem
more efficient.
I haven't tested this system. Is it really faster? By how much?
Depends on the environment and what is happening inside the loop. In
most cases it is relatively faster.

I tested the following in Firefox

// ----------------------

var start = (new Date()).getTime();
var a = [];
var i=100000;
while (i--) {
a=i;}

a.reverse();
console.log((new Date()).getTime() - start); // ~800

// ----------------------

var start = (new Date()).getTime();
var a = [];
for (var i=0; i<100000; i++) {
a=i;}

console.log((new Date()).getTime() - start); // ~900

// ----------------------

The first version with reverse must be a little faster because the
memory alloted for the array does not need to be dynamically. Sound
reasonable?


It is the comparison that is faster (boolean vs. numeric comparison.)
For what it's worth, 13% more time for the forward version when the
array is huge (100000) doesn't really bother me because it would be
very unlikely to deal with an array that big in a browser scripting
task. I don't mind the reverse way but it is a little more confusing
to understand.
Right.
That surprises me. You could eliminate the extra string concatenation
though.

If the function is to be optimized a lot more needs to be done than
that. The CSS selector string should be compiled into a JavaScript
string which represents a function and then that function string is
evaluated. This function can be cached to be used later. This sort of
optimization is really important if selectors with several simple
selectors (e.g. div div div) because those tasks are so much more
laborious. For simple selectors it is not so important.

[snip]
I spliced together a new version of function.
[snip]


getElementsByClassName and getElementsByCssSelector functions are both
sugar on top of what the browser offers. Given the limited scope of
what a getElementsByClassName function can do I think it would be best
to just implement it as one of the getElementsByCssSelector functions.
I think it makes sense to include it as a one-liner that calls gEBCS.
I really don't mind either way (thanks to automated testing! :D). So
you are suggesting the following in two files?
getEBCN = function(cn, d) = {
/* lots of code*/
};
getEBCS = function(s, d) = {
return getEBCN(s.substring(1), d);
};
No, exactly the opposite.
Ok.

[snip]

I haven't tested this thoroughly.
var queryXPath, resolve;
if (isFeaturedMethod(doc, 'evaluate')) {
resolve = function() { return 'http://www.w3.org/1999/xhtml';};
queryXPath = function(ancestor, tagName, className) {
var l, r, bSnapshot, docNode = (ancestor.nodeType == 9)?ancestor:
(ancestor.ownerDocument || doc);

If ancestor is from a different document and doc is used, then the doc
is not the one that contains ancestor. It seems to me this could be a
big problem if ancestor is in an XHTML document and doc is an HTML
document. Isn't it necessary to walk up the DOM from ancestor to find
it's ownerDocument?

You mean if the element node doesn't have an ownerDocument property?
I don't see that as possible in a browser that supports XPath, but I
could be wrong. Granted the || doc fallback is ambiguous.
var queryResults = [];
var queryString = ['.//', ((xmlParseMode(docNode))?'html:':''),
tagName, ((className)?"[contains(concat(' ', @class, ' '), ' " +
className + " ')]":'')].join('');

Why use join() instead of just adding the bits together with string
concatenation?

In the original query function, which accepted multiple query
parameters, this was in a loop and I thought the join would be faster.
r = docNode.evaluate(queryString, ancestor,
(xmlParseMode(docNode))?resolve:null,
global.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

Please use a 70 character line width. This is very difficult to read
and time consuming to reformat so it is readable.


l = r.snapshotLength;
while (l--) { queryResults[l] = r[l]; }
return queryResults;
};
}
var getEBCN;
var getEBCS = (function() {
var m, // regexp matches and temp var in loop
tn, // tagName in s
id, // id in s
cn, // className in s
els, // candidate elements for return
ns, // elements to return
i, // loop index
el; // temp element variable
var get = (function() {
if (typeof queryXPath != 'undefined' && typeof getEBI !=
'undefined') {
return function(d, id, tn, cn) {
if (id) {
ns = [];
el = getEBI(id);
if (el) {
if ((tn == '*' || el.tagName.toLowerCase() == tn) &&
(!cn || ((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ') > -1))) {
ns = [el];
}
}
return ns;
}
else {
return queryXPath(d, tn, cn);
}
};
}
if (typeof getEBI != 'undefined' &&
typeof getEBTN != 'undefined' &&
// match appears first in ECMAScript in v3 so test
String.prototype.match) {
return function(d, id, tn, cn) {
// Need this because getEBI only takes document object
// as optional second argument but getEBCS takes any
// element or a document as second argument.
els = (id && (d.nodeType == 9 || (!d.nodeType && !
d.tagName))) ?
((el=getEBI(id, d)) ? [el] : [])
:
getEBTN(tn, d);
ns = [];
i = els.length;
while (i--) {
el = els;
// Could call an external hasClassName function but in line
// it here for efficiency. There are no cross browser issue
// with checking className.
if ((!cn ||
((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ')>-1)) &&
// If tn != '*' then el necessarily has tagname
property.
(tn == '*' || el.tagName.toLowerCase() == tn) &&
(!id || el.id == id)) {
ns[ns.length] = el;
}
}
return ns.reverse();
};
}
})();

if (get) {
return function(s, d) {
m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : '*';
m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;
m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;
return get(d || doc, id, tn, cn);
};
}
})();
if (getEBCS) {
getEBCN = function(s, d) {
return getEBCS('.' + s, d);
};
}

I didn't think the code for using xpath was going to be so big. Given


Well, the actual XPath code is pretty small. It is the inline
duplication of code that has grown the gEBCS function, but that is
necessary for the branch that loops.
how big this code is I think speed profiles would be good if this is
to be recommended over the non-xpath version. I think that there won't
be too much difference for simple selectors.

It is an order or magnitude faster for all cases, but won't have a big
impact unless the fallback branch loops through lots of results (e.g.
finding className matches for any tagName.) I definitely think this
should be an alternate version. Especially if you think we will want
to provide a wrapper for gEBCS that does more than just tagNames and
classNames.
I don't know.

What I would like to know is why in Prototype.js they turn off using
xpath for any browser with "AppleWebKit" in navigator.userAgent.

Who knows why they do anything? Looking through Rails tickets makes
you wonder if they know. Googling for combinations of "XPath bug",
"WebKit" and "Safari" yielded nothing. I suspect that there might be
a limitation in some older Safari version that defeats some of the
more outlandish of their supported queries, but that is just a guess.

Do
early versions of Safari have a bug in the xpath implementation. If
there is a bug, a feature test would be needed.

I agree if a bug can be confirmed.
 
D

David Mark

Slightly similar. It doesn't completely work around the IE bug (and I
am not convinced it needs to), but it does add some value to the
implementation. The gEBCS wrapper as it stands is probably only
implementing half of what the w3c method will do. I don't think that
is necessarily a bad thing, but it may cause some confusion in that
the two branches will diverge considerably once there is a featured
w3c method. And BTW, it wouldn't hurt to add the test for it and
associated branch now (assuming we are going to wrap it.)
Wrapping an unstable and/or unimplemented spec seems to be jumping the
gun a bit.
Ok, I'll yield. We will do less testing!
That seems like an iffy test for a document node. It would be more
robust to test if it is not an element node (see the gEBTN wrapper.)
Please post a replacement for the above line.
See below.
ns = [];
for (i=0, ilen=els.length; i<ilen; ++i) {
Does it not make sense to use while (--i) and reverse the results
after the loop? At least for large numbers of nodes, it would seem
more efficient.
I haven't tested this system. Is it really faster? By how much?
Depends on the environment and what is happening inside the loop. In
most cases it is relatively faster.
I tested the following in Firefox
// ----------------------
var start = (new Date()).getTime();
var a = [];
var i=100000;
while (i--) {
a=i;}

a.reverse();
console.log((new Date()).getTime() - start); // ~800
// ----------------------
var start = (new Date()).getTime();
var a = [];
for (var i=0; i<100000; i++) {
a=i;}

console.log((new Date()).getTime() - start); // ~900
// ----------------------
The first version with reverse must be a little faster because the
memory alloted for the array does not need to be dynamically. Sound
reasonable?
For what it's worth, 13% more time for the forward version when the
array is huge (100000) doesn't really bother me because it would be
very unlikely to deal with an array that big in a browser scripting
task. I don't mind the reverse way but it is a little more confusing
to understand.
If the function is to be optimized a lot more needs to be done than
that. The CSS selector string should be compiled into a JavaScript
string which represents a function and then that function string is
evaluated. This function can be cached to be used later. This sort of
optimization is really important if selectors with several simple
selectors (e.g. div div div) because those tasks are so much more
laborious. For simple selectors it is not so important.

For example

if (typeof getEBI != 'undefined' &&
typeof getEBTN != 'undefined') {

var getEBCS;

(function() {

//http://elfz.laacz.lv/beautify/
var compile = function(s) {
var m, // regexp matches
tn, // tagName in s
id, // id in s
cn, // className in s
f; // the function body

m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : null;

m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;

m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;

f = 'var i,els,el,m,ns=[];';
if (id) {
f += 'els=(!d || d.documentElement) ?' +
'((el=getEBI("'+id+'", d)) ? [el] : []) :' +
'getEBTN("'+(tn||'*')+'", d);'
}
else {
f += 'els = getEBTN("'+(tn||'*')+'", d);';
}

f += 'i = els.length;' +
'while (i--) {' +
'el = els;' +
'if (';
if (id) {
f += 'el.id=="'+id+'"';
}
if ((cn||tn) && id) {
f += '&&';
}
if (tn) {
f += 'el.tagName.toLowerCase() == "' + tn + '"';
}
if (cn && tn) {
f += '&&';
}
if (cn) {
f += '((m = el.className) &&' +
'(" "+m+" ").indexOf(" '+cn+' ")>-1)';
}
f += '){' +
'ns[ns.length] = el;' +
'}' +
'}';

f += 'return ns.reverse();';

return new Function('d', f);
}

var cache = {};

getEBCS = function(s, d) {
if (!cache) {
cache = compile(s);
}
return cache(d);
}

})();

}

// -------------------------------


That looks like it would speed up the IE branch considerably. It will
still be slower than browsers that can use XPath though.
I may have been wrong saying that

ns[ns.length] = el;

is faster than

ns.push(el)

It seemed to me that push would be faster. I haven't had time to test
it though. As I use a wrapper for push, I have typically used the
former method, which is obviously faster than calling a wrapper. But,
for example, the XPath code doesn't need to worry about
Array.prototype.push, so it would be a okay to use it without the
wrapper.
// --------------------------------

If you find out that there is no problem with xpath and safari or that

It will be impossible to prove that there isn't one. I can only say
that I can't find a reference to one.
there is a feature test, perhaps we can compare the two for speed.

If a bug can be confirmed, I am sure I can write a feature test for
it. There isn't really a need to compare them for speed as XPath is
going to be faster than looping in virtually every case.
 
D

David Mark

If you find out that there is no problem with xpath and safari or that
there is a feature test, perhaps we can compare the two for speed.

I just spotted a mistake in the queryXPath function. Remove
"global." Also, for this version, the queryString variable is
unneeded (can just be an expression passed to evaluate.)

It also looks like there could be some additional consolidation
between the two branches if size is an issue and the XPath branch
could get away with calling generalized hasClass and tagName
comparison functions (that's the longest line in that branch.)
 
D

David Mark

[snip]

I consolidated a few lines, shortened a few variable names and removed
a couple of unneeded remnants from my original version (as well as the
match test from your branch.)

Note that I did update your document node test in the previously
posted version. I think it is better as it won't be fooled by ancient
browsers that don't support documentElement (and those are the ones
that will take that branch.) Also, you need the previously posted
xmlParseMode function to run this.

As for speed, I added a hundred or so extraneous elements to my test
page and short-circuited the XPath branch, and the result went from
instantaneous to about half a second for a couple of simple test cases
(e.g. searching the entire document for an element by className.)

And revisiting the feature test argument from a few posts back, I
really thing that isFeaturedMethod should be used, instead of things
like:

if (document.getElementById)

I don't know of any browser that returns a truthy, but non-callable
value for this host method, but when you consider that every host
method should be tested in uniform fashion, you can imagine that at
least one of them might do so in some past agent. And if you exclude
that possibility, there is still the ActiveX issue to deal with.
Those host methods throw a script error when tested this way. All it
would take for the above example to break in IE8 would be for MS to
decide to implement some or all document nodes as ActiveX objects. If
that sounds far-fetched, realize that they already implement some
element nodes as ActiveX objects (e.g. anchors that link to news
resources.) I account for that possibility in my getAttribute
wrapper. In other words, this example may look perfectly benign, but
one line can currently throw a script error in IE and the other may in
the future.

var a = document.getElementById('myanchor');
if (a.href) { ... }

I would prefer:

var a, doc = this.document;
if (doc && isFeaturedMethod(doc, 'getElementById') {
a = doc.getElementById('myanchor');
if (getAttribute(a, 'href')) { ... }
}

Or shortened to assume the global document property:

var a;
if (isFeaturedMethod(document, 'getElementById') {
a = document.getElementById('myanchor');
if (getAttribute(a, 'href')) { ... }
}

Granted, you wouldn't have to use a getAttribute wrapper, you could
use any function that takes the 'unknown' type into account.
I don't see this as paranoia as the problem case exists today (in IE7)
and in fact caused a script error on a group member's site a few
months back.

Here is the updated function. Looks like a couple of lines still
wrapped. Sorry. I may start linking to snippets as I really dislike
dealing with these ng issues.

var queryXPath, resolve;

if (isFeaturedMethod(doc, 'evaluate')) {
resolve = function() { return 'http://www.w3.org/1999/xhtml'; };
queryXPath = function(a, tn, cn) {
var l, r, docNode = (a.nodeType == 9)?
a:(a.ownerDocument || doc);
var q = [];
r = docNode.evaluate(['.//',
((xmlParseMode(docNode))?'html:':''),
tn,
((cn)?"[contains(concat(' ', @class, ' '),
' " + cn + " ')]":'')].join(''),
a,
(xmlParseMode(docNode))?resolve:null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null);
l = r.snapshotLength;
while (l--) { q[l] = r[l]; }
return q;
};
}

var getEBCN;
var getEBCS = (function() {
var m, // regexp matches and temp var in loop
tn, // tagName in s
id, // id in s
cn, // className in s
els, // candidate elements for return
ns, // elements to return
i, // loop index
el; // temp element variable
var get = (function() {
if (typeof queryXPath != 'undefined' &&
typeof getEBI != 'undefined') {
return function(d, id, tn, cn) {
if (id) {
ns = [];
if (el = getEBI(id)) {
if ((tn == '*' || el.tagName.toLowerCase() == tn) &&
(!cn || ((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ') > -1))) {
ns = [el];
}
}
return ns;
}
return queryXPath(d, tn, cn);
};
}
if (typeof getEBI != 'undefined' &&
typeof getEBTN != 'undefined') {

return function(d, id, tn, cn) {
// Need this because getEBI only takes document object
// as optional second argument but getEBCS takes any
// element or a document as second argument.
els = (id && (d.nodeType == 9 || (!d.nodeType && !
d.tagName))) ?
((el=getEBI(id, d)) ? [el] : [])
:
getEBTN(tn, d);
ns = [];
i = els.length;
while (i--) {
el = els;
// Could call an external hasClassName function but in
line
// it here for efficiency. There are no cross browser
issue
// with checking className.
if ((!cn ||
((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ')>-1)) &&
// If tn != '*' then el necessarily has tagname
property.
(tn == '*' || el.tagName.toLowerCase() == tn) &&
(!id || el.id == id)) {
ns[ns.length] = el;
}
}
return ns.reverse();
};
}
})();

if (get) {
return function(s, d) {
m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : '*';

m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;

m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;

return get(d || doc, id, tn, cn);
};
}
})();

if (getEBCS) {
getEBCN = function(s, d) {
return getEBCS('.' + s, d);
};
}
 
D

David Mark

What I would like to know is why in Prototype.js they turn off using
xpath for any browser with "AppleWebKit" in navigator.userAgent. Do
early versions of Safari have a bug in the xpath implementation. If
there is a bug, a feature test would be needed.

I found it. It is as I suspected. They found a specific selector
syntax that failed in Safari 3 and instead of feature testing for that
issue, they disabled XPath altogether (based on the userAgent
string.) The post did say that they plan to implement proper feature
testing for this in the next release. Why they don't just scrap the
whole project and start over is a mystery.

http://groups.google.com/group/prototype-core/browse_thread/thread/ae21402d7f7d253c

This won't be an issue for now. Quoting from that thread:

"...the failures are related to the ":first-of-type/:first-child"
pseudoclasses (and their brethren)"

If and when we decide to add such selectors, we will have to revisit
this issue.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,189
Latest member
CryptoTaxSoftware

Latest Threads

Top