/*
Ajax Link Tracker, version 2.2

Copyright (c) 2006 Glenn Jones

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

You must give the original author attribution. For any reuse or
distribution, you must make clear to others the licence terms of this
work.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

function AjaxLinkTracker() {

    var me;
    if (this.constructor == AjaxLinkTracker){
        me = this;
    }else{
        me = arguments[arguments.length-1];
    }

	// Configuration
	// ------------
	me.apiURL = 'http://www.crdp.umontreal.ca/link-tracker/'; // must be an absolute URL
	me.apiDomain = 'www.crdp.umontreal.ca';
	//me.stylesheet = {URL : 'http://www.umontreal.ca/CSS/DG/link-tracker.css', encoding : 'iso-8859-1'};
	me.displayCount = true;
	me.displayPercent = true;
	me.displayLabel = true;
	me.numberDays = 30; //1-30
	me.clickOffSet = 10;
	/* me.regions = [
		["bandeau-UdeM", /^UM/],
		["bandeau-commun", /^bib-b-/],
		["cartouche", /^bib-c-/],
		["menu", /^bib-m-/],
		["contenu", /.*/]
		]; */
	me.defaultPages = ["index.html", "index.htm", "index.php"];
	me.debug = false;
	// -----------

	/**
	* Regular expressions for normalizing white space.
	* Throughout, whitespace is defined as one of the characters
	*  "\t" TAB \u0009
	*  "\n" LF  \u000A
	*  "\r" CR  \u000D
	*  " "  SPC \u0020
	*
	* This does not use Javascript's "\s" because that includes non-breaking
	* spaces (and also some other characters).
	*/

	/**
	* Version of |data| that doesn't include whitespace at the beginning
	* and end and normalizes all whitespace to a single space.  (Normally
	* |data| is a property of text nodes that gives the text of the node.)
	*
	* @param s    The string whose data should be returned
	* @return     A string giving the contents of the text node with
	*             whitespace collapsed.
	*/
	me.normalizeString = function ( s ) {
		// Use ECMA-262 Edition 3 String and RegExp features
		s = s.replace(/[\t\n\r ]+/g, " ");
		if (s.charAt(0) == " ") {
			s = s.substring(1, s.length);
		}
		if (s.charAt(s.length - 1) == " ") {
			s = s.substring(0, s.length - 1);
		}
		return s;
	}
	
	me.getBaseURL = function(URL) {
		var URL = URL.toString();
		URL = URL.replace(/#.*/, '');
		for (i=0; i<me.defaultPages.length; i++) {
			URL = URL.replace(me.defaultPages[i], '');
		}
		return URL;
	}
	me.isOnSecureDomain = (document.location.host == me.apiDomain) ? true : false;
	me.url = me.getBaseURL(document.location);
	me.listeners = [];
	me.ajaxURL = '';
	
	me.loadingDialog = document.createElement('div');
	me.loadingDialog.id = 'LT-loading-dialog';
	me.loadingDialog.innerHTML = '<div id="LT-loading"><img src="/skin/img/modele/spinner.gif"> Chargement...</div>';
			
	me.makeUniqueId = function(id, n) {
		n = n || 1;
		if(document.getElementById(id + '-' + n )) {
			n = n+1;
			return me.makeUniqueId(id, n);
		} else {
		return id + '-' + n;
		}
	}
	me.addLinkTracking = function(){
		if (!document.getElementsByTagName) return false;
		if (!me.isOnSecureDomain) return false;

		// Create ajax objects
		me.clickedXHR = new XHRConnection();
		me.getClicksXHR = new XHRConnection();
		
		// Adding link to CSS stylesheet
		//me.addStyle(me.stylesheet.URL, me.stylesheet.encoding);
		
		// find links in document (that is all 'a[href]' and 'area' elements)
		links = document.links;

		// if link does not have a id add one
		for (var i = 0; i < links.length; i++) {
			me.addEvent( links[i], 'mousedown', me.recordClick, false );
			me.addEvent( links[i], 'keypress', me.linkKeyPress, false );
			if (! links[i].getAttribute('id') ) {
				var newId = 'link-' + createID(links[i].href, me.normalizeString( me.getInnerText(links[i]) ));
				if (document.getElementById(newId)) {
					if (me.debug) {DebugConsole.write('id ' + newId + ' already used on node ' + links[i].tagName + '[href=\'' + links[i].href + '\'], text "' + me.normalizeString(me.getInnerText(links[i])) + '". Making a new one.');}
					newId = me.makeUniqueId(newId);
					if (me.debug) {DebugConsole.write('... new id is ' + newId);}
				}
				links[i].setAttribute('id', newId );
			}
		}
		
		// find buttons in document
		inputs = document.getElementsByTagName('input');
		for (var i = 0; i < inputs.length; i++) {
		
			type = inputs[i].getAttribute('type');
			
			// only attach events to buttons
			if ( type == 'submit' || type == 'button' || type == 'image' || type == 'reset' ){
				me.addEvent( inputs[i], 'mousedown', me.recordClick, false );
				me.addEvent( inputs[i], 'keypress', me.linkKeyPress, false );
				// if button does not have a id add one
				if (! inputs[i].getAttribute('id') ){
					inputs[i].setAttribute('id','button-' + i);
				}	
			}
		}
	}
	
	me.linkKeyPress = function(e) {
		// check for return key press
		var keyID = (window.event) ? event.keyCode : e.keyCode;
		if (keyID == 13) {
			me.recordClick(e);
		}
	}

	me.recordClick = function(e) {
		// records click information using ajax
		source = me.findSourceElement(e);
		tag = source.tagName;
		if (me.debug) {DebugConsole.write('Recording a \'click\' event from element type ' + tag);}
		var id,label,target,sessionCookie,persistentCookie,pageRegion
		
		switch (tag) {
			case 'IMG' :
				if( source.parentNode.tagName == 'A' )
				{
					id = source.parentNode.getAttribute('id');
					target = source.parentNode.href;
				}
				label = source.getAttribute('alt');
				if (label == null || normalizeString(label) == '') {
					label =  source.getAttribute('title');
				}
				if (label == null || normalizeString(label) == '') {
					label = '[img]';
				}
				if (me.debug) {DebugConsole.write("Processed information items for element type : " + tag + "#" + id);}
				break;
	
			case 'A' :
				id = source.getAttribute('id');
				target = source.href;
				label = me.normalizeString( me.getInnerText( source ) );
				if (label == '' ) {
					label = '[' + id + ']';
				}
				if (me.debug) {DebugConsole.write("Processed information items for element type : " + tag + "#" + id);}
				break;
	
			case 'INPUT' :
				// input type='submit' or input type='image'
				id = source.getAttribute('id');
				// get a label. If none, fallback to the id
				label = source.getAttribute('value') || source.getAttribute('alt') || id;
	
				if( source.getAttribute('type') == 'submit' || source.getAttribute('type') == 'image' ) {
					target = me.getFormTarget( source );
				} 				
				else if (source.getAttribute('type') == 'reset') {
					target = '[reset]';
				}				
				else {
					target = '[script]';
				}
				if (me.debug) {DebugConsole.write("Processed information items for element type : " + tag + "#" + id);}
				break;
	
			case 'AREA' :
				id = source.getAttribute('id');
				target = source.href;
				label = source.getAttribute('alt');
				if (label == null || normalizeString(label) == '') {
					label =  source.getAttribute('title');
				}
				if (label == null || normalizeString(label) == '') {
					label = '[area]';
				}
				if (me.debug) {DebugConsole.write("Processed information items for element type : " + tag + "#" + id);}
				break;
			
			default : 
				// Huston, we have a problem!
				if (me.debug) {
					DebugConsole.write("ERROR: A click event has been raised on an element that could not be processed :");
					DebugConsole.write("  tagName : " + tag);
					DebugConsole.write("  id : " + source.getAttribute('id'));
					DebugConsole.write("  target : " + source.href);
					DebugConsole.write("  innerHTML : " + source.innerHTML);
					DebugConsole.write("Canceling 'click' event recording...");
				}
				return
			}

		sessionCookie = ( readCookie('session-cookie') ) ? readCookie('session-cookie') : 'disabled';
		persistentCookie = ( readCookie('persistent-cookie') ) ? readCookie('persistent-cookie') : 'disabled';

		pageRegion = me.getRegion(id);
		
		id = encodeURIComponent( id );
		target = encodeURIComponent( target );
		label = encodeURIComponent( label );
		sessionCookie = encodeURIComponent(sessionCookie);
		persistentCookie = encodeURIComponent(persistentCookie);
		pageRegion = encodeURIComponent(pageRegion);
		me.ajaxURL = me.apiURL + 'addclick.php?id=' + id + '&label=' + label + '&target=' + target + '&url=' + me.url + '&session_cookie=' + sessionCookie + '&persistent_cookie=' + persistentCookie + '&page_region=' + pageRegion + '&rand='+Math.random();
		if (me.debug) {DebugConsole.write("Sending click data to : " + me.ajaxURL);}
		me.clickedXHR.send( me.ajaxURL, 'get', me.beenClicked, null  );
		
	}
	
	me.beenClicked = function( obj ) {
		//alert( obj.responseText );
	}
	
	me.getClickThroughInfo = function (){
		// get click data using ajax
		document.body.appendChild(me.loadingDialog);
		me.ajaxURL = me.apiURL + 'getclicks.php?url=' + me.url + '&days=' + me.numberDays + '&rand='+Math.random();
		if (me.debug) {DebugConsole.write("Loading data from : " + me.ajaxURL);};
		me.getClicksXHR.send( me.ajaxURL, 'get', me.displayClickThroughs, null  );
	}		

	me.displayClickThroughs = function( obj ){
		// display click through data
	
		if (!document.getElementsByTagName) return false;
		
		if (me.debug) {DebugConsole.write(" ... " + obj.statusText + " (" + obj.status + ")");}
		
		if(obj.responseXML)
			node = obj.responseXML;
		if(obj.responseXml)
			node = obj.responseXML;	
			//alert(node.xml);
		
		if (node.parseError && node.parseError.errorCode != 0) {
			// throwing parse errors in IE. Need to fix this in Mozilla
			if (me.debug) {DebugConsole.write('Response XML parsing status : ' + node.parseError.errorCode);}
			if (me.debug) {DebugConsole.write('Error details :');}
			if (me.debug) {DebugConsole.write(' reason : ' + node.parseError.reason );}
			if (me.debug) {DebugConsole.write(' line : ' + node.parseError.line );}
			if (me.debug) {DebugConsole.write(' position : ' + node.parseError.linepos );}
			if (me.debug) {DebugConsole.write(' source : ' + node.parseError.srcText);}
		
			var loadingDialog = document.getElementById('LT-loading-dialog');
			me.loadingDialog = document.body.removeChild(loadingDialog);
			alert('Erreur. Les données de l\'application link tracker sont illisibles.\n\nDétails de l\'erreur : \n' + node.parseError.reason + '\nS.V.P. contactez le webmestre.');
			return true
		}
		
		if(node.childNodes[0].nodeType == 7) {
			rootNode = node.childNodes[1];
		}else{
			rootNode = node.childNodes[0];
		}
		var linkNodes = rootNode.getElementsByTagName('link');
		for (var i = 0; i < linkNodes.length; i++) {
			var linknode = linkNodes[i];
			count = linknode.getAttribute('count');
			percent = linknode.getAttribute('percent');
			id = linknode.getAttribute('id');
			label = linknode.childNodes[0].nodeValue;
			
			if ( document.getElementById(id) ) {
			
				eltLink =  document.getElementById(id);
				
				eltDiv = document.createElement( 'div' );
				eltDiv.className = 'linklabel';
				
				var text = ''; 
				
				if( me.displayCount )
					text += count + ' ';
					
				if( me.displayPercent )
					text += '(' + percent + '%)';
					
				if( me.displayLabel ) {
					eltDiv.setAttribute('title',label);
				}
				
				eltText = document.createTextNode( text );
				eltDiv.appendChild( eltText );
				document.body.appendChild( eltDiv );
				
				Drag.init(eltDiv, eltDiv);
				
				ileft = parseInt( me.getPageOffsetLeft( eltLink ) ) + me.clickOffSet;
				itop = parseInt( me.getPageOffsetTop( eltLink ) ) + me.clickOffSet;
				
				if (eltLink.tagName == 'AREA') {
					if ( eltLink.getAttribute('nohref') != null ) {
						// we have a linked area on a client-side map image
						// positionning assuming the image map uses @shape="rect"
						var coords = eltLink.getAttribute('coords');
						var s = document.getElementsByTagName('IMG');
						for (j=0; j<s.length; j++) {
							if (s.item(j).getAttribute('usemap')) {
								if (s.item(j).getAttribute('usemap') == '#' + eltLink.parentElement.id) {
									sourceImage = s.item(j);
									ileft = parseInt( me.getPageOffsetLeft( sourceImage ) ) + me.clickOffSet;
									itop = parseInt( me.getPageOffsetTop( sourceImage ) ) + me.clickOffSet;
									break;
								}
							}
						}
						if (coords != null) {
							coords = coords.split(',');
							ileft += parseInt(coords.shift());
							itop  += parseInt(coords.shift());
						}
					}
				}
				
				eltDiv.style.left = ileft + 'px';
				eltDiv.style.top = itop + 'px';
				
			}
		} 
		me.labelsCreated = true;
		me.labelsDisplayed = true;
		
		var loadingDialog = document.getElementById('LT-loading-dialog');
		me.loadingDialog = document.body.removeChild(loadingDialog);
	}
	
	me.getClickThroughListInfo = function (){
		// get click data using ajax
		document.body.appendChild(me.loadingDialog);
		me.ajaxURL = me.apiURL + 'getclicks.php?url=' + me.url + '&days=' + me.numberDays + '&rand='+Math.random();
		if (me.debug) {DebugConsole.write("Loading data from : " + me.ajaxURL);}
		me.getClicksXHR.send( me.ajaxURL, 'get', me.displayClickThroughsList, null  );
	}	

	me.displayClickThroughsList = function( obj ){
		// display click through list interface

		if (!document.getElementsByTagName) return false;
		
		if (me.debug) {DebugConsole.write(" ... " + obj.statusText + " (" + obj.status + ")");}
		
		if(obj.responseXML)
			node = obj.responseXML;
		if(obj.responseXml)
			node = obj.responseXML;	
		
		if (node.parseError && node.parseError.errorCode != 0) { 
			// throwing parse errors in IE. Need to fix this in Mozilla
			if (me.debug) {DebugConsole.write('Response XML parsing status : ' + node.parseError.errorCode);}
			if (me.debug) {DebugConsole.write('Error details :');}
			if (me.debug) {DebugConsole.write(' reason : ' + node.parseError.reason );}
			if (me.debug) {DebugConsole.write(' line : ' + node.parseError.line );}
			if (me.debug) {DebugConsole.write(' position : ' + node.parseError.linepos );}
			if (me.debug) {DebugConsole.write(' source : ' + node.parseError.srcText);}
		
			var loadingDialog = document.getElementById('LT-loading-dialog');
			me.loadingDialog = document.body.removeChild(loadingDialog);
			alert('Erreur. Les données de l\'application link tracker sont illisibles.\n\nDétails de l\'erreur : \n' + node.parseError.reason + '\nS.V.P. contactez le webmestre.');
			return true
		}

		if(node.childNodes[0].nodeType == 7) {
			rootNode = node.childNodes[1];
		}else{
			rootNode = node.childNodes[0];
		}
		var linkNodes = rootNode.getElementsByTagName('link');

		// Building the list widget
		
		var strHTML = '';
		
		var eltList = document.createElement('div');
		eltList.id = 'ce-master-list-panel';

		var eltCEListPanel = document.createElement('div');
		eltCEListPanel.id = 'ce-list-panel';

		var eltCEListTop = document.createElement('div');
		eltCEListTop.id = 'ce-list-top';
		eltCEListTop.innerHTML = '<span id="ce-list-title">Clicks pour cette page depuis les ' + me.numberDays + ' derniers jours <span id="ce-view-xml">(<a href="' + me.ajaxURL + '" target="_blank">format XML</a>)</span></span>';
		eltCEListPanel.appendChild(eltCEListTop);
		
		var eltCEListCont = document.createElement('div');
		eltCEListCont.id = 'ce-list-cont';

		var eltLink = document.createElement('a');
		eltLink.id = 'ce-list-close';
		eltLink.innerHTML = 'Fermer';
		eltCEListCont.appendChild(eltLink); 
		
		var eltTable = document.createElement('table');
		eltTable.id = 'ce-list';
		eltTable.className = 'sortable';
		eltTable.setAttribute('border','0');

		// Table head
		
		var eltTableHead = document.createElement('thead');
		var tr = document.createElement('tr');

		th = document.createElement('th');
		th.id = 'ce-col-th-label';
		th.className = 'ce-col-label';
		th.innerHTML = 'Lien';
		tr.appendChild(th);

		th = document.createElement('th');
		th.id = 'ce-col-th-region';
		th.className = 'ce-col-region';
		th.innerHTML = 'Région';
		tr.appendChild(th);
		
		th = document.createElement('th');
		th.id = 'ce-col-th-clicks';
		th.className = 'ce-col-clicks';
		th.innerHTML = 'Clics';
		tr.appendChild(th);
		
		th = document.createElement('th');
		th.id = 'ce-col-th-pourcent';
		th.className = 'ce-col-pourcent';
		th.innerHTML = 'Pourcent';
		tr.appendChild(th);
		eltTableHead.appendChild(tr);
		
		eltTable.appendChild(eltTableHead);
		
		// Table body
		
		var eltTableBody = document.createElement('tbody');	

		var totalCount = rootNode.getAttribute('total-count');
		var totalPercent = 0;
		var td;
		
		if (linkNodes.length == 0) {
			var eltTableRow = document.createElement('tr');
			
			td = document.createElement('td');
			td.className = 'ce-col-no-data';
			td.colSpan = 4;
			td.innerHTML = '<em>Aucune donnée pour cette page</em>';
			eltTableRow.appendChild(td);
			eltTableBody.appendChild(eltTableRow);
		}

		for (var i = 0; i < linkNodes.length; i++) {
			var linkNode = linkNodes[i];
			count = linkNode.getAttribute('count');
			//percent = Math.round(parseFloat(count) / totalCount * 10000) / 100;
			percent = linkNode.getAttribute('percent');
			id = linkNode.getAttribute('id');
			pageRegion = linkNode.getAttribute('page-region');
			label = linkNode.childNodes[0].nodeValue;
			
			var eltTableRow = document.createElement('tr');
			
			td = document.createElement('td');
			td.className = 'ce-col-label';
			td.innerHTML = makeBreakable(label);
			td.setAttribute('title',label);
			eltTableRow.appendChild(td);
			
			td = document.createElement('td');
			td.className = 'ce-col-region';
			td.innerHTML = pageRegion;
			eltTableRow.appendChild(td);
			
			td = document.createElement('td');
			td.className = 'ce-col-clicks';
			td.innerHTML = count;
			eltTableRow.appendChild(td);
			
			td = document.createElement('td');
			td.className = 'ce-col-percent';
			td.innerHTML = percent + '%';
			eltTableRow.appendChild(td);
			
			eltTableBody.appendChild(eltTableRow);
			
			totalPercent += parseFloat(percent);
		} 

		eltTable.appendChild(eltTableBody);
		
		// Table foot
		
		var eltTableFoot = document.createElement('tfoot');

		eltTableRow = document.createElement('tr');

		th = document.createElement('th');
		th.className = 'ce-col-label';
		th.colSpan = 2;
		th.innerHTML = 'Total';
		eltTableRow.appendChild(th);
		
		th = document.createElement('th');
		th.className = 'ce-col-clicks';
		th.innerHTML = totalCount;
		eltTableRow.appendChild(th);
		
		th = document.createElement('th');
		th.className = 'ce-col-percent';
		th.innerHTML = Math.ceil(totalPercent * 100) / 100 + '%';
		eltTableRow.appendChild(th);
		
		eltTableFoot.appendChild(eltTableRow);

		eltTable.appendChild(eltTableFoot);
		
		eltCEListCont.appendChild(eltTable);
		eltCEListPanel.appendChild(eltCEListCont);

		var eltCEListBot = document.createElement('div');
		eltCEListBot.id = 'ce-list-bot';
		eltCEListBot.innerHTML = '&nbsp;';
		eltCEListPanel.appendChild(eltCEListBot);
		
		eltList.appendChild(eltCEListPanel);
		
		// hide form controls behind
		
		if (browser.isIE && browser.versionMinor < 7) {
			// Cover form widgets on IE6-
			// Inspired from http://dotnetjunkies.com/WebLog/jking/archive/2003/07/21/488.aspx
			var shim = document.createElement('div');
			shim.innerHTML = '<iframe id="shim" src="javascript:false;" scrolling="no" frameborder="0" style="display: none; position: absolute; background-color: transparent; border: 0;"></iframe>';
			shim = shim.firstChild;
			eltCEListPanel.shim = shim;
			eltList.appendChild(shim);
		}
		
		document.body.appendChild(eltList);
		
		Drag.init(eltCEListTop, eltCEListPanel);
		ileft = (screen.availWidth - 552) / 2;
		itop = 20;
		eltCEListPanel.style.left = ileft + 'px';
		eltCEListPanel.style.top = itop + 'px'; 
			
		if (eltCEListPanel.shim) {
			var shim = eltCEListPanel.shim;
			shim.style.zIndex = eltCEListPanel.currentStyle.zIndex - 1;
			shim.style.left = ileft + 'px';
			shim.style.top = itop + 'px'; 
			shim.style.width = eltCEListPanel.offsetWidth;
			shim.style.height = eltCEListPanel.offsetHeight;
			shim.style.filter='progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)';
			shim.style.display = 'block';
			eltCEListPanel.onDrag = function(x,y) {
				shim.style.left = eltCEListPanel.offsetLeft;
				shim.style.top = eltCEListPanel.offsetTop;
			}
		}			

		// Adding event listeners
		me.addEvent( eltLink, 'click', function(e) {eltList.parentNode.removeChild(eltList);}, false );
		sortTable(eltTable, 2, true, 0);
		me.addEvent(document.getElementById('ce-col-th-label'), 'click', function(e) {sortTable(eltTable, 0)}, false);
		me.addEvent(document.getElementById('ce-col-th-region'), 'click', function(e) {sortTable(eltTable, 1, false, 2)}, false);
		me.addEvent(document.getElementById('ce-col-th-clicks'), 'click', function(e) {sortTable(eltTable, 2, true, 0)}, false);
		
		var loadingDialog = document.getElementById('LT-loading-dialog');
		me.loadingDialog = document.body.removeChild(loadingDialog);
	}
	
	me.keyCheck = function (e) {
			
		if (!me.isOnSecureDomain) return false;
		if (!document.getElementById) return false;
		
		// check to see if user press keys
		var keyID = (window.event) ? event.keyCode : e.keyCode;
		var ctrlKey = (window.event) ? event.ctrlKey : e.ctrlKey;
		
		if((keyID == 88)&&(ctrlKey == true)) { 
			
			// 'ctrl x' clicked
			
			eltLabels = me.getElementsByClassName('linklabel');
			if( eltLabels.length == 0 ) {
				me.getClickThroughInfo();
			}else{
				for (var i = 0; i < eltLabels.length; i++) {
					eltLabels[i].parentNode.removeChild(eltLabels[i]);
				}
			}
		}
		
		if((keyID == 90)&&(ctrlKey == true)) { 
			
			// 'ctrl z' clicked
			
			eltLinkList = document.getElementById('ce-master-list-panel');
			if (eltLinkList) {
				eltLinkList.parentNode.removeChild(eltLinkList);
			} else {
				me.getClickThroughListInfo();
			}
		}
	}
	
	me.getInnerText = function( node, text ) {
		// returns the text of any element node
		var text = text || '';

		for (var i = 0; i < node.childNodes.length; i++) {
		
			if( node.childNodes[i].nodeType == 3 ) {
				text += node.childNodes[i].nodeValue;
			}	
			if( node.childNodes[i].nodeType == 1 ) {//alert(node.childNodes[i].outerHTML);
				if (node.childNodes[i].tagName == 'IMG' && node.childNodes[i].getAttribute('alt') ) {
					text += node.childNodes[i].getAttribute('alt');
				} else {
					text = me.getInnerText( node.childNodes[i], text);
				}
			}
		}
		return text;
	}
	
	me.getFormTarget = function( elt ) {
		// returns the form action attribute from 
		// if given the child node of that form
		target = null;
		parentElt = elt.parentNode;
		if( parentElt.nodeType == 1 ) {
			if( parentElt.tagName == 'FORM' ) {
				target = parentElt.getAttribute('action');
			}else {
				target = me.getFormTarget( elt.parentNode );
			}
		}else {
			target = me.getFormTarget( elt.parentNode );
		}
		return target;
	}
	
	me.addEvent = function( elm, evType, fn, useCapture ) {
		// Updated version which captures passed events 
		if (elm.AddEventListener) 
		{ 
			elm.AddEventListener(evType, fn, useCapture); 
			return true; 
		} else if (elm.attachEvent) { 
			var r = elm.attachEvent('on' + evType, fn);
			me.listeners[me.listeners.length] = [ elm, evType, fn ];
			return r; 
		} else {
			var xEventFn = elm['on' + evType];
			if (typeof elm['on' + evType] != 'function') 
			{
				elm['on' + evType] = fn;
			} else {
				elm['on' + evType] = function(e) { xEventFn(e); fn(e); };
			}
		}
	}
	
	me.unload = function(){
		// page unload event which removes circular references
		// that may cause memory leaks in IE 5/6
		if( window.attachEvent ){
			for (var i = 0; i < me.listeners.length; i++) {
				me.listeners[i][0].detachEvent( 'on' + me.listeners[i][1], me.listeners[i][2] );
			}
		}
	}
	
	me.getElementsByClassName = function( className ) {
		// returns a collection of element nodes which 
		// have the passed className
		var children = document.getElementsByTagName('*') || document.all;
		var elements = new Array();
		for (var i = 0; i < children.length; i++) 
		{
			var child = children[i];
			var classNames = child.className.split(' ');
			for (var j = 0; j < classNames.length; j++) 
			{
				if (classNames[j] == className) 
				{
					elements.push(child);
					break;
				}
			}
		}
		return elements;
	}
	
	me.findSourceElement = function(e) {
		// finds event source
		if (typeof e == 'undefined')
			var e = window.event;

		var source;
		if (typeof e.currentTarget != 'undefined') {
			source = e.currentTarget;
		} /*else if (typeof e.target != 'undefined') {
			source = e.target;
		}*/ else if (typeof e.srcElement != 'undefined') {
			source = e.srcElement;
			// ugly ack for IE: crawls through the ancestor axis (2 levels) to find the currentTarget
			if ( getEventModel() == 'IE' && source.tagName != 'A') {
				if ( source.parentNode.tagName == 'A') {
					if ( source.parentNode.tagName == 'A' ) {
						source = source.parentNode;
					} else {
						if (source.parentNode.parentNode.tagName == 'A') {
							source = source.parentNode.parentNode;
						}
					}
				}
			}					
		} else {
			return true;
		}

		if (source.nodeType == 3)
			source = source.parentNode;
		
		return source;
	}
	
	me.getPageOffsetLeft = function(elt) {
		var x;
		x = elt.offsetLeft;
		if (elt.offsetParent != null)
			x += me.getPageOffsetLeft(elt.offsetParent);
		return x;
	}
	
	me.getPageOffsetTop = function(elt) {
		var y;
		y = elt.offsetTop;
		if (elt.offsetParent != null)
			y += me.getPageOffsetTop(elt.offsetParent);
		return y;
	}

	me.getRegion = function(id) {
		if (me.degub) { DebugConsole.write("Evaluating the page region where is the current link (" + id + ")"); }

		var r = me.regions;

		for (i=0; i<r.length; i++) {
			if (me.degub) { DebugConsole.write("  ... trying " + me.regions[i][0]) };
			if ( id.match( r[i][1] ) ) {
			if (me.degub) { DebugConsole.write("  Found : " + me.regions[i][0]) };
				return r[i][0];
			}
		}
		return ""

	}
	
	me.addStyle = function(URL, encoding) {
		l = document.createElement('link');
		l.setAttribute('rel','stylesheet');
		l.setAttribute('href',URL);
		l.setAttribute('type','text/css');
		l.setAttribute('charset',encoding);
		document.getElementsByTagName('HEAD')[0].appendChild(l);
		
		if (me.degub) { DebugConsole.write("Added link to stylesheet : " + URL); }
	}
	
	//------------------------------------------


	// dom-drag.js
	// 09.25.2001
	// http://boring.youngpup.net/2001/domdrag/
	var Drag = {

		obj : null,

		init : function(o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper)
		{
			o.onmousedown = Drag.start;
			o.hmode = bSwapHorzRef ? false : true ;
			o.vmode = bSwapVertRef ? false : true ;

			o.root = oRoot && oRoot != null ? oRoot : o ;

			if (o.hmode  && isNaN(parseInt(o.root.style.left  ))) o.root.style.left   = '0px';
			if (o.vmode  && isNaN(parseInt(o.root.style.top   ))) o.root.style.top    = '0px';
			if (!o.hmode && isNaN(parseInt(o.root.style.right ))) o.root.style.right  = '0px';
			if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) o.root.style.bottom = '0px';

			o.minX = typeof minX != 'undefined' ? minX : null;
			o.minY = typeof minY != 'undefined' ? minY : null;
			o.maxX = typeof maxX != 'undefined' ? maxX : null;
			o.maxY = typeof maxY != 'undefined' ? maxY : null;

			o.xMapper = fXMapper ? fXMapper : null;
			o.yMapper = fYMapper ? fYMapper : null;

			o.root.onDragStart = new Function();
			o.root.onDragEnd = new Function();
			o.root.onDrag = new Function();
		},

		start : function(e)
		{
			
			var o = Drag.obj = this;
			e = Drag.fixE(e);
			var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
			var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
			o.root.onDragStart(x, y);

			o.lastMouseX = e.clientX;
			o.lastMouseY = e.clientY;

			if (o.hmode) {
				if (o.minX != null) o.minMouseX = e.clientX - x + o.minX;
				if (o.maxX != null) o.maxMouseX = o.minMouseX + o.maxX - o.minX;
			} else {
				if (o.minX != null) o.maxMouseX = -o.minX + e.clientX + x;
				if (o.maxX != null) o.minMouseX = -o.maxX + e.clientX + x;
			}

			if (o.vmode) {
				if (o.minY != null) o.minMouseY = e.clientY - y + o.minY;
				if (o.maxY != null) o.maxMouseY = o.minMouseY + o.maxY - o.minY;
			} else {
				if (o.minY != null) o.maxMouseY = -o.minY + e.clientY + y;
				if (o.maxY != null) o.minMouseY = -o.maxY + e.clientY + y;
			}

			document.onmousemove = Drag.drag;
			document.onmouseup = Drag.end;

			return false;
		},

		drag : function(e)
		{
			e = Drag.fixE(e);
			var o = Drag.obj;

			var ey = e.clientY;
			var ex = e.clientX;
			var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
			var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
			var nx, ny;

			if (o.minX != null) ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX);
			if (o.maxX != null) ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX);
			if (o.minY != null) ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY);
			if (o.maxY != null) ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY);

			nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
			ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));

			if (o.xMapper) nx = o.xMapper(y)
			else if (o.yMapper) ny = o.yMapper(x)

			Drag.obj.root.style[o.hmode ? 'left' : 'right'] = nx + 'px';
			Drag.obj.root.style[o.vmode ? 'top' : 'bottom'] = ny + 'px';
			Drag.obj.lastMouseX = ex;
			Drag.obj.lastMouseY = ey;

			Drag.obj.root.onDrag(nx, ny);
			return false;
		},

		end : function()
		{
			document.onmousemove = null;
			document.onmouseup   = null;
			Drag.obj.root.onDragEnd(    parseInt(Drag.obj.root.style[Drag.obj.hmode ? 'left' : 'right']), 
										parseInt(Drag.obj.root.style[Drag.obj.vmode ? 'top' : 'bottom']));
			Drag.obj = null;
		},

		fixE : function(e)
		{
			if (typeof e == 'undefined') e = window.event;
			if (typeof e.layerX == 'undefined') e.layerX = e.offsetX;
			if (typeof e.layerY == 'undefined') e.layerY = e.offsetY;
			return e;
		}
	};
	
	me.addEvent( window, 'load', me.addLinkTracking, false );
	me.addEvent( document, 'keydown', me.keyCheck, false );
	me.addEvent( window, 'unload', me.unload, false );
}

// disable link tracking for IE/Mac

var ajaxLinkTracker = (browser.isMac && browser.isIE) ? false : new AjaxLinkTracker();

function XHRConnection() { 
    var me;
    if (this.constructor == XHRConnection){
        me = this
    }else{
        me = arguments[arguments.length-1]
    }
    
	me.Request = me.createXHR();
	 
    me.handler = function () {
    	if (me.Request.readyState == 1) {
    		me.processLoadingState();
    	}
		if (me.Request.readyState == 4) {
			if (me.Request.status == 200) {
		
				me.processResponse();
			}
			else {
				me.processResponseError();
			}
		}
	}
	
	me.send = function ( url, action, fnOK, fnNotOK ) {
	    me.URL = url;
		me.Action = action;
		me.fnOK = fnOK;
		me.fnNotOK = (fnNotOK) ? fnNotOk : new Function();
		if( me.Request != null ){
			me.Request.open(me.Action, me.URL, true);
			me.Request.onreadystatechange = me.handler;
			me.Request.send(null);
		}else{
			alert('Could not load XHR object');
		}
	}
}

XHRConnection.prototype.createXHR = function() {
    try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {}
    try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {}
    try { return new XMLHttpRequest(); } catch(e) {}
    return null;
}

XHRConnection.prototype.processResponse = function () {
	this.fnOK(this.Request);
}

XHRConnection.prototype.processResponseError = function () {
	this.fnNotOK(this.Request);
}

XHRConnection.prototype.processLoadingState = function () {
}

/*	=======================================================================
	sorttable.js
	=======================================================================
*/

/*
	Source: http://www.kryogenix.org/code/browser/sorttable/

	CREDITS:
		this script is the combination and mutation of two other sort table scripts
		i found on the internet and would not be here if it were not for the work of others
		so to give credit where it is due:

		kyrogenix.org: Stuart Landridge
		http://www.kryogenix.org/days/2003/11/04/sortable
			-sortables_init
			-ts_makeSortable
			-ts_resortTable
			-getParent

		scottandrew.com: Scott Andrew
		http://www.scottandrew.com/weblog/articles/cbs-events
			-addEvent


		brainjar.com: Mike Hall
		http://www.brainjar.com/dhtml/tablesort/
			-sortTable
			-compareValues
			-getTextValue
			-normalizeString
			-makePretty
			-setRanks

		rawlinson.us: Bill Rawlinson
		http://www.rawlinson.us
			- combination of all the above with some changes as noted below

		You can have this script, use it to your hearts content and you can delete all the comments
		from this paragraph and below - just make sure you leave the credit in for the first three guys
		since they did the really hard work and I just stuck it together.


		the sorttable function as well as make pretty and rank functions are basically untouched except
		for an improvement to the sorttable function which allows for greater accuracy in the search
		as it looks at each cell in order from left to right in a row instead of just a secondary cell
		for ordering matches in the cell to be sorted.  I did add a couple of extra parameters to the sorttable
		function that let you set some things into motion optionally.


		Basically Mike Halls sorttable works pretty well, however, it had two shortcomings:
		1. the first time I tried to sort a column it was kind of slow and unresponsive - i think because
		of the makepretty call.
		2. you had to insert javascript code into each column of every table you wanted sortable - and I prefer a more
		unobtrusive DHTML (an idea i first learned about at http://youngpup.net/Mastah.aspx?year=2001&filename=labels)
		experience personally.

		so what goes on here?

		1. sorttables_init finds all the tables with the class of sortable and a unique ID (important to have both!)
			a. takes the first row of each of the sortable tables on the page and turns it into a header row.
			b. in the header row each cell that is of class sortColumn is then fixed with an onclick event that calls the ts_resortTable script
			c. then calls the makepretty function to add "zebra-stripes" to the table and passes in an invalid column id so no column is noted
				as sorted by default.
		2. ts_resortTable figures out what column was clicked on by looking at the DOM with a starting point at the link that was clicked
		3. ts_resortTable then calls the sortTable script which actually does all the sorting
		4. when sortTable is complete it calls makepretty again to fix up the zebra-stripes and voila, you have a nicely ordered, pretty table
		5. whats more, your page has ZERO javascript on it.  just nice structurally sound markup! pretty cool eh?


		some caveats:
		this stuff expects good valid html. what does this mean?
			A. your table needs an id value - and that id really needs to be unique.
			B. your table needs to be structurally sound 
			c. if you want makepretty to work you need some classes defined:
				alternateRow
				sortedColumn
			d. remember to give your table the class name sortable and each column you want sortable the classname sortColumn


*/

//............................................................................//
//
//uncomment to make this load properly:
//			
// if you need other functions to happen on window load make a new function called page_init
// put all of your functions in page_init and then call addEvent(window, "load", page_init);
//
//
if ( !(browser.isMac && browser.isIE)) {
	addEvent(window, "load", sortables_init);
}
//
//............................................................................//


var gblReverseSort		= 1;
var gblShowRanks		= 1;
var gblDefaultColumn	= -1;
var gblMakePretty		= false;
var gblCaseSensitive	= false;

function sortables_init() {
    // Find all tables with class sortable and make them sortable
    if (!document.getElementsByTagName) return;
    tbls = document.getElementsByTagName("table");
    for (ti=0;ti<tbls.length;ti++) {
        thisTbl = tbls[ti];
        if (((' '+thisTbl.className+' ').indexOf("sortable") != -1) && (thisTbl.id)) {
            ts_makeSortable(thisTbl);
		} else {
			if(gblMakePretty){
				makePretty(thisTbl.getElementsByTagName('tbody')[0],-1,gblDefaultColumn);
			}
		}
    }
}

function ts_makeSortable(table) {
	var tblEl = table.getElementsByTagName('tbody')[0];
	if(gblMakePretty){
		makePretty(tblEl,-1,gblDefaultColumn);
	}

	if (table.rows && table.rows.length > 0) {
        var firstRow = table.rows[0];
    }
    if (!firstRow) return;
    // We have a first row: assume it's the header, and make its contents clickable links
    for (var i=0;i<firstRow.cells.length;i++) {
        var cell = firstRow.cells[i];
		if(cell.className == 'sortColumn'){
			var txt = getTextValue(cell);
			var linkURL = window.location;
			if(cell.title.length)
				linkURL = linkURL + '&amp;sortby='+cell.title;
			else
				linkURL = linkURL + '#';
			cell.innerHTML = '<a href="'+linkURL+'" class="sortheader" onclick="ts_resortTable(this);return false;">'+txt+'<span class="sortarrow">&nbsp;</span></a>';
		}
    }

}

function ts_resortTable(lnk) {
    // get the span
    var span;
    for (var ci=0;ci<lnk.childNodes.length;ci++) {
        if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
    }
    var spantext = getTextValue(span);
    var td = lnk.parentNode;
    var column = td.cellIndex;
    var table = getParent(td,'TABLE');
    
	ARROW = '';
	if (span.getAttribute("sortdir") == 'down') {
        ARROW = '&uarr;';
        //newRows.reverse();
        span.setAttribute('sortdir','up');
    } else {
        ARROW = '&darr;';
        span.setAttribute('sortdir','down');
    }
    // Delete any other arrows there may be showing
    var allspans = document.getElementsByTagName("span");
    for (var ci=0;ci<allspans.length;ci++) {
        if (allspans[ci].className == 'sortarrow') {
            if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
                allspans[ci].innerHTML = '&nbsp;';
            }
        }
    }
    span.innerHTML = ARROW;
	sortTable(table, column, gblReverseSort, gblDefaultColumn, gblShowRanks, gblMakePretty);
}

function getParent(el, pTagName) {
	if (el == null) return null;
	else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase())	// Gecko bug, supposed to be uppercase
		return el;
	else
		return getParent(el.parentNode, pTagName);
}

function addEvent(elm, evType, fn, useCapture)
// addEvent and removeEvent
// cross-browser event handling for IE5+,  NS6 and Mozilla
// By Scott Andrew
{
  if (elm.addEventListener){
    elm.addEventListener(evType, fn, useCapture);
    return true;
  } else if (elm.attachEvent){
    var r = elm.attachEvent("on"+evType, fn);
    return r;
  } else {
    alert("Handler could not be removed");
  }
} 

//-----------------------------------------------------------------------------
// sortTable(id, col, rev, nmc, rank)
//
//  id  - ID of the TABLE, TBODY, THEAD or TFOOT element to be sorted.
//  col - Index of the column to sort, 0 = first column, 1 = second column,
//        etc.
//  rev - If true, the column is sorted in reverse (descending) order
//        initially.
//  nmc - Index of the "name" column.  0=first column, 1=second column, etc...
//	rnk - If true, the first column (0) is a rank column and we need to calculate
//		  the rows overall ranking for the current sort column.
//
// Note: the name column (index 0 or 1) is used as a secondary sort column and
// always sorted in ascending order. If rank col exists then index is 1 else 0.
//-----------------------------------------------------------------------------

function sortTable(table, col, rev, nmc, rnk, mp) {
  // Get the table or table section to sort.
   //var tblEl = document.getElementById(id);
	var tblEl = table.getElementsByTagName('tbody')[0];
	

  // The first time this function is called for a given table, set up an
  // array of reverse sort flags.
  if (tblEl.reverseSort == null) {
    tblEl.reverseSort = new Array();
    // Also, assume the team name column is initially sorted.
    tblEl.lastColumn = 1;
  }

  // If this column has not been sorted before, set the initial sort direction.
  if (tblEl.reverseSort[col] == null)
    tblEl.reverseSort[col] = rev;

  // If this column was the last one sorted, reverse its sort direction.
  if (col == tblEl.lastColumn){
    tblEl.reverseSort[col] = !tblEl.reverseSort[col];
  }

  // Remember this column as the last one sorted.
  tblEl.lastColumn = col;

  // Set the table display style to "none" - necessary for Netscape 6 
  // browsers.
  var oldDsply = tblEl.style.display;
  tblEl.style.display = "none";

  // Sort the rows based on the content of the specified column using a
  // selection sort.

  var tmpEl;
  var i, j;
  var minVal, minIdx;
  var testVal;
  var cmp;
  for (i = 0; i < tblEl.rows.length - 1; i++) {
    // Assume the current row has the minimum value.
    minIdx = i;
    minVal = getTextValue(tblEl.rows[i].cells[col]);

    // Search the rows that follow the current one for a smaller value.
    for (j = i+1; j < tblEl.rows.length; j++) {
      testVal = getTextValue(tblEl.rows[j].cells[col]);
      cmp = compareValues(minVal, testVal);
      // Negate the comparison result if the reverse sort flag is set.
      if (tblEl.reverseSort[col])
        cmp = -cmp;
      // Sort by the each consecutive column until we find one that isnt equal or we run out of columns
	  if (cmp == 0 && col != nmc){
		for(var coli = 0; coli < tblEl.rows[j].cells.length; coli++){
			if (coli != col)
			{
				cmp = compareValues(getTextValue(tblEl.rows[minIdx].cells[coli]),
                    getTextValue(tblEl.rows[j].cells[coli]));
				
				if(cmp!=0)
					break;
			}
		}
		
	  }
      // If this row has a smaller value than the current minimum, remember its
      // position and update the current minimum value.
      if (cmp > 0) {
        minIdx = j;
        minVal = testVal;
      }
    }

    // By now, we have the row with the smallest value. Remove it from the
    // table and insert it before the current row.
    if (minIdx > i) {
      tmpEl = tblEl.removeChild(tblEl.rows[minIdx]);
      tblEl.insertBefore(tmpEl, tblEl.rows[i]);
    }
  }

  // Make it look pretty.
  if(mp){
  makePretty(tblEl, col, nmc);
  }

  // set rankings
  if(rnk){
  setRanks(tblEl, col, rev);
  }

  // Restore the table's display style.
  tblEl.style.display = oldDsply;

  return false;
}

//-----------------------------------------------------------------------------
// Functions to get and compare values during a sort.
//-----------------------------------------------------------------------------

// This code is necessary for browsers that don't reflect the DOM constants
// (like IE).
if (document.ELEMENT_NODE == null) {
  document.ELEMENT_NODE = 1;
  document.TEXT_NODE = 3;
}

function getTextValue(el) {
  var i;
  var s;
  // Find and concatenate the values of all text nodes contained within the
  // element.
  s = "";
  try{
	  for (i = 0; i < el.childNodes.length; i++)
		if (el.childNodes[i].nodeType == document.TEXT_NODE)
		  s += el.childNodes[i].nodeValue;
		else if (el.childNodes[i].nodeType == document.ELEMENT_NODE &&
				 el.childNodes[i].tagName == "BR")
		  s += " ";
		else
		  // Use recursion to get text within sub-elements.
		  s += getTextValue(el.childNodes[i]);
  }catch(err){}
  return normalizeString(s);
}

function compareValues(p1, p2) {
  var v1, v2;
  var f1, f2;

  var compVal = -1;

  v1=p1;
  v2=p2;

  // If the values are dates, convert them to date objects.
  f1 = new Date(v1);
  f2 = new Date(v2);
  if (!isNaN(f1) && !isNaN(f2)) {
    v1 = f1;
    v2 = f2;
  }

  // If the values are floats, convert them to float objects.
  if(v1==p1){
	  f1 = parseFloat(v1);
	  f2 = parseFloat(v2);
	  if (!isNaN(f1) && !isNaN(f2)) {
		v1 = f1;
		v2 = f2;
	  }
  }
  
  // If the values are currency, convert them to float objects.
  if(v1==p1){
	  f1 = parseCurrency(v1);
	  f2 = parseCurrency(v2);
	  if (!isNaN(f1) && !isNaN(f2)) {
		v1 = f1;
		v2 = f2;
	  }
  }

  // Compare the two values.
  if (v1 == v2)
	compVal = 0;
  if (v1 > v2)
	compVal = 1

	return compVal;
}

function parseCurrency(vS){
	// returns a currency string back as a float
	var currency = vS;
	if(isValid(vS,'Currency')){
		var cleanRegex = '[^0-9\.]';
		var reClean = new RegExp(cleanRegex,'gi');
		currency = vS.toString().replace(reClean,"");
	}
	currency = parseFloat(currency);

	return currency;
}

var valid = new Object();
	// REGEX Elements
	// matches zip codes
	valid.zipCode = /\d{5}(-\d{4})?/;
	// matches $17.23 or $14,281,545.45 or ...
	valid.Currency = /\$\d{1,3}(,\d{3})*\.\d{2}/;
	// matches 5:04 or 12:34 but not 75:83
	valid.Time = /^([1-9]|1[0-2]):[0-5]\d$/;
	//matches email
	valid.emailAddress = /^.+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,3}|[0-9]{1,3})(\]?)$/;
	// matches phone ###-###-####
	valid.phoneNumber = /^\(?\d{3}\)?\s|-\d{3}-\d{4}$/;
	// International Phone Number
	valid.phoneNumberInternational = /^\d(\d|-){7,20}/;
	// IP Address
	valid.ipAddress = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
	// Date xx/xx/xxxx
	valid.Date = /^\d{1,2}(\-|\/|\.)\d{1,2}\1\d{4}$/;
	// State Abbreviation
	valid.State = /^(AK|AL|AR|AZ|CA|CO|CT|DC|DE|FL|GA|HI|IA|ID|IL|IN|KS|KY|LA|MA|MD|ME|MI|MN|MO|MS|MT|NB|NC|ND|NH|NJ|NM|NV|NY|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VA|VT|WA|WI|WV|WY)$/i;
	// Social Security Number
	valid.SSN = /^\d{3}\-\d{2}\-\d{4}$/;

function isValid(vString,vType){
	var thePat = valid[vType]; 
	return thePat.exec(vString); 
}
// Regular expressions for normalizing white space.
var whtSpEnds = new RegExp("^\\s*|\\s*$", "g");
var whtSpMult = new RegExp("\\s\\s+", "g");

function normalizeString(s) {

  s = s.replace(whtSpMult, " ");  // Collapse any multiple whites space.
  s = s.replace(whtSpEnds, "");   // Remove leading or trailing white space.
  if (!gblCaseSensitive) { s = s.toLowerCase(); }

  return s;
}

//-----------------------------------------------------------------------------
// Functions to update the table appearance after a sort.
//-----------------------------------------------------------------------------

// Style class names.
var rowClsNm = "alternateRow";
var colClsNm = "sortedColumn";

// Regular expressions for setting class names.
var rowTest = new RegExp(rowClsNm, "gi");
var colTest = new RegExp(colClsNm, "gi");

function makePretty(tblEl, col, nmc) {

  var i, j, namecol;
  var rowEl, cellEl;

  
	
  // Set style classes on each row to alternate their appearance.
  for (i = 0; i < tblEl.rows.length; i++) {
   rowEl = tblEl.rows[i];
   rowEl.className = rowEl.className.replace(rowTest, "");
    if (i % 2 != 0)
      rowEl.className += " " + rowClsNm;
    rowEl.className = normalizeString(rowEl.className);
    // Set style classes on each column (other than the name column) to
    // highlight the one that was sorted.
    for (j = 0; j < tblEl.rows[i].cells.length; j++) {
		  cellEl = rowEl.cells[j];
		  cellEl.className = cellEl.className.replace(colTest, "");
		  if (j == col)
			cellEl.className += " " + colClsNm;
		  cellEl.className = normalizeString(cellEl.className);
	  }
  }

  // Find the table header and highlight the column that was sorted.
  var el = tblEl.parentNode.tHead;
  if(el){
	  rowEl = el.rows[el.rows.length - 1];
	  // Set style classes for each column as above.
	  for (i = 0; i < rowEl.cells.length; i++) {
		cellEl = rowEl.cells[i];
		cellEl.className = cellEl.className.replace(colTest, "");
		// Highlight the header of the sorted column.
		/*if (i == col)
		  cellEl.className += " " + colClsNm;
		  cellEl.className = normalizeString(cellEl.className);
		  */
	  }
  }
}

function setRanks(tblEl, col, rev) {

  // Determine whether to start at the top row of the table and go down or
  // at the bottom row and work up. This is based on the current sort
  // direction of the column and its reversed flag.

  var i    = 0;
  var incr = 1;
  if (tblEl.reverseSort[col])
    rev = !rev;
  if (rev) {
    incr = -1;
    i = tblEl.rows.length - 1;
  }

  // Now go through each row in that direction and assign it a rank by
  // counting 1, 2, 3...

  var count   = 1;
  var rank    = count;
  var curVal;
  var lastVal = null;

  while (col > 0 && i >= 0 && i < tblEl.rows.length) {

    // Get the value of the sort column in this row.
    curVal = getTextValue(tblEl.rows[i].cells[col]);

    // On rows after the first, compare the sort value of this row to the
    // previous one. If they differ, update the rank to match the current row
    // count. (If they are the same, this row will get the same rank as the
    // previous one.)
    if (lastVal != null && compareValues(curVal, lastVal) != 0)
        rank = count;
    // Set the rank for this row.
    tblEl.rows[i].rank = rank;

    // Save the sort value of the current row for the next time around and bump
    // the row counter and index.
    lastVal = curVal;
    count++;
    i += incr;
  }

  // Now go through each row (from top to bottom) and display its rank. Note
  // that when two or more rows are tied, the rank is shown on the first of
  // those rows only.

  var rowEl, cellEl;
  var lastRank = 0;

  // Go through the rows from top to bottom.
  for (i = 0; i < tblEl.rows.length; i++) {
    rowEl = tblEl.rows[i];
    cellEl = rowEl.cells[0];
    // Delete anything currently in the rank column.
    while (cellEl.lastChild != null)
      cellEl.removeChild(cellEl.lastChild);
    // If this row's rank is different from the previous one, Insert a new text
    // node with that rank.
    if (col > 0 && rowEl.rank != lastRank) {
      cellEl.appendChild(document.createTextNode(rowEl.rank));
      lastRank = rowEl.rank;
    }

  }
}

//-----------------------------------------------------------------------------
// Functions to generate a permanent unique ID based on the link's URL and text.
//-----------------------------------------------------------------------------

function createID(URL , text) {

	return Crc16Str(URL) + '-' + Crc16Str(text);
}

/*
  CRC-16 (as it is in ZMODEM) in table form

  Copyright (c) 1989 AnDan Software. You may use this program, or
  code or tables extracted from it, as long as this notice is not
  removed or changed.
*/
var Crc16Tab = new Array(
0x0000,0x1021,0x2042,0x3063,0x4084,0x50A5,0x60C6,0x70E7,0x8108,0x9129,0xA14A,0xB16B,0xC18C,
0xD1AD,0xE1CE,0xF1EF,0x1231,0x0210,0x3273,0x2252,0x52B5,0x4294,0x72F7,0x62D6,0x9339,0x8318,
0xB37B,0xA35A,0xD3BD,0xC39C,0xF3FF,0xE3DE,0x2462,0x3443,0x0420,0x1401,0x64E6,0x74C7,0x44A4,
0x5485,0xA56A,0xB54B,0x8528,0x9509,0xE5EE,0xF5CF,0xC5AC,0xD58D,0x3653,0x2672,0x1611,0x0630,
0x76D7,0x66F6,0x5695,0x46B4,0xB75B,0xA77A,0x9719,0x8738,0xF7DF,0xE7FE,0xD79D,0xC7BC,0x48C4,
0x58E5,0x6886,0x78A7,0x0840,0x1861,0x2802,0x3823,0xC9CC,0xD9ED,0xE98E,0xF9AF,0x8948,0x9969,
0xA90A,0xB92B,0x5AF5,0x4AD4,0x7AB7,0x6A96,0x1A71,0x0A50,0x3A33,0x2A12,0xDBFD,0xCBDC,0xFBBF,
0xEB9E,0x9B79,0x8B58,0xBB3B,0xAB1A,0x6CA6,0x7C87,0x4CE4,0x5CC5,0x2C22,0x3C03,0x0C60,0x1C41,
0xEDAE,0xFD8F,0xCDEC,0xDDCD,0xAD2A,0xBD0B,0x8D68,0x9D49,0x7E97,0x6EB6,0x5ED5,0x4EF4,0x3E13,
0x2E32,0x1E51,0x0E70,0xFF9F,0xEFBE,0xDFDD,0xCFFC,0xBF1B,0xAF3A,0x9F59,0x8F78,0x9188,0x81A9,
0xB1CA,0xA1EB,0xD10C,0xC12D,0xF14E,0xE16F,0x1080,0x00A1,0x30C2,0x20E3,0x5004,0x4025,0x7046,
0x6067,0x83B9,0x9398,0xA3FB,0xB3DA,0xC33D,0xD31C,0xE37F,0xF35E,0x02B1,0x1290,0x22F3,0x32D2,
0x4235,0x5214,0x6277,0x7256,0xB5EA,0xA5CB,0x95A8,0x8589,0xF56E,0xE54F,0xD52C,0xC50D,0x34E2,
0x24C3,0x14A0,0x0481,0x7466,0x6447,0x5424,0x4405,0xA7DB,0xB7FA,0x8799,0x97B8,0xE75F,0xF77E,
0xC71D,0xD73C,0x26D3,0x36F2,0x0691,0x16B0,0x6657,0x7676,0x4615,0x5634,0xD94C,0xC96D,0xF90E,
0xE92F,0x99C8,0x89E9,0xB98A,0xA9AB,0x5844,0x4865,0x7806,0x6827,0x18C0,0x08E1,0x3882,0x28A3,
0xCB7D,0xDB5C,0xEB3F,0xFB1E,0x8BF9,0x9BD8,0xABBB,0xBB9A,0x4A75,0x5A54,0x6A37,0x7A16,0x0AF1,
0x1AD0,0x2AB3,0x3A92,0xFD2E,0xED0F,0xDD6C,0xCD4D,0xBDAA,0xAD8B,0x9DE8,0x8DC9,0x7C26,0x6C07,
0x5C64,0x4C45,0x3CA2,0x2C83,0x1CE0,0x0CC1,0xEF1F,0xFF3E,0xCF5D,0xDF7C,0xAF9B,0xBFBA,0x8FD9,
0x9FF8,0x6E17,0x7E36,0x4E55,0x5E74,0x2E93,0x3EB2,0x0ED1,0x1EF0);

function Crc16Add(crc,c)
/*
  'crc' should be initialized to 0x0000.
*/
{
  return Crc16Tab[((crc>>8)^c)&0xFF]^((crc<<8)&0xFFFF);
}

function Crc16Str(str)
{
  var n;
  var len=str.length;
  var crc;

  crc=0;
  for (n=0; n<len; n++)
  {
    crc=Crc16Add(crc,str.charCodeAt(n));
  }
  return crc;
}

function makeBreakable(s) {

	if (s.length > 48) {
		s = s.substr(0,45) + '<span class="ce-ellipsis-mark">...</span><span class="ce-ellipsis-trailing">' + s.substr(45) + '</span>';
	}
	
	return s	
}


