// sm-common.js - Shared Functions - Copyright (c) 2006 ScriptingMagic.com

if (typeof ScriptingMagic == 'undefined') var ScriptingMagic = {};

/***************************************************************************
 *
 *  Common functions
 *
 ***************************************************************************/
if (typeof ScriptingMagic.Common == 'undefined')
{
	// This should have been included in String!
	String.prototype.trim = function()
	{
	    return this.replace(/^\s*(.*\S)*\s*$/, '$1');
	};
	
	ScriptingMagic.Common = {	

		version: 1.0,
		release: new Date('Nov 2, 2006'),
		
		findElements: function(tagName, attributes, list)
		{
			if (!list)
				list = [];
	
			var elements = document.getElementsByTagName(tagName);
			var i, element, add, attribute;
			
			for (i = 0; i < elements.length; i++)
			{
				element = elements[i];
				
				add = true;
				
				if (attributes)
					for (p in attributes)
					{
						attribute = element.getAttribute(p);

						if (!attribute && (p.toLowerCase() == 'for'))
							attribute = element.htmlFor; // IE madness
							
						if (!attribute || (attribute.toLowerCase() != attributes[p].toLowerCase()))
							add = false;
					}
				
				if (add)
					list[list.length] = element;
			}
	
			return list;			
		},
		
		findLabel: function(element)
		{
			var l = element.getAttribute('sm:label');
			
			if (l)
				return l;
				
			if(!l && element.tagName == 'INPUT')
			{
				if ((element.getAttribute('type') || '').toLowerCase() == 'radio')
				{
					var i, list = element.form.elements[element.name];
					for (i = 0; i < list.length; i++)
						if (l = list[i].getAttribute('sm:label'))
							break;
				}
			}
			
			if (!l && element.id)
			{
				var e = ScriptingMagic.Common.locateLabel(element.id);

				if (e)
				{
					l = ScriptingMagic.Common.extractText(e);
					l = l.replace(/:\s*$/, '');
				}
			}			
			if (!l)
				l = element.name || element.id;

			if (l)
				element.setAttribute('sm:label', l); // Cache it for later
				
			return l;
		},
		
		locateLabel: function(id)
		{
			if (id == null)
				return null;
				
			var list = findElements('label', { 'for': id });

			if (list.length > 0)
				return list[0];

			return null;
		},
		
		extractText: function(node)
		{
			if (node.nodeType == 3)
				return node.data;

			var text = '', i;
	
			for (i = node.childNodes.length - 1; i >= 0; i--)
				text += ScriptingMagic.Common.extractText(node.childNodes[i]);
			
			return text;
		},
	
		lookupEventHandler: function(target, eventType, handler)
		{
			if (typeof target.smEventHandlers == 'undefined') return null;
			if (typeof target.smEventHandlers[eventType] == 'undefined') return null;
			return target.smEventHandlers[eventType][handler.toString()];
		},
	
		saveEventHandler: function(target, eventType, handler, fn)
		{
			if (typeof target.smEventHandlers == 'undefined') target.smEventHandlers = [];
			if (typeof target.smEventHandlers[eventType] == 'undefined') target.smEventHandlers[eventType] = [];
			target.smEventHandlers[eventType][handler.toString()] = fn;
		},
	
		addEventHandler: function(target, eventType, handler)
		{
			if (target.addEventListener)
			{
				if (navigator.userAgent.match(/safari/i) && target.tagName == 'A' && eventType == 'click')
				{
					/*
					 *  This nasty hack is necessary because Safari doesn't handle
					 *  preventDefault correctly for links :(
					*/
	
					var realHandler = handler;
	
					var fn = function() { return ScriptingMagic.Common.preventDefaultForSafari(realHandler, arguments); };
	
					ScriptingMagic.Common.saveEventHandler(target, eventType, handler, fn);
	
					target.addEventListener(eventType, fn, false);
	
					handler = ScriptingMagic.Common.eventReturnForSafari;
					/*
					 *  Drop through to legacy event handler code
					*/
				}
				else
					return target.addEventListener(eventType, handler, false);
			}
			else if (target.attachEvent)
			{
				var fn = ScriptingMagic.Common.eventHandlerFunctionForIE(handler);
				ScriptingMagic.Common.saveEventHandler(target, eventType, handler, fn);
				return target.attachEvent('on' + eventType, fn);
			}
	
			var oldHandler = target['on' + eventType];
	
			target['on' + eventType] = (!oldHandler) ?
				handler :
				function(event) { return oldHandler(event) && handler(event); };
		},
	
		removeEventHandler: function(target, eventType, handler)
		{
			if (target.addEventListener)
			{
				if (navigator.userAgent.match(/safari/i) && target.tagName == 'A' && eventType == 'click')
				{
					/*
					 *  This nasty hack is necessary because Safari doesn't handle
					 *  preventDefault correctly for links :(
					*/
	
					var fn = ScriptingMagic.Common.lookupEventHandler(target, eventType, handler);
	
					if (typeof fn == 'function')
						return target.removeEventListener(eventType, fn, false);
				}
				else
					return target.removeEventListener(eventType, handler, false);
	
			}
			else if (target.attachEvent)
			{
				var fn = ScriptingMagic.Common.lookupEventHandler(target, eventType, handler);
				if (typeof fn == 'function')
					return target.detachEvent('on' + eventType, fn);
			}
	
			/*
			 *  Sorry, no way to remove legacy handlers yet :(
			*/
		},
	
		/*
		 *  This function creation has to occur outside of addEventHandler
		 *  to avoid memory leaks in IE
		*/
		eventHandlerFunctionForIE: function(handler)
		{
			return function() { ScriptingMagic.Common.eventHandlerForIE(handler, arguments); };
		},
	
		eventReturnForSafari: function(event)
		{
			var returnValue = event.target.eventReturn;
	
			delete event.target.eventReturn;
			
			return returnValue;
		},
	
		preventDefaultForSafari: function(handler, originalArguments)
		{
			if (arguments.length == 0)
			{
				this.target.eventReturn = false;
				return;
			}
	
			var event = originalArguments[0];
	
			event.target.eventReturn = true;
	
			var preventDefault = event.preventDefault;
	
			event.preventDefault = ScriptingMagic.Common.preventDefaultForSafari;
	
			var value = handler.apply(event.target, originalArguments);
	
			event.preventDefault = preventDefault;
	
			return value;
		},
	
		/*
		 *  Derived from Professional JavaScript for Web Developers Chapter 9
		*/
		eventHandlerForIE: function(handler, originalArguments)
		{
			var event = originalArguments[0];
	
			event.charCode = (event.type == "keypress") ? event.keyCode : 0;
			event.eventPhase = 2;
			event.isChar = (event.charCode > 0);
			event.pageX = event.clientX + document.body.scrollLeft;
			event.pageY = event.clientY + document.body.scrollTop;
			event.preventDefault = ScriptingMagic.Common.preventDefaultForIE;
			if (event.type == "mouseout")
				event.relatedTarget = event.toElement;
			else if (event.type == "mouseover")
				event.relatedTarget = event.fromElement;
			event.stopPropagation = ScriptingMagic.Common.stopPropagationForIE;
			event.target = event.srcElement;
			event.timeStamp = new Date().getTime();
			
			return handler.apply(event.target, originalArguments);
		},
	
		preventDefaultForIE: function() { this.returnValue = false; },
		stopPropagationForIE: function() { this.cancelBubble = true; },
		
		getSelectionIndices: function(element)
		{
			// If we weren't passed an element use document
			if (!element)
				element = document;

			// IE version
			if (element.createTextRange && !window.opera)
			{
				// Get the selected text range
				var selectedRange = document.selection.createRange();
	
				if (element.tagName == 'TEXTAREA')
				{
					// Get the complete text range for the element
					var range = selectedRange.duplicate();
					
					range.moveToElementText(element);
					range.setEndPoint('EndToEnd', range);
		
					// Set to the same variables as the ones built into Mozilla 
					element.selectionStart = range.text.length - selectedRange.text.length;
					element.selectionEnd = element.selectionStart + selectedRange.text.length;
				}
				else
				{
					// Get the complete text range for the element
					var startRange = element.createTextRange().duplicate();
					var endRange = element.createTextRange().duplicate();
		
					// Make sure the selected range is within the range for the element
					if ((startRange.compareEndPoints('StartToStart', selectedRange) == 1) ||
						(startRange.compareEndPoints('EndToEnd', selectedRange) == -1))
					{
						//mongus.error('Error: Caret appears to be outside the element!');
						return false;
					}
		
					// Place start and end at the beginning and end for the element
					var start = 0;
					var end = startRange.text.length;
		
					// Collapse the ranges to 0 width
					startRange.collapse(true);
					endRange.collapse(false);
		
					// Expand the end of the start range until we hit the selection's start
					while ((startRange.compareEndPoints('EndToStart', selectedRange) == -1) && (start < end))
					{
						startRange.moveEnd('character', 1);
						start++;
					}
		
					// Expand the start of the end range until we hit the selection's end
					while ((endRange.compareEndPoints('StartToEnd', selectedRange) == 1) && (start < end))
					{
						endRange.moveStart('character', -1);
						end--;
					}
		
					// Set to the same variables as the ones built into Mozilla 
					element.selectionStart = start;
					element.selectionEnd = end;
				}
			}
	
			return true;
		},
		
		addClass: function(element, className)
		{
			if (element.className && element.className.match(new RegExp('^(.* )?' + className + '( .*)?$')))
				return;
				
			var classes = element.className + ' ' + className;
			
			element.className = classes.trim();
		},
		
		removeClass: function(element, className)
		{
			var regex = new RegExp('^(.* )?' + className + '( .*)?$');
			
			if (!element.className || !element.className.match(regex))
				return;
			
			element.className = element.className.replace(regex, '$1 $2').replace(/\\s+/, ' ').trim();
		},
		
		callUserDefinedFunction: function(fn, args)
		{
			try
			{
				if (typeof fn == 'function')
					return fn.apply(this, args);

				var value = eval(fn);
				
				if (typeof value == 'function')
					return value.apply(this, args);

				return value;
			}
			catch (e)
			{
				return fn;
			}
		}
		
	};

	/*
	 *  Make functions easily accessible
	*/
	var findElements = ScriptingMagic.Common.findElements;
	var addEventHandler = ScriptingMagic.Common.addEventHandler;
	var removeEventHandler = ScriptingMagic.Common.removeEventHandler;
	var addClass = ScriptingMagic.Common.addClass;
	var removeClass = ScriptingMagic.Common.removeClass;
}