function FaderDivManager(pId, pContentsArray, pConfigArray, pFadeRates, pCrossfade, pHasControls, pControlsNumbered, pName) {

// pId.................id attribute of HTML div container to be used by this fader
//                     simple, String of id of parent div in HTML page - required
// pContentsArray......contents to be displayed  - required
//                     each element is the complete HTML for the contents to be displayed, when the 
//                     element is fully visible - required
//                     array of strings (HTML specifications), one per content element
// pConfigArray........associative array of configuration style as key/value pairs for container div
//                     keys are simple, String attribute names in JavaScript form, e.g., borderColor
//                     values are simple, String style values - required
// pFadeRates..........the rates at which the elements in the contentsArray should be faded out 
//                     (index [0]) and in (index [1]) - if these differ, the out-fading content 
//                     will vanish before the in-fading content starts to appear - optional with 
//                     default of 3000 msecs for both
//                     rates can be cited as an array of arrays, one pair for each element of content, 
//                     an array, containing a single array pair to be applied for all content elements, 
//                     or an array, containing an array with a single element that will be used for both 
//                     rates for all contents
// pCrossfade..........Boolean indicator of whether content elements that have the same fade rates 
//                     should be simultaneously faded, i.e., outgoing and incoming content fade at
//                     the same time - if omitted for contents with the same rates of in/out fade, 
//                     the default is simultaneous fading - optional with default of true - overridden, 
//                     if the fade rates are not the same
// pHasControls........Boolean to indicate whether the panel of user controls should be included 
// pControlsNumbered...whether to put numbers on the controls, if they are provided
// pName...............simple String name for the instance to be created - defualt name is fdm

	var ua = navigator.userAgent;
	var isIE = -1 != ua.indexOf('MSIE');

	var startAutoTimeout;
	var inFadeTimerArray = new Array();
	var outFadeTimerArray = new Array();

	var currentIdx = 0;
	var autoFadeRate = 2000;
	var defaultConfigArray = new Array();
		defaultConfigArray['height'] = '200px';
		defaultConfigArray['width'] = '200px';
	
	var contentsArray, nextIdx, fadeRates, crossfade, fadeTimer, containerId; 
	var ucHeight = '20%';
	var ucTop = '80%';
	var ucWidth = '100%';

	var controlListBgColor = '#DDD';
	var controlOnColor = 'red';
	var controlOffColor = 'blue';
	var crossfade = true;

	// fade state values are:
	// next-most active fading behavior: {'out', 'in', 'none'}
	//    out  ==> sequential out-/in-fading are both queued, but 
	//             fading out steps are still on stack, or cross-
	//             fading has been chosen and the content fading 
	//             out is still more opaque than that fading in
	//    in   ==> out-fading has completed infading is in progress 
	//             for sequential out- followed by in-fading, or 
	//             cross-fading has been elected and content fading 
	//             in is more opaque than that fading out
	//    none ==> fading is not currently active
	var fadeState = 'out';

	function get(eid) {
		return document.getElementById(eid);
	}

	this.containerId = null;
	
	this.name = 'fdm';

	this.setName = function(n) {
		this.name = n;
	}
	
	this.getName = function() {
		return this.name;
	}

	this.setFadeState = function(f) {
		fadeState = f;
	}

	this.getFadeState = function() {
		return fadeState;
	}

	this.getFadeInRate = function(idx) {
		return fadeRates[idx][1];
	}

	this.getFadeOutRate = function(idx) {
		return fadeRates[idx][0];
	}

	this.setFadeRates = function(r) {
		var last = contentsArray.length;
		if ('undefined' == r) r = [[3000, 3000]];
		else if (1 == r.length) {
			fadeRates = new Array();
			for (var i = 0; i < last; i++) {
				fadeRates[i] = r[0];
			}
		}
		else fadeRates = r;
	}

	this.setStartAutoTimeout = function(t) {
		startAutoTimeout = t;
	}

	this.getStartAutoTimeout = function() {
		return startAutoTimeout;
	}

	this.setAutoFadeRate = function(r) {
		autoFadeRate = r;
	}

	this.getAutoFadeRate = function() {
		return autoFadeRate;
	}

	this.setControlsNumbered = function(b) {
		controlsNumbered = b;
	}

	this.getControlsNumbered = function() {
		return controlsNumbered;
	}

	this.setControlOffColor = function(c) {
		controlOffColor = c;
	}

	this.getControlOffColor = function() {
		return controlOffColor;
	}

	this.setControlOnColor = function(c) {
		controlOnColor = c;
	}

	this.getControlOnColor = function() {
		return controlOnColor;
	}

	this.setControlListBgColor = function(c) {
		controlListBgColor = c;
	}

	this.getControlListBgColor = function() {
		return controlListBgColor;
	}

	this.setContainerId = function(i) {
		this.containerId = i;
	}

	this.getContainerId = function() {
		return this.containerId;
	}

	this.setCrossfade = function(b) {
		crossfade = b;
	}

	this.getCrossfade = function() {
		return crossfade;
	}

	this.setHasControls = function(b) {
		hasControls = b;
	}

	this.getHasControls = function() {
		return hasControls;
	}

	this.setCurrentIdx = function(i) {
		currentIdx = i;
	}

	this.getCurrentIdx = function() {
		return currentIdx;
	}

	this.setConfigArray = function(c) {
		configArray = c;
	}

	this.getConfigArray = function() {
		return configArray;
	}

	this.setContentsArray = function(c) {
		contentsArray = c;
	}

	this.getContentsArray = function() {
		return contentsArray;
	}

	this.setContentsArrayElement = function(c, idx) {
		var oldC = (idx < this.getContentsArray().length && 'undefined' != contentsArray[idx])?this.getContentsArrayElement[idx]:null;
		contentsArray[idx] = c;
		return oldC;
	}

	this.getContentsArrayElement = function(idx) {
		if (idx >= this.getContentsArray().length)  throw 'FaderDivManager.getContentsArrayElement(): index out of bounds';
		return contentsArray[idx];
	}

	this.setUcTop = function(t) {
		ucTop = t;
	}

	this.getUcTop = function() {
		return ucTop;
	}

	this.setUcHeight = function(h) {
		ucHeight = h;
	}

	this.getUcHeight = function() {
		return ucHeight;
	}

	this.setUcWidth = function(w) {
		ucWidth = w;
	}

	this.getUcWidth = function() {
		return ucWidth;
	}

	this.setFadeInRate = function(r, idx) {
		fadeRates[idx][1] = r;
	}

	this.getFadeInRate = function(idx) {
		return fadeRates[idx][1];
	}

	this.setFadeOutRate = function(r, idx) {
		fadeRates[idx][0] = r;
	}

	this.getFadeOutRate = function(idx) {
		return fadeRates[idx][0];
	}

	this.setCurrentContent = function(c) {
		var id = this.getContainerId() + 'Current';
		var cd = get(id);
		cd.innerHTML = c;
	}

	this.getCurrentContent = function() {
		var id = this.containerId + 'Current';
		var cd = get(id);
		return cd.innerHTML;
	}

	this.setNextContent = function(c) {
		var id = this.containerId + 'Next';
		var nd = get(id);
		nd.innerHTML = c;
	}

	this.getNextContent = function() {
		var id = this.containerId + 'Next';
		var nd = get(id);
		return nd.innerHTML;
	}

	this.configure = function(configArray) {
		if ('undefined' == typeof configArray) configArray = this.getConfigArray();
		var id = this.getContainerId();
		var pd = get(id);
		var s = pd.style;
		for (skey in this.getConfigArray()) {
			eval('s.' + skey + '="' + this.getConfigArray()[skey] +'"');
		}
		if ('undefined' == typeof s.position || 'static' == s.position) {
			 s.position == 'relative';
			 s.top = '0';
			 s.left = '0';
		}
		var cDiv = document.createElement('DIV');
		cDiv.id = id + 'Current';
		cDiv.style.position = 'absolute';
		cDiv.style.top = '0';
		cDiv.style.left = '0';
		var nDiv = document.createElement('DIV');
		nDiv.id = id + 'Next';
		nDiv.style.position = 'absolute';
		nDiv.style.top = '0';
		nDiv.style.left = '0';
		if (isIE) {
			cDiv.style.filter = 'alpha(opacity=100)';
			nDiv.style.filter = 'alpha(opacity=0)';
		}
		else {
			cDiv.style.opacity = '1';
			nDiv.style.opacity = '0';
		}
		pd.appendChild(cDiv);
		pd.appendChild(nDiv);
		if(hasControls) {
			var ucId = id + 'UserControls';
			var uc = document.getElementById(ucId);
			if (null != uc) {
				try {
					pd.removeChild(uc);
				}
				catch (e) { 
					; //eat it 
				}
			}
			var ucDiv = document.createElement('DIV');
			ucDiv.id = ucId;
			ucDiv.style.position = 'absolute';
			ucDiv.style.top = (100 - parseInt(this.getUcHeight())) + '%';
			ucDiv.style.left = '0';
			ucDiv.style.height = this.getUcHeight();
			ucDiv.style.width = this.getUcWidth();
			ucDiv.style.padding = '0';
			ucDiv.style.backgroundColor = this.getControlListBgColor();
			ucDiv.innerHTML = this.createUserControls();
			pd.appendChild(ucDiv);
		}
	}

	this.createUserControls = function() {
		var last = this.getContentsArray().length;
		var c = '<ul style="list-style-type: none; ' +
			'position: relative; top: ' + this.getUcTop() + '; left: 0; ' +
			'margin: 0; padding: 0; ">';
		for (var i = 0; i < last; i++) {
			var content = this.getContentsArrayElement(i);
			var hintStart = content.indexOf('title="') + 'title="'.length;
			var hint = content.substring(hintStart);
			var hintEnd = hint.indexOf('"');
			hint = hint.substring(0, hintEnd);
			c += '<li style="float: left; cursor: pointer;' +
				'height: 42%; width: ' + ((80/(last + 1))) + '%; ' +
				'background-color: ' + ((0 == i)?this.getControlOnColor():this.getControlOffColor()) + '; ' +
				'font-family: Arial, sans-serif; font-weight: bold; text-align: center; color: white; ' +
				'font-size: 90%; vertical-align:middle; ' +
				'margin: 0 0 0 ' + (20/(last + 2)) + '%;" ' +
				'id="' + this.getContainerId() + 'Control' + i + '" ' +
				'onclick="' + this.name +'.doControlCall(this, ' + i + ');" ' +
				'title="' + hint + '" '+
				'>' + (this.getControlsNumbered()?i+1:'&nbsp;') +'</li>';
		}
		c += '<li style="float: left; cursor: pointer;' +
			'height: 42%; width: ' + ((80/(last + 1))) + '%; ' +
			'background-color: ' + this.getControlOffColor() + '; ' +
			'font-family: Arial, sans-serif; font-weight: bold; text-align: center; color: ' +  this.getControlOnColor() + '; ' +
//			'font-size: ' +((isIE)?100:100) + '%; ' +
			'font-size: 100%; ' +
			'margin: 0 0 0 ' + (20/(last + 2)) + '%;" ' +
			'id="' + this.getContainerId() + 'PlayControl" ' +
			'onclick="' + this.name +'.doPlay(this);" ' +
			'title="start or stop auto-play of rotating content" '+
			'><span style="line-height:100%;vertical-align:' + ((isIE)?15:8) + '%;">||</span></li>';
		c += '</ul>';
		return c;
	}

	this.updateByFadeState = function() {
		var cs = get(this.getContainerId() + 'Current').style;
		var ns = get(this.getContainerId() + 'Next').style;
		if ('in' == this.getFadeState()) {
			if (isIE) {
				cs.filter = 'alpha(opacity=0)';
				ns.filter = 'alpha(opacity=100)';
			}
			else {
				cs.opacity = '0';
				ns.opacity = '1';
			}
			this.setNextControlOn();
			this.setCurrentIdx((this.getCurrentIdx() + 1)%this.getContentsArray().length);
		}
		else if ('out' == this.getFadeState()) {
			if (isIE) {
				cs.filter = 'alpha(opacity=100)';
				ns.filter = 'alpha(opacity=0)';
			}
			else {
				cs.opacity = '1';
				ns.opacity = '0';
			}
		}
		this.setFadeState('none');
	}

	this.setNextControlOn = function() {
		if (hasControls) {
			var cc = get(this.getContainerId() + 'Control' + currentIdx);
			var nc = get(this.getContainerId() + 'Control' + (currentIdx + 1)%this.getContentsArray().length);
			cc.style.backgroundColor = this.getControlOffColor();
			nc.style.backgroundColor = this.getControlOnColor();
		}
	}

	this.doPlay = function(cRef) {
		this.stopAutoFade();
		this.updateByFadeState();
		var playing = -1 !=cRef.innerHTML.indexOf('||');
		if (playing) {
			cRef.innerHTML = '<span style="line-height:100%;vertical-align:' + ((isIE)?20:1) + '%;">&gt;</span></li>';
			// perform a doControl() call on the current content and control, in case the call to 
			// this function occurred while the corresponding content was fading in - synchronizes  
			// unaligned aspects of control and content state for next doPlay() call
 			var cIdx = this.getCurrentIdx();
			var cId = this.getContainerId() + 'Control' + cIdx;
			var cRef = get(cId);
			this.doControlCall(cRef, cIdx);
		}
		else {		
			cRef.innerHTML = '<span style="line-height:100%;vertical-align:' + ((isIE)?15:8) + '%;">||</span></li>';
			this.startAutoFade();
		}
	}

	this.doControlCall = function(cRef, idx) {
		// {cRef}...element reference to control that was clicked
		// {idx}....index of content corresponding to control that was clicked
		this.stopAutoFade();
		var id = this.getContainerId() + 'Control' + currentIdx;
		var cc = get(id);
		cc.style.backgroundColor = this.getControlOffColor();
		cRef.style.backgroundColor = this.getControlOnColor();
		this.setCurrentContent(this.getContentsArrayElement(idx));
		var cs = get(this.getContainerId() + 'Current').style;
		var ns = get(this.getContainerId() + 'Next').style;
		if (isIE) {
			cs.filter = 'alpha(opacity=100)';
			ns.filter = 'alpha(opacity=0)';
		}
		else {
			cs.opacity = '1';
			ns.opacity = '0';
		}
		this.setCurrentIdx(idx);
		var pid = this.getContainerId() + 'PlayControl';
		var pc = get(pid);
		pc.innerHTML = '<span style="line-height:100%;vertical-align:' + ((isIE)?20:1) + '%;">&gt;</span></li>';
	}

	this.fade = function(nIdx) {
		var last = contentsArray.length;
		if ('undefined' == typeof nIdx) nIdx = (currentIdx + 1)%last;
		this.setNextContent(this.getContentsArray()[nIdx]);
		var inRate = this.getFadeInRate(nIdx);
		var outRate = this.getFadeOutRate(currentIdx);
		if (!crossfade || inRate != outRate) {
			for (var i = 1; i < 101; i++) {
				var cmd = this.name + '.fadeOut(' + i + ')';
				// first queued out-fade command needs to update update fadeState to indicate 
				// that current container is fading out, in case a click on the auto-play 
				// control occurs
				if (1 == i) cmd =  this.name + '.setFadeState("out");' + cmd;
				outFadeTimerArray[i] = setTimeout(cmd, (outRate/100)*i);
			}
			for (var i = 1; i < 101; i++) {
				var cmd = this.name + '.fadeIn(' + i + ')';
				// first queued in-fade command needs to update update fadeState to indicate 
				// that current container is fading in, in case a click on the auto-play 
				// control occurs
				if (1 == i) { 
					cmd = this.name + '.setFadeState("in");' +  this.name + '.setNextControlOn();' + cmd;
				}
				inFadeTimerArray[i] = setTimeout(cmd, outRate + (inRate/100)*i);
			}
			outFadeTimerArray[0] = setTimeout(this.name + '.finishFade(' + nIdx + ')', inRate + outRate); 
		}
		else {
			// since inRate == outRate for cross-fading, all timers are set using inRate
			for (var i = 1; i < 101; i++) {
				var oCmd = this.name + '.fadeOut(' + i + ')';
				var iCmd = this.name + '.fadeIn(' + i + ')';
				// set fadeState to indicate out-fading at start
				if (1 == i) oCmd =  this.name + '.setFadeState("out");' + oCmd;
				// at mid-point of fade processing, reset fadeState to indicate in-fading
				if (51 == i) iCmd = this.name + '.setFadeState("in");' +  this.name + '.setNextControlOn();' + iCmd;
				outFadeTimerArray[i] = setTimeout(oCmd, (inRate/100)*i);
				inFadeTimerArray[i] = setTimeout(iCmd, (inRate/100)*i);
			}
			outFadeTimerArray[0] = setTimeout(this.name + '.finishFade(' + nIdx + ')', inRate); 
		}
	}

	this.finishFade = function(nIdx) {
		this.setCurrentContent(this.getNextContent());
		var cs = get(this.getContainerId() + 'Current').style;
		var ns = get(this.getContainerId() + 'Next').style;
		if (isIE) {
			cs.filter = 'alpha(opacity=100)';
			ns.filter = 'alpha(opacity=0)';
		}
		else {
			cs.opacity = '1';
			ns.opacity = '0';
		}
		if (hasControls) {
			var cc = get(this.getContainerId() + 'Control' + currentIdx);
			cc.style.backgroundColor = this.getControlOffColor();
			var nc = get(this.getContainerId() + 'Control' + nIdx);
			nc.style.backgroundColor = this.getControlOnColor();
		}
		currentIdx = nIdx;
		this.setFadeState('none');
	}
	
	this.fadeIn = function(i) {
		var s = get(this.containerId + 'Next').style;
		if (isIE) {
			s.filter = 'alpha(opacity=' + i + ')';
		}
		else {
			s.opacity = '' + .01*i;
		}
	}

	this.fadeOut = function(i) {
		var s = get(this.containerId + 'Current').style;
		if (isIE) {
			s.filter = 'alpha(opacity=' + (100 - i) + ')';
		}
		else {
			s.opacity = '' + .01*(100 - i);
		}
	}

	this.startAutoFade = function(idx) {
		var last = contentsArray.length;
		if ('undefined' == typeof idx) idx = (currentIdx + 1)%last;
		this.stopAutoFade();
		this.autoFade(idx);
	}

	this.autoFade = function(idx) {
		var last = contentsArray.length;
		this.fade(idx);
		var cmd = this.name + '.autoFade(' + ((idx + 1)%last) + ')';
		var delay = autoFadeRate + this.getFadeInRate(idx);
		delay += ((!crossfade) || (this.getFadeInRate(idx) != this.getFadeOutRate(idx)))?this.getFadeOutRate(idx):0;
		fadeTimer = setTimeout(cmd, delay);
	}

	this.stopAutoFade = function() {
		clearTimeout(this.getStartAutoTimeout());
		clearTimeout(fadeTimer);
		var last = outFadeTimerArray.length;
		for (var i = 1; i < last; i++) {
			clearTimeout(outFadeTimerArray[i]);
			clearTimeout(inFadeTimerArray[i]);
		}
		clearTimeout(outFadeTimerArray[0]);
	}

	if ('undefined' == typeof pId) throw 'FaderDivManager: parameter error - no div id given';
	else this.setContainerId(pId);
	if ('undefined' == typeof pContentsArray) throw 'FaderDivManager: parameter error - no contents given';
	else this.setContentsArray(pContentsArray);
	if ('undefined' == typeof pConfigArray) this.setConfigArray(defaultConfigArray);
	else this.setConfigArray(pConfigArray);
	if ('undefined' == typeof pFadeRates) this.setFadeRates();
	else this.setFadeRates(pFadeRates);
	if ('undefined' == typeof pCrossFade) this.setCrossfade(true);
	else this.setCrossfade(pCrossfade);
	if ('undefined' == typeof pHasControls) this.setHasControls(true);
	else this.setHasControls(pHasControls);
	if ('undefined' == typeof pControlsNumbered) this.setControlsNumbered(false);
	else this.setControlsNumbered(pControlsNumbered);
	if ('undefined' != typeof pName) this.setName(pName);

	this.configure(this.getConfigArray());
}
