reptile7's JavaScript blog
Tuesday, October 12, 2010
Check Your Values at the URL
Blog Entry #193

Previously we have discussed two Beyond HTML : JavaScript sector tutorials whose scripts set and act on an HTTP cookie:
(1) "So, You Want To Set A Cookie, Huh?", which we checked over in Blog Entries #144 and #145, and
(2) "So You Want A Cookie Counter, Huh?", which was covered in Blog Entry #185.
The "Cookie Counter" script's cookie is not really a typical cookie in that it is only meant for one Web page and contains no user-specific data. In contrast and more normally, the "Set A Cookie" script's cookie is meant to be carried across Web pages and its value attribute is set via user input into a text box.

Today we will take up the sector's "A Quick Tutorial on JavaScript Variable Passing", which complements the "Set A Cookie" tutorial by offering a non-cookie way to carry information across Web pages. In sum, the "JavaScript Variable Passing" tutorial code
(a) sets up a link between two pages - let's say from jspass1.html to jspass2.html - via a form and its action attribute;
(b) uses the form's method="get" attribute to load the form's data - i.e., its control values, which are specified by the user - into a query string tacked onto the action URL; and
(c) extracts the user's inputs at the action destination via standard String object methods.

Let's begin our analysis with a look at the jspass1.html form code:

<form method="link" action="jspass2.html">
<b>Type your first name:</b> <input type="text" name="FirstName"><br>
<b>Type your last name:</b> <input type="text" name="LastName"><br>
<input type="submit" value="Click and See">

The form's code contains one 'red flag', that being the form element's method="link" attribute. The method attribute of the form element has two valid values: get and post; link isn't one of them. Proprietarily, neither Netscape nor Microsoft lists link as a possible value for the method attribute. The HTML 4.01 Specification's "Notes on invalid documents" section stipulates, If a user agent encounters an attribute value it doesn't recognize, it should use the default attribute value. Get is the default method value and thus method="link" should be functionally equivalent to method="get", and this is indeed what I see on my computer.

And what does method="get" do for us? The above form holds a pair of labeled text boxes plus a submit button. Let's say we enter Joe into the FirstName field and Burns into the LastName field, and then click the button. The browser subsequently appends to the action URL a query string comprising
(a) a question mark (?) followed by
(b) a FirstName=Joe&LastName=Burns string encoding the name/value data for the form's successful controls (the submit button is not a successful control because it doesn't have a name attribute); an equals sign (=) separates each name from its value whereas an ampersand (&) separates the two name/value pairs.
This is all discussed in more detail in the HTML 4.01 Specification's "Form submission" section. Finally, the browser sends off a request for the jspass2.html?FirstName=Joe&LastName=Burns resource, which in practice simply takes us to the jspass2.html page - as Joe puts it, the query string doesn't harm the movement to another page - even though the full URL of the page we arrive at does include the query string as is shown by the browser window's address bar at that page.

At this point, we have accessed the jspass2.html?FirstName=Joe&LastName=Burns resource and are now ready to fish the jspass1.html form values out of its URL. Here's the code Joe uses to grab the value of the FirstName field:
<form name="joe">
<input type="hidden" name="burns">

<script type="text/javascript">
var locate = window.location;
document.joe.burns.value = locate;
var text = document.joe.burns.value;

function delineate(str) {
	theleft = str.indexOf("=") + 1;
	theright = str.lastIndexOf("&");
	return str.substring(theleft, theright); }

document.write("First Name is " + delineate(text));

The location property of the window object is given a locate identifier. However, window.location is not merely a property but is also a reference for the client-side Location object. The Location object stringifies to the current page's full URL via its toString( ) method but is not itself a string - typeof window.location returns object - and calling a String object method on a Location object will throw an error. Joe deals with this situation by assigning locate to the value of a name="burns" hidden control in a name="joe" form. The value property of the client-side Hidden object has a string data type and therefore the document.joe.burns.value = locate; assignment implicitly calls locate.toString( ), which returns the http://hostname/[pathname/]jspass2.html?FirstName=Joe&LastName=Burns page URL as a string. As the burns value, the URL string is subsequently assigned to a text variable.

In the tutorial comment thread, "openstud" correctly points out that a

var locate = window.location.toString( );

statement would give a stringified locate without the need for a hidden field and on which String object methods could be called; even more straightforwardly, a location.href expression would directly return the full URL as a string.

Anyway, the text string is next passed to a delineate( ) function, whose call is nested in a document.write( ) command; the delineate( ) declaration gives text a(n) str identifier. Here's the delineate( ) play-by-play:
(1) The index of the starting J of Joe in the str string is located and assigned to a theleft variable.
(2) On the following line, the index of str's & is located and assigned to a theright variable. Joe uses the lastIndexOf( ) method for this operation, which is OK because there's only one & in str, but the indexOf( ) method is what we should really be using here (and would have to use if the query string held more than two name/value pairs).
(3) Finally, str.substring(theleft, theright) (Joe) is returned to the delineate( ) function call and First Name is Joe is written to the page.

The value of the LastName field is extracted via an analogous delineate2( ) function:
function delineate2(str) {
	point = str.lastIndexOf("=");
	return str.substring(point + 1, str.length); }
document.write("Last Name is " + delineate2(text));
The tutorial's "Placing the Value" section shows how to load the delineate( ) and delineate2( ) returns into text boxes - I trust I don't need to go over this code for you. Of course, the DOM today allows us to load these returns into any can-contain-#PCDATA element (back when the tutorial was written (late 1999/early 2000), MSIE users could have done this via a document.all("elementID") or document.getElementById("elementID") command, but Netscape users were out of luck in this regard).

Limitations, or not

The tutorial introduction features a script demo that works fine as far as it goes. However, the tutorial also sports a "Limitations" section in which Joe identifies three limitations of his value-passing code:

(1) To begin with, the information is not saved after each surfing like it is with a cookie. True enough. Joe continues, In order for this to work, you must ensure the user moves in a linear fashion. The "linear" part is not right, but moving a query string from page to page does require
(a) a site to provide its own navigation and
(b) the user to use that navigation.

(2) Next, the way I have this set up, you can only transfer two [values] from page to page. In fact, Joe's code is easily retooled so as to carry multiple values across pages.

(3) Also, the method I have written here isn't very friendly to spaces. If the user puts a space in either of their two text boxes, that space will be replaced by a plus sign.

We'll take these limitations on in order of increasing difficulty.

Spaces to plus signs to spaces

Suppose that a Mary Ellen Le Blanc visits the jspass1.html page. She types Mary Ellen into the FirstName field and Le Blanc into the LastName field, and then clicks the button. At the jspass2.html page, she'll see a http://hostname/[pathname/]jspass2.html?FirstName=Mary+Ellen&LastName=Le+Blanc URL in the browser window's address bar and on the jspass2.html page itself she'll see:

First Name is Mary+Ellen
Last Name is Le+Blanc
First Name:
Last Name:

For an HTTP get transaction, space characters in form name/value data are conventionally escaped to plus signs (cf. the aforecited HTML 4.01 "Form submission" section) - spaces can be escaped in other ways but for now we're dealing with +s. URLs cannot validly contain literal space characters - the use of spaces in URLs is "unsafe" for reasons detailed in Section 2.2 of RFC 1738 - and thus the +s in the address bar URL are there to stay. As for the jspass2.html page display, however, we can easily convert the page URL's +s back to spaces via:

var text = location.href.replace(/\+/g, " ");

The replace( ) method of the String object is documented here. The above command runs through location.href and replaces each instance of /\+/, a regular expression representation of a literal + character, with a space. The resulting string is assigned to text, which can be fed to delineate( ) and delineate2( ) as in the original script.

Regular expression notes:
+ is a regular expression metacharacter - it's a quantifier signifying one or more occurrences of its preceding operand - and must be literalized via preceding it with a backslash (a /+/ pattern throws a syntax error, BTW).
• Without the g flag, only the first + in location.href will be replaced by a space.

Lagniappe for our diacritical friends

The W3C notes, The get method restricts form data set values to ASCII characters. Hmmm... Suppose that a Günther Schmidt visits the jspass1.html page. He types Günther into the FirstName field and Schmidt into the LastName field, and then clicks the button. What happens?

The ü in Günther lies outside the ASCII range but is still a "Latin-1" character. For the get query string, the ü will be percent-encoded, i.e., replaced by its two-digit hexadecimal ISO-8859-1 code position - FC in this case - preceded by a percent sign (%). Accordingly, at the jspass2.html page Günther will see a
?FirstName=G%FCnther&LastName=Schmidt query string
at the end of the URL in the browser window's address bar and the
document.write("First Name is " + delineate(text)); output on the page will be
First Name is G%FCnther.

How might we convert %FC back to ü at the jspass2.html page? Classical JavaScript offered a top-level unescape( ) function that would allow us to do just that:

var text = unescape(location.href);

Unfortunately, Netscape deprecated unescape( ) and its complementing escape( ) function for JavaScript 1.5; superseding unescape( ) and escape( ) is a set of four functions - decodeURI( ), decodeURIComponent( ), encodeURI( ), and encodeURIComponent( ) - that decode/encode URIs or the components thereof in accord with the UTF-8 character encoding scheme. It gets worse: the decodeURI( ) and decodeURIComponent( ) functions cannot be used to decode the http://hostname/[pathname/]jspass2.html?FirstName=G%FCnther&LastName=Schmidt URL because %FC by itself is an illegal UTF-8 escape sequence. (Cf. the "Codepage layout" table on Wikipedia's UTF-8 page: %FC is only legit if it appears at the start of a UTF-8-encoded 6-byte sequence. In corroboration, I find that decodeURIComponent("?FirstName=G%FCnther&LastName=Schmidt") throws a "malformed URI sequence" error when using Firefox.)

Now what? Mozilla's decodeURI( ) page states that decodeURI( ) [d]ecodes a Uniform Resource Identifier (URI) previously created by encodeURI( ) or by a similar routine. It follows that if we want to use decodeURI( ) or decodeURIComponent( ) to decode our 'Günther URL' at the jspass2.html page, we're going to have to circumvent the jspass1.html page's get method code in favor of a DIY approach that creates, UTF-8-encodes, and links to that URL from scratch. And this is not so difficult to do: simply add to the jspass1.html form a

<input type="button" value="UTF-8 Click and See" onclick="passValues(this.form.FirstName, this.form.LastName);" />

push button that when clicked calls the following function:
function passValues(field1, field2) {
	qString = "?" + + "=" +  field1.value + "&" + + "=" +  field2.value;
	location.href = encodeURI("jspass2.html" + qString); }
ü is UTF-8-encoded to %C3%BC, which is derived from a 11000011 10111100 bit pattern that is itself derived from ü's 11111100 ISO-8859-1 bit pattern. On the jspass2.html side, the %C3%BC in our homemade query string can be converted back to ü via:

var text = decodeURI(location.href);

Interestingly, upon UTF-8-encoding the URL, some browsers (Firefox, Safari, Chrome) display the unescaped ü in the address bar at the jspass2.html page whereas others (Opera, Netscape 9) display the %C3%BC escape sequence.

We'll sort out the remaining two code limitations in the following entry.


Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)