Friday, July 28, 2006

JavaScript Random Character Generator

This is about the generation of random characters. This is just a quick little generator that I put together so I could populate a dynamic table with specific kinds of data for different columns.

This generator is included in another blog post, see the references below for more information on how I dynamically created that table.

Purpose

The purpose for this generator is to create different types of test data of different lengths. It could be modified or enhanced to fit almost any need.

The Character Types

I have kept the character types simple. There are numeric, upper and lower cased characters, and a couple mixes of these three types. No punctuation or spaces are generated. I was only need single word entities and as such, the need for spaces was not important.

If spaces are important, then new character types could be added that would generate sentence or paragraph styled blocks of random characters with spaces randomly places such that the average word size would be about 5 or 6 characters in length. Matter of fact, the punctuation that could be used could also be integrated such that different statistical benchmarking could influence which punctuation is used and where.

The Arrays

I want to discuss the purpose of the Arrays so they make a little more sense. The code is documented but if one is not going to use this as I did, then the Arrays may not mean much.

First off, where this was used was within a dynamically created table. The size could be as large as the user wants, or as small as one cell.

The issue I had, was with how to identify a consistency among the columns as far as what character types they would be and a little more consistency on their length too. The reason for this, was that I was wanting to have a table that would mimic more or less, a spreadsheet worth of data. This means that a column that has numbers, should not have any characters in it, same with a column that may represent a name, it should not have numbers interspersed on some rows.

Enter the colCharType and colMaxSize Arrays. Using these two Arrays, I can now ensure a character type uniformity that I would not be able to achieve otherwise. I will also be able to prevent wild ranges of sizes from occurring by specifying a maximum size for the number of characters. So the columns will be a bit more uniform in their appearance.

One enhancement would be to add a third Array call colMinSize which will set the minimum bounds. The reason why this would be helpful is that if you have a maximum size of 18 on a column you will still get a few rows that will contain nothing or just a few characters. So for our maximum size of 18, we may want to set a minimum size of 12. That will really help keep the columns a bit more uniformed looking and would be a great improvement.

If you have noticed, I have only defined 12 elements in both Arrays. Didn't I say that the user could enter as many columns as they like? Of course I did. I am able to get around this limitation by the use of the modulus operator in JavaScript. It is extremely useful for conditions such as these. It is used within the functions getColCharType() and getColMaxSize() where the parameter is the column number. Column 12 uses the 12th element of the Arrays and column 13 uses the first! So there will be a consistent pattern that will repeat every 12 columns.

The Source Code

I have documented the source code so it should answer most of your questions and explain what is going on. If you have any questions, please let me know.




<script type='text/javascript'>

/* Random Character Generator - Copyright 2006 by Scott Tabar - tabarATfuseDOTnet
*
* All of these functions were created from scratch, except where noted.
* I grant you the right to do what ever you wish with these.
* All I ask is that you give me credit for what I have created. If these scripts
* will be used in a product that will make you or an entity money in any shape or
* form, I just want to let you know that I don't expect a single penny of it. I
* would be honored if you will drop me a line to let me know what product it was
* used in with a reference to company name, URL, ISBN, etc.
*
* The initial purpose for the random character generators was
* to allow random text to be generated within table cells to test sorting
* performance on an arbitrary table size that could be dynamically changed without
* reloading the page. Functions and arrays have been created to be able
* to provide a reproducible data type for a given column so there will be consistency
* when a column is sorted.
*/

/* These character types are used to define what kind of random data should
* be generated for a given cell.
*/
var CHAR_TYPE_NUMERIC = 1;
var CHAR_TYPE_CHAR = 2;
var CHAR_TYPE_CHAR_UPPER = 3;
var CHAR_TYPE_CHAR_LOWER = 4;
var CHAR_TYPE_MIXED = 5;

/* This array defines what data type should be generated for the given column
*/
var colCharType = new Array(
    CHAR_TYPE_MIXED, CHAR_TYPE_MIXED, CHAR_TYPE_MIXED,
    CHAR_TYPE_CHAR_LOWER, CHAR_TYPE_CHAR_UPPER, CHAR_TYPE_CHAR,
    CHAR_TYPE_CHAR, CHAR_TYPE_CHAR, CHAR_TYPE_NUMERIC,
    CHAR_TYPE_NUMERIC, CHAR_TYPE_NUMERIC, CHAR_TYPE_NUMERIC);

/* This array defines the max number of characters that should be generated for
* the given column. With a value of 10, the column will then have from none
* to 10 characters generated.
*/
var colMaxSize = new Array( 10, 4, 4, 10, 1, 2, 18, 8, 4, 10, 10, 2 );


/* This function takes any integer number as a parameter and maps
* it to the set in the colCharType array by returning the appropriate
* value from that array as if the array repeats infinitely.
*
* This function uses the modulus function to remap the integer parameter
* to a usable value.
*/
function getColCharType( x )
{
return colCharType[ x % colCharType.length ];
}


/* This function takes any integer number as a parameter and maps
* it to the set in the colMaxSize array by returning the appropriate
* value from that array as if the array repeats infinitely.
*
* This function uses the modulus function to remap the integer parameter
* to a usable value.
*/
function getColMaxSize( x )
{
return colMaxSize[ x % colMaxSize.length ];
}

/* This function actually performs all of the generation of random data based upon
* the two parameters charType and length. Length defines the maximum length, not
* a fixed length such that if the value is 10, then result could have a length of
* zero or anything up to a length of 10 characters.
*
* Currently, no character type generates punctuation or white spaces. It is
* intended to generate simple single "word" entities.
*/
function rndText( charType, length )
{
var result = "";

for( var x = 0; x < length; x++ )
{
var cType = charType;

if ( cType == CHAR_TYPE_MIXED ) {
cType = (Math.round(Math.random() * 3) + 1);
}
if ( cType == CHAR_TYPE_CHAR ) {
cType = (Math.round(Math.random() * 1) + 3);
}

if ( cType == CHAR_TYPE_NUMERIC ) {
result += createRandomNumbers();
} else
if ( cType == CHAR_TYPE_CHAR_UPPER ) {
result += createRandomUpperCaseLetter();
} else
if ( cType == CHAR_TYPE_CHAR_LOWER ) {
result += createRandomLowerCaseLetter();
}
}
return result;
}

/* Returns a random number between zero and the numeric value of the parameter.
*/
function rndNumMax( maxNum )
{
return Math.round(Math.random() * maxNum);
}

/* Returns one character that is randomly selected between 0 and 9.
*
* Thanks to wwww.CodeHouse.com for the following inspiration:
* http://www.codehouse.com/javascript/tips/random_letter/
*/
function createRandomNumbers()
{
return String.fromCharCode(48 + Math.round(Math.random() * 9));
}

/* Returns one character that is randomly selected between a and z.
*
* Thanks to wwww.CodeHouse.com for the following inspiration:
* http://www.codehouse.com/javascript/tips/random_letter/
*/
function createRandomLowerCaseLetter()
{
return String.fromCharCode(97 + Math.round(Math.random() * 25));
}

/* Returns one character that is randomly selected between a and Z.
*
* Thanks to wwww.CodeHouse.com for the following inspiration:
* http://www.codehouse.com/javascript/tips/random_letter/
*/
function createRandomUpperCaseLetter()
{
return String.fromCharCode(65 + Math.round(Math.random() * 25));
}

</script>




References

Dynamic Table Generation - Where this rnd char generation is at work!

Creating random characters

Thursday, July 27, 2006

Safari Table Sorting Performance Issue

This document will provide simple steps to have a dynamic table generator run in your browser. You can use this to generate large tables of various sizes to see how different browsers perform when sorting tables.

Unfortunately, this blog does not allow the use of scripts, so enclosed in the following text box is all the code for one web page. A few steps to take now for hours of enjoyment. Yes, I know, I need to get a server to post these simple test pages to!

1. Select everything in the box and then copy it to the clipboard. May be tricky... sorry... Try selecting a few rows at the top, then scroll to the bottom, then with the SHIFT key being held down, click the mouse below the final line which contains <&/html>>
2. Open a text editor and paste it in a new document.
3. Save that document with any name, such as SafariDynamicTable.html.
4. Next follow the link below to download the Standardista Table Sort code and save the enclosed folder in the same directory.
5. Open the saved HTML document, such as SafariDynamicTable.html in the browser of your choice.




<html>
<head>

<title>Safari Table Sorting - Performance Issues</title>

<script type='text/javascript'>

/* Random Character Generator - Copyright 2006 by Scott Tabar - tabarATfuseDOTnet
*
* All of these functions were created from scratch, except where noted.
* I grant you the right to do what ever you wish with these.
* All I ask is that you give me credit for what I have created. If these scripts
* will be used in a product that will make you or an entity money in any shape or
* form, I just want to let you know that I don't expect a single penny of it. I
* would be honored if you will drop me a line to let me know what product it was
* used in with a reference to company name, URL, ISBN, etc.
*
* The initial purpose for the random character generators was
* to allow random text to be generated within table cells to test sorting
* performance on an arbitrary table size that could be dynamically changed without
* reloading the page. Functions and arrays have been created to be able
* to provide a reproducible data type for a given column so there will be consistency
* when a column is sorted.
*/

/* These character types are used to define what kind of random data should
* be generated for a given cell.
*/
var CHAR_TYPE_NUMERIC = 1;
var CHAR_TYPE_CHAR = 2;
var CHAR_TYPE_CHAR_UPPER = 3;
var CHAR_TYPE_CHAR_LOWER = 4;
var CHAR_TYPE_MIXED = 5;

/* This array defines what data type should be generated for the given column
*/
var colCharType = new Array(
CHAR_TYPE_MIXED, CHAR_TYPE_MIXED, CHAR_TYPE_MIXED, CHAR_TYPE_CHAR_LOWER, CHAR_TYPE_CHAR_UPPER, CHAR_TYPE_CHAR,
CHAR_TYPE_CHAR, CHAR_TYPE_CHAR, CHAR_TYPE_NUMERIC, CHAR_TYPE_NUMERIC, CHAR_TYPE_NUMERIC, CHAR_TYPE_NUMERIC);

/* This array defines the max number of characters that should be generated for
* the given column. With a value of 10, the column will then have from none
* to 10 characters generated.
*/
var colMaxSize = new Array( 10, 4, 4, 10, 1, 2, 18, 8, 4, 10, 10, 2 );


/* This function takes any integer number as a parameter and maps
* it to the set in the colCharType array by returning the appropriate
* value from that array as if the array repeats infinitely.
*
* This function uses the modulus function to remap the integer parameter
* to a usable value.
*/
function getColCharType( x )
{
return colCharType[ x % colCharType.length ];
}


/* This function takes any integer number as a parameter and maps
* it to the set in the colMaxSize array by returning the appropriate
* value from that array as if the array repeats infinitely.
*
* This function uses the modulus function to remap the integer parameter
* to a usable value.
*/
function getColMaxSize( x )
{
return colMaxSize[ x % colMaxSize.length ];
}

/* This function actually performs all of the generation of random data based upon
* the two parameters charType and length. Length defines the maximum length, not
* a fixed length such that if the value is 10, then result could have a length of
* zero or anything up to a length of 10 characters.
*
* Currently, no character type generates punctuation or white spaces. It is
* intended to generate simple single "word" entities.
*/
function rndText( charType, length )
{
var result = "";

for( var x = 0; x < length; x++ )
{
var cType = charType;

if ( cType == CHAR_TYPE_MIXED ) {
cType = (Math.round(Math.random() * 3) + 1);
}
if ( cType == CHAR_TYPE_CHAR ) {
cType = (Math.round(Math.random() * 1) + 3);
}

if ( cType == CHAR_TYPE_NUMERIC ) {
result += createRandomNumbers();
} else
if ( cType == CHAR_TYPE_CHAR_UPPER ) {
result += createRandomUpperCaseLetter();
} else
if ( cType == CHAR_TYPE_CHAR_LOWER ) {
result += createRandomLowerCaseLetter();
}
}
return result;
}

/* Returns a random number between zero and the numeric value of the parameter.
*/
function rndNumMax( maxNum )
{
return Math.round(Math.random() * maxNum);
}

/* Returns one character that is randomly selected between 0 and 9.
*
* Thanks to wwww.CodeHouse.com for the following inspiration:
* http://www.codehouse.com/javascript/tips/random_letter/
*/
function createRandomNumbers()
{
return String.fromCharCode(48 + Math.round(Math.random() * 9));
}

/* Returns one character that is randomly selected between a and z.
*
* Thanks to wwww.CodeHouse.com for the following inspiration:
* http://www.codehouse.com/javascript/tips/random_letter/
*/
function createRandomLowerCaseLetter()
{
return String.fromCharCode(97 + Math.round(Math.random() * 25));
}

/* Returns one character that is randomly selected between a and Z.
*
* Thanks to wwww.CodeHouse.com for the following inspiration:
* http://www.codehouse.com/javascript/tips/random_letter/
*/
function createRandomUpperCaseLetter()
{
return String.fromCharCode(65 + Math.round(Math.random() * 25));
}

</script>



<script type='text/javascript'>

/* Dynamic Table Generator - Copyright 2006 by Scott Tabar - tabarATfuseDOTnet
*
* All of these functions were created from scratch, except where noted.
* I grant you the right to do what ever you wish with these.
* All I ask is that you give me credit for what I have created. If these scripts
* will be used in a product that will make you or an entity money in any shape or
* form, I just want to let you know that I don't expect a single penny of it. I
* would be honored if you will drop me a line to let me know what product it was
* used in with a reference to company name, URL, ISBN, etc.
*
* The initial purpose for the random character generators was
* to allow random text to be generated within table cells to test sorting
* performance on an arbitrary table size that could be dynamically changed without
* reloading the page. Functions and arrays have been created to be able
* to provide a reproducible data type for a given column so there will be consistency
* when a column is sorted.
*/


function buildTable()
{
var rows = document.getElementById( "rows" ).value;
var cols = document.getElementById( "cols" ).value;
var useTextareas = document.getElementById( "useTextarea" ).checked;

buildTable2( rows, cols, useTextareas );
}

/* This function will build a table based upon the given parameters for the number
* of columns and rows. It will fill in each cell with randomly generated data
* based which the data type and max length of the data will be defined by the
* column position.
*
* There is a third parameter useTextareas which will enclose the random data
* if it is desired to be used. The purpose of the use of the TEXTAREA tag is
* to get a feel for sorting performance when the table cell contains more than
* just plain text. Matter of fact, try generating a table with the dimensions
* of 50 x 100 first without TEXTAREA and then with. Observe how much slower
* the sorting can be.
*
*/
function buildTable2( maxRows, maxCols, useTextareas )
{

// Build Header
var thr = document.getElementById( "tableHeaderRow" );
for( var x = 0; x < maxCols; x++ )
{
var cell = document.createElement("th");
cell.innerHTML = "Col " + (x + 1);
thr.appendChild(cell);
}


// Build body
var tb = document.getElementById( "tableBody" );

for( var y = 0; y < maxRows; y++ )
{
var row = document.createElement("tr");


for( var x = 0; x < maxCols; x++ )
{
var cell = document.createElement("td");

if ( useTextareas )
{
var textArea = document.createElement("textarea");

textArea.rows = 2;
textArea.cols = 6;
textArea.innerHTML = rndText( getColCharType(x), rndNumMax( getColMaxSize(x) ) );

cell.appendChild( textArea );
} else {
cell.innerHTML = rndText( getColCharType(x), rndNumMax( getColMaxSize(x) ) );
}

row.appendChild(cell);
}

tb.appendChild(row);
}


}

/* This function removes the headers from the table and also the body. It prepares the
* table to be regenerated.
*/
function clearTable()
{
// clear Header
var thr = document.getElementById( "tableHeaderRow" );
while ( thr.hasChildNodes() ) {
thr.removeChild( thr.lastChild );
}

// Clear body
var tb = document.getElementById( "tableBody" );
while ( tb.hasChildNodes() ) {
tb.removeChild( tb.lastChild );
}
}

/* This function rebuilds the actual table. It assembles all of the parameters and
* calls all functions in the proper order.
*
* This function logs performance status to the screen so the user will know how long
* it takes to clear a table or to build a table. It also logs the characteristics of
* each table when it regenerates it.
*
* Finally at the end, it makes a call to the Standardist Table Sorting init funtion
* to have the regenerated table re-enabled for the auto sorting. It should be noted
* that the STS function named headingClicked has been slightly modified to record
* performance information to the on-screen log. The first line of the function has
* the following added:
* var date1 = new Date();
* and the very last few lines have the following added right before the "return false;":
* var date2 = new Date();
* var debug = document.getElementById( "debug" ).innerHTML;
* debug += "Time to sort table " + (date2 - date1) + " ms.<br>";
* document.getElementById( "debug" ).innerHTML = debug;
*
*/
function rebuildTable()
{

var date1 = new Date();
clearTable();
var date2 = new Date();

var date3 = new Date();
buildTable();
var date4 = new Date();

var rows = document.getElementById( "rows" ).value;
var cols = document.getElementById( "cols" ).value;
var useTextareas = document.getElementById( "useTextarea" ).checked;

var debug = document.getElementById( "debug" ).innerHTML;

debug += "Time to clear old table " + (date2 - date1) + " ms. ";
debug += "Time to build new table " + (date4 - date3) + " ms: " +
rows + " rows x " + cols + " cols with" +
(useTextareas ? "" : "out") + " TEXTAREAs. <br>";

document.getElementById( "debug" ).innerHTML = debug;

// Calls the Stanardista Table Sorting init function to re-enable the regenerated table.
standardistaTableSortingInit();

return false;
}


/* Calls buildTable() function on page load.
*/
window.onload = function() { buildTable(); };
</script>


</head>
<body>

<H1>Safari Table Sorting - Performance Issues</H1>

<p>
This test dynamically builds a table and allows you to dynamically alter the table size at any time. It uses randomly generated data to fill the table cells, of which all cells contain a TEXTAREA element to add to the complexity level of the sorting.

<p>
This table uses a non-intrusive table sorting JavaScript function. It is called Standardista Table Sorting by Neil Crosby. See the link under references. What makes this so nice, is that it goes out on it's own and finds all the tables that are in need of being setup for sorting and then it puts the hooks in to each column heading. It is very easy to setup and it runs very quickly on small trivial tables. I used this source code to reconfirm that the sorting is being performed correctly and that nothing odd is going on. I did find out that a replaceChild() would remove a node before it adds it to the end if that node already exists in the parent. This means that more code can be eliminated due to not having to track and manually remove any obsolete or duplicated nodes. ie... performance gain!

<p>
Sadly though, performance does suck when the quantity of data approaches what is typical for UGS. That is 37 columns and about 100+ rows. Of which it takes about 209 seconds! 100 rows with 50 columns takes AlMOST 500 seconds to sort when using TEXTAREA!

<p>
To use this, when it initially loads, it will build a 8 rows x 37 columns table and you can dynamically change the table size at any time. It includes wrapping all generated text with a TEXTAREA tag initially. Transaction information will be logged to the screen and will not be lost during resizing of the table. If the table parameters are changed and the Rebuild Table button is CLICKED, it will regenerate the whole table dynamically without performing a page reload.

<br><br>
<b>Runtime Log:</b>
<div id="debug">Initial table is 8 rows x 37 cols.<br> </div>

<br />


<table>
<tr>
<td>Rows:</td><td><input type=text size="10" id="rows" value="8" /></td>
<td>Cols:</td><td><input type=text size="10" id="cols" value="37" /></td>
<td>Use TEXTAREAs?:</td><td><input type=checkbox id="useTextarea" value="TEXTAREA" checked /></td>
<td><input type=button value="Rebuild Table" onClick="rebuildTable();" /></td>
</tr>
</table>


<script type='text/javascript' src='standardista_table_sorting/common.js'></script>
<script type='text/javascript' src='standardista_table_sorting/css.js'></script>
<script type='text/javascript' src='standardista_table_sorting/standardista-table-sorting.js'></script>


<table class="sortable" cellspacing=0 cellpadding=1 border=1>
<col width="200px"></col>
<colgroup width="200px"></colgroup>
<thead>
<tr id="tableHeaderRow">
</tr>
</thead>
<tfoot>
<tr>
<td colspan="8" align="right"><b>Dummy Totals:</b></td>
<td><b>2388</b></td>
<td><b>482 Days</b></td>
<td><b>293,398,283.93</b></td>
</tr>
</tfoot>
<tbody id="tableBody"></tbody>
</table>



<b>References</b><br />

<a href="http://www.workingwith.me.uk/articles/scripting/standardista_table_sorting">Table Sorting with Safari support - Standardista Table Sorting</a> <br />
<a href="http://sourceforge.net/project/showfiles.php?group_id=162528">Download Standardista Table Sorting from SourceForge.net</a><br />

<a href="http://www.codehouse.com/javascript/tips/random_letter/">Creating random characters</a> <br />

</body>
</html>






What Is So Dynamic About This Table?

The table that is generated can be regenerated on the fly without having to reload the page. It logs all transactions: clearing the table, building the table, and sorting the table. It is good to see what happens under different conditions such as table size (large number of rows vs columns or vice-a-versa) and plain text versus text enclosed in an HTML tag (TEXTAREA is used in this test, but it can be any tag like Anchor or DIV).

You have control of the table size. Change the value and regenerate the table. Go ahead, it is fun to see how large you can make it and what your browser does with it. If you make it too large and you do perform a sort, it may lockup your browser for quite a while until the JavaScript is done. WARNING: There is no way to terminate it except for killing the browser! So be fore warned!

The table sort algorithm is all dynamic. Which means as you regenerate the table, the table will automatically be prepared for being sortable on any number of columns. The only catch is that the TABLE has the class="sortable" and the table must contain a TBODY element. See the link below for additional information on the Standardista Table Sort code by Neil Crosby.


Adding Performance Logging in STS!

Why would you want to? Good question! Because it will record how long it took to perform each sort. So when you resize the table, you will still be able to see the prior results.

How? Follow these simple directions...
1) Modify the file named standardista_table_sorting/standardista-table-sorting.js. Change the function named headingClicked with the two following modifications:
a) To the first line of the function add:
   var date1 = new Date(); 

b) To the very last few lines right before the "return false;":
   var date2 = new Date();
var debug = document.getElementById( "debug" ).innerHTML;
debug += "Time to sort table " + (date2 - date1) + " ms.<br>";
document.getElementById( "debug" ).innerHTML = debug;

2) Save the changes and reload the test page.

Now every time a sort is performed, it will be logged on the screen.


How to Use

First try to sort any column. Notice how an arrow indicates what column it is sorted by and in what direction. Try a few other columns. Note the log entries that are being added at the top of the page.

Go ahead and change the number of rows and columns and click on the Rebuild Table button, but for this time make the table smaller than it defaults to. You can have all the auto generated text be placed inside TEXTAREA tags, which will illustrate how bogged down JavaScript can become when table cells contain more than just simple plain text.

Now make the table 100 rows and 50 columns without the TEXTAREA. Rebuild the table. Try a few sorts. Then click the check box to enable the TEXTAREA tags and then rebuild the table. Click on one column and wait. Be prepared to wait for awhile. It is running and it will end, but give it about 500 seconds! Of course it depends upon your CPU and system platform, but you will notice that when TEXTAREA tags are used, it grinds to a halt!


One Bug in STS!

Please note that I have found both a bug and a fix for the same bug. I have submitted a bug report on SourceForge.net and I am sure Neil or one of his friends will apply the bug fix as soon as they are able.

The nature of this bug is due to the unusual way I am using STS. Unusual in the fact that the STS init function is actually called more than once. As such, there is a slight glitch that occurs.

This glitch is that the number of rows in the table will be doubled.

How to reproduce this? Easy, click on any column, say column 5. Regenerate the table. No you don't have to change any settings. Then click on the same column to sort the newly generated table. The results, the rows are doubled.

As an exercise you can figure out what is causing it. Hint: Has to do with the behavior of appendChild(), but not directly.


The Safari Sorting Issue

If you have not figured it out, Safari is VERY slow when it comes to sorting tables that contain more than just plain text. In our example I have used TEXTAREA to contain the generated text, but it can be just about any other tag too.

I don't think the issue is so much the TEXTAREA tag, as much as inserting or removing rows that have many cells with the nested nodes within a table. So I am not sure what exactly is going on, but I do suspect the engine that renders the content in Safari, or the engine that is updating the DOM is has TONS of work it has to perform. There has to be a more efficient way of doing what ever it is doing, since IE for windows is fairly quick with the same work load. Personally I suspect when a node is added, it performs a text based paste in to the "spot", then it has to regenerate the whole DOM. I don't think it is truly treating a Node as an object and just simply inserting the reference to that object in the parent's list of childNodes. if it were, then I would expect quicker response. But then again, re-rendering the whole document (if that is what is happening) may be consuming so much of the time too.

Is there any way to improve the performance? Not really. I have tried a few things and I have actually attained better performance than what STS is able to achieve, but it is a propriety script going against a special condition and I took liberties to optimize it. Since STS is very dynamic and nonintrusive, the same mods cannot be made to it. If anybody knows of a way to kick up the performance when using TEXTAREAs, please let me know!



References


Table Sorting with Safari support - Standardista Table Sorting

Download Standardista Table Sorting from SourceForge.net


Creating random characters

Friday, July 21, 2006

Safari and the broken JavaScript TD.cellIndex property

There is a JavaScript property that Safari does not properly implement. It is the TD.cellIndex. It always returns a zero value.

Where would such a property would be used? Most commonly in sorting of HTML tables through JavaScript. Generally, the header cell of each column has an onClick="tableSort(this);" so within the sorting function, through the use of cellIndex the JavaScript will know what column the user wants to sort, and through multiple uses of parentElement the function can back out to the TBODY element of the TABLE to work with the contained nodes which contains the TR elements and each TR element contains more nodes which are the TD elements.

Simple? Make Sense? Good! Now you can probably figure out on your own how to perform a dynamic sort on an HTML table without relying upon any other JavaScript. That gives me an idea on another Blog Posting... Hyper Table Sorting! Coming soon to a browser near you!

This is a simple fix. If it is ever fixed in a future release of Safari, it will automatically disable itself (except when the true value of cellIndex is zero) to help ensure the best performance.

There are a few assumptions to keep this example simple:
1) the object that is passed in to this function will only be a TD object.
2) It is an acceptable performance hit to always go through the for loop when the true cellIndex is zero. The first element will be picked off, so the hit should not be too bad. The general consensus being that the first column of a table may be the default sort column so the odds of it be resorted on that column is reduced.
3) Safari browsers will be common. If they were not, may want to add a hook such as "isSafari && obj.cellIndex == 0" to allow other browsers to bypass the for loop.


function cellIndexFn( obj )
{
  var ci = ( obj.cellIndex == 0 ? -1 : obj.cellIndex );

  for ( var x = 0; ci == -1 && x < obj.parentElement.cells.length; x++ )
  {
    if ( obj === obj.parentElement.cells[x] ) {
      ci = x;
    }
  }
  return ci;
}


The following is a simple way to test for a Safari browser:

  var isSafari = navigator.userAgent.indexOf("Safari") != -1;



The modified code that will contain the check for Safari to minimize the performance hit:

function cellIndexFn( obj )
{
  var isSafari = navigator.userAgent.indexOf("Safari") != -1;
  var ci = ( isSafari && obj.cellIndex == 0 ? -1 : obj.cellIndex );

  for ( var x = 0; ci == -1 && x < obj.parentElement.cells.length; x++ )
  {
    if ( obj === obj.parentElement.cells[x] ) {
      ci = x;
    }
  }
  return ci;
}



Referances
This page covers many good ideas and topics, but see example 3.27 and the related code around it.
Great article in Object Detection. Can help prevent many pitfalls with future releases to any browser. A must read!

Friday, July 14, 2006

MacBook Pro Review

I have been working with a 17" MacBook Pro for about three weeks now. There are some nice features about it and some that I think could be improved upon. Instead of creating a long boring document that details every last item, I am going to just simply put the pros and cons in a simple list.

The good!
+ Large 17" screen is bright and clear!
+ The large 17" screen is easy to open.
+ There is a power meter built in to the battery. Push the button and 5 LEDs show you the power level.
+ Illuminated keyboard. It is nice how the keys glow!
+ Duel Intel processors! Intel? Intel! Gee that still sounds odd! After all those years of Motorola it almost does not feel right. I guess it is now safe to stop bashing Intel? ;-)
+ Most applications having built-in spell checking! It appears to be an OS hook.
+ Long battery life. It appears like I can get about 3 to 3.5 hours of documenting and web surfing on a fully charged battery. The time may be longer if the keyboard back lighting or the screen were turned all the way down.


The Bad!
- The Large 17" screen is only designed to sit on a horizontal surface. The screen does not want to stay open if the computer is tilted at angles of around 25 degrees or more. This can make it difficult to use in a chair or bed if your feet are up.
- The large 17 screen is too small! Well at least the resolution. It is 1680x1050 which is not large enough to view 1080i HiDef QuickTime movies at 100% resolution. About 25% of the width gets cut off of the Car's trailer when viewing the 1080i trailer.
- The power meter that is built in to the battery may not be too accurate. The other day I shut down the computer and when I saw it started to close the desktop I stuck it in the computer bag. About 6 hours later I found the contents of the bag hot not to mention the computer. It must have hung during shutdown. Well, the battery was dead since it would not power on the computer but the power meter did show 2 LEDs.
- The keyboard. Not enough keys to perform the common tasks. Usually have to use modifiers, such as CTRL, OPTION, or APPLE which can be a pain at times, if there is even a mapping!
- The keyboard when booting Windows XP and you need to perform a CTRL-ALT-DEL to log on to the computer!
- Single button track pad. Many applications require the use of a right-click to be fully functional. I am finding that I have to hold down the CTRL key frequently when clicking in order to get my work done. Too much additional hand movements are required.
- The touch pad is too far away from the keyboard. You have to take your hand off the keyboard to use it, so it tends to waste some time. Same goes for the track pad button.
- PC Isolation! Ok, I have not tried to use XP too much in the last three weeks (give me a good reason why ;-) ), but here are a few things that irk me.
    - When the mac is up and running, I cannot copy any files to the PC partition. This dumb founds me! Perhaps it is a permissions thing...
    - When the windows XP is up and running, there is NO access to the Mac hard drive. So right now, there is NO way to transfer files between the OSes or even to share. Major pain!

Novel!
! The latch. Yeah it is neat! There are two metal spring loaded hooks that sit in the lid of the laptop (the springs hold them up). They are flush with the case and will not catch anything when the lid is open. But as the lid comes close to the base unit (as in closing the lid), there are magnets that pull the hooks down to engage in holding the lid close. Yes I know it is not a new gadget for the Mac notebooks, (I also have a g3 notebook with the same kind of hooks), but it sure beats those plastic fangs that are common place on the PC notebooks.
! The sensors to enable the keyboard lighting are two in number and located under the speaker grills. The first one is to the left of the power button almost around the 7 O'Clock position: Cover it with your finger. The second one is in the left speaker at the top, about one inch from the left. To find the second one, slowly drag your finger across the top of the left speaker grill cover. If all else fails, use your whole hand to cover both grills.
! The power connector! Good use for magnets (that and the lid latches). It is interesting that it actually is not plugged in, but just being held in contact with the contacts. It also self aligns so it simple to hook up. If they have a more powerful magnet, then it could plug itself in if the two connectors were in the same vicinity of say an inch or two: just lay the MBP on the desk and it auto plugs itself in... no docking! As it is, for the last week, when I have been getting the MBP setup on the desk, I just pass the top left corner near the plug as I set it on the desk and it does plug itself in automatically! No kidding! It does have to come within about 1/3 of an inch to perform this feat!
! When the computer is off, hold the open lid between your eyes and a strong light source with the LCD facing you. An image of an apple will appear on the screen. No it is not magic, but if you cannot think what causes it, I will not spoil your fun in trying to figure it out.

Enhancements / Features?
? The keyboard. There is the space...
? The rear hinge has a rubber strip which is about 55% the width of the computer. It would be nice to have a similar rubber strip running near the front of the computer on the bottom side. The case as it is, is thin and slick. It will make the computer a bit easier to hold on to.
? In keeping with the theme of magnetic power cable, can Apple come up with a magnetic docking station? Perhaps all they would need is Networking, phone, firewire, and USB. That can be done with a few simple connectors, right? ;-) Actually, if the connector is similar to the power with about 4 metal pins, then centered between a few of the pins could be optical connectors so the data transfer could be done via digital optics. I am sure low powered LASERs should be fairly cheap by now, and the bandwidth of one fiber channel should handle the bandwidth of firewire, USB, and networking combined! Yeah that's the ticket! They could even call it FiberDoc!

Thursday, July 06, 2006

Safari TEXTAREA No Scroll Bars

Safari may not treat TEXTAREA tags the same as with other web browsers when it comes to displaying scroll bars. There are two different causes for the lack of scroll bars, both of which I do have some solutions.

TEXTAREA rows Set to a Value Which is Too Small To Display



The first problem is an easy one to fix. The problem is that if TEXTAREA rows is set to a value less than 5, it cannot display the scroll bars. The reason for this, is that it takes all five rows to display the smallest slider and the two stacked arrow buttons below it. If Safari cannot display the graphics for the slider, it omits them altogether and instead shows a gray bar on the right-hand side of the TEXTAREA.

Keep in mind that depending upon what font size you are using and the characteristics of that font, the minimum number of rows may differ. But generally for the plain default for Safari should be around 5 rows.

Some Examples of this problem - All have the same amount of text:



TEXTAREA - 1 Row - No Scroll bar:


TEXTAREA - 2 Rows - No Scroll bar:


TEXTAREA - 3 Rows - No Scroll bar:


TEXTAREA - 4 Rows - No Scroll bar:


TEXTAREA - 5 Rows - Scroll bar!!!:


Observe on this last example how the scroll bar is at its smallest.


Safari TEXTAREA does not always Display a Scroll Bar when Rows is 5 or Greater



Believe it or not, but just because you set rows to 5 on the TEXTAREA tag, does not ensure that you will always get a scroll bar when you need it. Why? I don't know. I think it is a legitimate bug on Safari's behalf.

The nature of this issue is that if you have rows set to five, the scroll bars will not appear until the text actually spans down to row seven! While the text is 6 rows long, you either cannot see the first row or the last row depending upon the position of the cursor.

The solution to this problem, is that instead of fully in-lining your text as follows:
<textarea cols="20" rows="5">Please assume there is six lines of text here!</textarea>

Just add a linefeed to the end of the text so that way the </textarea> tag begins on a new line in the source document. Back on your application server, you may have to strip off any trailing whitespace to preserve data integrity, if it is even an issue. An example would be:
<textarea cols="20" rows="5">Please assume there is six lines of text here!
</textarea>

The way this solution works, is that when your actual text finally overflows on to row 6, because you added that extra linefeed to the source, it is actually overflowing to row 7 and enables the scroll bar.

The user cannot really see that extra linefeed and does not really realize it is there. This solution does allow the TEXTAREAs to be properly displayed with scroll bars upon page load if they need it.

The limitation that this has, is with the actual users. If the position the cursor after the final linefeed and they start typing before the text rows reach that threshold, then the same behavior will occur, unfortunately. Play around with the example below to see how it behaves.

EXAMPLE of Bad Overflow



TEXTAREA - 5 Rows - 4 Rows of text PLUS Linefeed - No Scroll Bar:


TEXTAREA - 5 Rows - 5 Rows of text PLUS Linefeed - No Scroll Bar:


TEXTAREA - 5 Rows - 6 Rows of text PLUS Linefeed - Scroll Bar!:


TEXTAREA - 5 Rows - 4 Rows of text NO Linefeed - No Scroll Bar:


TEXTAREA - 5 Rows - 5 Rows of text NO Linefeed - No Scroll Bar:


TEXTAREA - 5 Rows - 6 Rows of text NO Linefeed - No Scroll Bar:


TEXTAREA - 5 Rows - 7 Rows of text NO Linefeed - Scroll Bar!:



PLEASE READ - Problems with the Examples!



How wonderful blogging is. Once again the examples have unexpected results when posted through the blog! In the source code I do have a linefeed, but the blog went ahead and inserted a <br /> in its place. So all of these examples are a bit hosed. I will need to try to find a way to preserve the linefeed. If anyone can provide the answer, I would greatly appreciate it!

Believe it or not, even though the <br /> has been added, all of the examples are displaying correctly in Safari as the headings state: So if it says it has no scroll bar, it has none. If it says it has a scroll bar, then it does have a scroll bar.

Other Information



It should also be noted that Safari also support the attribute wrap which is not part of the HTML 4.01 standard. Possible values are: soft, hard, and off.


How Other Browsers Handle these Conditions



First off, let me state that I do not have access currently to too many different browsers. If you have one that is not listed in this list or of a different version that behaves differently, please post a message stating how it handles these conditions. Thanks you!

Safari v2.0.3 - Mac OS X v10.4.6 - MacBook Pro - 2.16 GHz Intel Core Duo - All tests and comments based up this configuration.

FireFox v1.5.0.4 - Mac OS X v10.4.6 - MacBook Pro - 2.16 GHz Intel Core Duo - There are no issues with the vertical scroll bar. It appears like when ever text does overflow, FireFox always displays the vertical scroll bar. One thing FireFox does do differently, is that when you set rows to 1 it displays 2 rows. At a minimum it displays the slider and the slider starts to cover up the two arrow buttons as needed. So when it is displaying two rows of text (rows=1) you just see the slider. When three rows of text (rows=2) you see the slider plus the down arrow button. Four and more rows you can see the slider and both buttons. So it appears like in all cases that I have setup for testing, the actual display is 1 line more than what rows has been set to.
Another obvious difference, is that FireFox enables the horizontal scroll bar for long text that has no spaces. If there are no spaces in the text, FireFox will not wrap it. That said, FireFox does give higher priority to wrapping text to try and eliminate the horizontal scroll bar. Safari will wrap long text with no spaces. The extra linefeed is NOT needed with FireFox nor is there any issue with any line of text overflowing without scroll bars being enabled.



References



Apple's Safari HTML Reference - Standard HTML Tags

Wednesday, July 05, 2006

Dynamically Sized TEXTAREA

I would like to share a little bit of JavaScript that I wrote: How to Make the TEXTAREA Dynamically Sizable so all text will be visible without the need of scroll bars. The purpose is to display all the text without scroll bars and to minimize the amount of white space between the end of the text and the bottom of the box.

This should work on most current browsers. Tested with Safari v2.0.3.

BACKGROUND
One of the issues with browser compatibility is in how the HTML TEXTAREA behaves within Safari. I will cover that topic later in detail.

An odd behavior with Apple's Safari browser is the way the HTML tag TEXTAREA deals with text that overflows the TEXTAREA's visible box. The issue is that when the TEXTAREA has text that overflows, it does not display the scroll bars until two lines of text have overflowed. Thus the first line of text is unaccessible or visible when typing at the end of the text, or the last line when typing any where else when the overflow condition first occurs. This can lead to text that is not viewable by the users, or at least difficult to view. This even manifests itself when displaying the data for the first time to the user: if the data overflows by one line, it will not display the scroll bars and the user will not be able to view that last line of data.

One solution to this problem is to have the users click within the TEXTAREA and either move the cursor with the arrow keys, or drag the cursor with the mouse to force the TEXTAREA to scroll the text manually even without the scroll bars. As you can guess, this would not be viable solution that the users would even accept (or at least lets hope the users won't accept this kind of a solution!).

Where this can become an issue, even if the scroll bars function as expected, is if the users are printing the contents of a page that makes use of a TEXTAREA with overflowing text. All the text will not be printed.


THE CODE - First the Basics
There are three parts to the code. The first is the JavaScript that performs the adjustment in the height. The second is putting the hooks within the TEXTAREA, with the last being the general call to preset the TEXTAREAs when the page is initially loaded.

First lets start off with the primary JavaScript. This should be placed within the document's header section.

This function updateTextareaHeight() will take the contents of the TEXTAREA that is being passed to this function and calculate the "theoretical" number of rows it should have. If the TEXTAREA element is displaying the text in a fixed width font, then it would be fairly accurate in the length, but if it is using a variable width font, then the number of rows may be overstated, but all text should be displayed.

The way it works is by counting not only the number of linefeeds (new lines), but it also takes (or tries to) in to consideration wrapping text, which becomes the extraRows value. The way it determines if text may wrap in this example, is to take to total length of the text in the TEXT area and divides it by the number of columns the TEXTAREA is configured for. If the sum of the extraRows and linefeed count does not match was the current value is for the TEXTAREA's rows attribute, then it is updated.

Please keep in mind that this is very simplistic in nature and will not perform well under all conditions. Matter of fact it can be broken quite easily. One technique that works well, is by using very large word sizes such that there are many word wraps with large amounts of white space to the right of the words, but very few, if any, linefeeds are used. This first example just illustrates what can be done in terms of simplicity.


<script>
/*
* Copyright 2006 by Scott Tabar
* Usage is granted as long as this credit remains in place.
* If used in a production/commerical product, then please drop
* me a line at http://scott-tabar-safari.blogspot.com
*/
function updateTextareaHeight(obj)
{
if ( obj == null ) {
return false;
}

// NOTE: This is using regular expressions to count spaces and linefeeds
var objText = obj.value;

var linefeedsArray = objText.match(/[\n\r]/g);
var linefeeds = ( linefeedsArray == null ? 0 : linefeedsArray.length);
var extraRows = Math.round( objText.length / obj.cols );

var newRows = linefeeds + extraRows;

if ( newRows != obj.rows ) {
obj.rows=newRows;
}
}
</script>

An example of how the JavaScript would be used within the TEXTAREA tag is as follows:


<textarea cols="20" rows="4" onkeyup="updateTextareaHeight(this);"
id="ta01">Some text goes here!</textarea>

To precondition the resizing of the text area, you can either attach a JavaScript function to the BODY's onload event which will get called when the page is done being loaded. Or just place some JavaScript in-line with the document, but at the end after all referenced TEXTAREAs. This will resize them while the page is still loading (ie... loading images). NOTE: Many TEXTAREAs can be pre-sized this way.
<script>
{
  updateTextareaHeight( document.getElementById( "ta01" ) );
}
</script>



That is the basics on what mechanisms need to be in place to auto size a TEXTAREA. See the next section on discussion about how to improve the sizing to eliminate overflow conditions or large amount of white space at the bottom of the text.


EXAMPLE - More Complex But More Accurate

This example combines all of the three code fragments listed above. To reset the example, just reload the page and start anew.

NOTE: Sorry... This blog will not support the use of the script tag so I can not in-line an example. So until I can find an alternative way of presenting it, you can just copy and paste the following into a new text document and open with your browser of choice.

If you done any playing around with the example above, you will be quick to find that it does not work too well. The concept may work great, but trying to guess what the user has typed in and how the browser is displaying the information is yet another thing altogether. The reason why this is so difficult, is that the TEXTAREA object does not report the actualRows that the text actually occupies. if it did, then this task would be quite simple.

Why is it so difficult to predict how many rows of text there will be? The answer lies in the complexity that is given by modern browsers: you can configure or change just about any aspect of how the TEXTAREA is displayed or how the font is rendered. You no longer are limited to just the plain font that the browser defaults to, but you can specify a specific font (and numerous backups if that one does not exist on the client's computer), font size, the font weight, kerning, tracking, horizontal and vertical spacing, font stretching, margins, and etc... Matter of fact, you can even write a JavaScript function that can take one character at a time and perform progressive or random changes so that a paragraph could contain a rainbow of colors, or each character could be a different size and font! So to put it mildly, the sky is the limit on how we can present the text in a TEXTAREA. Don't forget word wrapping in the TEXTAREA for that may be out of our control (aside from hinting that can be performed).

In the following example, I have added a few more matrices to try and better tune the control of how the TEXTAREA is sized. There are a few areas that I have tried to quantify to see if better control can be attainable. Next is a list of these measures and how they are "supposed" to help! ;-)

1. fontFamilyAdj - This is a very simplistic implementation. It assumes that the Courier font is the only mono-spaced font you will be using, and all other fonts will be proportionally spaced. This does fit our example so keep it in mind if you need to expand upon it. It assumes that for a monospaced font, that the weight should be greater than 1.0 for less characters can fit on a line in a TEXTAREA compared to a proportionally spaced font. For example, enter all lowercase i's. Conversely though, try entering all uppercase W's too. The point being, on average, more characters will fit per line with proportionally spaced characters.

2. linfeeds - Since we are using RegularExpressions to count the linefeeds and also the spaces and large words, there exists the strong chance that none exists, and as such a null will be returned. So in the cases were we are needing to count the elements of the result array to get our numbers, there is used an intermediate variable to hold such a value. This prevents the need to perform the Regular Expression twice: first to see if there are any results, second to get the count of the results. Anyway for linefeeds, we are only counting newlines and carriage returns. We are not interested in vertical tabs, form feeds, or others.

3. spaces - Counting the number of spaces can give us the average word size. We can also use the spaces and linefeeds when the density get very low to improve the accuracy, but in this example, it is not implemented as such (ie... low density conditions with a high ratio of linefeeds + spaces implies that there is really nothing to the document except for the linefeeds so using just the linefeeds may be the most accurate result).

4. avgWordSize - This really is not being used in the example below. Perhaps in combination with largeWordSize and adjColumns you can find an equation that would estimate the amount amount of white space that is caused by word wrapping. This could improve the estimation of the number of rows quite considerably.

5. adjColumns - Uses the fontFamilyAdj value to modify the column size to more accurately express how many characters really does fit on one line in a TEXTAREA.

6. largeWordSize - This is the smallest size for a large word, which can cause a large waste of white space if a large word is actually wrapped. This is the factor that tries to keep the TEXTAREA from overflowing if many big words are used.

7. largeWordCount - The number of large words that exist in the document.

8. density - Takes the adjColumns and multiplies it by the number of linefeeds and the extraRows (as calculated up to that point) to arrive at a theoretical number of characters that can fit in the TEXTAREA. It then uses that number as the divisor for the total character count of the text. The density, when very high indicates there are less linefeeds or other conditions involved and that the number of rows can be a few less. When the density appears to fall between .2 and .7, then there tends to be a larger number of linefeeds and/or larger words which can increase the likelihood of word wrapping and as such, more lines are needed to prevent an overflow condition. The value of linefeeds are also backed off a little under these conditions too. But when the density is less than .2, then you will find a high number of linefeeds.

9. newRows - is the combined total of the linefeeds and the final value of extraRows. Only if this value changes will the TEXTAREA.rows be modified.

One thing to note, is that currently, this code is not perfect. It behaves much better for medium to large amounts of text, but if you start typing words it will start resizing it self all over the place. Yes it does need some fine tuning, but the point I was wanting to make is that it is NOT an easy task and that there are many variables involved. The addition of
 && objText.length > adjColumns && linefeeds > 0
to the density check really adds a fair amount of stability to keep the box from jumping. Try removing it to see what happens.


<html>
<head>

<script>
/*
* Copyright 2006 by Scott Tabar
* Usage is granted as long as this credit remains in place.
* If used in a production/commerical product, then please drop
* me a line at http://scott-tabar-safari.blogspot.com
*/
function updateTextareaHeight(obj)
{
if ( obj == null ) {
return false;
}

// NOTE: This is using regular expressions to count spaces and linefeeds
var objText = obj.value;
var fontFamilyAdj = obj.style.fontFamily == "Courier" ? 1.1 : .9;
var linefeedsArray = objText.match(/[\n\r]/g);
var linefeeds = ( linefeedsArray == null ? 0 : linefeedsArray.length);
var spacesArray = objText.match(/[ \t]/g);
var spaces = ( spacesArray == null ? 1 : spacesArray.length + 1);

var avgWordSize = Math.round( obj.value.length / spaces + .5);
// A large word is greater than 35% of the adj cols
var adjColumns = Math.round(obj.cols / fontFamilyAdj);
var largeWordSize = Math.round( adjColumns * .35 );

var regExpStr = "\\w{" + largeWordSize + ",}";
var largeWordArray = objText.match( new RegExp( regExpStr, "g" ));
var largeWordCount = (largeWordArray == null ? 0 : largeWordArray.length);

var extraRows = Math.round( obj.value.length / adjColumns) + 1;
extraRows += Math.round( largeWordCount * .5 );

// Density tries to measure large white space vs ideal fill
var density = Math.round( objText.length / (adjColumns *
(linefeeds + extraRows)) * 100 ) / 100;
if ( density < .7 && density > .2 &&
objText.length > adjColumns && linefeeds > 0 ) {
extraRows += Math.round( fontFamilyAdj / density + .5);
extraRows -= Math.round( linefeeds * (linefeeds < 20 ? .2 : .1) );
}

var newRows = linefeeds + extraRows;

var debugStr = "TEXTAREA id=" + obj.id + " newRows= " + newRows +
" linefeeds=" + linefeeds + " extraRows=" + extraRows +
" density=" + density +
" fontFamilyAdj=" + fontFamilyAdj +
" spaces=" + spaces +
" avgWordSize=" + avgWordSize + " cols=" + obj.cols +
" adjColumns=" + adjColumns +
" largeWordsize=" + largeWordSize +
" largeWordCount=" + largeWordCount;
document.getElementById( "debugText" ).innerText = debugStr;


if ( newRows != obj.rows ) {
obj.rows=newRows;
}
}</script>

</head>
<body>

<form>
<h1>>Dynamically Sized TEXTAREA</h1>

<textarea cols="20" rows="4" onkeyup="updateTextareaHeight(this);" id="textarea01" style="font-family:Times;">This is an example of a Proportional Font! Some text goes here! I have taken the liberty to add a little more text for this example to show how the TEXTAREA can be dyanmically adjusted in height when the page is initially loaded. Please remember from the example above that the original TEXTAREA was only 20 columns wide (and still is) but was only 4 rows high. There should be no scroll bars!</textarea>
<textarea cols="20" rows="4" onkeyup="updateTextareaHeight(this);" id="textarea02" style="font-family:Courier;">This is an example of a Fixed Width Font! Some text goes here! I have taken the liberty to add a little more text for this example to show how the TEXTAREA can be dyanmically adjusted in height when the page is initially loaded. Please remember from the example above that the original TEXTAREA was only 20 columns wide (and still is) but was only 4 rows high. There should be no scroll bars!</textarea>


<br /><br />
<b>Debug Information</b>
<div id="debugText" />

</form>
<script>
{
updateTextareaHeight( document.getElementById( "textarea01" ) );
updateTextareaHeight( document.getElementById( "textarea02" ) );
}
</script>
</body>
</html>


OTHER CONSIDERATIONS
Please be advised that if you use
line-height
and change the browser's default, then it will effect the behavior in one of two ways. 1) If the value is set too hight, say around 190% or greater, it will increase the likelihood of the text overflowing the TEXTAREA so scroll bars will appear. 2) If it is set to a very small value, say 10%, then more than one line can overflow before the scroll bars appear. Remember, the whole purpose of the Dynamically Sized TEXTAREA to to display the full text in a TEXTAREA element without the use of the scroll bars and to minimize the amount of white space between the end of the text and the bottom of the box.

Also note that proportional fonts will be a bit more tricky to trying to reduce the amount of white space at the end of the text and the prevention of the scroll bars from appearing. This may require a bit more fine tuning of the equation/technique involved.

The biggest issue you may find with fixed width fonts, is the way they tend to wrap text on a word boundry. This is fine, unless your column width is too narrow and there are large words such that there there tends to be a fair amount of white space to the right of a line when that lines contains no linefeed.

One alternative would be to use the TEXTAREA only for editing. If there is a page that will be read-only or used for printing, then it may be a better idea to use a DIV tag instead with a style="border:2px solid #000000;width=20em;", for example. This will place all contained text within a box. Do not set the height and this box will adjust for the height of the text. This especially would be best for printing.

REFERENCES
Information on Regular Expressions. Some good information on JavaScript too.

MacBook Pro and the CTRL-ALT-DEL Keys

The MacBook Pro (MBP) combined with Bootcamp allows the Mac to run Windows XP natively. The catch being that when the MBP's XP is configured to use a corporate domain it requires the use of CTRL-ALT-DEL to get to the login screen when the MBP's keyboard does not have an DEL key.

Enter the Windows Utility Manager. This tool allows you to turn on and off the various accessibility programs. You can turn off the text reader which will read just about everything on the screen. But what you are really wanting to do is to turn on the On-Screen Keyboard program. On Windows XP you use the Windows key + U to activate the Windows Utility Manager.

On the MBP when booting XP the Apple key is mapped to the Windows key. At the start screen when it is asking you to press CTRL-ALT-DEL key, press Apple + U key combination. Once the Windows Utility manger starts, you can go ahead and start the on-Screen Keyboard program. Now hold down the CTRL-ALT keys on the MBP keyboard and then use the mouse to click on the DEL key on the On-Screen Keyboard.

Once you are able to get in to Windows, yes you can remap the keys, but you must make it to windows in the first place.

REFERENCES:
http://www.windowsnetworking.com/articles_tutorials/Windows-XP-Keyboard-Shortcuts.html - Tip 7