form serialization (Code Worth Recommending Project)

R

Richard Cornford

The problem is when the value attribute is intentionally blank.

<select>
<option value="">select a country</option>
<option value="number1">USA</option>
</select>

In that case, there will be an error in IE (doesn't support
Element.prototype.hasAttribute).
<snip>

IE did not take that branch in the original code so that is irrelevant.
My code was only intended to illustrate the short-circuiting of the
testing, Which only needed illustrating once in context (at least for
everyone except you).

Richard.
 
D

dmark

Below is my current version of the simple serializeFormUrl function.

I've changed the documentation format. In the dependency section "a"
means a required and assumed feature, "r" means a required and tested
feature. I don't mind if the documentation system evolves over the
implementation of the first few interfaces. If this is on the right
track that is fine with me for now.

I removed the use of c.push() in favor of c[c.length]. This change
reduces dependencies, is faster (at least in FF2), reduces code size
(no need for feature test), and does not require any extra variables.

I reduce the indent to two spaces since usenet has such short line
lengths (approx 70 chars). I personally prefer four spaces but I think
two will be better for this project.

If there are any remaining offenses in this implementation of the
interface that make this implementation inadequate for recommendation,
please post an edited version.

/**
* @object serializeFormUrl [function]
* All elements of the form that have a non-empty name
* attribute and are not disabled will be serialized.
*
* Serialize form data in a format similar to the
* <a href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/
forms.html#h-17.13.4.1">
* application/x-www-form-urlencoded</a> standardized format.
*
* The handling of whitespace different from the standard.
* According to the x-www-form-urlencoded standard,
* a space should be encoded as a "+" and a newline
* should be encoded as a "%0D%0A".
*
* The JavaScript encodeURIComponent function is used
* to encode the form data. A space is encoded as "%20".
* On Windows operating system a new line is encoded
* as "%0D%0A". On Mac OS X a new line is encded as
* just "%0A".
*
* @param f [form]
* The form element to be serialized.
*
* @returns [string]
* The serialized form.
*
* @dependencies
* r - ES3 - encodeURIComponent
* a - ES1 - Array.prototype.join
* a - DOM1 - HTMLFormElement.elements
* a - DOM1 - HTMLFormElement.elements.length
* a - DOM1 - HTMLFormElement.elements.name
* a - DOM1 - HTMLFormElement.elements.value
* a - DOM1 - HTMLFormElement.elements.disabled
*/

// test for required feature
if (typeof encodeURIComponent != 'undefined') {

var serializeFormUrl = function(f) {
var i, // elements loop index
ilen, // elements loop length
e, // element
es = f.elements,
c = []; // the serialized name=value pairs

for (i=0, ilen=es.length; i<ilen; i++) {
e = es;
if (e.name && !e.disabled) {
c[c.length] = encodeURIComponent(e.name) + "=" +
encodeURIComponent(e.value);
}
}
return c.join('&');
};



}


If two inputs have the same name, the result will be incorrect. Also,
push has been excluded for the sake of backward compatibility, but was
there was ever an agent that featured encodeURIComponent, but not
Array.prototype.push? So why not create a wrapper that falls back to
escape when encodeURIComponent is unavailable?

Also, would it really be that much of a performance hit to switch (or
branch) on each element's type and deal with checkboxes and radio
buttons? It certainly wouldn't increase the size significantly. The
complete solution for select elements would double the size and add a
little more processing overhead, so perhaps that should be in its own
version. Personally, I have always used a single general function for
this. I suppose if I ever had a project that excluded select elements
and needed to serialize extremely long forms, perhaps I would crop out
the select handling. Otherwise, it seems like too much granularity,
resulting in the extra work required to document and maintain two
versions.

Speaking of granularity, does it make sense for a function like this
to be an island? I can't see it being of much use outside of a basic
Ajax module. Building with functions as independent units is an
interesting idea, but seems like overkill for most projects.
 
P

Peter Michaux

Below is my current version of the simple serializeFormUrl function.
I've changed the documentation format. In the dependency section "a"
means a required and assumed feature, "r" means a required and tested
feature. I don't mind if the documentation system evolves over the
implementation of the first few interfaces. If this is on the right
track that is fine with me for now.
I removed the use of c.push() in favor of c[c.length]. This change
reduces dependencies, is faster (at least in FF2), reduces code size
(no need for feature test), and does not require any extra variables.
I reduce the indent to two spaces since usenet has such short line
lengths (approx 70 chars). I personally prefer four spaces but I think
two will be better for this project.
If there are any remaining offenses in this implementation of the
interface that make this implementation inadequate for recommendation,
please post an edited version.
/**
* @object serializeFormUrl [function]
* All elements of the form that have a non-empty name
* attribute and are not disabled will be serialized.
*
* Serialize form data in a format similar to the
* <a href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/
forms.html#h-17.13.4.1">
* application/x-www-form-urlencoded</a> standardized format.
*
* The handling of whitespace different from the standard.
* According to the x-www-form-urlencoded standard,
* a space should be encoded as a "+" and a newline
* should be encoded as a "%0D%0A".
*
* The JavaScript encodeURIComponent function is used
* to encode the form data. A space is encoded as "%20".
* On Windows operating system a new line is encoded
* as "%0D%0A". On Mac OS X a new line is encded as
* just "%0A".
*
* @param f [form]
* The form element to be serialized.
*
* @returns [string]
* The serialized form.
*
* @dependencies
* r - ES3 - encodeURIComponent
* a - ES1 - Array.prototype.join
* a - DOM1 - HTMLFormElement.elements
* a - DOM1 - HTMLFormElement.elements.length
* a - DOM1 - HTMLFormElement.elements.name
* a - DOM1 - HTMLFormElement.elements.value
* a - DOM1 - HTMLFormElement.elements.disabled
*/

// test for required feature
if (typeof encodeURIComponent != 'undefined') {
var serializeFormUrl = function(f) {
var i, // elements loop index
ilen, // elements loop length
e, // element
es = f.elements,
c = []; // the serialized name=value pairs
for (i=0, ilen=es.length; i<ilen; i++) {
e = es;
if (e.name && !e.disabled) {
c[c.length] = encodeURIComponent(e.name) + "=" +
encodeURIComponent(e.value);
}
}
return c.join('&');
};


If two inputs have the same name, the result will be incorrect.


How so?
Also,
push has been excluded for the sake of backward compatibility,

Not only for backwards compatibility. I tested in at least FF2 and
c[c.length] was quite a bit faster than c.push()

but was
there was ever an agent that featured encodeURIComponent, but not
Array.prototype.push?

Perhaps not but using Array.prototype.push takes more characters since
it's existence also needs to be tested. A positive test for
encodeURIComponent does not imply the presence of
Array.prototype.push. That would be object inference and that is not
good enough. I know you weren't suggesting using object inference but
rather were questioning the avoidance of using Array.prototype.push.

So c[c.length] is less code, runs faster and is more backwards
compatible. There is really no good reason to use
Array.prototype.push.

So why not create a wrapper that falls back to
escape when encodeURIComponent is unavailable?

The Mozilla site

<URL: http://developer.mozilla.org/en/doc...fined_Functions:escape_and_unescape_Functions>

states that

"The escape and unescape functions do not work properly for non-ASCII
characters and have been deprecated."

Since escape is not a true replacement of encodeURIComponent it isn't
a sufficient fallback. If the escape fallback was included, a
developer who tests only on browsers with encodeURIComponent and sees
that the page works would be fooled because the page may fail on
browsers without encodeURIComponenet but with escape. These kind of
fallbacks really worry me.

The YUI event library had one of these non-equivalent fallbacks that
made me aware of this. Early Safari 2 and earlier browsers could not
prevent default for a click event if the click event listener was
attached using addEventListener. In YUI the fallback for safari _only_
was to assign to the legacy element.onclick. The implementation would
clobber any existing element.onclick property. I thought this was
unfortunate as many developers don't test on Safari.

Given how few browsers in use do not have encodeURIComponent, I think
it is ok to leave serializeFormUrl undefined in those browsers. There
is a very good chance that in these older browsers even if
serializeFormUrl is defined that the other functions needed for a non-
trivial script would not be defined. serializeFormUrl will not be
defined in some old browsers and where to draw the line in browser
history is a grey area. After rethinking this, thanks to your
prompting, I think I've drawn the line in the right place.

Also, would it really be that much of a performance hit to switch (or
branch) on each element's type and deal with checkboxes and radio
buttons?

No it probably wouldn't be and this is the type of argument Matt Kruse
makes for general purpose libraries and against which Richard Cornford
argues. What I'm discovering with this "multiple implementations"
approach is that different people will think different amounts of code
in the "simplest" of the multiple implementations. One great thing
about a code repository with multiple implementations is that varying
degrees of functionality can be included. There can be bare bones
implementations up to the usually sufficient functionality of the
popular general purpose libraries and even more full featured
implementations. It is a win-win-win code repository philosophy.

It certainly wouldn't increase the size significantly. The
complete solution for select elements would double the size and add a
little more processing overhead, so perhaps that should be in its own
version. Personally, I have always used a single general function for
this.

As have I and one will be included as one of the multiple
implementations. It may be that this implementation is the one that
most people want to use.

I suppose if I ever had a project that excluded select elements
and needed to serialize extremely long forms, perhaps I would crop out
the select handling. Otherwise, it seems like too much granularity,
resulting in the extra work required to document and maintain two
versions.

This is something I'm interested to watch evolve over time. Certainly
there is some art in this balance. Since this (and general Ajax code
components) are the first components I want to make sure to try out
this system.

Speaking of granularity, does it make sense for a function like this
to be an island?

Consistent code design is valuable.

I can't see it being of much use outside of a basic
Ajax module.

Indeed that is likely where it will be used.

Building with functions as independent units is an
interesting idea, but seems like overkill for most projects.

But it doesn't take any/much code overhead to stick with this
philosophy. The structure of the code I've developed so far seems very
good. I think it is worth a shot and to see what happens.

Peter
 
D

David Mark

Below is my current version of the simple serializeFormUrl function.
I've changed the documentation format. In the dependency section "a"
means a required and assumed feature, "r" means a required and tested
feature. I don't mind if the documentation system evolves over the
implementation of the first few interfaces. If this is on the right
track that is fine with me for now.
I removed the use of c.push() in favor of c[c.length]. This change
reduces dependencies, is faster (at least in FF2), reduces code size
(no need for feature test), and does not require any extra variables.
I reduce the indent to two spaces since usenet has such short line
lengths (approx 70 chars). I personally prefer four spaces but I think
two will be better for this project.
If there are any remaining offenses in this implementation of the
interface that make this implementation inadequate for recommendation,
please post an edited version.
/**
* @object serializeFormUrl [function]
* All elements of the form that have a non-empty name
* attribute and are not disabled will be serialized.
*
* Serialize form data in a format similar to the
* <a href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/
forms.html#h-17.13.4.1">
* application/x-www-form-urlencoded</a> standardized format.
*
* The handling of whitespace different from the standard.
* According to the x-www-form-urlencoded standard,
* a space should be encoded as a "+" and a newline
* should be encoded as a "%0D%0A".
*
* The JavaScript encodeURIComponent function is used
* to encode the form data. A space is encoded as "%20".
* On Windows operating system a new line is encoded
* as "%0D%0A". On Mac OS X a new line is encded as
* just "%0A".
*
* @param f [form]
* The form element to be serialized.
*
* @returns [string]
* The serialized form.
*
* @dependencies
* r - ES3 - encodeURIComponent
* a - ES1 - Array.prototype.join
* a - DOM1 - HTMLFormElement.elements
* a - DOM1 - HTMLFormElement.elements.length
* a - DOM1 - HTMLFormElement.elements.name
* a - DOM1 - HTMLFormElement.elements.value
* a - DOM1 - HTMLFormElement.elements.disabled
*/
// test for required feature
if (typeof encodeURIComponent != 'undefined') {
var serializeFormUrl = function(f) {
var i, // elements loop index
ilen, // elements loop length
e, // element
es = f.elements,
c = []; // the serialized name=value pairs
for (i=0, ilen=es.length; i<ilen; i++) {
e = es;
if (e.name && !e.disabled) {
c[c.length] = encodeURIComponent(e.name) + "=" +
encodeURIComponent(e.value);
}
}
return c.join('&');
};
}

If two inputs have the same name, the result will be incorrect.

How so?


The value will be an array, which this code will skip as arrays have
no name property. You have to loop through the elements and find the
unique names first, then loop through the names.

Also, somebody suggested that an additional parameter to indicate
which submit button should be used, in the event of multiple buttons,
and that seems like a good addition.
Also,
push has been excluded for the sake of backward compatibility,

Not only for backwards compatibility. I tested in at least FF2 and
c[c.length] was quite a bit faster than c.push()

Interesting. I had always heard the opposite about simulating push
that way. I can't imagine it makes a huge difference unless the form
has thousands of elements, but good to know nonetheless.
Perhaps not but using Array.prototype.push takes more characters since
it's existence also needs to be tested. A positive test for
encodeURIComponent does not imply the presence of
Array.prototype.push. That would be object inference and that is not
good enough. I know you weren't suggesting using object inference but
rather were questioning the avoidance of using Array.prototype.push.

Right, you wouldn't skip the test for push in any event.
So c[c.length] is less code, runs faster and is more backwards
compatible. There is really no good reason to use
Array.prototype.push.

Sounds good to me.
The Mozilla site

<URL:http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide:Predef...>

states that

"The escape and unescape functions do not work properly for non-ASCII
characters and have been deprecated."
Right.


Since escape is not a true replacement of encodeURIComponent it isn't
a sufficient fallback. If the escape fallback was included, a
developer who tests only on browsers with encodeURIComponent and sees
that the page works would be fooled because the page may fail on
browsers without encodeURIComponenet but with escape. These kind of
fallbacks really worry me.

I understand what you mean. It would have to be documented that
certain characters are not encoded properly and it is probably not
worth the trouble as I can't think of a browser other than IE5.0 that
would be able to do Ajax (via ActiveX) and does not include
encodeURIComponent.
The YUI event library had one of these non-equivalent fallbacks that
made me aware of this. Early Safari 2 and earlier browsers could not
prevent default for a click event if the click event listener was
attached using addEventListener. In YUI the fallback for safari _only_
was to assign to the legacy element.onclick. The implementation would
clobber any existing element.onclick property. I thought this was
unfortunate as many developers don't test on Safari.

That's complete insanity. Does it still do this?
Given how few browsers in use do not have encodeURIComponent, I think
it is ok to leave serializeFormUrl undefined in those browsers. There
is a very good chance that in these older browsers even if
serializeFormUrl is defined that the other functions needed for a non-
trivial script would not be defined. serializeFormUrl will not be
defined in some old browsers and where to draw the line in browser
history is a grey area. After rethinking this, thanks to your
prompting, I think I've drawn the line in the right place.


No it probably wouldn't be and this is the type of argument Matt Kruse
makes for general purpose libraries and against which Richard Cornford
argues. What I'm discovering with this "multiple implementations"
approach is that different people will think different amounts of code
in the "simplest" of the multiple implementations. One great thing
about a code repository with multiple implementations is that varying
degrees of functionality can be included. There can be bare bones
implementations up to the usually sufficient functionality of the
popular general purpose libraries and even more full featured
implementations. It is a win-win-win code repository philosophy.

I understand that, but how granular does a dozen line function need to
be? It seems like it will be slow going to build this repository into
a comprehensive resource.
As have I and one will be included as one of the multiple
implementations. It may be that this implementation is the one that
most people want to use.

I suspect it will be. It seems most JavaScript developers today
couldn't care less about file size. In fact, most don't understand
how to compare file sizes.
This is something I'm interested to watch evolve over time. Certainly
there is some art in this balance. Since this (and general Ajax code

Yes, it can be tricky to come up with the right granularity in any
library, but especially for a general purpose repository.
components) are the first components I want to make sure to try out
this system.


Consistent code design is valuable.

What I meant was that perhaps the function should be part of a module.
Indeed that is likely where it will be used.


But it doesn't take any/much code overhead to stick with this
philosophy. The structure of the code I've developed so far seems very
good. I think it is worth a shot and to see what happens.

I suspect that what will happen is that most JS developers will stick
with the garbage they are currently using and trust that the idiots
that developed it will constantly update the abstracted browser
sniffing. It is a really bleak outlook for Web applications. People
are starting to build UI components on top of jQuery (of all things.)
I've seen blog entries by developers who state they are changing all
of their sites to use this library because the new "skinned" widgets
"look cool." Never mind that they are built on a swamp-like
foundation, predictably buggy (even in the major browsers) and there
is only one "skin." This is the mentality of today's JavaScript
developer. And if YUI is doing things like you mentioned above, then
it is trash too. That's unfortunate as that one is likely here to
stay.
 
P

Peter Michaux

On Dec 3, 11:39 pm, (e-mail address removed) wrote:
Below is my current version of the simple serializeFormUrl function.
I've changed the documentation format. In the dependency section "a"
means a required and assumed feature, "r" means a required and tested
feature. I don't mind if the documentation system evolves over the
implementation of the first few interfaces. If this is on the right
track that is fine with me for now.
I removed the use of c.push() in favor of c[c.length]. This change
reduces dependencies, is faster (at least in FF2), reduces code size
(no need for feature test), and does not require any extra variables.
I reduce the indent to two spaces since usenet has such short line
lengths (approx 70 chars). I personally prefer four spaces but I think
two will be better for this project.
If there are any remaining offenses in this implementation of the
interface that make this implementation inadequate for recommendation,
please post an edited version.
/**
* @object serializeFormUrl [function]
* All elements of the form that have a non-empty name
* attribute and are not disabled will be serialized.
*
* Serialize form data in a format similar to the
* <a href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/
forms.html#h-17.13.4.1">
* application/x-www-form-urlencoded</a> standardized format.
*
* The handling of whitespace different from the standard.
* According to the x-www-form-urlencoded standard,
* a space should be encoded as a "+" and a newline
* should be encoded as a "%0D%0A".
*
* The JavaScript encodeURIComponent function is used
* to encode the form data. A space is encoded as "%20".
* On Windows operating system a new line is encoded
* as "%0D%0A". On Mac OS X a new line is encded as
* just "%0A".
*
* @param f [form]
* The form element to be serialized.
*
* @returns [string]
* The serialized form.
*
* @dependencies
* r - ES3 - encodeURIComponent
* a - ES1 - Array.prototype.join
* a - DOM1 - HTMLFormElement.elements
* a - DOM1 - HTMLFormElement.elements.length
* a - DOM1 - HTMLFormElement.elements.name
* a - DOM1 - HTMLFormElement.elements.value
* a - DOM1 - HTMLFormElement.elements.disabled
*/
// test for required feature
if (typeof encodeURIComponent != 'undefined') {
var serializeFormUrl = function(f) {
var i, // elements loop index
ilen, // elements loop length
e, // element
es = f.elements,
c = []; // the serialized name=value pairs
for (i=0, ilen=es.length; i<ilen; i++) {
e = es;
if (e.name && !e.disabled) {
c[c.length] = encodeURIComponent(e.name) + "=" +
encodeURIComponent(e.value);
}
}
return c.join('&');
};
}
If two inputs have the same name, the result will be incorrect.


The value will be an array, which this code will skip as arrays have
no name property.


In which browser? In FF2 the following works as I expected.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">

<html lang="en">
<head>
<title>form test</title>

<script type="text/javascript">

if (typeof encodeURIComponent != 'undefined') {
var urlencode = function(s) {
return encodeURIComponent(s);
};
}

if (typeof urlencode != 'undefined') {
var serializeFormUrlencoded = function(f) {
var e, // element
es = f.elements,
c = []; // the serialized name=value pairs

for (var i=0, ilen=es.length; i<ilen; i++) {
e = es;
if (e.name && !e.disabled) {
c[c.length] = urlencode(e.name) + "=" +
urlencode(e.value);
}
}
return c.join('&');
};
}

</script>
</head>
<body>

<form action="#" onsubmit="return false;">
<p><input type="text" name="foo" value="alpha"></p>
<p><input type="text" name="foo" value="beta"></p>

<p><input type="submit" value="serializeFormUrlencoded"
onclick="alert(serializeFormUrlencoded(this.form));"></p>
<p><input type="submit" value="console.log"
onclick="console.log(this.form.elements);"></p>

</form>

</body>
</html>



You have to loop through the elements and find the
unique names first, then loop through the names.

Also, somebody suggested that an additional parameter to indicate
which submit button should be used, in the event of multiple buttons,
and that seems like a good addition.

I made a note to return to this.

Not only for backwards compatibility. I tested in at least FF2 and
c[c.length] was quite a bit faster than c.push()

Interesting. I had always heard the opposite about simulating push
that way. I can't imagine it makes a huge difference unless the form
has thousands of elements, but good to know nonetheless.

Maybe simulating push by writing a function to do the push is slower
because of the extra function call to a user function?

[snip]
That's complete insanity. Does it still do this?

There is still special code for versions of webkit before version 418.
Now they don't just clobber the exiting element.onclick attribute
blindly. They replace it with the fallback technique listener function
and then attach the original function to the fallback technique. It is
still a non-equivalent fallback because a developer may assign to
element.onclick and clobber the fallback technique listener in Safari.
However the developer wouldn't notice this in testing unless they test
on old versions of Safari. I don't expect many developers would either
assign to element.onclick if they are using YUI or test in older
Safari. I have never seen any YUI documentation warning about this
problem. To me this is a situation to program to the lowest common
denominator so that testing in any browser would reveal a problem.

I understand that, but how granular does a dozen line function need to
be? It seems like it will be slow going to build this repository into
a comprehensive resource.

We don't need to implement every of the multiple implementations. That
is actually not realistic. Just one or a few of each function.

I think aggregating some code will go quite quickly. There are plenty
of things to learn and discuss along the way and these details need
discussion anyway. There are also plenty of quickly written libraries
out there. We don't need another one :)

I'd like to see nice Ajax and event code. They are certainly part of
the core of any JavaScript resource. (I have my idea for an Ajax
library mapped out. I think the modularity is good and I'll be posting
it to the group later. <URL:http://cljs.michaux.ca/trac/browser/
branches/peterAjax/src/ajax>. By the way, if you would like trac wiki
and ticket privileges please send me an email.)

I suspect it will be.

As do I.

An important aspect of the multiple implementations is it opens the
door for comp.lang.javascript contributors to agree or compromise, in
a sense. There is no way the contributors would agree if we were
seeking the "one true solution." It is something we can try out and
see how it goes. There is no real harm along the way. The first few
multiple implementations will show a lot about how it works.

[snip]
What I meant was that perhaps the function should be part of a module.

What do you mean by "module"?


I suspect that what will happen is that most JS developers will stick
with the garbage they are currently using and trust that the idiots
that developed it will constantly update the abstracted browser
sniffing.

I'm not completely worried about everyone else. I'd like to learn more
and have productive discussions about browser scripting with
experienced developers. The setOpacity discussion is a perfect example
of something that can be added to the code repository when that
discussion ends.

[snip]

Peter
 
D

David Mark

On Dec 3, 11:39 pm, (e-mail address removed) wrote:
Below is my current version of the simple serializeFormUrl function.
I've changed the documentation format. In the dependency section "a"
means a required and assumed feature, "r" means a required and tested
feature. I don't mind if the documentation system evolves over the
implementation of the first few interfaces. If this is on the right
track that is fine with me for now.
I removed the use of c.push() in favor of c[c.length]. This change
reduces dependencies, is faster (at least in FF2), reduces code size
(no need for feature test), and does not require any extra variables.
I reduce the indent to two spaces since usenet has such short line
lengths (approx 70 chars). I personally prefer four spaces but I think
two will be better for this project.
If there are any remaining offenses in this implementation of the
interface that make this implementation inadequate for recommendation,
please post an edited version.
/**
* @object serializeFormUrl [function]
* All elements of the form that have a non-empty name
* attribute and are not disabled will be serialized.
*
* Serialize form data in a format similar to the
* <a href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/
forms.html#h-17.13.4.1">
* application/x-www-form-urlencoded</a> standardized format.
*
* The handling of whitespace different from the standard.
* According to the x-www-form-urlencoded standard,
* a space should be encoded as a "+" and a newline
* should be encoded as a "%0D%0A".
*
* The JavaScript encodeURIComponent function is used
* to encode the form data. A space is encoded as "%20".
* On Windows operating system a new line is encoded
* as "%0D%0A". On Mac OS X a new line is encded as
* just "%0A".
*
* @param f [form]
* The form element to be serialized.
*
* @returns [string]
* The serialized form.
*
* @dependencies
* r - ES3 - encodeURIComponent
* a - ES1 - Array.prototype.join
* a - DOM1 - HTMLFormElement.elements
* a - DOM1 - HTMLFormElement.elements.length
* a - DOM1 - HTMLFormElement.elements.name
* a - DOM1 - HTMLFormElement.elements.value
* a - DOM1 - HTMLFormElement.elements.disabled
*/
// test for required feature
if (typeof encodeURIComponent != 'undefined') {
var serializeFormUrl = function(f) {
var i, // elements loop index
ilen, // elements loop length
e, // element
es = f.elements,
c = []; // the serialized name=value pairs
for (i=0, ilen=es.length; i<ilen; i++) {
e = es;
if (e.name && !e.disabled) {
c[c.length] = encodeURIComponent(e.name) + "=" +
encodeURIComponent(e.value);
}
}
return c.join('&');
};
}
If two inputs have the same name, the result will be incorrect.
How so?

The value will be an array, which this code will skip as arrays have
no name property.

In which browser? In FF2 the following works as I expected.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">

<html lang="en">
<head>
<title>form test</title>

<script type="text/javascript">

if (typeof encodeURIComponent != 'undefined') {
var urlencode = function(s) {
return encodeURIComponent(s);
};
}

if (typeof urlencode != 'undefined') {
var serializeFormUrlencoded = function(f) {
var e, // element
es = f.elements,
c = []; // the serialized name=value pairs

for (var i=0, ilen=es.length; i<ilen; i++) {
e = es;
if (e.name && !e.disabled) {
c[c.length] = urlencode(e.name) + "=" +
urlencode(e.value);
}
}
return c.join('&');
};
}

</script>
</head>
<body>

<form action="#" onsubmit="return false;">
<p><input type="text" name="foo" value="alpha"></p>
<p><input type="text" name="foo" value="beta"></p>


I tried this in IE6 and it did work. When I tested similar code with
every possible type of input element, I ran into something that
required iterating through the names, rather than the input elements
to keep the results consistent with an actual form submit. IIRC, it
was because I was calling another function to retrieve the value of
any type of input element by name. Apparently this version is okay as
written.
<p><input type="submit" value="serializeFormUrlencoded"
onclick="alert(serializeFormUrlencoded(this.form));"></p>
<p><input type="submit" value="console.log"
onclick="console.log(this.form.elements);"></p>

</form>

</body>
You have to loop through the elements and find the
unique names first, then loop through the names.
Also, somebody suggested that an additional parameter to indicate
which submit button should be used, in the event of multiple buttons,
and that seems like a good addition.

I made a note to return to this.
Also,
push has been excluded for the sake of backward compatibility,
Not only for backwards compatibility. I tested in at least FF2 and
c[c.length] was quite a bit faster than c.push()
Interesting. I had always heard the opposite about simulating push
that way. I can't imagine it makes a huge difference unless the form
has thousands of elements, but good to know nonetheless.

Maybe simulating push by writing a function to do the push is slower
because of the extra function call to a user function?

I didn't mean that. Supposedly, just the basic assignment was slower,
but apparently that is not the case.
[snip]
That's complete insanity. Does it still do this?

There is still special code for versions of webkit before version 418.
Now they don't just clobber the exiting element.onclick attribute
blindly. They replace it with the fallback technique listener function
and then attach the original function to the fallback technique. It is
still a non-equivalent fallback because a developer may assign to
element.onclick and clobber the fallback technique listener in Safari.
Right.

However the developer wouldn't notice this in testing unless they test
on old versions of Safari. I don't expect many developers would either
assign to element.onclick if they are using YUI or test in older
Safari. I have never seen any YUI documentation warning about this
problem. To me this is a situation to program to the lowest common
denominator so that testing in any browser would reveal a problem.

I agree.
We don't need to implement every of the multiple implementations. That
is actually not realistic. Just one or a few of each function.

I think aggregating some code will go quite quickly. There are plenty
of things to learn and discuss along the way and these details need
discussion anyway. There are also plenty of quickly written libraries
out there. We don't need another one :)

That's for sure.
I'd like to see nice Ajax and event code. They are certainly part of
the core of any JavaScript resource. (I have my idea for an Ajax
library mapped out. I think the modularity is good and I'll be posting
it to the group later. <URL:http://cljs.michaux.ca/trac/browser/
branches/peterAjax/src/ajax>. By the way, if you would like trac wiki
and ticket privileges please send me an email.)

I have a pretty comprehensive and re-usable "requester" object and an
"updater" that inherits from it. I'll be interested to see how it
compares to your module.
As do I.

An important aspect of the multiple implementations is it opens the
door for comp.lang.javascript contributors to agree or compromise, in
a sense. There is no way the contributors would agree if we were
seeking the "one true solution." It is something we can try out and
see how it goes. There is no real harm along the way. The first few
multiple implementations will show a lot about how it works.

Certainly this project is useful to me to compare what I have to what
is agreed to be the best solution(s). I've already updated my
selected option function based on one of the first snippets.
[snip]
What I meant was that perhaps the function should be part of a module.

What do you mean by "module"?

A group of functions related by the context in which they are
typically used (e.g. Ajax requests.)
I'm not completely worried about everyone else. I'd like to learn more
and have productive discussions about browser scripting with
experienced developers. The setOpacity discussion is a perfect example
of something that can be added to the code repository when that
discussion ends.

That would be a good one to add as it seems everybody wants it, but
few (if any) libraries implement it properly. IIRC, YUI botched it
pretty badly.
 
P

Peter Michaux

A group of functions related by the context in which they are
typically used (e.g. Ajax requests.)

I am primarily concerned with algorithm correctness. The various
functions produced by this project could be packaged many ways for use
in projects. I want to make sure the code repository is flexible
enough to use the code many ways. One function per file seems like a
good way to go for the repository. Developers can then concatenate
files together as they choose the appropriate implementations of each
function for their situations.

For example, I like some kind of "namespacing". I inject my code into
a legacy website and worrying less about namespace collisions is piece
of mind. One potential packaging I think about is wrapping the
concatenated files. For example, Ajax related functions may be grouped
like this.

var CLJS = CLJS || {};

(function() {

// concatenated code from repository
// * hasAttribute
// * urlencode
// * serializeFormUrlencoded
// * sendAjax
// * ...

// export one/some of the functions
if (typeof sendAjax != 'undefined') {

CLJS.sendAjax = sendAjax;

}

})();


Another option for "namespacing" would be like the ubiquitous "MM_"
functions.


var global = this;

(function() {

// concatenated code from repository
// * hasAttribute
// * theDocumentElement
// * urlencode
// * serializeFormUrlencoded
// * sendAjax
// * ...

// export one/some of the functions
if (typeof sendAjax != 'undefined') {

global.CLJS_sendAjax = sendAjax;

}

})();


If the code repository works out well, it should be easy to use it as
the core functionality building blocks and wrap it with the YUI,
jQuery, ajaxtoolbox, etc APIs.

Peter
 
D

David Mark

I am primarily concerned with algorithm correctness. The various
functions produced by this project could be packaged many ways for use
in projects. I want to make sure the code repository is flexible
enough to use the code many ways. One function per file seems like a
good way to go for the repository. Developers can then concatenate
files together as they choose the appropriate implementations of each
function for their situations.

For example, I like some kind of "namespacing". I inject my code into
a legacy website and worrying less about namespace collisions is piece
of mind. One potential packaging I think about is wrapping the
concatenated files. For example, Ajax related functions may be grouped
like this.

var CLJS = CLJS || {};

(function() {

// concatenated code from repository
// * hasAttribute
// * urlencode
// * serializeFormUrlencoded
// * sendAjax
// * ...

// export one/some of the functions
if (typeof sendAjax != 'undefined') {

CLJS.sendAjax = sendAjax;

}

})();

That's almost exactly what my API looks like.
Another option for "namespacing" would be like the ubiquitous "MM_"
functions.

var global = this;

(function() {

// concatenated code from repository
// * hasAttribute
// * theDocumentElement
// * urlencode
// * serializeFormUrlencoded
// * sendAjax
// * ...

// export one/some of the functions
if (typeof sendAjax != 'undefined') {

global.CLJS_sendAjax = sendAjax;

}

})();

I don't care for that as it is too much pollution.
If the code repository works out well, it should be easy to use it as
the core functionality building blocks and wrap it with the YUI,
jQuery, ajaxtoolbox, etc APIs.

I assume you mean object wrappers that mimic these interfaces. I
recently wrote one for my API that is somewhat like jQuery's
interface. I don't particularly like that interface, but I didn't do
it for my benefit. Not unexpectedly, the thin object layer wrapped
around API functions turned out to be a very efficient and flexible
solution.
 
P

Peter Michaux

As a result of this thread it became apparent some developers may need
to correct the different handling of whitespace by encodeURIComponent
and the application/x-www-form-urlencoded spec. Below are the two
implementations currently in a branch of the code repository. Any
comments before these are considered ok and move to the repository
trunk?

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

if (typeof encodeURIComponent != 'undefined') {

var urlencode = function(s) {
return encodeURIComponent(s);
};

}


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


var urlencode = (function() {

var f = function(s) {
return encodeURIComponent(s).replace(/%20/,'+'
).replace(/(.{0,3})(%0A)/g,
function(m, a, b) {return a+(a=='%0D'?'':'%0D')+b;}
).replace(/(%0D)(.{0,3})/g,
function(m, a, b) {return a+(b=='%0A'?'':'%0A')+b;});
};

// try-catch is shorter than version below and shouldn't shy away
// from ECMAScript 3 syntax.
try {
if (f('\n \r') == '%0D%0A+%0D%0A') {
return f;
}
}
catch(e) {}

// Don't need try-catch because of automatic type conversion
// to string of second argument in older browsers (IE4 && NN4.0).
// O6 doesn't error when function passed as second argument but
// also doesn't do any replacement: the string stays the same.
// if (typeof encodeURIComponent != 'undefined' &&
// String.prototype.replace &&
// f('\n \r') == '%0D%0A+%0D%0A') {
// return f;
// }

})();

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

Comments, documentation and final tests are still pending. I need to
get a sense of where this library is headed before I finalize those
things with input from the group, of course.

I think the prerequisite functions for an Ajax library will be
finished soon.

Peter
 
D

David Mark

As a result of this thread it became apparent some developers may need
to correct the different handling of whitespace by encodeURIComponent
and the application/x-www-form-urlencoded spec. Below are the two
implementations currently in a branch of the code repository. Any
comments before these are considered ok and move to the repository
trunk?

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

if (typeof encodeURIComponent != 'undefined') {

var urlencode = function(s) {
return encodeURIComponent(s);
};

}

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

var urlencode = (function() {

var f = function(s) {
return encodeURIComponent(s).replace(/%20/,'+'
).replace(/(.{0,3})(%0A)/g,
function(m, a, b) {return a+(a=='%0D'?'':'%0D')+b;}
).replace(/(%0D)(.{0,3})/g,
function(m, a, b) {return a+(b=='%0A'?'':'%0A')+b;});
};

Seems like this could be done using the older replace method (without
function arguments.)
// try-catch is shorter than version below and shouldn't shy away
// from ECMAScript 3 syntax.
try {
if (f('\n \r') == '%0D%0A+%0D%0A') {
return f;
}
}
catch(e) {}

I can never remember if try/catch was introduced in IE5 or 5.5. I
think try/catch should be avoided in low-level modules, but as this
one is for Ajax functionality, I don't think it matters (Ajax handling
will need it anyway.)
 
P

Peter Michaux

Below is the more complex form serialization implementation in a
branch of the repository. This one even contains an attempt at
documentation although it isn't so great.

I'd like to know other opinions of assuming the existance of
Array.protoype.join. It is very easy to test for it and the test can
even be short as adding "&&[].join".

I'm not assuming Array.prototype.join because some other new features
are required for this function to even work. That would be object
inference.

I'm assuming Array.prototype.join because it was part of the
ECMAScript 1 spec and that is from a long time ago. I have never
tested for Array.prototype.join anywhere but my JavaScript code is
mostly for behind login.

The Code Worth Recommending project is about upholding high standards
so a check for Array.prototype.join wouldn't cause me concern but
there are other things being assumed in this function that are equally
old: for example, forms have an "elements" object property.

If Array.prototype.join is not tested then that sets a precedent for
the whole project. I was thinking the following feature testing
principle might be a good one

===========

Test for the existence of all features that were introduced after NN4
and IE4. (i.e. features that were not in both NN4 and IE4)

For features that were in NN4 and IE4, only test for their existence
if there is a newer known browser that does not have the feature.

Test that a feature works properly if there has been a known buggy
implementation of that feature or implementations with various
abilities (e.g. String.prototype.replace) in NN4, IE4 or newer
browsers.

===========

Peter


/**
* @object serializeFormUrlencoded [function]
* All elements of the form of type
* * select-one
* * select-multiple
* * radio
* * checkbox
* * text
* * password
* * hidden
* * textarea
* with a non-empty name property and are not disabled
* will be serialized.
*
* Serialize form data in a format similar or identical to the
* <a href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/
forms.html#h-17.13.4.1">
* application/x-www-form-urlencoded</a> standardized format.
* The handling of whitespace is determined by which
* urlencode function is used with this function.
*
* @param f [form]
* The form element to be serialized.
*
* @returns [string]
* The serialized form.
*
* @dependencies
* r - user - urlencode
* r - user - getOptionValue
* a - ES1 - Array.prototype.join
* a - DOM1 - HTMLFormElement.elements
* a - DOM1 - HTMLFormElement.elements.length
* a - DOM1 - HTMLFormElement.elements.name
* a - DOM1 - HTMLFormElement.elements.type
* a - DOM1 - HTMLFormElement.elements.value
* a - DOM1 - HTMLFormElement.elements.disabled
* a - DOM1 - HTMLSelectElement.options
* a - DOM1 - HTMLSelectElement.selectedIndex
* a - DOM1 - HTMLOptionElement.selected
* a - DOM1 - HTMLInputElement.checked
*/

// test for required features
if (typeof urlencode != 'undefined' &&
typeof getOptionValue != 'undefined') {

var serializeFormUrlencoded = function(f) {
var e, // form element
n, // form element's name
o, // option element
es = f.elements,
c = []; // the serialization data parts

function add(n, v) {
c[c.length] = urlencode(n) + "=" + urlencode(v);
}

for (var i=0, ilen=es.length; i<ilen; i++) {
e = es;
n = e.name;
if (n && !e.disabled) {
switch (e.type) {
// The 'select-one' case could reuse 'select-multiple' case
// The 'select-one' case code is an optimization for
// serialization processing time.
case 'select-one':
if (e.selectedIndex >= 0) {
add(n, getOptionValue(e.options[e.selectedIndex]));
}
break;
case 'select-multiple':
for (var j=0, jlen=e.options.length; j<jlen; j++) {
o = e.options[j];
if (o.selected) {
add(n, getOptionValue(o));
}
}
break;
case 'checkbox':
case 'radio':
if (e.checked) {
add(n, e.value);
}
break;
case 'text':
case 'password':
case 'hidden':
case 'textarea':
add(n, e.value);
} // switch
}
}

return c.join('&');
};

}
 
P

Peter Michaux

Seems like this could be done using the older replace method (without
function arguments.)




I can never remember if try/catch was introduced in IE5 or 5.5.

IE 5 has try/catch.

Peter
 
P

Peter Michaux

The last open issue for form serialization is the optional argument
for adding a name=value pair of the button pushed to initiate an Ajax
form submission.

I agree this is a worthwhile optional argument; however, I really
don't like adding features to basic functions like a form
serialization function. This is *exactly* how the mainstream popular
libraries become bloated. Everyone that needs a particular feature and
it is added to the base implementation. The Scriptaculous and YUI drag
drop libraries are particularly good examples that I've looked at that
suffer from this sort of feature creep.

So adding features is a slippery slope and one that makes me
particularly nervous. To add this feature I suggest we use a wrapper
that can wrap any of the serializeFormUrlencoded implementations.


// one of the many serializeFormUrlencoded implementations.

if (typeof urlencode != 'undefined'
/* other checks */ ) {

var serializeFormUrlencoded = function(f) {

//...

};

}

// In a seperate file define the wrapper function
// which automatically wraps the serializeFormUrlencoded
// function.

if (typeof serializeFormUrlencoded != 'undefined') {

serializeFormUrlencoded = (function() {

var original = serializeFormUrlencoded;

return function(f, options) {
var extras = [];
if (options && options.extras) {
var oes = options.extras;
for (var i=0, ilen=oes.length; i<ilen; i++) {
extras[extras.length] = urlencode(oes.key) +
'=' +
urlencode(oes.value);
}
}
var result = original.apply(this, arguments);
return result +
((result && extras) ? '&' : '') +
extras.join('&');
};

})();

}

Using a wrapper obviously takes more space than to just add the
optional argument directly to each implementation of
serializeFormUrlencoded. The modularity of the wrapper is good
isolation of the optional parameter implementation. Someone else may
want a simpler button implementation that can only handle one extra
button parameter that will always have the key "button".

This type of wrapper system is what I'm using in the Ajax library I'll
propose soon for the repository. It seems to have solved the problem
of bloat very nicely and I'd bet that almost everyone will only use a
couple of the wrappers that suit their particular tastes in sugary
APIs. So although the wrapper system may not seem so persuasive here I
believe the general approach to modular software is a good one.

Peter
 
D

David Mark

Below is the more complex form serialization implementation in a
branch of the repository. This one even contains an attempt at
documentation although it isn't so great.

I'd like to know other opinions of assuming the existance of
Array.protoype.join. It is very easy to test for it and the test can
even be short as adding "&&[].join".

I'm not assuming Array.prototype.join because some other new features
are required for this function to even work. That would be object
inference.

I'm assuming Array.prototype.join because it was part of the
ECMAScript 1 spec and that is from a long time ago. I have never
tested for Array.prototype.join anywhere but my JavaScript code is
mostly for behind login.

I think it is safe to skip testing for anything introduced in
JavaScript 1.1.
The Code Worth Recommending project is about upholding high standards
so a check for Array.prototype.join wouldn't cause me concern but
there are other things being assumed in this function that are equally
old: for example, forms have an "elements" object property.

If Array.prototype.join is not tested then that sets a precedent for
the whole project. I was thinking the following feature testing
principle might be a good one

===========

Test for the existence of all features that were introduced after NN4
and IE4. (i.e. features that were not in both NN4 and IE4)

For features that were in NN4 and IE4, only test for their existence
if there is a newer known browser that does not have the feature.

Test that a feature works properly if there has been a known buggy
implementation of that feature or implementations with various
abilities (e.g. String.prototype.replace) in NN4, IE4 or newer
browsers.

Makes sense to me.
===========

Peter

/**
* @object serializeFormUrlencoded [function]
* All elements of the form of type
* * select-one
* * select-multiple
* * radio
* * checkbox
* * text
* * password
* * hidden
* * textarea
* with a non-empty name property and are not disabled
* will be serialized.
*
* Serialize form data in a format similar or identical to the
* <a href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/
forms.html#h-17.13.4.1">
* application/x-www-form-urlencoded</a> standardized format.
* The handling of whitespace is determined by which
* urlencode function is used with this function.
*
* @param f [form]
* The form element to be serialized.
*
* @returns [string]
* The serialized form.
*
* @dependencies
* r - user - urlencode
* r - user - getOptionValue
* a - ES1 - Array.prototype.join
* a - DOM1 - HTMLFormElement.elements
* a - DOM1 - HTMLFormElement.elements.length
* a - DOM1 - HTMLFormElement.elements.name
* a - DOM1 - HTMLFormElement.elements.type
* a - DOM1 - HTMLFormElement.elements.value
* a - DOM1 - HTMLFormElement.elements.disabled
* a - DOM1 - HTMLSelectElement.options
* a - DOM1 - HTMLSelectElement.selectedIndex
* a - DOM1 - HTMLOptionElement.selected
* a - DOM1 - HTMLInputElement.checked
*/

// test for required features
if (typeof urlencode != 'undefined' &&
typeof getOptionValue != 'undefined') {

var serializeFormUrlencoded = function(f) {
var e, // form element
n, // form element's name
o, // option element
es = f.elements,
c = []; // the serialization data parts

function add(n, v) {
c[c.length] = urlencode(n) + "=" + urlencode(v);
}

for (var i=0, ilen=es.length; i<ilen; i++) {
e = es;
n = e.name;
if (n && !e.disabled) {
switch (e.type) {
// The 'select-one' case could reuse 'select-multiple' case
// The 'select-one' case code is an optimization for
// serialization processing time.
case 'select-one':
if (e.selectedIndex >= 0) {


I think != -1 is clearer as no other negative values are possible.
add(n, getOptionValue(e.options[e.selectedIndex]));
}
break;
case 'select-multiple':
for (var j=0, jlen=e.options.length; j<jlen; j++) {
o = e.options[j];
if (o.selected) {
add(n, getOptionValue(o));
}
}
break;
case 'checkbox':
case 'radio':
if (e.checked) {
add(n, e.value);
}

For the 'checkbox' type, if no value is defined, the default is "on."
break;
case 'text':
case 'password':
case 'hidden':
case 'textarea':
add(n, e.value);
} // switch

You might want to put this code in its own function as it is sometimes
useful to query the value of just one form element by name. Granted,
it would be less efficient for this function and would raise the issue
I brought up before about multiple elements with the same name.
 
R

Richard Cornford

Peter said:
Below is the more complex form serialization implementation in a
branch of the repository. This one even contains an attempt at
documentation although it isn't so great.

I'd like to know other opinions of assuming the
existance of Array.protoype.join. It is very easy
to test for it and the test can
even be short as adding "&&[].join".

I would not bother testing for a join method. But if it is to be tested
for I don't think the shortness of the test code is not necessarily the
best guide as to which test to use. I would be inclined to directly test
for the property of the - Array.prototype - rather than create an
otherwise unused Array object instance.

... that are equally old: for example, forms have
an "elements" object property.

The very first form objects had - elements - collections, so don't worry
about them.

Test for the existence of all features that were introduced
after NN4 and IE4. (i.e. features that were not in both
NN4 and IE4)

For features that were in NN4 and IE4, only test for their
existence if there is a newer known browser that does not
have the feature.

Test that a feature works properly if there has been a known
buggy implementation of that feature or implementations with
various abilities (e.g. String.prototype.replace) in NN4,
IE4 or newer browsers.

===========

That doesn't seem unreasonable as a general principle, but one aspect of
the multiple implementations strategy is that there are contexts where
enough can be known up-front to eliminate the need for feature testing
(mostly Intranets, particularly the IE only ones).

for (var i=0, ilen=es.length; i<ilen; i++) {
e = es;
n = e.name;
if (n && !e.disabled) {
switch (e.type) {
// The 'select-one' case could reuse 'select-multiple' case
// The 'select-one' case code is an optimization for
// serialization processing time.
case 'select-one':
if (e.selectedIndex >= 0) {
add(n, getOptionValue(e.options[e.selectedIndex]));
}
break;
case 'select-multiple':
for (var j=0, jlen=e.options.length; j<jlen; j++) {
o = e.options[j];
if (o.selected) {
add(n, getOptionValue(o));
}
}
break;

<snip>
Ice Browser has a bug where the - type - of a select element is always
reported as "select" regardless of whether it is 'multiple' or 'one',
but the boolean - multiple - property of the element is appropriately
set.

Incidentally, some of the indentation in this post is horrible.

Richard.
 
R

Richard Cornford

Peter said:
On Dec 6, 2:20 pm, David Mark wrote:

IE 5 has try/catch.

As JScript versions can be updated independently of the browser that
assertion should be applied to JScript 5.0.

However, - finally - did not get introduced until JScript 5.5 (Which
does not matter much in context but should be mentioned for
completeness).

Richard.
 
D

David Mark

The last open issue for form serialization is the optional argument
for adding a name=value pair of the button pushed to initiate an Ajax
form submission.

I agree this is a worthwhile optional argument; however, I really
don't like adding features to basic functions like a form
serialization function. This is *exactly* how the mainstream popular
libraries become bloated. Everyone that needs a particular feature and
it is added to the base implementation. The Scriptaculous and YUI drag
drop libraries are particularly good examples that I've looked at that
suffer from this sort of feature creep.

So adding features is a slippery slope and one that makes me
particularly nervous. To add this feature I suggest we use a wrapper
that can wrap any of the serializeFormUrlencoded implementations.

// one of the many serializeFormUrlencoded implementations.

if (typeof urlencode != 'undefined'
/* other checks */ ) {

var serializeFormUrlencoded = function(f) {

//...

};

}

// In a seperate file define the wrapper function
// which automatically wraps the serializeFormUrlencoded
// function.

if (typeof serializeFormUrlencoded != 'undefined') {

serializeFormUrlencoded = (function() {

var original = serializeFormUrlencoded;

return function(f, options) {
var extras = [];
if (options && options.extras) {
var oes = options.extras;
for (var i=0, ilen=oes.length; i<ilen; i++) {
extras[extras.length] = urlencode(oes.key) +
'=' +
urlencode(oes.value);
}
}
var result = original.apply(this, arguments);


Why not just call original(f)?
return result +
((result && extras) ? '&' : '') +
extras.join('&');
};

})();

}

Using a wrapper obviously takes more space than to just add the
optional argument directly to each implementation of
serializeFormUrlencoded. The modularity of the wrapper is good
isolation of the optional parameter implementation. Someone else may
want a simpler button implementation that can only handle one extra
button parameter that will always have the key "button".

This type of wrapper system is what I'm using in the Ajax library I'll
propose soon for the repository. It seems to have solved the problem
of bloat very nicely and I'd bet that almost everyone will only use a
couple of the wrappers that suit their particular tastes in sugary
APIs. So although the wrapper system may not seem so persuasive here I
believe the general approach to modular software is a good one.

This is similar to what I do, but I replace object methods, rather
than global functions. This is because I build with modules, not
functions, so each module initializes in its own anonymous function.
I do like the idea of a function-based build process, but I think it
would take an automated process to make me switch to such a thing. It
would be necessarily more complex than the batch files I currently use
to concatenate modules.
 
P

Peter Michaux

The last open issue for form serialization is the optional argument
for adding a name=value pair of the button pushed to initiate an Ajax
form submission.
I agree this is a worthwhile optional argument; however, I really
don't like adding features to basic functions like a form
serialization function. This is *exactly* how the mainstream popular
libraries become bloated. Everyone that needs a particular feature and
it is added to the base implementation. The Scriptaculous and YUI drag
drop libraries are particularly good examples that I've looked at that
suffer from this sort of feature creep.
So adding features is a slippery slope and one that makes me
particularly nervous. To add this feature I suggest we use a wrapper
that can wrap any of the serializeFormUrlencoded implementations.
// one of the many serializeFormUrlencoded implementations.
if (typeof urlencode != 'undefined'
/* other checks */ ) {
var serializeFormUrlencoded = function(f) {



// In a seperate file define the wrapper function
// which automatically wraps the serializeFormUrlencoded
// function.
if (typeof serializeFormUrlencoded != 'undefined') {
serializeFormUrlencoded = (function() {
var original = serializeFormUrlencoded;
return function(f, options) {
var extras = [];
if (options && options.extras) {
var oes = options.extras;
for (var i=0, ilen=oes.length; i<ilen; i++) {
extras[extras.length] = urlencode(oes.key) +
'=' +
urlencode(oes.value);
}
}
var result = original.apply(this, arguments);


Why not just call original(f)?


If multiple wrappers are used (as they are in the Ajax code I'm
working on) then inner wrappers may depend on other properties of the
options object.

This is similar to what I do, but I replace object methods, rather
than global functions. This is because I build with modules, not
functions, so each module initializes in its own anonymous function.
I do like the idea of a function-based build process, but I think it
would take an automated process to make me switch to such a thing. It
would be necessarily more complex than the batch files I currently use
to concatenate modules.

I'm not sure what you mean and not completely clear what a "module" is
to you.

Peter
 
D

David Mark

The last open issue for form serialization is the optional argument
for adding a name=value pair of the button pushed to initiate an Ajax
form submission.
I agree this is a worthwhile optional argument; however, I really
don't like adding features to basic functions like a form
serialization function. This is *exactly* how the mainstream popular
libraries become bloated. Everyone that needs a particular feature and
it is added to the base implementation. The Scriptaculous and YUI drag
drop libraries are particularly good examples that I've looked at that
suffer from this sort of feature creep.
So adding features is a slippery slope and one that makes me
particularly nervous. To add this feature I suggest we use a wrapper
that can wrap any of the serializeFormUrlencoded implementations.
// one of the many serializeFormUrlencoded implementations.
if (typeof urlencode != 'undefined'
/* other checks */ ) {
var serializeFormUrlencoded = function(f) {
//...
};
}
// In a seperate file define the wrapper function
// which automatically wraps the serializeFormUrlencoded
// function.
if (typeof serializeFormUrlencoded != 'undefined') {
serializeFormUrlencoded = (function() {
var original = serializeFormUrlencoded;
return function(f, options) {
var extras = [];
if (options && options.extras) {
var oes = options.extras;
for (var i=0, ilen=oes.length; i<ilen; i++) {
extras[extras.length] = urlencode(oes.key) +
'=' +
urlencode(oes.value);
}
}
var result = original.apply(this, arguments);

Why not just call original(f)?

If multiple wrappers are used (as they are in the Ajax code I'm
working on) then inner wrappers may depend on other properties of the
options object.


Okay, change that to original(f, options).
I'm not sure what you mean and not completely clear what a "module" is
to you.

A module is a file with one or more functions in it. It is the basic
building block I use for applications. Your proposed strategy is more
granular in that it is using individual functions as building blocks.
I assume your build process will wrap multiple API functions in an
anonymous function, populating methods of an object at the end, all in
one file. My build process concatenates module files, which is less
flexible and adds a bit of a redundancy in the code (each has its own
anonymous function, each must do similar tests at the outset, etc.)
On the plus side, it was very trivial to implement and requires no
extraneous meta data for versioning (versions are part of each
module's filename.)
 
P

Peter Michaux

That doesn't seem unreasonable as a general principle, but one aspect of
the multiple implementations strategy is that there are contexts where
enough can be known up-front to eliminate the need for feature testing
(mostly Intranets, particularly the IE only ones).

Yes I meant those rules for scripts destined for the general web. I
will add this to the principles and post them in the project wiki.


[snip]
Ice Browser has a bug where the - type - of a select element is always
reported as "select" regardless of whether it is 'multiple' or 'one',
but the boolean - multiple - property of the element is appropriately
set.

Oh joy! Here is a new version...


if (typeof urlencode != 'undefined' &&
typeof getOptionValue != 'undefined' &&
String.prototype.match) {

var serializeFormUrlencoded = function(f) {
var e, // form element
n, // form element's name
t, // form element's type
o, // option element
es = f.elements,
c = []; // the serialization data parts

function add(n, v) {
c[c.length] = urlencode(n) + "=" + urlencode(v);
}

for (var i=0, ilen=es.length; i<ilen; i++) {
e = es;
n = e.name;
if (n && !e.disabled) {
t = e.type;
if (t.match(/^select/)) {
// The 'select-one' case could reuse 'select-multiple' case
// The 'select-one' case code is an optimization for
// serialization processing time.
if (t == 'select-one' || !e.multiple) {
if (e.selectedIndex >= 0) {
add(n, getOptionValue(e.options[e.selectedIndex]));
}
}
else {
for (var j=0, jlen=e.options.length; j<jlen; j++) {
o = e.options[j];
if (o.selected) {
add(n, getOptionValue(o));
}
}
}
}
else if (t.match(/^checkbox|radio$/)) {
if (e.checked) {
add(n, e.value);
}
}
else if (t.match(/^text|password|hidden|textarea$/)) {
add(n, e.value);
}
}
}

return c.join('&');
};

}

Thanks,
Peter
 

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,756
Messages
2,569,540
Members
45,025
Latest member
KetoRushACVFitness

Latest Threads

Top