/*
	blender.js -- image slideshow with smooth blending
	
	v2.09, 2008-09-19, Christian Augustin
*/
/*
	JavaScript 1.3, W3C DOM only
	
	Intended "audience":
	- Netscape 6+
	- Mozilla 1.0+
	- Safari 1.2+
	- Microsoft Internet Explorer 5+ (Windows)
	- and all those supporting "opacity" in the future ...
	
	(Fallback to simple image switching without blending on other browsers.)
	
	Changes:
	2008-09-19, cma: Blender is now restartet when it gets invisible and visible again.
	2008-09-11, cma: CSS class "printLast" added.
	2007-11-28, cma: Use of onDomReady added, MSIE 7 fixes.
	2007-11-28, cma: Added "cycles" to stop blending after n cycles.
	2007-04-09, cma: No "display: block" with img ...
	2007-04-05, cma: Visibility "inherit" for use with cards etc.
	2006-01-09, cma: CSS cleanup for Firefox 1.5, included basic print styles.
	2006-01-03, cma: Small CSS bug fixed in initialisation sequence.
	2005-11-01, cma: Bugfix with hidden loader img; refined documentatione.
	2005-10-26, cma: Slight modifications to anticipate MSIE 7.
	2005-10-25, cma: Problems fixed, simplified code, parameters moved to class attribute.
	2005-10-24, cma: Blender.setOpacity optimized to boost Gecko performance!
	2005-10-23, cma: Re-coded, W3C DOM only, simplified CSS injection, better Gecko performance.
	2005-06-28, cma: Fixing addCssRule with Safari.
	2005-05-16, cma: Warnings cleanup; initComplete and cancelInit added.
	2005-04-05, cma: Stylesheet manipulation changed (document.write is more
						reliable than the DOM approach ...).
						Testing/using presets object.
	2005-04-05, cma: Self-containing stylesheet manipulation.
	2005-04-04, cma: Small (non-functional) change in init(); added usage comments.
	2005-02-24, cma: Auto-Init implemented.
	
	Implemented parameters (embeded in DIV class):
	- show
	- blend
	- delta
	- cycles
	
	To be implemented:
	- noblend (with blend0 and delta100 doing the same)
	- heuristic to switch of blending on large images/slow browsers/slow machines
	- "sliding" transitions (a little bit old-style but useful with large images)
	- start/stop functionality
	
	Include reference to Blender.js in HTML head:
	
		<script type="text/javascript" src="xxx/blender.js"></script>
	
	Usage (example with 2750 ms between blends, blending over 250 ms,
	5% per step and 3 cycles):
	
		<div class="blender show2750 blend250 delta5 cycles3">
			<img class="loader" ... />
			<img class="first" ... />
			<img ... />
			...
		</div>
	
	Pseudo-classes as parameters are optional and used to override default values.
	The loader image is optional, the first image is shown with 50% opacity
	while loading (underneath the loader image, so transparency effects
	can be used).
	
	Some essential style definitions are automatically included on running this script.
	
	Include this stylesheet (literally or as part of the main stylesheet) to hide
	loader image if JavaScript is off and for special print formatting:
	
		.blender .loader {
			display: none;
		}
		
		@media print {
			.blender img {
				width: 47% !important;
				height: auto !important;
				margin-right: 1.4% !important;
				margin-top: 0.5em !important;
			}
		}
		
	Include further style rules for special formatting needs (especially for the
	blender DIV).
		
*/


/* ===========================
   Constructor function ...
=========================== */

function Blender(obj) {

	if (!obj) return;
	this.element = obj;
	
	/* Register this blender ... */
	this.n = Blender.blenders.length;
	Blender.blenders[this.n] = this;

	/* Set basic properties ... */
	this.items = new Array();
	this.actual = 0;
	this.next = 1;
	this.show = Blender.show;
	this.blend = Blender.blend;
	this.delta = Blender.delta;
	this.step = 0;
	this.timerID = null;
	this.cycle = 0;
	this.cycles = 0;

	/* Extract arguments from ID ... */
	var args = (obj.className) ? obj.className.toLowerCase().split(/\s+/) : [];
	
	/* Scan arguments if given ... */
	for (var i=1; i<args.length; i++) {
		var a = args[i];
		if (a.indexOf("show") == 0) {
			tmp = parseInt(a.substring(4, a.length), 10);
			if (tmp && !isNaN(tmp)) this.show = tmp;
		} else if (a.indexOf("blend") == 0) {
			tmp = parseInt(a.substring(5, a.length), 10);
			if (tmp && !isNaN(tmp)) this.blend = tmp;
		} else if (a.indexOf("delta") == 0) {
			tmp = parseInt(a.substring(5, a.length), 10);
			if (tmp && !isNaN(tmp)) this.delta = tmp;
		} else if (a.indexOf("cycles") == 0) {
			this.cycles = 1;
			tmp = parseInt(a.substring(6, a.length), 10);
			if (tmp && !isNaN(tmp)) this.cycles = tmp;
		}
	}
		
	/* Re-calculate this.blend to represent blend slice time ... */
	this.blend = Math.round(this.blend / (100 / this.delta));
	
	/* Get images to blend ... */

	var maxHeight = 0;
	var elm = obj.firstChild;
	while (elm) {
		// if (elm.nodeType == 1 && elm.tagName != '!') {
		if (elm.nodeType == 1 && elm.tagName.match(/img/i)) {
			if (elm.className && elm.className.match(/loader/)) {
				elm.style.display = 'none';
			} else {
				this.items[this.items.length] = elm;
				/*
				if (typeof elm.readyState == 'undefined') {
					elm.readyState = 'loading';
					elm.onload = function() { this.readyState = 'loaded'; };
				};
				*/
				if (elm.className && elm.className.match(/first/)) {
					if (typeof elm.style.opacity != 'undefined') elm.style.opacity = 1
					else if (typeof elm.style.MozOpacity != 'undefined') elm.style.MozOpacity = 1
					else if (typeof elm.style.KhtmlOpacity != 'undefined') elm.style.KhtmlOpacity = 1
					else if (typeof elm.style.filter != 'undefined') elm.style.filter = 'alpha(opacity=100)';
				}
			}
		}
		elm = elm.nextSibling;
	}
	
	var lastItem = this.items[this.items.length - 1];
	lastItem.className = (lastItem.className) ? lastItem.className + ' last' : 'last';

	/* Let's play ... */
	//this.timerID = window.setTimeout("Blender.blenders[" + this.n + "].nextStep()", this.show);
	this.start();
	
}



/* =======================
   Instance methods ...
======================= */

Blender.prototype.nextStep = function() {
	var actualItem = this.items[this.actual];
	var nextItem = this.items[this.next];
	var time = this.blend;
	//if (!nextItem.readyState.match(/loaded|complete/i)) {
		/* next image not loaded, we have to wait ... */
	//} else if (this.step == 0) {
	if (this.step == 0) {
		/* start a new blending process ... */
		this.step += this.delta;
		actualItem.style.zIndex = '1';
		nextItem.style.zIndex = '100';
		Blender.setOpacity(nextItem, this.step);
	} else {
		/* we are in a blending process ... */
		this.step += this.delta;
		if (this.step < 100) {
			Blender.setOpacity(nextItem, this.step);
		} else {
			/* blend completed, prepare next blending ... */
			Blender.setOpacity(nextItem, 100);
			Blender.setOpacity(actualItem, 0);
			this.step = 0;
			this.actual = (++this.actual < this.items.length) ? this.actual : 0;
			this.next = (++this.next < this.items.length) ? this.next : 0;
			time = this.show;
			if (this.next == 0) ++this.cycle;
		};
	};
	if (!this.cycles || this.cycle < this.cycles) {
		this.timerID = window.setTimeout("Blender.blenders[" + this.n + "].nextStep()", time);
	};
}

Blender.prototype.reset = function() {
	this.stop();
	Blender.setOpacity(this.items[this.actual], 0);
	Blender.setOpacity(this.items[this.next], 0);
	Blender.setOpacity(this.items[0], 100);
	this.actual = 0;
	this.next = 1;
	this.step = 0;
	this.timerID = null;
	this.cycle = 0;
}

Blender.prototype.stop = function() {
	if (!this.timerID) { return; };
	window.clearTimeout(this.timerID);
	this.timerID = null;
}

Blender.prototype.start = function() {
	if (this.timerID || !this.isVisible()) { return; };
	var time = (this.step == 0) ? this.show : this.blend;
	this.timerID = window.setTimeout("Blender.blenders[" + this.n + "].nextStep()", time);
}
	
	
Blender.prototype.isVisible = function() {
	/*
		If we can't get the current style, we don't know if the blender
		is visible or not -- but if we assume "visible" it is less disruptive ...
	*/
	var elm = this.element;
	var cs = Blender.getCurrentStyle(elm);
	if (!cs) return true;
	var n = elm;
	while (n && n.nodeType == 1) {
		cs = Blender.getCurrentStyle(n);
		if (cs.visibility == 'hidden' || cs.display == 'none') return false;
		n = n.parentNode;
	}
	return true;
};



/* ==========================
   Class properties ...
========================== */

/* Array to register references for timer callbacks ... */
Blender.blenders = new Array();

/* Set default values ... */
Blender.show = 2750;         // default time to show img in ms
Blender.blend = 250;         // default time to blend imgs in ms
Blender.delta = 10;          // default opacity delta in %

/* Check for global presets ... */
if ((typeof presets != 'undefined') && presets.blenders) {
	if (typeof presets.Blender.show != 'undefined') Blender.show = presets.Blender.show;
	if (typeof presets.Blender.blend != 'undefined') Blender.blend = presets.Blender.blend;
	if (typeof presets.Blender.delta != 'undefined') Blender.delta = presets.Blender.delta;
}

/* Testing for some browsers ... */
Blender.ua = navigator.userAgent;
Blender.isMSIE = (!window.opera && document.all && document.styleSheets);
Blender.isSafari = (Blender.ua.indexOf('AppleWebKit/') != -1);
Blender.isGecko = (Blender.ua.indexOf('Gecko/') != -1);
Blender.isWin = (Blender.ua.indexOf('Win') != -1);
Blender.isMac = (Blender.ua.indexOf('Mac') != -1);
Blender.isLinux = (Blender.ua.indexOf('Linux') != -1);
Blender.avoidOpacity = (Blender.isGecko && ((Blender.isMac &&
	((Blender.ua.indexOf('rv:1.6') != -1) || (Blender.ua.indexOf('rv:1.7a') != -1))) || Blender.isLinux));



/* ====================
   Class methods ...
==================== */

Blender.findBlenders = function() {
	var divs = document.getElementsByTagName('div');
	if (divs) {
		for (var i=0; i<divs.length; i++) {
			var n = divs[i];
			if (n.className && n.className.match(/(^|\s)blender($|\s)/))
				new Blender(n);
		}
	}
}

Blender.setOpacity = function(elm, op) {
	/* op is opacity in percent! */
	if (!elm || !elm.style) return;
	var s = elm.style;
	// Check bounderies and fix image flicker with Geckos:
	op = (op >= 100) ? ((Blender.isGecko) ? 99.999 : 100) : ((op < 0) ? 0 : op);
	// fix opacity bug in Mac Gecko rv1.6:
	if ((op > 0) && Blender.avoidOpacity) op = 100;
	var opn = op/100;
	if ((typeof s.filter != 'undefined') && typeof s.opacity == 'undefined') {
		// In MSIE 6, setting opacity has to be done while object is visible!
		if (op > 0) s.visibility = 'inherit';
		s.filter = 'alpha(opacity=' + op + ')';
		if (op == 0) s.visibility = 'hidden';
	} else {
		// This uses a trick to boost Gecko performance when opn = 0
		if (op == 0) s.visibility = 'hidden';
		if (typeof s.opacity != 'undefined') {s.opacity = (opn == 0 && Blender.isGecko) ? 1 : opn}
		else if (typeof s.MozOpacity != 'undefined') {s.MozOpacity = (opn == 0 && Blender.isGecko) ? 1 : opn}
		else if (typeof s.KhtmlOpacity != 'undefined') {s.KhtmlOpacity = opn};
		if (op > 0) s.visibility = 'inherit';
	}
}

Blender.getCurrentStyle = function(elm) {
	if (!elm) return null;
	return (elm.currentStyle)
		? elm.currentStyle
		: ((document.defaultView && document.defaultView.getComputedStyle)
		? document.defaultView.getComputedStyle(elm, null)
		: null);
};

Blender.updateLayout = function() {
	for (var i=0; i < Blender.blenders.length; i++) {
		var b = Blender.blenders[i];
		if (!b.isVisible()) b.reset()
		else b.start();
	}
};



/* =================
   Initialize ...
================= */

if (document.getElementsByTagName 
	&& document.createElement
	&& (!window.opera || document.createDocumentFragment)) {

	Blender.initComplete = false;

	document.write('<style type="text/css">\n');
	document.write('.blender {position: relative; margin: 0; padding: 0;}\n');
	document.write('.blender img {position: absolute; top: 0; left: 0; visibility: hidden;}\n');
	document.write('.blender img.first {position: relative; visibility: inherit;}\n');
	if (!Blender.avoidOpacity) {
		if (Blender.isGecko) {
			document.write('.blender img.first {opacity: 0.5; -moz-opacity: 0.5;}\n');
		} else if (Blender.isMSIE && Blender.ua.match(/MSIE [567]\./)) {
			document.write('.blender img.first {filter: alpha(opacity=50);}\n');
		} else
			document.write('.blender img.first {opacity: 0.5; -khtml-opacity: 0.5;}\n');
	};
	document.write('.blender .loader {z-index: 10; visibility: inherit;}\n');
	document.write('@media print {\n');
	document.write('.blender img {\n');
	document.write('position: relative !important;\n');
	document.write('visibility: inherit !important;\n');
	document.write('display: inline !important;\n');
	document.write('opacity: 1 !important;\n');
	if (Blender.isGecko) 
		document.write('-moz-opacity: 1 !important;\n')
	else if (Blender.isMSIE && Blender.ua.match(/MSIE [567]\./))
		document.write('filter: alpha(opacity=100) !important;\n')
	else
		document.write('-khtml-opacity: 1 !important;\n');
	document.write('}\n');
	document.write('.blender .loader {display: none !important;}\n');
	document.write('.printLast img {display: none !important;}\n');
	document.write('.printLast img.last {display: inline !important;}\n');
	document.write('}\n');
	document.write('<\/style>\n');

	Blender.init = function() {
		if (Blender.initComplete) return true;
		Blender.findBlenders();
		window.setInterval(Blender.updateLayout, 500);
		Blender.initComplete = true;
		return true;
	}
	
	if (window.onDomReady) {
		window.onDomReady(Blender.init);
	} else if (typeof pageComplete == 'function') {
		Blender.oldPageComplete = pageComplete || function(){};
		pageComplete = function() {
			Blender.oldPageComplete();
			Blender.init();
		};
	} else {
		Blender.oldOnload = window.onload || function(){};
		window.onload = function() {
			Blender.oldOnload();
			Blender.init();
		};
	};

}

/* End of Blender.js */

