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
No comments:
Post a Comment