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:

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:

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 />

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) {