reptile7's JavaScript blog
Wednesday, March 28, 2007
 
Resize, Redisplay, Retool
Blog Entry #71

Before we get started...
In Blog Entry #69, we discussed the onError event handler as a means of flagging a broken image. It has recently come to my attention that the Beyond HTML : JavaScript section of HTML Goodies contains a "The onerror Event Handler" tutorial; it's pretty skeletal, and it applies onError to the window object and not to an image object, but it still deserves a mention, so we'll give it one here.

And now, back to the Script Tips #45-48 Script and its second script element. Having determined the correct values of high and wide in our last episode

Height:
258
Width:
199
Enter New Height:
New Width Equals
Height:
258
Width:
199
Enter New Width:
New Height Equals

we are ready to resize the angel.jpg image. As shown, in Table #1 the user can enter a new image height into the text field in the third cell; clicking the fourth cell's "Solve" button then calculates a corresponding width and displays it in the fifth cell's text field. Table #2 similarly accepts a new image width and determines a corresponding height. (This isn't an either/or situation - Tables #1 and #2 can be used at the same time if for some reason you wanted to do that.)

In this context, height and width are directly proportional. Drawing inspiration from the treatment of Charles' Law in a typical general chemistry textbook, we can write:

Image height-width relationship

h1 is angel.jpg's intrinsic height (258 pixels)
w1 is angel.jpg's intrinsic width (199 pixels)
h2 is a new angel.jpg height
w2 is a new angel.jpg width

For Table #1, h2 is algebraically an independent variable and w2 is the dependent variable; vice versa for Table #2. Rearrangement of the height-width relationship gives:

w2 as a function of h2; h2 as a function of w2

Resizing the image: the newW( ) and newH( ) functions

So, suppose the user enters a new height for angel.jpg into Table #1 and then clicks the adjacent "Solve" button, which triggers the second script element's newW( ) function. In terms of my variables and equations above, newW( ) would be coded as:

var h1; var w1; var h2; var w2;
function newW( ) {
h1 = high;
w1 = wide;
h2 = document.calc.h2.value;
w2 = (w1/h1)*h2;
document.calc.width2.value = Math.round(w2); }

The newW( ) function calculates a proportional angel.jpg width w2, which is rounded to the nearest integer via the round( ) method of the Math object and then displayed in the width2 text field in Table #1's fifth cell.

(The original newW( ) function uses the variables a, b, c, and d for h1, w1, h2, and w2, respectively, and formulates the w2 or d calculation as (b*c)/a - I find this semantically unsatisfying, shall we say.)

For scaling a new angel.jpg width inputted into Table #2, the second script element follows newW( ) with an analogous newH( ) function. In terms of my variables and equations above, newH( ) would be coded as:

function newH( ) {
h1 = high;
w1 = wide;
w2 = document.calc.wb2.value;
h2 = (h1/w1)*w2;
document.calc.height2.value = Math.round(h2); }
/*The original newH( ) function uses the variables a, b, e, and f for h1, w1, w2, and h2, respectively.*/

The newH( ) function calculates a proportional angel.jpg height h2, which is again rounded to the nearest integer and then displayed in the height2 text field in Table #2's fifth cell.

The entered new height/width can of course be larger or smaller than angel.jpg's intrinsic dimensions: a height input of 400 gives a new width of 309, a width input of 50 gives a new height of 65, and so on.

"The newW( ) and newH( ) functions are rather similar - is there some way we can fold them into a single function?"

Indeed there is, but first, a bit of commentary on...

Displaying the resized image: the newWin( ) and newWin2( ) functions

The last cells in Tables #1 and #2 hold "Let Me See It" buttons that, when clicked, respectively trigger the second script element's newWin( ) and newWin2( ) functions, which display the resized angel.jpg image in a new, 500×500 window and which Joe discusses in Script Tip #48. There's nothing in the newWin( ) and newWin2( ) functions that we haven't covered in detail previously. Both functions deploy the path, c or f, and d or e variables in a document.write( ) command to build an img element in much the same way that we custom-built a body element in the Script Tips #19-20 Script. Also, we coded a new window in an opener document as recently as in the Script Tips #21-24 Script.

There are, however, a couple of newWin( )/newWin2( ) issues that I want to touch on:

(1) With respect to the new window's dimensions in the window.open( ) command, Joe says:
Then we offer the height and width of the new window. I thought about setting up the script so that the height and width would conform to the new image size, but decided against it. It was a lot of coding for very little event. Besides, if the user creates an image that is too large for the 500×500 window, they can always click the maximize button.
A lot of coding?? Not at all, Joe...

function newWin( ) {
g = eval(c) + 20;
h = d + 20;
var OpenWindow = window.open("", "newwin", "height=" + g + ",width=" + h); // etc.

The code above adds 10 pixels of "padding" to the angel.jpg image and then sets the new window's height and width so that the edges of the window's content area coincide with the padding edge. (Without the padding, slight scrolling will be needed to see the image's right and bottom content edges.) Note that c is a string (object) that must be numberified via the top-level eval( ) function (the top-level Number( ) function will also convert c to a number); otherwise, 20 is concatenated with c (and not arithmetically added to c).

Moreover, I can't vouch for a PC, but on my iMac, the new window doesn't have a "maximize button" unless I add a resizable feature to the third window.open( ) parameter.

(2) Regarding the values that are assigned to the name properties of the new windows, Joe first states, [For the newWin( ) function,] the name 'newwin' is assigned to the new window. It will never come into play in this script, but format requires you to have it, and then later says, Notice that [for the newWin2( ) function] the name of the window has changed. It is now 'newwin2'. You can't have two windows with the same name on the same page. We learned in Blog Entry #25 that the second, windowName window.open( ) parameter can be an empty string, and if desired, you can alternatively formulate both window.open( ) statements as

var OpenWindow = window.open("", "", "height=500,width=500");

and still be able to use Tables #1 and #2 simultaneously.

Other code possibilities: two into one

There's no shortage of duplicate code in the Script Tips #45-48 Script, and we can easily fuse the newW( ) and newH( ) functions, the newWin( ) and newWin2( ) functions, and Tables #1 and #2 into single blocks of code if we are willing to forgo the separate/simultaneous use of Tables #1 and #2.

Let's start with Tables #1 and #2. Trivially, we can subtract Table #1's end-tag and Table #2's start-tag to give a two-row, twelve-cell table. But given the close similarities between the two tables, we can do better than that.

The approach described below will code the following one-row table:

Height:
258
Width:
199
Choose a new...
Height
Width
Enter a Value:
The new

equals:

And here's the CSS we'll use:

body { background-color: #ffccff; }
table { background-color: #fdf99d; border: outset 12px #ffccff;}
td { text-align: center; border: solid thin #c6a0c6; }
/*CSS border styles are helpfully illustrated in HTML Goodies' "CSS and Borders" tutorial.*/
td#cell1 { text-align: left; }
span { font-weight: bold; }
/*FYI: unlike the corresponding attributes for the img element, neither the border attribute of the table element nor the align attribute of the td element has been deprecated, but I'm going to separate them from the script's HTML anyway - at least the <td> tags below will be a bit less cluttered.*/

In Tables #1 and #2, the first cells are identical and the second cells are identical. If desired, the two first cells and the two second cells can be merged separately, although my own preference is to roll these four cells into one - the coding below builds on the previous post's "Method #2" for obtaining angel.jpg's intrinsic dimensions:

<td>Height:<br />
<span id="span0"></span><br />
Width:<br />
<span id="span1"></span></td>

The two tables' third cells allow the user to input into text fields a new height and a new width for the angel.jpg image; in contrast, my table's second cell uses two radio buttons to force a height or width choice

<td id="cell1">Choose a new...<br />
<input type="radio" name="newdim" /> Height<br />
<input type="radio" name="newdim" /> Width</td>

and its third cell provides a single text field for the new height/width value:

<td>Enter a Value:<br />
<input size="5" name="d2" /></td>

The fourth cells of Tables #1 and #2 differ only in their onclick="newW( );" and onclick="newH( );" function calls and are otherwise identical. Later we will replace the newW( ) and newH( ) functions with a new newD( ) function, and my table's fourth cell condenses the two "Solve" buttons accordingly:

<td><input type="button" value="Solve" onclick="newD( );" /></td>

In the text fields of their fifth cells, Tables #1 and #2 can display a calculated new width and a calculated new height for the angel.jpg image; in contrast, my table's fifth cell holds
(a) an upper text field that will display a Width or Height string depending on whether the user checks the second cell's "Height" or "Width" radio button, respectively, and
(b) a lower text field for displaying the calculated new width or height:

<td>The new<br />
<input name="dimname" size="8" /><br />
equals:<br />
<input name="dimvalue" size="5" /></td>

The sixth cells of Tables #1 and #2 differ only in their onclick="newWin( );" and onclick="newWin2( );" function calls and are otherwise identical. Later we will replace the newWin( ) and newWin2( ) functions with a new newWin( ) function, and my table's sixth cell condenses the two "Let Me See It" buttons accordingly:

<td><input type="button" value="Let Me See It" onclick="newWin( );" /></td>

My table concludes with a seventh-cell reset button:

<td><input type="Reset" /></td>

Rolling right along, we now return to a question we posed earlier: can the second script element's newW( ) and newH( ) functions be recast as one function, more specifically a function in terms of the common variables h1, w1, h2, and w2? It wouldn't seem so at first glance, because newW( ) and newH( ) convert their independent variables to their dependent variables by reciprocal conversion factors - w1/h1 and h1/w1, respectively - however, it is simple enough to fork these conversions in a single function via two if statements:

var h1; var w1; var h2; var w2;
function newD( ) {

if (document.calc.newdim[0].checked) {
h2 = document.calc.d2.value;
w2 = (w1/h1)*h2;
document.calc.dimname.value = "Width";
document.calc.dimvalue.value = Math.round(w2); }

if (document.calc.newdim[1].checked) {
w2 = document.calc.d2.value;
h2 = (h1/w1)*w2;
document.calc.dimname.value = "Height";
document.calc.dimvalue.value = Math.round(h2); } }

N.B. You can replace the dimname text field with a span element and write the Width and Height strings as DOM text nodes à la the previous post, e.g.:

document.getElementById("spanID").innerHTML = "Width"; // or
document.getElementById("spanID").appendChild(document.createTextNode("Height"));

However, the reset button won't clear them in this case!

We lastly proceed to the newWin( ) and newWin2( ) functions, which are smoothly blended to give one function now that we've harmonized the calculated new height and width as h2 and w2, respectively; here is my preferred code:

function newWin( ) {
var OpenWindow = window.open("", "newwin", "resizable,height=500,width=500");
OpenWindow.document.write("<body>");
OpenWindow.document.title = "Image";
OpenWindow.document.body.style.backgroundColor = "black";
OpenWindow.document.body.style.textAlign = "center";
OpenWindow.document.write("<img>");
OpenWindow.document.images[0].src = path;
OpenWindow.document.images[0].style.height = h2;
OpenWindow.document.images[0].style.width = w2; }

You may be wondering, "Can we remove newwin's style features from the newWin( ) function and put them in the opener document's CSS?" "Not if you want them to be applied to newwin" is what I find on my computer.

New height/width data validation

(A full account of the ways in which a user can 'misinteract' with the script would require another entry, so I'll keep this brief.)
The Script Tips #45-48 Script will validly act on height/width inputs of 1 and 1000000, but these values will obviously not give useful resized-image displays. If you would like to restrict the user to, say, a 50-1000 pixel range, then the following code, to be placed in the newD( ) function and before its first if statement, will do the trick:

x2 = document.calc.d2.value;
if (!/^\d{2,4}$/.test(x2) || x2<50 || x2>1000) {
window.alert("Please enter a number in the range 50-1000, inclusive.");
document.calc.reset( ); }

The !/^\d{2,4}$/.test(x2) expression catches any inputs that are not two-, three-, or four-digit numbers - we wouldn't want the user to enter !@#$, would we now?

In the next post, we'll move on to the Script Tips #49-51 Script, which allows the user to assemble and view various frames arrangements.

reptile7

Monday, March 19, 2007
 
Dimensions Delayed and DOM-Determined
Blog Entry #70

Back to the Script Tips #45-48 Script, whose first script element we dissected in the previous entry. So, let's suppose that the user correctly enters angel.jpg into the prompt( ) box and clicks "OK". When the page loads, the user sees the angel.jpg image and, per the HTML at the bottom of the Script Tips #45-48 Script, a "Resize It" heading plus two one-row tables for manipulating the image. The first two cells of each table display respectively the intrinsic height and width of the image - or do they?

The intrinsic dimensions of the angel.jpg image are:
Height=258 pixels, Width=199 pixels
On my iMac, here's what happens when I access the script demo page: for a first-time visit - if the angel.jpg image is not cached - Netscape 7.02 displays Height=24, Width=24 in the tables at the bottom of the page; similarly, MSIE 5.1.6 displays Height=26, Width=26. Upon reloading the page (and going through the prompt( ) dialog a second time), Netscape always displays the intrinsic dimensions, whereas with MSIE I still see Height=26, Width=26.

So what's going on? Watching the page load, it is clear that when either browser hits the

else document.write("<img name='thepic' src=" + path + ">");

command line that posts the image, it doesn't just stop while angel.jpg loads, but continues to move through the document source. Specifically, the browser proceeds to the second script element and executes the followng two statements:

var high = document.thepic.height;
var wide = document.thepic.width;

If angel.jpg has not loaded, then high and wide return the default dimensions of an empty <img /> placeholder, which are ≅25×25 in my case (Joe reports 40×40 on his machine in Script Tip #45). Subsequently, high and wide are picked up by the first two cells of each table:

<td align="center">Height:<br><script language="javascript">document.write("<b>"+ high +"</b>")</script></td>
<td align="center">Width:<br><script language="javascript">document.write("<b>"+ wide +"</b>")</script></td>

The script uses the wide/high and high/wide ratios to resize the image, so it is important that the high and wide values are correct. (Conversely, the 25×25 or 40×40 dimensions wouldn't be an issue if angel.jpg were a square image, but it isn't.) Obviously, then, we need to ensure that angel.jpg has completely loaded before high and wide are set. Perhaps the easiest way to do this - in theory, at least - is to preload the angel.jpg image:

// Before the prompt( ) command in the first script element, insert:
thepic = new Image( );
thepic.src = "angel.jpg";

In practice, I find that these lines of code make no difference when using Netscape, which continues to display Height=24, Width=24 (MSIE does show the intrinsic dimensions); moreover, preloading goes against the idea of spontaneously choosing an image and working with it. Well, what about putting the high and wide declarations in a function that is delayed by a window.setTimeout( ) command? This is a step in the right direction, but again, a setTimeout( ) command will not stall the browser, which will barrel on to the table code and promptly throw "'high' is undefined" and "'wide' is undefined" runtime errors.

Optimally, we would write high, wide, and the contents of the table cells that contain them in a single, time-delayed function, and it turns out that this isn't difficult to do, although it will require us to make use of some specialized DOM tools. Outlined below are two cross-browser methods for reliably and reproducibly generating correct values for high and wide.

(1) Method #1 employs the DOM innerHTML property, which sets or gets all of the markup and content within a given element, quoting Mozilla. The innerHTML property was originally developed by Microsoft as a proprietary MSIE extension but is now supported by other browsers; however, it's not listed in the "Attributes" section of the DOM Level 3 Core Specification's Element Interface.

Let's begin by recoding the first two cells of the two tables as follows:

Table #1:
<td align="center" id="cell0"></td>
<td align="center" id="cell1"></td>
Table #2:
<td align="center" id="cell6"></td>
<td align="center" id="cell7"></td>
<!--We'll address the align="center" attribute, and other style features of the Script Tips #45-48 Script, in a subsequent post.-->

It just occurred to me that none of the scripts previously discussed on this blog has featured an HTML table, so let me give you a couple of references here:
• For the basics of HTML table creation, check out HTML Goodies' "So, You Want A Table, Huh?" tutorial.
• The W3C discusses HTML tables in Chapter 11 of the HTML 4.01 Specification.

Next, we remove the high and wide declarations in the second script element and, building on the code given in the previous post, put the following function in the first script element and after the imageFileDialog( ) and badImage( ) functions:

var high; var wide;
function highwide( ) {
high = document.thepic.height;
wide = document.thepic.width;
// In the following statements, <br /> cannot be replaced with \n or &#10;.
document.getElementById("cell0").innerHTML = "Height:<br /><b>" + high + "</b>";
document.getElementById("cell1").innerHTML = "Width:<br /><b>" + wide + "</b>";
document.getElementById("cell6").innerHTML = "Height:<br /><b>" + high + "</b>";
document.getElementById("cell7").innerHTML = "Width:<br /><b>" + wide + "</b>"; }

Finally, we can call the highwide( ) function after, say, a 500-millisecond time delay via an img element onload attribute:

<img name="thepic" src="" alt="Here's where the image should be." onload="window.setTimeout('highwide( );',500);" />

We've heretofore always used the onLoad event handler with the body element, but we briefly noted here in Blog Entry #10 that it can also be used with the img element. In its Description of the onLoad event handler, Netscape notes, For images, the onLoad event handler indicates the script to execute when an image is displayed. Do not confuse displaying an image with loading an image.

(2) Method #2 is more in sync with the script's original design. Instead of writing the entire contents of the table cells, we will again add the values of high and wide to the cells' preexisting Height/Width:<br><b>x</b> content; this time, however, we'll write high and wide not via separate JavaScript scripts but as new DOM "text nodes." Consider the first cell in the first table:

<td align="center">Height:<br><script language="javascript">document.write("<b>" + high + "</b>")</script></td>

With an eye on separating structure and presentation at a later point, let's replace the script element with a span element as follows:

<td align="center">Height:<br /><span id="span0" style="font-weight:bold;"></span></td>

We can now append high as a text node "child" to the span element via the following two commands:

var highValue = document.createTextNode(high);
document.getElementById("span0").appendChild(highValue);

Or one command, if you prefer:

document.getElementById("span0").appendChild(document.createTextNode(high));

Mozilla's DOM Reference has a page here for the createTextNode( ) method of the document object and a page here for the appendChild( ) method. In the DOM Level 3 Core Specification, the createTextNode( ) method is, appropriately, listed in the Document Interface, whereas the appendChild( ) method is listed in the Node Interface (and not in the Element Interface).

Let's put it all together à la Method #1 above. We begin by recoding the first two cells of the two tables:

Table #1:
<td align="center">Height:<br /><span id="span0" style="font-weight:bold;"></span></td>
<td align="center">Width:<br /><span id="span1" style="font-weight:bold;"></span></td>
Table #2:
<td align="center">Height:<br /><span id="span6" style="font-weight:bold;"></span></td>
<td align="center">Width:<br /><span id="span7" style="font-weight:bold;"></span></td>

We again remove the high and wide declarations in the second script element and then put a retooled highwide( ) function in the first script element:

var high; var wide;
function highwide( ) {
high = document.thepic.height;
wide = document.thepic.width;
document.getElementById("span0").appendChild(document.createTextNode(high));
document.getElementById("span1").appendChild(document.createTextNode(wide));
document.getElementById("span6").appendChild(document.createTextNode(high));
document.getElementById("span7").appendChild(document.createTextNode(wide)); }

Method #1's

<img name="thepic" src="" alt="Here's where the image should be." onload="window.setTimeout('highwide( );',500);" />

highwide( ) function call can be used 'verbatim' for Method #2.

And that wraps up today's foray into the DOM. In the following post, we'll finally get around to resizing/redisplaying the angel.jpg image, and also perhaps revamp the script a bit.

reptile7

Labels: , , ,


Saturday, March 10, 2007
 
Super Resize Me, Part 1
Blog Entry #69

In Blog Entry #15, we discussed the properties of the 'client-side' image object. For that entry, I put together a demo that uses onClick event handlers to write a thumbnail image's width, height, hspace, vspace, and border properties. (The W3C states here that these properties have all been deprecated; however, the width and height properties are listed in the HTML 4.01 Strict DTD.) Since then, our image concerns have focused on writing the image src property in various image-flipping contexts: an animation, a random banner display, etc.

In the next few posts, we return the spotlight to the image width and height properties as we look over HTML Goodies' JavaScript Script Tips #45, #46, #47, and #48, which together address a script that allows a user to scale proportionally an image's dimensions. The user first selects an image file from a local disk or from the Web and then chooses a new height or width for the image; for its part, the script calculates a corresponding width or height, respectively, based on the image's intrinsic dimensions, and then resizes the image accordingly. For the user's convenience, the resized image is displayed in a new window.

The Script Tips #45-48 Script can be found in HTML Goodies' /legacy/beyond/javascript/stips/ subdirectory here and is reproduced in the div below:

<!-- The following creates the prompt and displays the image. -->

<script language="javascript">

var path = prompt("Where will I find the image? Put in full URL or Hard Drive Path and Image Name For example: C:/directory/image.jpg","Only image name required if in same directory");

if (path == "Only image name required if in same directory")
{
alert('Come on, put in an image name');
javascript:location.reload( );
}

if (path == null)
{
alert('Do not click Cancel');
javascript:location.reload( );
}

else
{document.write("<img name=thepic src=" +path+ ">");}

</script>


<!-- The following contains the two resize functions and two new window functions. -->

<script language="javascript">

var high = document.thepic.height;
var wide = document.thepic.width;

function newW( )
{
a = high;
b = wide;
c = document.calc.h2.value;
d = (b*c)/a;
document.calc.width2.value = Math.round(d);
}

function newH( )
{
a = high;
b = wide;
e = document.calc.wb2.value;
f = (a*e)/b;
document.calc.height2.value = Math.round(f);
}

function newWin( )
{
var OpenWindow=window.open("", "newwin", "height=500,width=500");
OpenWindow.document.write("<html>");
OpenWindow.document.write("<title>Image</title>");
OpenWindow.document.write("<body bgcolor='000000'>");
OpenWindow.document.write("<center>");
OpenWindow.document.write("<img src=" +path+ " height=" +c+ " width=" +d+ ">");
OpenWindow.document.write("</center>");
OpenWindow.document.write("</html>");
}

function newWin2( )
{
var OpenWindow=window.open("", "newwin2", "height=500,width=500");
OpenWindow.document.write("<html>");
OpenWindow.document.write("<title>Image</title>");
OpenWindow.document.write("<body bgcolor='000000'>");
OpenWindow.document.write("<center>");
OpenWindow.document.write("<img src=" +path+ " height=" +f+ " width=" +e+ ">");
OpenWindow.document.write("</center>");
OpenWindow.document.write("</html>");
}

</script>

<!-- The following contains the two tables that appear on the page. -->

<h2>Resize It</h2>

<form name="calc">

<table border="12" cellspacing="0" cellpadding="4" bgcolor="#fdf99d">

<tr>
    <td align="center">Height:<br><script language="javascript">document.write("<b>"+high+"</b>")</script></td>
    <td align="center">Width:<br><script language="javascript">document.write("<b>"+wide+"</b>")</script></td>
    <td align="center">Enter New Height:<br><input type="text" size="5" name="h2"></td>
    <td align="center"><input type="button" value="Solve" onclick="newW( );"></td>
    <td align="center">New Width Equals<br><input type="text" name="width2" size="10"></td>
    <td align="center"><input type="button" onclick="newWin( );" value="Let Me See It"></td>
</tr>

</table>

<table border="12" cellspacing="0" cellpadding="4" bgcolor="#fdf99d">

<tr>
    <td align="center">Height:<br><script language="javascript">document.write("<b>"+high+"</b>")</script></td>
    <td align="center">Width:<br><script language="javascript">document.write("<b>"+wide+"</b>")</script></td>
    <td align="center">Enter New Width:<br><input type="text" size="5" name="wb2"></td>
    <td align="center"><input type="button" value="Solve" onclick="newH( );"></td>
    <td align="center">New Height Equals<br><input type="text" name="height2" size="10"></td>
    <td align="center"><input type="button" onClick="newWin2( );" value="Let Me See It"></td>
</tr>

</table>

</form>

Joe provides for the Script Tips #45-48 Script a demo that acts on an "angel.jpg" image:

This is the script's demo image, angel.jpg.

Importantly, the demo page's source contains a body element start-tag not present in the script itself; in my attempts to run the script, I find, at least when using Netscape 7.02, that a <body> tag is in fact a necessary inclusion (this isn't the first situation in which I've observed the supposedly "optional" <body> tag to be, uh, not so optional).

Prompt( ) possibilities and the image file name

When the user follows the "See it in Action" link to the demo page, then a prompt( ) box initially pops up soliciting the user for an image file name:

Prompt( ) box for the image file name

The user's input is outputted/assigned to the variable path. The Script Tips #45-48 Script's first script element holds the prompt( ) command therefor and also an if...if...else code block for dealing with the user's possible prompt( ) box responses, which Joe discusses in Script Tip #45; in source order:

(1) If the user doesn't enter a file name into the prompt( ) box input field and clicks the "OK" button, then the condition of the first if statement, path=="Only image name required if in same directory", returns true: a "Come on, put in an image name" alert( ) message pops up and the page is reloaded via the following command:

javascript:location.reload( );

This command is in the form of a JavaScript URL, which is unnecessary; location.reload( ) by itself is good enough. The reload( ) method of the location object is treated here in the JavaScript 1.3 Client-Side Reference. A location.reload( ) command is equivalent to a history.go(0) command, which we have used previously to refresh documents.

(2) If the user clicks the prompt( ) box's "Cancel" button, then the condition of the second if statement, path==null, returns true: a "Do not click Cancel" alert( ) message pops up and the page is reloaded as described above. Contra Script Tip #45, null is not a "command" but is syntactically a primitive value/data type.

(3) If the user does enter a file name into the prompt( ) box input field and clicks the "OK" button, and if the file name is correct (i.e., contains no typos), then the else statement writes the image to the page via a document.write( ) command:

else document.write("<img name='thepic' src=" + path + ">");
/*It would be good form to put single quotes around the src attribute value, but I've left them out for clarity.*/

The else statement would ordinarily also be executed if the first if statement's condition is true and the second if statement's condition is false, but this is preempted here by the first if statement's location.reload( ) command.

In practice...
On my computer, Netscape 7.02 handles the if...if...else code block without any problems, but MSIE 5.1.6 will not execute a location.reload( ) command unless it is coordinated with a user event (as noted here in Blog Entry #18, MSIE 5.1.6 places the same restriction on the execution of window.status="Message" statements). It is simple enough, however, to functionize the prompt( )/if...if...else code and trigger it with an onLoad event handler; the code below works with both browsers:

<script type="text/javascript">

var path;

function imageFileDialog( ) {

path = window.prompt("Where will I find the image? Put in full URL or Hard Drive Path and Image Name For example: C:/directory/image.jpg","Only image name required if in same directory.");

document.thepic.src = path;

if (path == "Only image name required if in same directory.") {
window.alert("Come on, put in an image name."); location.reload( ); }

if (path == null) {
window.alert("Do not click Cancel."); location.reload( ); } }

</script>

<body onload="imageFileDialog( );">
<img name="thepic" src="" alt="Here's where the image should be." />

Joe briefly considers a fourth prompt( ) possibility:
The user enters incorrect information.
Solution: I can't predict incorrect info, so that results in a broken image, which gives a 40X40 height and width.

We may not be able to "predict" an incorrect image file name, but we can most certainly intercept it. Towards this end, my first approach was to compare path with a suitable regular expression:

var imageFileName = /^[a-z_]\w*\.(gif|jpg|png)$/i;
if (!imageFileName.test(path)) {
window.alert(path + " is an incorrect image file name - please try again.");
location.reload( ); }

For the imageFileName regexp pattern:
• After the ^ start-of-string anchor, [a-z_] matches a single letter (lowercase or uppercase - note the i flag after the pattern) or underscore character.
\w* matches zero or more letters, numbers, or underscores.
\. matches the period preceding the image file extension - recall that the period is a regexp metacharacter that (outside of square brackets) must be escaped with a backslash.
(gif|jpg|png) matches either the gif, jpg, or png image file extension; the pattern ends with the $ end-of-string anchor.

I don't know what restrictions, if any, the Windows platform puts on image file names; however, the Mac platform doesn't require an image file name to begin with a letter or underscore, or end with a .xyz extension. In any case, the imageFileName pattern contains a fatal, fundamental flaw: it won't catch a simple misspelling. What to do? Fortunately, a more general and effective solution is at hand: if an incorrect file name "results in a broken image," then we can tie this outcome to an alert( ) message and a page reload via the onError event handler, which [e]xecutes JavaScript code when an error event occurs; that is, when the loading of a document or image causes an error, quoting Netscape.

To the above imageFileDialog( ) function, and after its second if statement, we can add:

if (path != "Only image name required if in same directory." && path != null)
document.thepic.onerror = badImage;

After the imageFileDialog( ) function, and in the same script element, we can code the badImage( ) function as:

function badImage( ) {
window.alert(path + " is an incorrect image file name - please try again.");
location.reload( ); }

Although other onError codings are possible, this formulation prevents the alert( ) message in the badImage( ) function from popping up if either of the first two imageFileDialog( ) if conditions is true.

With respect to the W3C's technical reports, the error event is briefly discussed in the "HTML event types" section of the DOM Level 2 Events Specification; however, onerror as an element attribute does not appear in the HTML 4.01 Specification's list of intrinsic events.

We next consider the determination of angel.jpg's intrinsic dimensions, which we'll discuss in detail in the following post.

reptile7

Labels: ,


Thursday, March 01, 2007
 
Google It, Part 3
Blog Entry #68

We continue today with our discussion of HTML Goodies' JavaScript Script Tips #42-44 and their multiple search engine script. Having detailed the inner workings of the Script Tips #42-44 Script in our last session, we are ready to ask, "And just how does it all play out in practice?"

Yahoo!
Altavista
WebCrawler
Excite
Lycos

As indicated, the Script Tips #42-44 Script searches the Yahoo!, AltaVista, WebCrawler, Excite, and/or Lycos search engines per the user's checkbox choices. Later in the post, we'll extend the script to other search engines; indeed, Joe notes in Script Tip #42 that the script in its original form searched up to 10 search engines.

In trial runs with the Script Tips #42-44 Script, I find that
(1) Yahoo! and WebCrawler searches work OK, but
(2) AltaVista, Excite, and Lycos searches do not.

An attempted AltaVista search first leads to a "Yahoo! - Incorrect URL" page (Wikipedia notes here that Yahoo! has owned AltaVista since March 2004); 60 seconds later, I am routed to http://www.altavista.com/*.

An attempted Excite search leads directly to http://www.excite.com/*.

An attempted Lycos search does lead to a page whose URL is http://www.lycos.com/cgi-bin/pursuit?query=search+term* but that is otherwise indistinguishable from http://www.lycos.com/.

(*To determine this information, I added a location feature to the third, windowFeatures parameter of the window.open( ) commands of the AltaVista, Excite, and Lycos if statements in the search( ) function, e.g.:
wind=window.open(search2,"newwindow2","location,width=700,height=200,scrollbars=yes");.)

Paralleling the Primer #18 Script and its faulty FullSearchUrl variable, the problem here lies in the partial URLs assigned to the value attributes of the checkbox input elements. The Script Tips #42-44 Script (like the rest of the HTML Goodies JavaScript Primers and Script Tips material) was written in the late 1990s, so it's not such a surprise that the AltaVista, Excite, and Lycos partial URLs are now outdated; perhaps what's surprising is that the Yahoo! and WebCrawler partial URLs are still functional.

And where do these partial URLs come from, anyway? In the "Checkboxes" section of Script Tip #42, Joe alleges, The code is pretty easy to find. You just go to the search page of the engine and look at the source code. I'm sure that's where the author got it. In response, I wonder, "Did Joe actually try this?" To be sure, a search engine's source code usually provides clues as to what the form of the search-results URL for that search engine will be, and sometimes you can put two and two together and come up with a functional URL. For example, consider the following form used by Lycos searchers:

<!--Line-breaks have been added for clarity.-->
<form method="GET" action="http://search.lycos.com" class="hpFrm" name="searchform">
<strong>SEARCH:</strong>
&#160;&#160;
<!--For user input:-->
<input type="text" style="width:450px; vertical-align:middle;" name="query" value="" id="hpSrchValue" />
&#160;&#160;
<!--The "GO GET IT!" submit button is coded by:-->
<input type=image src="http://ly.lygo.com/ly/hp/ggiBut.gif" style="vertical-align:middle;" width="86px" height="24px" border="0" />
&#160;&#160;
<!--The "powered by Ask™.com" image is coded by:-->
<img src="http://ly.lygo.com/ly/srch/ask/askSrch.gif" width="40" height="32" style="vertical-align:top;" />
</form>
<!--&#160; is a numeric character reference for a non-breaking space.-->

The user types a search term into the query text box and then clicks the "GO GET IT!" submit button (the <input type="image"> element is discussed here in the HTML 4.01 Specification), whereupon the searchform form is sent off to http://search.lycos.com via the HTTP GET method. Before the fact, then, we would expect the URL for a page of Lycos search results to be a variant of http://search.lycos.com?query=search+term, in which a query=search+term query string is appended to the search.lycos.com hostname, and with respect to the Script Tips #42-44 Script, we would similarly expect http://search.lycos.com?query= to be a workable partial-URL value for the Lycos checkbox - and both expectations prove correct, more or less:

(A) An actual Lycos search using Gila monster as a search term returns a "Lycos Search Results: results for Gila monster" page whose URL is http://search.lycos.com/?query=Gila+monster&x=27&y=8 (you will probably get different values of x and y in the query string if you try this yourself).

(B) When I enter http://search.lycos.com?query=Gila+monster in the browser window's address bar field and hit the return key, the results are browser-dependent:
(i) Netscape 7.02 takes me to the same page as for (A) above - more specifically, it's the same page contentwise but the URL is http://search.lycos.com/?query=Gila+monster;
(ii) MSIE 5.1.6 takes me to a "The web site address you entered could not be found" page; however, if a slash is inserted after the search.lycos.com hostname, then an http://search.lycos.com/?query=Gila+monster input gives a successful search (à la (B)(i)).

I've used Lycos as an example here because the searchform form's action attribute value can be used 'as is' in piecing together a "Lycos Search Results" URL; this is not the case for the corresponding forms in the Yahoo!, AltaVista, and WebCrawler sources, and good luck in fishing anything meaningful out of Excite's insanely complicated source. I nonetheless encourage you, as an exercise, to rummage through AltaVista's source and see if you can craft a working "AltaVista Search" URL; subsequently, you should check your URL's validity via your browser's address bar.

Looking over those checkbox value attribute values, however, I'd bet the farm (or at least a venti Frappuccino) that the Script Tips #42-44 Script's author ("Mujib" or whoever it was) obtained them not from anyone's source code but via the approach used by Joe in his answer to the Primer #18 Assignment: All I did was go to a search engine, do a search and copy the address from the Location bar. Ta Da! Consider the AltaVista checkbox value:

value="http://www.altavista.digital.com/cgi-bin/query?pg=q&what=web&fmt=.&q="

I am highly skeptical that the pg=q, what=web, and fmt=. name=value pairs in the query string above were obtained from AltaVista's source way back when. In any case, we can easily apply (or reapply) Primer #18's do-a-search-and-grab-the-URL method to the Script Tips #42-44 Script and thus bring it up to date:

AltaVista
An actual search gives:
http://www.altavista.com/web/results?itag=ody&q=Gila+monster&kgs=1&kls=0
In the Script Tips #42-44 Script, I find that I can get away with using:
value="http://www.altavista.com/web/results?q="

(FYI: the itag=ody name=value pair is for an <input type="hidden"> element, whereas the kgs=1 and kls=0 name=value pairs are for http://www.altavista.com/'s "SEARCH: USA" and "RESULTS IN: All languages" radio buttons, respectively. Might some users need these name=value pairs to be in the partial URL? Perhaps - I don't know.)

Excite
An actual search gives:
http://msxml.excite.com/info.xcite/search/web/Gila%2Bmonster
In the Script Tips #42-44 Script, you can use:
value="http://msxml.excite.com/info.xcite/search/web/"

Results for Lycos are given above, but let's repeat them anyway.
An actual search gives:
http://search.lycos.com/?query=Gila+monster&x=27&y=8
In the Script Tips #42-44 Script, you can use:
value="http://search.lycos.com/?query="

(I have no idea what the x=27 and y=8 name=value pairs are for, but they are evidently unnecessary vis-à-vis the partial URL.)

Other search engines

In Blog Entry #34, we searched Google using an http://www.google.com/search?q= partial URL. What other search engines might we want to search?

According to the Nielsen NetRatings Search Engine Ratings and the comScore Media Metrix Search Engine Ratings provided by the Reviews, Ratings & Tests section of Search Engine Watch, the top five search engines as of July 2006 are:
1. Google
2. Yahoo!
3. MSN
4. AOL
5. Ask

To add, say, Ask.com to the Script Tips #42-44 Script's stable of search engines, we can augment the script with the following blocks of code:

(1) In the searching form:

<input type="checkbox" name="ask" value="http://www.ask.com/web?q=" />Ask.com<br />
<!--The value value was obtained by the Primer #18 method.-->

(2) In the search( ) function:

if (document.searching.ask.checked) {
search6 = document.searching.ask.value;
search6 += key;
window.open(search6,"newwindow6","width=700,height=200,scrollbars,location,resizable"); }
// It always annoys me when I can't resize a window.

Your Assignment

Add MSN Search (which is now Live Search) and AOL Search to the Script Tips #42-44 Script. For each search engine,
(a) carry out a normal search and
(b) determine a suitable searching checkbox value; then,
(c) write out and
(d) insert into the script
blocks of code corresponding to those for Ask.com above. Finally,
(e) test your code by carrying out MSN/Live Search and AOL Search searches using the Script Tips #42-44 Script.

We'll switch gears in the next entry and check over a script that scales proportionally the width/height of an image and that spans Script Tips #45-48.

reptile7


Powered by Blogger

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