if (!d3) throw( "d3 object has not been instantiated! d3.base.js should precede this script." );
if (typeof( d3.browser ) != "undefined") throw( "d3.browser functionality has already been included!  review script order." );

d3.Validator = { 
	_about : "d3 forms validation object, v1.0.001",
	_errors : [],
	patterns : []
}

d3.Validator.patterns[ "#simple-text"] = { mask: /^[\w\,\.\s\'\-\&]*$/i , msg : "can only contain\nletters, digits, and these characters: , . ' - &" };
d3.Validator.patterns[ "#ssn" ] = { mask: /^\d{3}\-d{2}\-d{4}/ , msg : "must be in 999-99-9999 format" };
d3.Validator.patterns[ "#phone" ] = { mask: /^\({0,1}\d{3}[\)\-\s]{1}\d{3}\-\d{4}/ , msg : "must be in (999)999-9999 format" };
d3.Validator.patterns[ "#email" ] = { mask: /^[a-zA-Z0-9._+&*# -]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*$/ , msg : "must be in \nxxxx@xxxxx.xxx format."	};
d3.Validator.patterns[ "#integer" ] = { mask: /^\d+/, msg : "must be a whole number" };
d3.Validator.patterns[ "#money" ] = { mask: /^\$*[\d\,]+(\.\d{0,2}){0,1}/, msg : "must be in $9,999.99 format." };
d3.Validator.patterns[ "#decimal" ] = { mask: /^\d+(\.\d*)*/, msg : "must be in 9999.999 format." };

d3.Validator.bindAll = function()
{
	for (var forms = new d3.Enumerator( document.forms ); !forms.atEnd(); forms.moveNext())
		d3.Validator.bind( forms.item() );
}

d3.Validator.bind = function ( form )
{
	form = $(form);
	LOG$( typeof(form) );
	if (!form) return;
	LOG$( "validator.bind for "+ d3.ifNot( form.id, form.name )  );
	var formInfo = Object.fromDomNode( form );
	for (var elements = new d3.Enumerator(form.elements); !elements.atEnd(); elements.moveNext())
	{
		var el = elements.item();
		var info = Object.fromDomNode( el );
		var s = d3.ifNot( el.type, el.tagName ).toLowerCase();
		var eventName = "onchange";
		var blur = true;
		switch (s)
		{
			case "password": case "text": case "textarea": case "select-multiple": case "select-one":
				eventName = "onchange"; blur = true; 
				break;
			case "checkbox": 
				eventName = "onclick"; 
				break;
			case "radio":
				eventName = "onclick"; blur = false; 
				break;
			default:
				eventName = ""; blur = false; 
				break;
		}

		if (blur) 
		{
			LOG$( "adding onblur/onfocus to "+ d3.ifNot( el.id, el.name ) );
			addEvent( el, "blur", d3.Validator.blur );
			addEvent( el, "focus", d3.Validator.focus );
		}
		eventName = eventName.substring( 2 );
		if (eventName != "")
		{
			LOG$( "adding on"+ eventName + " to "+ d3.ifNot( el.id, el.name ) );
			addEvent( el, eventName, d3.Validator.validateItem );
		}
	}
	addEvent( form, "submit", d3.Validator.validateAll );
}

d3.Validator.blur = function( evt )
{
	d3.html.setClass( d3.events.getTarget( evt ), "active", false );
}

d3.Validator.focus = function( evt )
{
	d3.html.setClass( d3.events.getTarget( evt ), "active", true );
}

d3.Validator.validateItem = function ( evt )
{
	LOG$( "in validateItem " + typeof(evt) );
	var target = d3.events.getTarget( evt );
	if (!target)
	{
		target = evt;
		evt = null;
	}
	DEBUG$( "validating "+ d3.ifNot( target.id, target.name ) );
	var info = Object.fromDomNode( target );
	var name = d3.ifNot( info["d3:name"], d3.ifNot( target.name, target.id ) );
	var msg = info["d3:error-text"];
	var MSG_PREFIX = "Value for '"+ name + "' ";
	if (d3.ifNot(info["d3:trim"],"true") == "true") target.value = target.value.trim();
	if (target.value == "" && info["d3:required"])
	{
		msg = d3.ifNot( msg, MSG_PREFIX + "is required" );
		return d3.Validator.error( msg, evt );
	}
	if (target.value.length < info["d3:min-length"])
	{
		msg = d3.ifNot( msg, MSG_PREFIX + "is too short ("+ info["d3:min-length"] + " character(s) required)");
		return d3.Validator.error( msg, evt );
	}
	if (target.value.length > info["d3:max-length"])
	{
		target.value = target.value.substring( 0, info["d3:max-length"] - 1 );
		msg = d3.ifNot( msg, MSG_PREFIX + "' has been truncated to "+ info["d3:max-length"] + " characters");
		return d3.Validator.error( msg, evt );
	}
	switch (info["d3:text-case"])
	{
		case "upper": target.value = target.value.toUpperCase(); break;
		case "lower": target.value = target.value.toLowerCase(); break;
	}
	var value = null;
	if (info["d3:pattern"] && target.value != "")
	{
		var pattern = info["d3:pattern"];
		switch (pattern)
		{	
			case "#integer": case "#money": case "#decimal":
				value = parseFloat(target.value.replace("$","").replace(",",""));
				if (isNaN( value ))
				{
					pattern = { mask : /\cD/,	// dummy mask control-d that won't match
						msg : "must be numeric." };
				}
				else
				{
					pattern = d3.Validator.patterns[pattern];
				}
				break;
			case "#date", "#time", "#datetime":
				var dt = Date.parseDate( target.value );
				if (isNaN( dt ))
				{
					pattern = {	mask : /\cD/,	// dummy mask control-d that won't match
						msg : "must be a valid "+ mask.substring( 1 ) };
				}
				else
				{
					target.value = dt.toString( pattern.substring( 1 ) );
					pattern = { mask : /\d/,		// dummy mask for a digit that will match
						msg : "" };
				}
				break;
			case "#phone": case "#email": case "#simple-text": case "#ssn":
				pattern = d3.Validator.patterns[pattern]; 
				break;
			default:
				pattern = { mask : new RegExp( mask ),
					msg : "is invalid" }; 
		}
		DEBUG$( "value="+ target.value + "; mask="+ pattern.mask + "; search="+ target.value.search( pattern.mask ) );
		if (target.value.search( pattern.mask ) < 0)
			return d3.Validator.error( d3.ifNot(msg, MSG_PREFIX + pattern.msg), evt );
	}
	var min = info["d3:min-value"];
	var max = info["d3:max-value"];
	if (value != null)
	{
		min = parseFloat(min);
		max = parseFloat(max);
	}
	else
		value = target.value;

	if (value < min)
		return d3.Validator.error( d3.ifNot( msg, MSG_PREFIX + "is invalid.\nMinimum value is "+ info["d3:min-value"] ), evt );
	if (target.value > max)
		return d3.Validator.error( d3.ifNot( msg, MSG_PREFIX + "is invalid.\nMaximum value is "+ info["d3:max-value"] ), evt );
	
	var url = info["d3:validation-url"];
	if (url && evt)		// if evt is present, validation is interactive
	{	// NOTE: d3.xml.js must be available for this code to work
		if (!d3.xml) d3.html.loadJavascript("d3.xml.js");
		var xml = d3.xml.formToXml( target.form );

		var uri = new d3.Uri( "", url );
		var response = d3.HttpRequest( uri.toString(), { body: xml } );
		if (!response.XMLDocument) return true;		// response will be validated after post
		response = response.XMLDocument.selectSingleNode( "response" );
		
		if (!response) return true;
		return (response.text == "true");
	}
	return true;
}

d3.Validator.validateAll = function( evt )
{
	LOG$( "in validateAll "+ typeof(evt) );
	var form = d3.events.getTarget( evt );
	if (!form)
	{
		form = evt;
		evt = null;
	}	
	
	d3.Validator._errors = [];
	var formInfo = Object.fromDomNode( form );
	for (var elements = new d3.Enumerator(form.elements); !elements.atEnd(); elements.moveNext())
		d3.Validator.validateItem( elements.item() );
	var msg = d3.Validator._errors.join("\n");
	if (msg.length > 0)
	{
		alert( msg, { caption: "Validation Error(s)" } );
		d3.events.preventDefault( evt );
		d3.events.stopPropagation();
		return false;
	}
	if (formInfo["d3:secure"] == "true")
	{
		var uri = new d3.Uri( null, form.action ).toSecure( true );
		form.action = uri.toString();
	}
	return true;
}

d3.Validator.error = function( msg, evt )
{
	if (evt)
	{
		d3.events.preventDefault( evt );
		var target = d3.events.getTarget( evt );
		if (target) target.focus();
		alert( msg, { caption: "Validation Error" } );
	}
	else
		this._errors.push( msg );
	return false;
}

d3.AjaxForm = {};

d3.AjaxForm.bindAll = function( )
{
	for (var k = 0; k < document.forms.length; k++)
		if (document.forms[k].getAttribute( "d3:submitter" ) == "AjaxForm")
			addEvent( document.forms[k], "submit", d3.AjaxForm.submit );
	if (!d3.xml) d3.html.loadJavascript("d3.xml.js");
}

d3.AjaxForm.submit = function( evt )
{
	d3.events.preventDefault( evt );
	var form = d3.events.getTarget( evt );
	if (!form) return false;
	if (evt && (evt.returnValue == false)) return false;
	var options = { async: false,
		method: d3.ifNot( form.method, "get" ).toLowerCase(),
		parameters: Object.fromHtmlForm( form ) };

	var uri = new d3.Uri( "", form.action );
	var response = d3.HttpRequest( uri.toString(), options );
	var s = "";
	//alert( response.about() );
	if (response.XMLDocument)
		s = d3.xml.getNodeText( response.XMLDocument.documentElement )
	else
		s = response.responseText;
	alert( s, { caption: "Submit" } );
	return false;
}

d3.Popup = { _div: null, _max: null, returnValue: null };
d3.Popup.dismiss = function( value )
{
	if (this._div) 
	{
		this._div.style.display = "none";
		this._mask.style.display = "none";
	}
	LOG$("popup dismissed with "+ value );
	this.returnValue = value;
}
d3.Popup._ok = function()
{
	this.dismiss( true );
}
d3.Popup._cancel = function()
{
	this.dismiss( false );
}
d3.Popup._init = function( )
{
	if (!this._div)
	{
		this._mask = document.createElement( "DIV" );
		this._mask.id = "popupMask";
		this._mask.style.position = "absolute";
		this._mask.style.zIndex = 101;
		this._mask.style.opacity = 0.70;
		this._mask.style.filter = "alpha(opacity=70)";
		this._mask.style.backgroundColor = "#d0d0d0";
		this._mask.style.margin = "0px;";
		//this._mask.style.cssText = "position: absolute; z-index: 101; opacity: 0.50; filter: alpha( opacity=50 ); background-color: #f0f0c0;";
		this._mask.style.display = "none";
		this._mask.innerHTML = "&nbsp;";
		this._div = document.createElement( "DIV" );
		this._div.id = "popup";
		this._div.style.position = "absolute";
		this._div.style.zIndex = 101;
		this._div.style.margin = "0px;";
		//this._div.style.cssText = "position: absolute; z-index: 101;";
		//this._div.style.display = "none";
		document.body.appendChild( this._mask );
		document.body.appendChild( this._div );
		addEvent( document.body, 'scroll', function(){ d3.Popup.dismiss('scroll') }, false )
		//addEvent( window, 'resize', function(){ d3.Popup.dismiss('resize') }, false )
	}
}

d3.Popup.show = function( text, options )
{
	if (!text) return;
	if (typeof(text) != "string") text = new String( text );
	if (!options) options = {};
	if (!this._div) this._init();

	var n = Math.min( 9, Math.ceil( text.length / 250 ));	// est. number of text blocks
	var winSize = d3.html.getWindowSize();

	var hw = { h: 1, w: 1 }
	if (n > 6) 
		hw = { h: 2, w: 3 };
	else if (n > 3)
		hw = { h: 2, w: 2 };
	else if (n > 1)
		hw = { h: 1, w: 2 };
	
	hw.h = hw.h * 250;
	hw.w = hw.w * 250;
	if (hw.w > winSize.width) hw.w = Math.max( 250, Math.floor(winSize.width / 250) * 250 );
	if (hw.h > winSize.height) hw.h = Math.max( 250, Math.floor(winSize.height / 250) * 250 );
	
	LOG$( hw.about() + " len="+ text.length );
	var innerHTML = [];
	innerHTML.push( "<div class='caption'>"+ d3.ifNot(options.caption,"&nbsp;") + "</div>" );
	innerHTML.push( "<div class='text' style='height:"+ (hw.h - 50) + "px; overflow: auto;'>"+ text + "</div>" );
	innerHTML.push( "<div class='buttons'>" );
	if (options.cancel)
		innerHTML.push( "<div onclick='d3.Popup._cancel()'>Cancel</div>" );
	innerHTML.push( "<div onclick='d3.Popup._ok()'>OK</div>" );
	innerHTML.push( "</div>" );
	this._div.innerHTML = innerHTML.join("");
	this._mask.style.width = winSize.width + "px";
	this._mask.style.height = winSize.height + "px";
	this._mask.style.top = winSize.scrollTop + "px";
	this._mask.style.left = winSize.scrollLeft + "px";
	var s = [ "width:", winSize.width, "px; height:", winSize.height, "px; top:", winSize.scrollTop, "px; left:", winSize.scrollLeft, "px" ].join("");
	LOG$( "mask css = "+ s );
//	this._mask.style.cssText = s;
	
	var top = Math.max( 5, Math.floor((winSize.height - hw.h) / 2) - 50 + winSize.scrollTop );
	var left = Math.max( 5, Math.floor((winSize.width - hw.w) / 2) + winSize.scrollLeft );
	s = ["width:", hw.w, "px; top:", top, "px; left:", left, "px" ].join("");
	LOG$( "popup css = "+ s );
	this._div.style.width = hw.w + "px";
	this._div.style.top = top + "px";
	this._div.style.left = left + "px";
//	this._div.style.cssText = s;
	this._mask.style.display = "block";
	this._div.style.display = "block";
	LOG$( innerHTML.join("") );
	LOG$( this._div.outerHTML );
}

d3.Sizer = function( obj, options )
{
	if (obj) this.init( obj, options );
	
	var _self = this;
	this._next = function( )
	{
		with (_self.sizing)
		{
			if (options.unsize == true && direction == -1) 
			{
				this.resize();
				return;
			}
			var w = originalWidth + (steps[stepNum] * deltaWidth);
			var h = originalHeight + (steps[stepNum] * deltaHeight);
			//alert( stepNum + " of "+ steps.length + ": w="+ originalWidth + ", nw="+ w + ", h="+ originalHeight + ", nh="+ h );
			if (deltaWidth > 0) object.style.width = Math.round( originalWidth + (steps[stepNum] * deltaWidth)) + "px";
			if (deltaHeight > 0) object.style.height = Math.round( originalHeight + (steps[stepNum] * deltaHeight)) + "px";
			stepNum += direction;
			if (stepNum >= 0 && stepNum < steps.length) 
				window.setTimeout( _self._next, 20 );
			else
			{
				direction *= -1;
				stepNum += direction;
			}
		}
	}
	
}

d3.Sizer.prototype.init = function( obj, options )
{
	if (!options) options = {};
	this.options = { 
		width: d3.ifNot( options.width, null ),
		height: d3.ifNot( options.height, null ),
		duration: d3.max( 50, d3.min( 1500, parseInt( d3.ifNot( options.duration, 300 ), 10 ) ) ),
		speed: d3.ifNot( options.speed, d3.Sizer.SPEED_CONSTANT ),
		unsize: ("true" == new String(options.unsize)) };
	obj.style.overflow = "hidden";
	if (this.options.height == "auto")
	{
		this.options.height = obj.offsetHeight;
		obj.style.height = "0px"; 
	}
	if (this.options.width == "auto")
	{
		this.options.width = obj.offsetWidth;
		obj.style.width = "0px"; 
	}
	this.sizing = { object: obj,
		originalWidth: obj.offsetWidth,
		originalHeight: obj.offsetHeight,
		deltaWidth: d3.ifNot( this.options.width - obj.offsetWidth, 0 ),
		deltaHeight: d3.ifNot( this.options.height - obj.offsetHeight, 0 ),
		steps: [1],
		stepNum: 1,
		direction: 1 };

	var n = Math.max( Math.abs( this.sizing.deltaWidth ), Math.abs( this.sizing.deltaHeight ) );
	var steps = Math.round(this.options.duration / 30);
	if ((n / steps) < 2) Math.round( steps = steps / 2 );		// move at least 2 pixels each step
	var a = [];
	if (steps <= 1)
		a = [1];
	else
	{ 
		for (var k = 1; k <= steps; k++)
		switch(this.options.speed)
		{
			case d3.Sizer.SPEED_QUICK: 
			{
				a.push( Math.log(k) / Math.log(steps) );
				break;
			}
			case d3.Sizer.SPEED_SLOW: 
			{
				a.push( Math.abs( 1 - Math.log(steps - k + 1) / Math.log(steps) ));
				break;
			}
			default:
			{
				a.push( (k - 1) / (steps - 1) );
				break;
			}
		}
	}
	this.sizing.steps = a;
}

d3.Sizer.prototype.size = function()
{
	if (!this.sizing) return;
	window.setTimeout( this._next, 35 );
}

d3.Sizer.prototype.resize = function()
{
	if (!this.sizing) return;
	with (this.sizing)
	{
		object.style.width = "";
		object.style.height = "";
		stepNum = direction = 1;
	}
}

d3.Sizer.sizers = [];
d3.Sizer.bindAll = function()
{
	for (var divs = new d3.Enumerator(d3.html.getTagsWithAttributes( document.body, "div", "d3:handler" )); !divs.atEnd(); divs.moveNext())
	{
		var o = Object.fromDomNode(divs.item());
		if (o["d3:handler"] == "Sizer") 
		{
			var options = { width: o["d3:width"],
					height: o["d3:height"],
					speed: o["d3:speed"],
					duration: o["d3:duration"],
					unsize: o["d3:unsize"] == "true",
					shrink: o["d3:shrink"] == "true" };
			
			var controller = o["d3:controller"];
			if (controller == "#first-child")
			{
				controller = null;
				var kids = d3.html.getChildElements( o['.'] );
				if (kids.length > 0) controller = kids[0];
			}
			if (controller == "#parent")
			{
				controller = d3.ifNot( o['.'].parentElement, o['.'].parentNode );
			}
			else 
				controller = $( controller );
			if (!controller || 0 == (d3.ifNot( options.width, 0 ) + d3.ifNot( options.height ))) continue;
			
			d3.Sizer.sizers.push( new d3.Sizer( o['.'], options ) );
			//LOG$( controller.about() );
			controller.onclick = function( evt ) 
				{	d3.Sizer.onclick( evt );
				}
			controller.setAttribute( "d3:sizer-data", d3.Sizer.sizers.length - 1 );
			//alert( controller.outerHTML.substring( 0, 128 ) + "\nsizer-data = "+ controller.getAttribute( "d3:sizer-data" ));
		}
	}
}

d3.Sizer.onclick = function( evt )
{
	var o = d3.events.getTarget( evt ); 
	while (o && !d3.isNumeric(o.getAttribute("d3:sizer-data")) ) o = d3.ifNot( o.parentElement, o.parentNode );
	if (!o) return;
	var d = parseInt( o.getAttribute( "d3:sizer-data" ), 10 );
	if (isNaN(d) || d < 0 || d > d3.Sizer.sizers.length) return;
	d3.Sizer.sizers[d].size();
}

d3.Sizer.SPEED_CONSTANT = 0;
d3.Sizer.SPEED_QUICK = 1;
d3.Sizer.SPEED_SLOW = -1;
d3.Sizer.SPEEDS = [ -1, 0, 1 ];

d3.Rotator = 
{
	items : [ ]
}

d3.Rotator.add = function( obj, options )
{
	var target = $(obj);
	if (!target) return;
	if (!options) options = { };
	var item = { 
		target: target,
		handle: this.items.length,
		interval: d3.ifNot( options.interval, d3.ifNot( target.getAttribute( "d3:interval" ), 300 ) ),
		children: null,
		url: d3.ifNot( options.url, d3.ifNot( target.getAttribute( "d3:url" ), "" ) ),
		sequence: d3.ifNot( options.sequence, d3.ifNot( target.getAttribute( "d3:sequence" ), "random" ) ),
		autoStart: d3.ifNot( options.autoStart, d3.ifNot( target.getAttribute("d3:auto-start" ), "true" ) ),
		childIndex: 0,
		stopped: true
	};
	this.items[ item.handle ] = item;
	item.interval = Math.bracket( parseInt( item.interval ), 1, 900 );
	if (typeof(item.interval) != "number") item.interval = 300;
	item.sequence = (new String(item.sequence)).toLowerCase();
	if (!["random","sequential"].has(item.sequence)) item.sequence = "random";
	var s = d3.ifNot( options.useChildren, d3.ifNot( target.getAttribute( "d3:use-children" ), "true" ) );
	if (s == "true" || s == true) item.children = d3.html.getChildElements( target );
	item.autoStart = (item.autoStart == "true" || item.autoStart == true);
	if (typeof(item.url) != "string" && !item.children) 
	{
		this.items[ item.handle ] = null;
		return null;
	}
	
	this.items[ item.handle ] = item;
	if (item.autoStart) this.start( item.handle );
	return item.handle;
};

d3.Rotator.remove = function( handle )
{
	if (!this.items[handle]) return;
	this.items[handle] = null;
}

d3.Rotator.start = function( handle )
{
	if (!this.items[handle]) return;
	this.items[handle].stopped = false;
	this.next( handle );
}

d3.Rotator.getHandleForObject = function (object)
{
	for (var k = 0; k < this.items.length; k++)
		if (this.items[k] && this.items[k].target == object) return this.items[k].handle;
	return null;
}

d3.Rotator.stop = function( handle )
{
	if (!this.items[handle]) return;
	this.items[handle].stopped = true;
}

d3.Rotator.toggle = function( handle )
{
	if (!this.items[handle]) return;
	if (this.items[handle].stopped)
		this.start( handle );
	else
		this.stop( handle );
}

d3.Rotator.next = function( handle )
{
	var item = this.items[ handle ];
	if ((!item) || item.stopped) return;

	if (item.children)
	{
		if (item.sequence == "random")
		{
			var n = Math.round( Math.random() * (item.children.length  - 1));
			if (n == item.childIndex) 
				n += (n == item.children.length) ? -1 : 1;
			item.childIndex = n;
		}
		else
		{
			item.childIndex += 1;
			if (item.childIndex >= item.children.length) item.childIndex = 0;
		}
		for (var k = 0; k < item.children.length; k++)
			item.children[k].style.display = "none";
	}
	else
	{
		if (!d3.xml) d3.html.loadJavascript("d3.xml.js");
		var uri = new d3.Uri( "", item.url );
		if (item.sequence == "random") 
		{
			if (uri.queryString == "") uri.queryString += "&";
			uri.queryString += "random=true";
		}
		var response = d3.HttpRequest( uri.toString(), { async: false, method: "get"} );
		if (!response.XMLDocument) return;		
		
		item.target.innerHTML = response.XMLDocument.documentElement.xml;	
	}
	this.fade( item.handle, 0, 10 );
	
	var s = "d3.Rotator.next( "+ item.handle + ")";
	window.setTimeout( s, item.interval * 1000 );
}

d3.Rotator.fade = function( handle, opacity, step )
{
	var item = this.items[handle];
	if (!item) return;
	if (isNaN( opacity )) return;
	
	if (item.children)
		item = item.children[ item.childIndex ];
	else
		item = item.target;
	
	if (isNaN( step )) step = 20;
	step = Math.bracket( Math.round( step ), 5, 25 );
	opacity = Math.bracket( Math.round( opacity ), 0, 100 );
	
	if ("block" != (new String(item.style.display)).toLowerCase()) item.style.display = "block";
	if (d3.env.IS_IE)
		item.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity="+ opacity + ")";
	else
		item.style.opacity = opacity / 100;
	
	if (opacity == 100) return;

	var s = "d3.Rotator.fade( "+ handle + ", "+ (opacity + step) + ", "+ step + ")";
	window.setTimeout( s, 50 ); 
}

d3.Accordian = { items : new d3.HashTable() };
d3.Accordian.getAccordianItem = function( name )
{
	return d3.Accordian.items.item(name);
}

d3.Accordian.bindAll = function( controllerTags, accordianTags )
{
	// controller tags have handlers attached
	if (!controllerTags) controllerTags = [ "DIV","SPAN","LI","A","P" ];		
	if (!(controllerTags instanceof Array)) controllerTags = [ controllerTags ];
	// accordian tags have companions attached
	if (!accordianTags) accordianTags = [ "DIV","SPAN","LI","A","P" ];
	if (!(accordianTags instanceof Array)) accordianTags = [ accordianTags ];
	var accList = new d3.HashTable();
	
	for (var accords = new d3.Enumerator( d3.html.getTagsWithAttributes( document.body, controllerTags, "d3:handler" ) );
		!accords.atEnd(); accords.moveNext() )
	{
		var acc = accords.item();
		var hName = acc.getAttribute("d3:handler");
		if (hName.startsWith( "moofx.Accordian" ))
		{
			var accInfo = d3.ifNot( accList.item( hName ), 
				new d3.NameValue( acc.getAttribute("d3:handler"), { clicks: [], shows: [], moofx: null } ) );
			for (var items = new d3.Enumerator( d3.html.getTagsWithAttributes( acc, accordianTags, "d3:companion" ) );
						!items.atEnd(); items.moveNext() )
			{
				var item = items.item();
				var companion = item.getAttribute("d3:companion" );
				if (companion == "#first-child") 
				{
					companion = null;
					var a = d3.html.getChildElements( item );
					if (a && a.length > 0) companion = a[0];
				}
				else if (companion == "#next-sibling" || companion == "#prior-sibling") 
				{
					var n = (companion == "#next-sibling") ? 1 : -1;
					companion = null;
					var a = d3.html.getChildElements( item.parentNode );
					for (var k = 0; k < a.length; k++)
						if (a[k] == item)
						{
							companion = a[k + n];
							break;
						}
				}
				else if (companion == "#parent")
				{
					companion = d3.ifNot( item.parentElement, item.parentNode );
				}
				else
					companion = $(companion);
					
				if (companion) 
				{
					accInfo.value.clicks.push( item );
					accInfo.value.shows.push( companion );
				}
			}
			if (accInfo.value.clicks.length > 0)
				accList.add( accInfo );
		}
	}
	for (var k = 0; k < accList.length; k++)
	{
		accInfo = accList.item( k );
		var a = [];
		for (var k = 0; k < accInfo.value.clicks.length; k++)
			a.push( accInfo.value.clicks[k].id + " -> "+ accInfo.value.shows[k].id );
		accInfo.value.moofx = new fx.Accordion( accInfo.value.clicks, accInfo.value.shows, { opacity: true } );
		for (var j = 0; j < accInfo.value.shows.length; j++)
		{
			accInfo.value.shows[j].style.display = "block";
			if (accInfo.value.clicks[j].getAttribute("d3:default") == "true")
				accInfo.value.moofx.showThisHideOpen( accInfo.value.shows[j] );
		}
		d3.Accordian.items.add( accInfo );
	}
	
}

d3.browser = true;
LOG$("d3.browser OK");