/*
  ObjectExaminer.js
  Version 0.82
  A JavaScript tool to examine object properties and map
  the applicable DOM.
  
  Usage: 
    1. Include the following line as the first script tag in
       the head section of your document:

       <script src="ObjectExaminer.js"></script>

    2. At a point in your script where you would like to see the 
       properties of some object, call examine, passing it a reference
       to the object and a string to identify it (most likely this
       will be the same as the object reference, but in quotes).
       For example, to examine the event object variable e in an
       event handler:

       examine(e, 'event');

       Or, to examine the document object:

       examine(document, 'document');

    3. Once the new window pops up, any elements that are objects
       will be hyperlinked; clicking the link will examine that object.

  Original version tested in Netscape 4.7, 4.8, 6.2, and IE 6.  Most recent changes
	have been tested in IE 6, 7, Firefox 1.5 under
  Windows XP.
	
  Created by Steve Claflin (steve@steveclaflin.com)

  Copyright 2007, Steve Claflin.  All rights reserved.

*/
/*  
  Enhancements under consideration:
  
    smaller text for large blocks of text (e.g., innerHTML)
  
    separate object properties from scalar properties
  
    add in a delay/hide to avoid messing up things like mouseover/mouseouts

*/

//User settable global variables

var arrayColor = 'FFFFEE';// bg color of Array Elements area
var arrayStyle = 'background-color:' + arrayColor + ';'; 
                          // style string for Array Elements area 
                          // customize as you wish
var propsColor = 'FFEEEE';// bg color of Properties area
var propsStyle = 'background-color:' + propsColor + ';';
                          // style string for Properties area 
                          // customize as you wish
var cellPadding = 3;
var fontSize = '10pt';    // used in style section for td elements

var useStyle = true;      // use styles (see testNS4Style below)
var useBg = false;
var testNS4Style = true;  // turns on test that sets useStyle false
                          // and useBg true in NS4 (NS4 seems to have
                          // problems with document.write and styles)
var 
 NS4DocCloseDelay = 1000; // delay time between document.write and
                          // document.close - apparently needed by NS4
var wTop = 0, wLeft = 0;  // base location of popup windows
var cascade = 20;         // amount by which to offset child window
var wShrinkW = 32;        // popup width = screen width - wShrinkW
var wShrinkH = 48;        // popup height = screen height - wShrinkH

var doSort = true;        // enables sorting of props list - setting
                          // false will speed things up in Netscape 6
var skipBadHrefs = false;  // works around a NS 6 bug that hangs up
                          // on host, hostname, and search with 
                          // a certain protocols (javascript, mailto,
                          // nntp, and telnet)
                          // set to false if your browser does not
                          // exhibit this problem
var legalHrefs = 'http https news ftp file gopher';
                          // space-delimited string of known non-problematic protocols
// Other global variables

var arrayLook, propsLook; // string using either style or bgcolor
                          // will set background color of that area
var iWin = 1;
var wins = new Array();
wins[0] = window;
var eltClones = new Array();
var oLink = '<a href="javascript: void 0" '
            + 'onclick="return examine(window.elt';
var legalHrefList = legalHrefs.split(' ');

//debug vars
var time1;


/* function examine(elt, name, myLevel, myOpener)
   Examines the object elt, popping up a window with array elements
   and properties.  
*/
function examine(elt,     // reference to element to examine
                 name,    // descriptive name to use in headings
								 noClone, // used to disable cloning (which is only needed for transient objects like events)
								          //  for AJAX use, supply "true"
                 myLevel, // depth of chain of windows for subelements
                          // should not be supplied by user (sublevels 
                          // use it from html code written by examine)
                 myOpener // index of window that opened this one
                          // should not be supplied by user 
                 ) {
//  time1 = new Date().getTime();

  var oldUnload = window.onunload;
/*
	window.onunload = new function() {
		for (i=0; i<wins.length; i++) wins[i].close();
		if (oldUnload != null) oldUnload();
		}
*/
  if (typeof elt != 'object') return;
  myId = iWin++;
  if (!noClone) eltClones[myId] = clone(elt);
	else eltClones[myId] = elt;
  var level = (typeof myLevel != 'undefined') ? myLevel : 0;
  var cas = level * cascade;
  if (typeof name == 'undefined') name = 'examined_object';
  if (typeof myOpener == 'undefined') myOpener = null;
  if (document.layers && testNS4Style) {
    useStyle = false;
    useBg = true;
    }
  arrayLook = useStyle ? (' style="' + arrayStyle + '"') :
              useBg ? ( ' bgcolor="' + arrayColor + '"') : '';
  propsLook = useStyle ? (' style="' + propsStyle + '"') :
              useBg ? ( ' bgcolor="' + propsColor + '"') : '';
  wins[myId] = window.open('','win' + myId,
                  'scrollbars,resizable,'
                  + 'HEIGHT=' + (screen.availHeight - wTop - cas - 64)
                  + ',WIDTH=' + (screen.availWidth - wShrinkW - wLeft - cas)
                  + ',TOP=' + (wTop + cas) + ',screenY=' + (wTop + cas)
                  + ',LEFT=' + (wLeft + cas) + ',screenX=' + (wLeft + cas));
  var myWin = wins[myId];
  var colTitle1A = '<tr' + arrayLook + '>'; 
  var colTitle1P = '<tr' + propsLook + '>';
  var colTitle2 =  '<th>Element<\/th>'
                   + '<th>Type<\/th>'
                   + '<th>Value<\/th>'
                   + '<th>Tag Name<\/th><\/tr>\n';
  var bodyTag = '<body onload="finish(' + myId + ',' + level 
                + ',' + myOpener + ');" onunload="cleanUp(' 
                + myId + ');">';
  var html = '<html>\n<head>\n<title>'
             + name + '<\/title>\n'
             + (useStyle ? 
                  '<style>\ntd { font-size:' + fontSize + '; }\n<\/style>\n'
                  : '')
             + '<script>\n'
             + 'var topOpener = window.opener;\n'
             + 'examine = topOpener.examine;\n'
             + 'finish = topOpener.finish;\n'
             + 'cleanUp = topOpener.cleanUp;\n'
             + 'myWinID = ' + myId + ';\n'
             + '</' + 'script>\n'
             + '<\/head>\n'
             + bodyTag
/*
             + '<form><input type="button" onclick="topOpener.finish(' + myId 
             + ',' + level 
             + ',' + myOpener + ');' 
             + '"><\/form>'
*/
             + (document.layers ? '<layer id="lWait" bgColor="yellow"><h1>Please wait ...</h1></layer>' : '')
             + '<h2>Elements of <i>' + name + '<\/i><\/h2>\n'
             + '<table cellpadding="' + cellPadding 
             + '" cellspacing="0" border="1">\n';
             
  var arrayElts = "", propElts = "";
  var skippableLink = skipBadHrefs 
                      && (typeof elt.tagName != 'undefined')
                      && (elt.tagName == 'A')
                      && (typeof elt.protocol  != 'undefined')
                      && isBadProtocol(elt.protocol);

  var i = 0;
  if ((typeof elt.length) != 'undefined') {
    for (i=0; i<elt.length; i++ ) {
      if ((typeof elt[i] == 'object') && elt[i] != null) isObj = true;
      else isObj = false;
      arrayElts += '<tr><td>'
                   + (isObj ? (oLink + '[\'' + i + '\'],\'' + escapeQuotes(name) 
                   + '[\\\'' + i + '\\\']\',' + (level+1) 
                   + ',' + myId+ ');">') : '') 
                   + i
                   + (isObj ? '</a>' : '') 
                   +"<\/td><td>"
                   + (typeof elt[i]) +  "</td><td>" 
                   + elt[i] + "&nbsp;</td><td>" 
                   + (elt[i] ? ((typeof elt[i].tagName == 'undefined') 
                                    ? "&nbsp;" : elt[i].tagName) : "&nbsp;")
                   + '<\/td><\/tr>\n';
      }
    }

  html += '<tr><td colspan="5"' + arrayLook
          + '><b>Array Elements<\/b><\/td><\/tr>\n';
  if (i==0) {
    html += '<tr><td' + arrayLook 
            + '>&nbsp;<\/td><td colspan="4"><i>none<\/i><\/td><\/tr>\n';
    }
  else html += colTitle1A + '<td' + arrayLook 
               + ' rowspan="' + (i+1) + '">&nbsp;<\/td>' 
               + colTitle2 + arrayElts;

  i = 0;
  sArray = new Array();
  for (x in elt) {
    sArray[i] = x;
    i++;
    }
  if (doSort) sort(sArray);
  for (x in sArray) { 
    x = sArray[x];
	  var eltX = 'unexaminable object';
		var typeofX = 'unknown type element';
    isObj = false;
    isPre = false;
    if (skippableLink 
        && (x == 'host' || x == 'hostname' || x == 'search')
       ) { } // skip it
    else if (isNaN(parseInt(x))) {
      try {
				eltX = elt[x];
				typeofX = typeof elt[x];
				if ((typeofX == 'object') && eltX != null) isObj = true;
				if ((typeofX == 'function') && eltX != null) isPre = true;
	      convertedX = ('' + eltX).replace(/</g, '&lt;').replace(/</g, '&lt;');
			} catch(e) {
				//alert(x);
				convertedX = x + ' - non-examinable element';
			}
      if (x == 'innerHTML' || x == 'outerHTML') isPre = true;
      propElts += '<tr><td>'
                  + (isObj ? (oLink + '.' +  x + ',\'' + escapeQuotes(name)
                  + '.' + x + '\', ' + (level+1) + ',' + myId + ');">') : '') 
                  + x
                  + (isObj ? '<\/a>' : '') 
                  +"<\/td><td>"
                  + (typeofX) +  "<\/td><td>" 
                  + (isPre ? ('<pre>' + convertedX + '<\/pre>') : convertedX)
                  + "&nbsp;<\/td><td>" 
                  + (eltX ? ((typeof eltX.tagName == 'undefined') 
                                    ? "&nbsp;" : eltX.tagName) : "&nbsp;")
                  + '<\/td><\/tr>\n';
      }
    }

  html += '<tr><td colspan="5"' + propsLook
          + '><b>Properties<\/b><\/td><\/tr>\n';
  if (i==0) {
    html += '<tr><td' + propsLook 
            + '>&nbsp;<\/td><td colspan="4"><i>none<\/i><\/td><\/tr>\n';
    }
  else html += colTitle1P + '<td' + propsLook
               + ' rowspan="' + (i+1) + '">&nbsp;<\/td>' 
               + colTitle2 + propElts;

  html += "<\/table>\n<\/body>\n" + "<\/html>\n";
  myWin.document.write(html);
  if (document.layers) {
    setTimeout('wins[' + myId + '].document.close();'
  	           + 'finish(' + myId + ',' + level + ',' + myOpener + ');',
               NS4DocCloseDelay);
    }
  else myWin.document.close();
  return false;
  }
	
/* function finish(myId, myLevel, openerId)
   Registers info about this window in the global wins array.	
*/
function finish(myId, myLevel, openerId) {
  wins[myId].xLevel = myLevel;
  wins[myId].xOpenerId = openerId;
  wins[myId].xOpener = wins[openerId];
  wins[myId].elt = eltClones[myId];
  if (document.layers) {
    wins[myId].document.lWait.visibility = "hide";
    }

//  alert("Load time: " + (new Date().getTime() - time1));
  return false;
  }

function cleanUp(winId) {
  wins[winId] = null;
  eltClones[winId] = null;
  }
	
/* function clone(elt)
   This clones the object.  function finish() sets it as a property
   of the window.  Necessary for transient items like event objects
   (because they exist in the parent window's thread, and therefore
   will have likely changed by the time a link is clicked in the
   new window).
*/
function clone(elt) {  
  var newElt = new Array();
  if ((typeof elt.length) != 'undefined') {
      for (i=0; i<elt.length; i++ ){
        newElt[i] = elt[i]; 
        }
  		}
  if ( skipBadHrefs 
 	   && (typeof elt.tagName != 'undefined') && (elt.tagName == 'A')
 	   && (typeof elt.protocol  != 'undefined') && isBadProtocol(elt.protocol)
 	   ) {
    for (x in elt) {
      if (x == 'host') newElt[x] = '';
      else if (x == 'hostname') newElt[x] = '';
      else if (x == 'search') newElt[x] = '';
      else newElt[x] = elt[x];
      }
    }
  else for (x in elt) {
    try {
			newElt[x] = elt[x];
		} catch(e) {
			newElt[x] = x + ' - non-cloneable element';
    }
	}

  return newElt;
  }
    
/* function escapeQuotes(name)
   Puts an escape character (\) in front of ' and " characters
    in a string.
*/
function escapeQuotes(name) {
  return name.replace(/'/g, '\\\'').replace(/"/g, '\\\"');
  }
  
/* function sort(array)
   Sorts an array using a bubble sort.
*/
function sort(sArray) {
  endI = endO = sArray.length - 1;
  var lastSwap = endI;
  for (h=0; h<endO; h++) {
    if (endI == 0) break;
    for (i=0, j=1; i<endI; i++,j++) {
      if (sArray[i] > sArray[j]) {
        temp = sArray[j]; sArray[j] = sArray[i];  sArray[i] = temp;
        lastSwap = i;
        }
      }
    endI = lastSwap;
    lastSwap = 0;
    }
  }
	
/* function isBadProtocol(protocol)
   Tests if the protocol is not on the legal list, returns true if 
   that is the case.
*/
function isBadProtocol(protocol) {
  for (i=0; i<legalHrefList.length; i++) {
    if (protocol.indexOf(legalHrefList[i]) == 0) return false;
    }
  return true;
  }


