/*

pdx.prototype.gadgets.projector: a prototype-based content slider
  v1.0, 2011/03/12, initial release
  v1.1, 2011/03/17, option to show only images
  v1.2, 2011/04/01, fixed the default renderer so when there is no title, it
                    won't show the overlay text box
  v1.3, 2011/05/24, major rewrite, much easier to set up, more flexible,
                    supports dud pager title, faster startup

  CC BY-SA 2011. Andras Kemeny (http://www.pdx.hu/)
    http://creativecommons.org/licenses/by-sa/3.0/legalcode
  NO WARRANTIES! however, it's tested on IE7/8, Firefox 3.5.x, Opera 10+,
    Chrome & Safari, and it works.
  REQUIRES prototype.js to work.
    http://www.prototypejs.org/
  REQUIRES script.aculo.us to work.
    http://script.aculo.us/

usage:

1. make sure there is a block element (preferrably a DIV, but it's not
   mandatory) that houses the projection. it can be relatively positioned in
   the flow or it can be absolutely positioned.
2. make sure there is a list from which we can gather all data needed. in this
   setup, i house a DL in the projection container where DT/DD tags represent
   the slides (image, link, title, text). go down and see the comments for
   _extractorDDDT() to see how i do it, but by overriding the collector option
   and writing your own collector method/function you can have such a structure
   any way you want.
3. make sure you either use the default slide renderer/image loader, or write
   your own function/method to do these tasks. see comments for _drawSlide()
   for more information.
4. call projector.init(housingElement,[{options}]) after the housing element
   and the slide list is in the html document.

important options(: defaults) for projector.init():

  switchDelay: 8
    the delay between two slides, in seconds
  slideIDPrefix: 'projSlide'
    the prefix string for all generated slides.
  pagers: 'numbers'
    if this is 'numbers', the pager control boxes will have page numbers in
    them (like: [1] [2] [3] etc.).
    if this is 'titles', the pager control boxes will have the respective
    slides' titles in them.
    if it's anything else, it's entirely up to the css to do something, and the
    page links will have a space for text.
  showPrevNext: true
    if true, we will show the previous and next slide controls.
  offsetTop: 0
  offsetLeft: 0
    sometimes the housing element's position is reported either erroneously,
    or a bit messed up, by clonePosition(). use these two values to counteract
    this so that the slides are put where they belong.
  positionContainer: true
    do a makePositioned() (IE fix) on the housing element? set to false if
    you lay the container out to an absolute position, otherwise, it's safe
    to leave it on true.
  removeOriginalContents: true
    delete all the children in the projection housing element? it's true as
    my default collector looks for the DL within the housing element, and it's
    good practice to clean up the container before we start putting the slides
    in it.
  slideCollector: false
    the default slide collection callback. set if your slide info shows up in
    different markup on your page; see _extractorDDDT's comments to learn how
    it works. if set to false, init sets it to projector._extractorDDDT.
  slideCreator: false
    the callback to create a new slide. you can override it if you want a
    different slide element structure. see the comments above _drawSlide() to
    learn how it works. if set to false, init sets it to projector._drawSlide.
  slideClicked: false
    callback to when the user clicks on any of the slides. if set to false, 
    projector.clickCurrentINS gets called, which sets window.location to the 
    link if there is one associated with the current slide. if you want to 
    override this, the function/method you specify must accept one parameter,
    the number of the slide that is clicked. you can find out the link from
    projector.slideData[<clicked>]['link'].

the following options work with default slide renderer. if you create a new
method/function for that, you can still use these, but if you create a whole
new structure, you won't need this.

  slideClassBase: 'projector'
    classname base for the elements. suffixes:
    "-slide": outermost DIV of the generated slide
    "-infobgr": the info (title/text) overlay background DIV
    "-info": the DIV containing the title and the text
    "-title": the DIV containing the title
    "-text": the DIV containing the text
    "-control": the outermost DIV containing the pagers
    "-prev": the A element of the "previous slide" control
    "-next": the A element of the "next slide" control
    "-page": the A element of a slide access control
    "-page-current": the current page A
    "-page-dud": the pager title A

*/

var projector = {
 mainDiv: null,
 controlDiv: null,
 currentSlide: 0,
 targetSlide: 0,
 maxSlides: 0,
 switchTimer: null,
 slides: [null],
 slideData: [null],
 dudTitle: false,
 dudLink: false,
 dudTarget: '_self',
 options: {
  switchDelay: 4,
  slideIDPrefix: 'projSlide',
  slideClassBase: 'projector',
  pagers: 'numbers',
  positionContainer: true,
  removeOriginalContents: true,
  slideCollector: false,
  slideCreator: false,
  slideClicked: false,
  offsetTop: 0,
  offsetLeft: 0,
  showPrevNext: true
 },
/* array _extractorDDDT():

extracts projector content list from the main container.

the following formats are recognized:

- the items must be in a list container: either <dl>, <ol> or <ul>, immediately
  contained within the main projector element.
- the slides can be either <dt>+<dd> elements (for <dl>), or <li> elements for
  <ol> and <ul>.
- the slide background image must be specified as either an "image" attribute
  of the <dt> or the <li> or as the first <img> child of the container.
- the slide link (URL) must be specified as the first <a href> element of the
  <dt> or <li> container element or as a "link" attribute of them (in this case
  the target is specified as a "linktarget" attribute). if using an <a href>,
  the "target" attribute is optional and must be either "_self" or "_blank" if
  given.
- the slide title must be the innerHTML part of the <a href> element or the
  first <span> child of the <dt> or <li> element (if not using links).
- the slide text must be either the <dd> element's contents for <dl> lists
  immediately after the <dt> element; or for <li> elements, the first <div>
  child of the <li> element.

preferred example:

 <div class="projectorDiv">
  <dl>
   <!-- the full feature set -->
   <dt><img src="slide-image-url"/><a href="slide-url" target="_self">slide-
    title</a></dt>
   <dd>slide-text</dd>
   <!-- have no image -->
   <dt><a href="slide-url" target="_self">slide-title</a></dt>
   <dd>slide-text</dd>
   <!-- have no link #1 -->
   <dt><img src="slide-image-url"/><a href="" target="_self">slide-
    title</a></dt>
   <dd>slide-text</dd>
   <!-- have no link #2 -->
   <dt><img src="slide-image-url"/>slide-title</a></dt>
   <dd>slide-text</dd>
   ...
  </dl>
 </div>

this is the default extractor. if you want to use a different structure to
represent your content slider originally, create your own function. it must
return either false if you can't parse the elements or if there's an error, or
return an array of plain objects like this:
 {
  image: 'url-to-slide-image'|false,
  link: 'url-to-jump-to'|false,
  linktarget: '_self'|'_blank',
  title: 'slide-title'|false,
  text: 'text-of-slide'|false
 }

both slide-title and text-of-slide can contain valid HTML tags, too. (they 
are added into the element by using element.innerHTML.)

special trick: if you want an extra element to go before all the other pagers,
set the item's (dt or li) class to <projector.options.slideClassBase>-page-dud,
and the title (and link, if specified) of that element will be prepended to
the pagers, as an <A class="<projector.options.slideClassBase>-page-dud">
element. if you encounter the dud item in your parser, set the
projector.dudTitle, projector.dudLink and projector.dudTarget variables in your
parser.

*/
 _extractorDDDT: function() {
	var classbase = projector.options.slideClassBase;
	var dlist = projector.mainDiv.select('dl');
	if (dlist.length==0) dlist = projector.mainDiv.select('ul');
	if (dlist.length==0) dlist = projector.mainDiv.select('ol');
	if (dlist.length==0) return(false);
	var items = dlist[0].childElements();
	if (items.length==0) return(false);
 	var result = [null];
 	var j;
 	var accu;
 	for(var i=0;i<items.length;++i) {
 		var cur = {'image':false,'link':false,'linktarget':'_self','title':false,'text':false};
 		if ((items[i].nodeName.toLowerCase()=='dt')||(items[i].nodeName.toLowerCase()=='li')) {
 			var isdud = false;
 			if (items[i].hasClassName(classbase+'-page-dud')) isdud = true;
 			var gottitle = false;
			if (items[i].hasAttribute('image')) {
				cur['image'] = items[i].getAttribute('image');
			}
			else if (items[i].down('img[src]')!==undefined) {
				cur['image'] = items[i].down('img[src]').getAttribute('src');
			}
			if (items[i].hasAttribute('link')) {
				cur['link'] = items[i].getAttribute('image');
				if (items[i].hasAttribute('linktarget')) cur['linktarget'] = items[i].getAttribute('linktarget');
			}
			else if (items[i].down('a[href]')!==undefined) {
				var subit = items[i].down('a[href]');
				if ((subit.getAttribute('href')!='')&&(subit.getAttribute('href')!='#')) {
					cur['link'] = subit.getAttribute('href');
					if (subit.hasAttribute('target')) cur['linktarget'] = subit.getAttribute('target');
				}
				cur['title'] = subit.innerHTML;
				gottitle = true;
			}
			if (gottitle===false) {
				if (items[i].down('span')!==undefined) cur['title'] = items[i].down('span').innerHTML;
			}
			if ((cur['title']!==false)&&(items[i].nodeName.toLowerCase()=='dt')&&(i<(items.length-1))&&(items[i+1].nodeName.toLowerCase()=='dd')) {
				cur['text'] = items[i+1].innerHTML;
				++i;
			}
			else if ((cur['title']!==false)&&(items[i].nodeName.toLowerCase()=='li')) {
				if (items[i].down('div')!==undefined) {
					cur['text'] = items[i].down('div').innerHTML;
				}
			}
			if ((cur['title']!==false)&&(cur['title'].strip()=='')) {
				cur['title'] = false;
				cur['text'] = false;
			}
			if ((cur['text']!==false)&&(cur['text'].strip()=='')) {
				cur['text'] = false;
			}
			if (isdud===true) {
				if (cur['title']!==false) {
					projector.dudTitle = cur['title'];
					projector.dudLink = cur['link'];
					projector.dudTarget = cur['linktarget'];
				}
			} else {
				result.push(cur);
			}
		}
 	}
 	return(result);
 },
/* element _drawSlide(int slideSerialNo):

draw a slide. you can create another method/function to do this, and then
specify the callback in the options. it must return a DIV (or another block
container) that has a correctly formed ID, a slideid attr set to currentCount,
and it must take care of binding projector.clickCurrent() to a mouseclick.
it is also highly advisable that you set up the first slide with the background
image already specified so that when the projector starts the first slide will
have a background image already loaded.

this method creates the following structure, where <id> is
  projector.options.slideIDPrefix+slideSerialNo.toString()
and <slideid> is
  slideSerialNo.toString()
and <backgroundstyle> is, for the first slide:
  style="background: url('{item.image}') no-repeat"
and options are like
  {options.optionname}
and the {item} object is like a returned array item from the collector method:

<div id="<id>" slideid="<slideid>" <backgroundstyle>
style="opacity:0;position: absolute;" 
class="{options.slideClassBase}-slide"><div
class="{options.slideClassBase}-infobgr"> </div><div 
class="{options.slideClassBase}-info" onclick="projector.clickCurrent();"><div
class="{options.slideClassBase}-title">{item.title}</div><div
class="{options.slideClassBase}-text">{item.text}</div></div></div>

if there is no image specified for the slide, then <backgroundstyle> is not
set.

if there is no title, no -infobgr and -info DIVs are created.

if the first character of the title is a !, no -infobgr and -info DIVs are 
created.

if there is no text, no -text DIV is created.

*/
 _drawSlide: function(currentCount) {
	var item = projector.slideData[currentCount];
	var classbase = projector.options.slideClassBase;
	var newSlide = new Element('div',{
	 'id':projector.options.slideIDPrefix+currentCount.toString(),
	 'slideid':currentCount.toString(),
	 'class':classbase+'-slide'
	 })
	newSlide.observe('click',projector.clickCurrent);
	if ((item['title']!==false)&&(item['title'].substr(0,1)!='!')) {
		var tcont = '<div class="'+classbase+'-title'+'">'+item['title']+'</div>';
		if (item['text']!==false) {
			tcont += '<div class="'+classbase+'-text'+'">'+item['text']+'</div>';
		}
		newSlide.appendChild(new Element('div',{'class':classbase+'-infobgr'}).update(' '));
		newSlide.appendChild(new Element('div',{'class':classbase+'-info'}).update(tcont).observe('click',projector.clickCurrent));
	}
	if (item['image']!==false) newSlide.setStyle({backgroundImage:'url('+item['image']+')',backgroundRepeat:'no-repeat'});
	newSlide.absolutize().setOpacity(0);
	return(newSlide);
 },
/* init(element projectorBlock[,object options]):
call it on window.load (not on dom:loaded, as in that stage the document is yet
to be rendered, therefore we couldn't position the slides properly), and tell
us what block element will house the projection. we automatically size all the
slides to match the housing element's dimensions.
*/
 init: function(idname) {
 	// get configured
 	if (arguments[1]) Object.extend(projector.options,arguments[1]);
 	if (!projector.options.slideCollector) projector.options.collector = projector._extractorDDDT;
 	if (!projector.options.slideCreator) projector.options.slideCreator = projector._drawSlide;
 	if (!projector.options.slideClicked) projector.options.slideClicked = projector.clickCurrentINS;
	var classbase = projector.options.slideClassBase;
 	// fetch main div
	projector.mainDiv = $(idname);
	if (projector.mainDiv===null) return(null);
	if (projector.options.positionContainer===true) projector.mainDiv.makePositioned();
	// collect slides data
	projector.slideData = projector.options.collector();
	if (projector.slideData===false) {
		projector.mainDiv.style.display = 'none';
		return(null);
	}
	if (projector.options.removeOriginalContents===true) projector.mainDiv.update('');
	projector.maxSlides = projector.slideData.length-1;
	projector.controlDiv = new Element('div',{'id':'projectorControl','class':classbase+'-control'});
	if (projector.dudTitle!==false) {
		var dlink = (projector.dudLink===false) ? "javascript:void(0);" : projector.dudLink;
		projector.controlDiv.appendChild(new Element('a',{'href':dlink,'class':classbase+'-page-dud','target':projector.dudTarget}).update(projector.dudTitle));
	}
	if (projector.options.showPrevNext===true)
		projector.controlDiv.appendChild(new Element('a',{'href':'javascript:projector.prev();void(0);','class':classbase+'-prev','title':'<'}).update(' '));
	var i;
	var pgcont;
	for(i=1;i<=projector.maxSlides;i++) {
		projector.slides.push(projector.options.slideCreator(i));
		projector.mainDiv.appendChild(projector.slides[i]);
		if (projector.options.pagers == 'numbers') pgcont = i.toString();
		else if (projector.options.pagers == 'titles') pgcont = projector.slideData[i]['title'];
		else pgcont = ' ';
		if (pgcont===false) pgcont = i.toString();
		if (pgcont.substr(0,1)=='!') pgcont = pgcont.substr(1);
		projector.controlDiv.appendChild(new Element('a',{'href':'javascript:projector.stopAndGoto('+i.toString()+');void(0);','projects':i.toString(),'class':classbase+'-page'}).update(pgcont));
	}
	if (projector.options.showPrevNext===true)
		projector.controlDiv.appendChild(new Element('a',{'href':'javascript:projector.next();void(0);','class':classbase+'-next'}).update(' '));
	projector.mainDiv.appendChild(projector.controlDiv);
	projector.controlDiv.absolutize();
	projector.resizedHap(null);
	Event.observe(window,'resize',projector.resizedHap);
 },
 resizedHap: function() {
 	if (projector.maxSlides>0) {
		for(var i=1;i<=projector.maxSlides;i++) {
			projector.slides[i].clonePosition(projector.mainDiv,{offsetLeft:projector.options.offsetLeft,offsetTop:projector.options.offsetTop});
		}
	 	projector.controlDiv.clonePosition(projector.mainDiv,{setWidth:false,setHeight:false,offsetLeft:projector.options.offsetLeft,offsetTop:projector.options.offsetTop});
	 	if (projector.currentSlide==0) {
	 		projector.targetSlide = 1;
	 		projector.switchTo();
	 	}
	 }
 },
 clickCurrent: function() {
 	if (projector.currentSlide!=0) {
		projector.options.slideClicked(projector.currentSlide);
	}
 },
 clickCurrentINS: function(which) {
 	if ((which>0)&&(projector.slideData[which])&&(projector.slideData[which]['link']!==false)) {
		if (projector.slideData[which]['linktarget']!='_self') {
			window.open(projector.slideData[which]['link']);
		} else {
			window.location = projector.slideData[which]['link'];
		}
 	}
 },
 stopAndGoto: function(which) {
 	if (projector.switchTimer!==null) clearTimeout(projector.switchTimer);
 	projector.targetSlide = which;
 	projector.switchTo();
 },
 prev: function() {
 	if (projector.switchTimer!==null) clearTimeout(projector.switchTimer);
 	if (projector.currentSlide == 1) {
 		projector.targetSlide = projector.maxSlides;
 	} else {
 		projector.targetSlide = projector.currentSlide-1;
 	}
 	projector.switchTo();
 },
 next: function() {
 	if (projector.switchTimer!==null) clearTimeout(projector.switchTimer);
 	if (projector.currentSlide == projector.maxSlides) {
 		projector.targetSlide = 1;
 	} else {
 		projector.targetSlide = projector.currentSlide+1;
 	}
 	projector.switchTo();
 },
 switchTo: function() {
 	projector.switchTimer = null;
 	if (projector.currentSlide!=0) {
 		new Effect.Opacity(projector.slides[projector.currentSlide],{from:1.0,to:0.0,duration:0.5});
 	}
 	projector.currentSlide = projector.targetSlide;
 	new Effect.Opacity(projector.slides[projector.currentSlide],{from:0.0,to:1.0,duration:0.5});
	var classbase = projector.options.slideClassBase;
 	projector.controlDiv.select('[projects]').each(function(el) {
 		var cw = el.getAttribute('projects');
 		if (parseInt(cw)!=projector.currentSlide) {
 			if (el.hasClassName(classbase+'-page-current')) el.removeClassName(classbase+'-page-current');
 		} else {
 			if (el.hasClassName(classbase+'-page-current')===false) el.addClassName(classbase+'-page-current');
 		}
 	});
 	projector.switchTimer = setTimeout(projector.next,projector.options.switchDelay*1000);
 }
}
document.observe('dom:loaded',projector.resizedHap);

