// IOTBS1.2[DC] :: Attack of the Body Switchers: "The Director's Cut"
// ***********************************************
// This copyright statement must remain in place for both personal and commercial use
// GNU General Public License -- http://www.gnu.org/copyleft/gpl.html
// ***********************************************
// Original concept by Andy Clarke -- http://www.stuffandnonsense.co.uk/
// DOM scripting by brothercake -- http://www.brothercake.com/
// Create element and attributes based on a method by beetle -- http://www.peterbailey.net/
//************************************************
function iotbs() { //open initialisation function 
//************************************************

//initialise the preferences manager ('canvas-element')
switcher = new switchManager('body');

/*****************************************************************************
 Define switching controls
*****************************************************************************/


//create a new switcher control ('container-id', 'label', '"selected" text')
var screenSwitcher = new bodySwitcher('screen-switcher', '', '');

//add a new class option ('classname', 'label')
screenSwitcher.defineClass('default', '1  ');
screenSwitcher.defineClass('high', '2  ');
screenSwitcher.defineClass('highvisibility', '3  ');

/*
var printSwitcher = new bodySwitcher('print-switcher', 'Print styles', ' [selected]');
printSwitcher.defineClass('default', 'Default');
printSwitcher.defineClass('small-sans', 'Small sans');
printSwitcher.defineClass('large-serif', 'Large serif');


var projectionSwitcher = new bodySwitcher('projection-switcher', 'Projection styles', ' [selected]');
projectionSwitcher.defineClass('default', 'Default');
projectionSwitcher.defineClass('fluid', 'Fluid layout');

*/
/*****************************************************************************
*****************************************************************************/



//close initialisation function
};


//global switch manager reference
var switcher;


//setup initialisation function
//.. gecko, safari, konqueror and generic
if(typeof window.addEventListener != 'undefined')
{
	window.addEventListener('load', iotbs, false);
}
//.. opera 7
else if(typeof document.addEventListener != 'undefined')
{
	document.addEventListener('load', iotbs, false);
}
//.. win/ie
else if(typeof window.attachEvent != 'undefined')
{
	window.attachEvent('onload', iotbs);
	
	//add DOM cleaning function for win/ie
	window.attachEvent('onunload', function()
	{
		//for each item in the document.all collection
		this.daLen = document.all.length;
		for(var i=0; i<this.daLen; i++)
		{
			//set its onchange property to null
			//so that if this object is one of our selectors
			//the property can be garbage collected
			document.all[i]['onchange'] = null;
		}
	});
}



//switch manager 
function switchManager(canvas)
{
	//string for storing the overall custom classname
	//I was originally storing it in the body class name directly
	//but 1.7+ mozilla builds were not honouring the trailing whitespace we need
	this.string  = '';
	
	//store reference to canvas element
	this.canvas = document.getElementsByTagName(canvas)[0];

	//store the initial classname
	this.initial = this.canvas.className;
	
	//if the default classname is empty, add "iotbs"
	//because we need there to be at least one classname already - 
	//the leading and trailing space in each custom classname is required, 
	//but you can't set a classname attribute as " something" (beginning with a leading space)
	//because that fails in some Opera 7 builds
	if(this.initial == '')
	{
		this.initial = 'itobs';
	}
	
	//look for a stored cookie
	this.cookie = this.read();

	//if it exists
	if(this.cookie != null)
	{
		//store cookie value to string
		this.string = this.cookie;
		
		//set new canvas class name
		this.canvas.className = this.initial + this.string;
	}
	
	//*** dev
	//document.title = '<' + this.canvas.className.replace(/( |%23)/g,'+') + '>   [' + this.string.replace(/( |%23)/g,'+') + ']';
	
};

//set a cookie method
switchManager.prototype.set = function(days)
{
	//format expiry date
	this.date = new Date();
	this.date.setTime(this.date.getTime() + ( days *24*60*60*1000));
	
	//store the string, replacing spaces with '#' so that leading spaces are preserved
	this.info = this.string.replace(/ /g,'#');
	
	//if the value is empty, set its expiry in the past to delete the cookie
	if(this.info == '') { this.date.setTime(0); }
	
	//create the cookie
	document.cookie = 'bodySwitcher=' + this.info
		+ '; expires=' + this.date.toGMTString() 
		+ '; path=/';
		
};

//read a cookie method
switchManager.prototype.read = function()
{
	//set null reference so we always have something to return
	this.cookie = null;
	
	//if a cookie exists
	if(document.cookie)
	{
		//if it's our cookie
		if(document.cookie.indexOf('bodySwitcher')!=-1)
		{
			//extract and store relevant information (turning '#' back into spaces)
			this.cookie = document.cookie.split('bodySwitcher=');
			this.cookie = this.cookie[1].split(';');
			this.cookie = this.cookie[0].replace(/#/g,' ');
		}
	}
	
	return this.cookie;
};



//switcher-control constructor
function bodySwitcher(divid, label, selected)
{
	//don't continue if the container doesn't exist
	if(document.getElementById(divid) == null) { return false; }

	//create an associate array of classnames and labels for this option
	//so we can later iterate through and remove them from the custom classname string
	//and so we have scope-global references to the key and value for each item
	this.classes = [], this.labels = [];

	//container div
	this.span = document.getElementById(divid);

	//definition list
	this.dl = this.span.appendChild(this.createElement('span'));

	//definition term [switcher heading containing label text]
	this.attrs = { 'text' : label };
	this.dt = this.dl.appendChild(this.createElement('span', this.attrs));
	
	//"selected" text
	this.selected = selected;
	
	return true;
};


//add a new class option method
bodySwitcher.prototype.defineClass = function(key, val)
{
	//don't continue if the list doesn't exist
	if(typeof this.dl == 'undefined') { return false; }
	
	//definition inside list
	this.item = this.dl.appendChild(this.createElement('b'));

	//if key is default
	if(key == 'default')
	{
		//add plain text inside definition
		this.link = this.item.appendChild(document.createTextNode(val + this.selected));
	}
	
	//else if cookie exists and value contains this key
	else if(switcher.cookie != null && switcher.cookie.indexOf(' ' + key + ' ')!=-1) 
	{
		//add plain text inside definition
		this.link = this.item.appendChild(document.createTextNode(val + this.selected));
		
		//if this is *not* the default item
		if(key != 'default')
		{
			//turn default text back into a link
			//we're doing it this way, instead of just creating a default link
			//because at the point where the default item is created
			//we don't know whether it should be a link or not
			//so we create it as plain text to begin with, and convert it here
			//because we know, now, that the default should be a link
			
			//so .. store existing text
			this.text = this.dl.childNodes[1].firstChild.nodeValue;
			
			//remove the text node
			this.dl.childNodes[1].removeChild(this.dl.childNodes[1].firstChild);
			
			//and replace it with a link to select the default
			this.attrs = { 'href' : 'javascript:void("' + this.classes[0] + '", "' + this.labels[0] + '")', 'text' : this.labels[0] };
			this.link = this.dl.childNodes[1].appendChild(this.createElement('a', this.attrs));
		}
	}

	//else if cookie doesn't exist or value doesn't contains this key
	else
	{
		//add link inside definition
		//javascript: uri is used to store key value
		//and so that the link is navigable with the keyboard
		this.attrs = { 'href' : 'javascript:void("' + key + '", "' + val + '")', 'text' : val };
		this.link = this.item.appendChild(this.createElement('a', this.attrs));
	}
	
	//create a global [within this scope] reference to 'this'
	var self = this;
	
	//bind onclick handler to list-item
	this.item.onclick = function()
	{
		//if this item has no link, don't continue
		if(this.getElementsByTagName('a').length == 0) { return false; }
		
		//run through classnames array
		this.classLen = self.classes.length;
		for(var i=0; i < this.classLen; i++)
		{
			//remove this key (custom class name) from string
			switcher.string = switcher.string.replace(' ' + self.classes[i] + ' ','');
		}

		//get key and value from link href
		//unescaping is necessary in safari
		this.key = unescape(this.firstChild.href).split('"')[1];
		this.val = unescape(this.firstChild.href).split('"')[3];

		//if it isn't default then add to string
		//we need both a leading and a trailing space to work with 
		//to avoid confusion with identical leading or trailing substrings in classnames,
		//such as "high" and "highcontrast" or "large-serif" and "small-serif"
		if(this.key != 'default')
		{
			switcher.string += ' ' + this.key + ' ';	
		}

		//get definitions in this list
		this.items = self.dl.getElementsByTagName('b');
		this.itemsLen = this.items.length;
		
		//for each definition
		for(i=0; i < this.itemsLen; i++)
		{
			//if it's the previously selected value it will only be a text node
			if(this.items[i].firstChild.nodeName == '#text')
			{
				//remove the text node
				this.items[i].removeChild(this.items[i].firstChild);
				
				//and replace it with a link to reselect this style
				this.attrs = { 'href' : 'javascript:void("' + self.classes[i] + '", "' + self.labels[i] + '")', 'text' : self.labels[i] };
				this.link = this.items[i].appendChild(self.createElement('a', this.attrs));
			}
		}
			
		//for each definition again
		//we're doing this as two discreet loops
		//because the first process needs to have run over the whole list already
		//so that the link we're sending focus to definitely exists
		for(i=0; i < this.itemsLen; i++)
		{
			//if it's this one
			if(this.items[i] == this)
			{
				//if this is the last item, send focus to the first item
				//if it's any other item, send focus to the next item
				//this is because the activated link just had the focus
				//and removing a focussed element can cause 
				//the page focus caret to be lost or reset to the top 
				//an added benefit is that it allows you to toggle between 
				//all styles in a group by repeatedly pressing enter
				this.items[ (i == this.itemsLen - 1 ? 0 : i + 1) ].firstChild.focus();
				
				//remove the link
				this.removeChild(this.firstChild);
				
				//add a text node label to replace the link
				this.appendChild(document.createTextNode(self.labels[i] + self.selected));
			}
		}
		
		//set new canvas class name
		switcher.canvas.className = switcher.initial + switcher.string;

		//store changes to a cookie which expires a year from now
		switcher.set(365);
		
		//*** dev
		//document.title = '<' + switcher.canvas.className.replace(/( |%23)/g,'+') + '>   [' + switcher.string.replace(/( |%23)/g,'+') + ']';

		return true;
	};

	//store the classname 
	this.classes[this.classes.length] = key;
	
	//store the link text labels
	this.labels[this.labels.length] = val;
	
	return true;
};


//create element and attributes method -- http://www.codingforums.com/showthread.php?s=&postid=151108
bodySwitcher.prototype.createElement = function(tag, attrs)
{
	//detect support for namespaced element creation, in case we're in the XML DOM
	this.ele = (typeof document.createElementNS != 'undefined') ? document.createElementNS('http://www.w3.org/1999/xhtml',tag) : document.createElement(tag);

	//run through attributes argument
	if(typeof attrs != 'undefined')
	{
		for(var i in attrs)
		{
			switch(i)
			{
				//create a text node
				case 'text' :
					this.ele.appendChild(document.createTextNode(attrs[i]));
					break;
				
				//create a class name
				case 'class' : 
					this.ele.className = attrs[i];
					break;
				
				//create a for attribute 
				case 'for' : 
					this.ele.setAttribute('htmlFor',attrs[i]);
					break;
				
				//create a generic attribute using IE-safe attribute creation
				default : 
					this.ele.setAttribute(i,'');
					this.ele[i] = attrs[i];
					break;
			}
		}
	}
	return this.ele;
};




