reptile7's JavaScript blog
Saturday, November 13, 2010
 
Accessing Query String Data, Continued
Blog Entry #196

We're currently working through an example that applies the scripts of HTML Goodies' "How to Use a JavaScript Query String Parser" tutorial to a uninamed set of checkboxes. Picking up where we left off last time, we now have in hand the array below

q["Browser"] = ["Internet Explorer", "Firefox", "Opera"];

and we're ready to print this baby out. Towards this end, we will load the q data into a dl (definition list) element

<dl>
<dt>Browser</dt>
<dd>Internet Explorer</dd>
<dd>Firefox</dd>
<dd>Opera</dd>
</dl>


so as to give the following display:
Browser
Internet Explorer
Firefox
Opera
The browsers on my computer uniformly indent dd element content by half a tab (as they do for li element content of both ul and ol lists), i.e., by four spaces in a monospace font.

Returning to the script in the first textarea field on the tutorial's third page, here's the code that dl-izes the q data:
d.write("<dl>");
for (k in q) {
	d.write("<dt>", entify(k), "</dt>");
	for (i = 0; i < q[k].length; i++)
		d.write("<dd>", entify(q[k][i]), "</dd>"); }
d.write("</dl>");
Because q is a mixed associative-ordinal array, we will use two types of loops to access its data:
(1) An outer, one-iteration for...in loop will load q's first-dimension index value - Browser - into a dt (definition term) element. (IMO, Microsoft does a better job of explaining the for...in statement than does Mozilla - I recommend the former link if for...in loops are new to you.)
(2) A nested, three-iteration for loop will respectively load q's second-dimension element values - Internet Explorer, Firefox, and Opera - into successive dd (definition description) elements.

Before these values are written to the page, however, they are run through the entify.js script's entify( ) function:
/* Convert unsafe characters to HTML entities: */
function entify(s) {
	return ("" + s).
	replace(/&/g, "&amp;").
	replace(/</g, "&lt;").
	replace(/"/g, "&quot;").
	replace(/>/g, "&gt;"); }
Its appearance notwithstanding, the entify( ) function comprises a single statement that carries out two main tasks:

(1) &, <, ", and > characters in the data are replace( )d by their corresponding character entity references to ensure that the browser prints them out as literal characters and does not act on them as metacharacters.

(2) Any non-string (e.g., boolean or number) values are stringified via concatenation with an empty string, as calling the replace( ) method on a non-string will throw an "s.replace is not a function" error.

• The first replace( ) operation returns a string that is acted on by the second replace( ) operation, which returns a string that is acted on by the third replace( ) operation, etc. - daisy-chaining String commands in this way is very much analogous to forming a document.objectA.objectB.property expression. We saw back in Blog Entry #3 that JavaScript's 'dot operator' can be followed by a line break without throwing an error.

&, <, ", and > are not regular expression metacharacters and do not need to be literalized with backslashes for their respective replace( ) operations as was necessary for the conversion of plus signs to spaces by the param.js param( ) function.

• Are the replace( ) operations really necessary? You definitely need the replace(/</g, "&lt;") operation to see a literal < because an unescaped < is always seen as a start-tag open delimiter by the browser (at least this is my experience). In contrast, the browser will interpret &, ", and > as literal characters if it finds them in non-metacharacter contexts; the &/"/> replace( )ments are therefore less crucial - they're a "let's be on the safe side" thing, shall we say, for isolated cases that might require them.

• Is the ("" + s) stringification of non-string ss really necessary? Not if your query string is generated via an HTTP get transaction: across the board, all HTML control values have a string data type. But if your query string is created from scratch and contains =value-less names, and if you assign boolean or number values to those names on the parsing side (recall the else q[name][q[name].length] = true; clause in the param.js ptq( ) function, e.g.), then yeah, you're gonna need it.

Getting back to our example, the entify( ) function has no effect on
(a) Browser, the lone k value for the for...in loop, or on
(b-d) Internet Explorer, Firefox, or Opera, the q[k][i] values of the nested for loop;
these values are all strings and don't contain any &/</"/> characters, and thus they are returned unchanged to the d.write( ) commands that load them into dt or dd elements as shown above.

A curious note concerning the implementation of the param.js/entify.js scripts appears at the top of the tutorial's third page:
Note: The script probably won't work on your local computer if you have a recent browser. Due to cross-site scripting vulnerabilities some browsers won't load the files from myjs.us when they're used in a local file, due to the possibility of malicious scripts accessing local files.
Why would anyone use the param.js/entify.js scripts at the myjs.us site? Anyway, upon copying it in its entirety to my desktop, I find that the tutorial code works very nicely with all of the regular expression-supporting browsers on my computer.

Another example

In the tutorial's "Introduction" section, the author states:
Using a JavaScript query string parser is one of my favorite techniques. I've used it for online photo galleries, tracking coupon codes, writing online order forms, creating some of the original browser-based Rich Text editors and many other things.
There's nothing in the tutorial that relates to any of these applications, I'm sorry to report, but the tutorial does at least conclude with a "JavaScript Form Processor" example that, all at the same page,
(a) solicits a first name from the user,
(b) puts the user's input in a query string, and then
(c) extracts the user's input from the query string to print out a greeting to the user.
The example's demo page is here and its code is given below:

<title>JavaScript Form Processor</title>
<script src="http://myjs.us/param.js"></script>
<script>
q = param( );
firstname = q.name1;
document.write("<h1>Hello ", firstname || "Anonymous", "!</h1>");
</script>
<form>
Type your first name: <input type="text" name="name1" />
<input type="submit" value="Submit" />
</form>


So, we click the JavaScript Form Processor link in the tutorial's "Another Example" section to go to the http://www.codelib.net/home/jkm/2008/189.html demo page, at which we are greeted by a Hello Anonymous! h1 heading (more on this below). We type a first name - say, Joe - into the name1 field and click the button.

The controls' containing form does not have an action attribute, whose default value has a #REQUIRED designation; the W3C caveats, User agent behavior for a[n action] value other than an HTTP URI is undefined. What happens? In practice, the unspecified action URL 'resolves' to that of the current document, and a link is set up to http://www.codelib.net/home/jkm/2008/189.html?name1=Joe via an HTTP get transaction. The demo page reloads and we now see http://www.codelib.net/home/jkm/2008/189.html?name1=Joe in the browser window's address bar.

Next, the param( ) and ptq( ) param.js functions process the ?name1=Joe location.search value to give a one-element array:

q["name1"][0] = "Joe";

The firstname = q.name1; assignment gives the q["name1"] array a firstname identifier (q["name1"] and q.name1 are equivalent expressions). Finally, the

document.write("<h1>Hello ", firstname || "Anonymous", "!</h1>");

line concatenates <h1>Hello , q's Joe value, and !</h1> to give a Hello Joe! h1 heading and writes it to the page.

firstname is not a string but an object reference for an array; its use in the document.write( ) command entails a call to firstname.toString( ), which returns the string Joe.

• The || logical operator [r]eturns [its first operand] if it can be converted to true; otherwise, returns [its second operand]. As a non-empty string, Joe is convertible to true in a Boolean context and therefore we see Joe in the h1 greeting.

When we first access the demo page, the page URL doesn't have a query string; as a result, location.search returns an empty string, which is processed by the param( ) and ptq( ) functions to give a q[""][0] = true; array. In this case, q.name1 (firstname) is undefined, a primitive value that converts to false in a Boolean context, and therefore we see Anonymous in the h1 greeting.

The astute reader will have noticed that the example code imports the param.js script but not the entify.js script. Per the above discussion of entify.js's entify( ) function, I find that a Jo&e, Jo"e, or Jo>e input into the name1 field can be incorporated into the h1 greeting without incident, but a Jo<e input gives a Hello Jo heading, i.e., the browser sees the < as the open delimiter of a start-tag - a start-tag that is not completed and thus the <e is cut off - and not as a literal character. (BTW, an <em>Joe</em> input will give an italicized Joe in the greeting.)

There are some other remarks I could make about the tutorial (e.g., regarding the tutorial's "URL Encoding" section, no, it is not a JavaScript "bug" that different interfaces are required to deal with different types of form control objects) but I think I've had my say at this point. We'll get back to our "here's where we're at in the Beyond HTML : JavaScript sector, and here's where we're going" discussion in the following entry.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

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