
/**
* This file contains a collection of JavaScript functions
* required by various Knickers components.
*
* This is not meant to be an extensive library;
* at some point we will be including a third-party JavaScript library for 
* underlying functionality -- the functions here are Knickers-specific functions.
*
*@author Knickers Dev Team <dev@knickersproject.org>
*$Id: common.js,v 1.24 2009/06/17 14:24:41 abassett Exp $
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is knickersproject.org code.
*
* The Initial Developer of the Original Code is
* SurfMerchants LLC.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*    Knickers Dev Team <dev@knickersproject.org>
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of either the MPL or the LGPL.
*
* ***** END LICENSE BLOCK *****
*/

/**
* Find a document element based on the DOM id.
* This will be replaced!!
*
* Inspired by Michael Foster's (Cross-Browser.com) xGetElementById
*
*@access public
*@param string DOM id of element to find
*@param object Optional document object (uses the current document by default)
*@return object
*/
function k_getElementById(elemid, doc)
{
	if(!doc)
		doc = document;
	
	if(typeof(elemid) != 'string') 
		return elemid;
	
	var ret;
	
	if(doc.getElementById) 
		ret = doc.getElementById(elemid);
	else if(doc.all) 
		ret = doc.all[elemid];
	else 
		ret = null;
	
	return ret;
}
 /**
* this is a specialized function for dynamically adding new things
* takes an id of a block containing inputs, figures out what they should
* be named and sticks them in front of the block
*
*@access public
*@param string DOM id of block of inputs
*@return object
*/
function k_addAdders(addBlockId)
{
 	var addBlock = k_getElementById(addBlockId);
	if(!addBlock)
		return alert('Could not add, sorry, please complain to jim');
	
	var holder = document.createElement(addBlock.tagName);
	if(addBlock.psuedoID == null)
		k_findSpawned(addBlock);
	else
		addBlock.psuedoID ++;
	var sRegExInput = new RegExp(addBlockId, "g"); 
	
	if(addBlock.tagName.toUpperCase() == 'TR')
	{
		var kids = addBlock.childNodes.length;
		for(var i=0;i<kids;i++)
		{
			var kid = addBlock.childNodes[i];
			//newKid = document.createElement(kid.tagName);
			var newKid = kid.cloneNode(false); //clone node instead of new so we get any attributes (like colspan)
			var addHTML = kid.innerHTML;
			if(addHTML)
			{
				addHTML = addHTML.replace(sRegExInput,addBlockId+'-'+addBlock.psuedoID);
				newKid.innerHTML = addHTML;
				holder.appendChild(newKid);
			}
		}
		addBlock.parentNode.insertBefore(holder,addBlock);
	}
	else
	{
		var addHTML = addBlock.innerHTML;
		addHTML = addHTML.replace(sRegExInput,addBlockId+'-'+addBlock.psuedoID);
		holder.innerHTML = addHTML;
		var kids = holder.childNodes.length;
		for(var i=0;i<kids;i++)
		{
			addBlock.parentNode.insertBefore(holder.childNodes[i].cloneNode(true),addBlock);
		}
	}
}

function k_cleanSubmit(form)
{
	if( !form.onsubmit ||
		(form.onsubmit && form.onsubmit()))
		form.submit();
}

function k_changePage(pageNum,currPageElemId)
{
	pageControl = k_getElementById(currPageElemId);
	pageControl.value = pageNum;
	k_cleanSubmit(pageControl.form);
	return false;
}

 
 /**
* this is a specialized function for dynamically adding new things
* takes an element and looks for other inputs with the same dynamic id
* as inputs in the given element
*
*@access private
*@param element to look for inputs in
*@return object
*/
function k_findSpawned(elem)
{
	var uglyID, cleanID, elemCount, inputs;
	var maxID = 0;
	//first try to find what DYN id this guy is
	var html = elem.innerHTML;
	var dynID = html.match(/:DYN\d+/);
	//then find its form
	inputs = elem.getElementsByTagName('input');
	if(inputs.length == 0)
		inputs = elem.getElementsByTagName('select');
	if(inputs.length != 0)
	{
		form = inputs[0].form;
		//then find any inputs with this DYN id
		elemCount = form.elements.length;
		for(var i=0;i<elemCount;i++)
		{
			if(form.elements[i].name && form.elements[i].name.match(dynID+'-'))
			{
				uglyID = form.elements[i].name.match(/-\d+\_/);
				if(uglyID)
					cleanID = uglyID[0].match(/\d+/)[0];
				//and find the max input id
				if(cleanID > maxID)
					maxID = cleanID
			}
		}
	}
	elem.psuedoID = (maxID/1)+1;
 }
 
  /**
* this is a specialized function for dynamically adding new things
* finds blocks of inputs and clears them out so they dont get submitted
*
*@access public
*@return object
*/
 function k_clearAdder()
 {
	formLen = document.forms.length;
	for(var i=0;i<formLen;i++)
	{
		elemLen = document.forms[i].elements.length
		for(var j=0;j<elemLen;j++)
		{
			elem = document.forms[i].elements[j];
			if (elem && elem.tagName == "FIELDSET")
				continue;
			if(elem && elem.name.match(/DYN\d+_/))
			{
				elem.parentNode.removeChild(elem);
				j--;
			}
		}
	}
 }
 
 /**
 *this function sets the output format for the page. 
 *
 * @access public
 * @return void
 * @param elem the form to set the output type on (this is probably the form that will be submitted)
 * @param string the output type 
 */
 function k_setOutputFormat(form, output_type)
 {
	 elem = form.PAGE___SUBMIT;
	 
	 if(elem)
		 elem.value = output_type;
		 
	 return true;
 }
 
 
 //***************** translation fxns *****************************
/**
* adds an input to the translation form, we input the file and the text field
* usually called onchange from a trasnlation input, requires there to be a form
* named Knickers_Translator
*
* @return void
* @access public
* @param element the element we are adding to the form
*/
function k_trans_update_string(tranNode)
{
	//make a the input for the string
	tranString = document.createElement("input");
	tranString.setAttribute("type", "hidden");
	tranString.setAttribute("name", tranNode.name);
	tranString.setAttribute("value", tranNode.value);
	document.Knickers_Translator.appendChild(tranString);
	
	//make the filename input
	tranFile = document.createElement("input");
	tranFile.setAttribute("type", "hidden");
	tranFile.setAttribute("name", tranNode.getAttribute('filePath'));
	tranFile.setAttribute("value", tranNode.getAttribute('filename'));
	document.Knickers_Translator.appendChild(tranFile);
}


/**
* called onload to put the page into translation mode. disables all non-transltion
* inputs and tries to clean up strings in links and buttons
*
* @return void
* @access public
*/
function k_start_translation_mode()
{

	formCount = document.forms.length
	
	for(var k=0;k<formCount;k++)
	{
		//invalidate this form!
		if(document.forms[k].name != 'Knickers_Translator')
		{
			elements = document.forms[k].elements;
			elemCount = elements.length;
			for (var j=0;j<elemCount;j++)
			{
				if(elements[j].type == 'button' || elements[j].type == 'submit') 
				{
					span = document.createElement("span");
					span.innerHTML =  elements[j].value;
					elements[j].parentNode.replaceChild(span,elements[j])
					j--;
					elemCount--;
				}
				else if(elements[j].className != 'translator')
					elements[j].disabled = true;
					
			}
		}
	}
	

	linkCount = document.links.length;
	for(var j=0; j<linkCount; j++)
	{
		tranny = __k_find_trans_input(document.links[j]);
		if(tranny)
		{
			document.links[j].parentNode.replaceChild(tranny,document.links[j]);
			//js ftw
			j--;
			linkCount--;
		}
	}

}


/**
* internal function used for cleaning things in links 
*
* @return element a translator input
* @access public
* @param element
*/
function __k_find_trans_input(elem)
{
	nodeCount = elem.childNodes.length
	for(var j=0;j<nodeCount;j++)
	{
		if(elem.childNodes[j].className == 'translator')
			return elem.childNodes[j];
		if(elem.childNodes[j].childNodes.length > 0)
		{
			node = __k_find_trans_input(elem.childNodes[j]);
			if(node)
				return node;
		}
	}
	return false;
}

/**
*  add a sort
*
* @return void
* @access public
* @param string namespace of this sorter
* @param string field to sort by
* @param string direction to sort in
*/
function __k_addSort(namespace,field,direction)
{
	var fieldElem = k_getElementById(namespace+'SORT_FIELD');
	var dirElem =  k_getElementById(namespace+'SORT_DIRECTION');
	if(!fieldElem || !dirElem)
		return alert('could not sort, master sort inputs not found.');
	
	fieldElem.value = field;
	dirElem.value = direction;
	
	k_cleanSubmit(fieldElem.form);
}

// ***** start simple AJAX mechanism ***** 
// states we provide to the callback funtion
var K_AJAX_NOT_SUPPORTED = -1;
var K_AJAX_LOADING = 1;
var K_AJAX_COMPLETE_FAILURE = -2;
var K_AJAX_COMPLETE_SUCCESS = 10;

// states from the internal request 
var XMLHTTP_RESPONSE_COMPLETE = 4;
var XMLHTTP_STATUS_SUCCESS = 200;

// this will store our cached requests
var k_AJAXRequests = new Object();

/**
* This is an attempt at a making AJAX requests relatively easy to deal with.
* Basically, you set up a callback function as an event handler for a few different 
* states. This callback will be called during the course of a request initiated by
* calling k_remoteRequest().
*
* This mechanism also provides some caching; that is, if you've already made a request 
* with a given url and post params, it will not make the request again -- it will
* simply call your callback function with a copy of the previous result.
*
* The callback function will always be called with an k_AJAXRequest object and a
* K_AJAX_ constant representing the state.
*
* Example usage:
* 
* function myCallback(request, status)
* {
* 	switch(status)
* 	{
* 		case K_AJAX_NOT_SUPPORTED:
* 			alert('Sorry, your browser does not appear to support remote requests.');
* 			break;
* 		case K_AJAX_LOADING:
* 			alert('Loading!');
* 			break;
* 		case K_AJAX_COMPLETE_FAILURE:
* 			alert('Sorry, we could not complete your request at this time.');
* 			break;
* 		case K_AJAX_COMPLETE_SUCCESS:
* 			alert('Complete!' + request.getResponseText());
* 			break;
* 	}
* }
* 
* k_remoteRequest('http://domain.com/somepage.php', 'myCallback');

*
*@access public
*@param string remoteURL - full absolute URL for the request
*@param string callbackFunction - gets called with the k_AJAXRequest object and a K_AJAX_ constant representing the state.
*@param string postParams - if these are provided, we will do a POST instead of a GET
*@param string otherParams - anything else you want for future reference, but not part of the actual request; accesible as AJAXRequest.otherParams.
*@param boolean async - should this request be make asynchronously? true by default.
*@param boolean nocache - normally requests are cached...if you don't want it to cache, send true here.
*@return k_AJAXRequest
*/
function k_remoteRequest(remoteURL, callbackFunction, postParams, otherParams, async, nocache)
{
	var key = remoteURL + '|' + postParams;
	
	// cached?
	if(!nocache && k_AJAXRequests[key])
	{
		// create a new object with our parameters,
		// and just set the response accordingly
		var cached = new k_AJAXRequest(remoteURL, callbackFunction, postParams, otherParams, async);
		cached.request = k_AJAXRequests[key].request;
		cached.status = k_AJAXRequests[key].status;
		eval(cached.callbackFunction)(cached, cached.status);
	}
	else
	{
		k_AJAXRequests[key] = new k_AJAXRequest(remoteURL, callbackFunction, postParams, otherParams, async);
		k_AJAXRequests[key].makeRequest();
	}
}

/**
* The following function is a simplified version 
* of the bind function from the "prototype.js" library.
*@see http://prototype.conio.net/, http://www.sergiopereira.com/articles/prototype.js.html
*
* Returns an instance of the function pre-bound to the 
* function(=method) owner object.
*/
Function.prototype.bind = function(objToOperateOn) 
{
	var that = this;
	return function() 
	{
		return that.apply(objToOperateOn, arguments);
	}
}

/**
* This is the AJAXRequest class. Typically you don't want to use this directly;
* call k_remoteRequest() instead.
*
*@access public
*@param string remoteURL - full absolute URL for the request
*@param string callbackFunction - gets called with the k_AJAXRequest object and a K_AJAX_ constant representing the state.
*@param string postParams - if these are provided, we will do a POST instead of a GET
*@param string otherParams - anything else you want for future reference, but not part of the actual request; accesible as AJAXRequest.otherParams.
*@param boolean async - should this request be make asynchronously? true by default.
*@return k_AJAXRequest
*/
function k_AJAXRequest(remoteURL, callbackFunction, postParams, otherParams, async)
{
	this.remoteURL = remoteURL;
	this.callbackFunction = callbackFunction;
	this.postParams = postParams;
	this.otherParams = otherParams;
	this.request = null;
	this.async = (async == null) ? true : async;
}

/**
* This simply calls the callback function when the request is complete,
* passing the AJAXRequest object.
* 
*/
k_AJAXRequest.prototype.onStateChange = function() 
{
	// if the request shows "complete"
	if(this.request.readyState == XMLHTTP_RESPONSE_COMPLETE)
	{
		if(this.request.status == XMLHTTP_STATUS_SUCCESS)
		{
			// all is well!
			this.status = K_AJAX_COMPLETE_SUCCESS;
			eval(this.callbackFunction)(this, K_AJAX_COMPLETE_SUCCESS);
		}
		else
		{
			// uh oh!
			this.status = K_AJAX_COMPLETE_FAILURE;
			eval(this.callbackFunction)(this, K_AJAX_COMPLETE_FAILURE);
		}
	}
}

/**
* This sets up and makes the request
*/
k_AJAXRequest.prototype.makeRequest = function()
{
	// create a request/transport object
	// this version for Firefox, Safari, etc...
	if (window.XMLHttpRequest)
		this.request = new XMLHttpRequest();
	// this for IE...
	else if (window.ActiveXObject)
		this.request = new ActiveXObject("Microsoft.XMLHTTP");
	else
	{
		this.status = K_AJAX_NOT_SUPPORTED;
		eval(this.callbackFunction)(this, K_AJAX_NOT_SUPPORTED);
		return false;
	}
	
	// set the state change callback function
	this.request.onreadystatechange = this.onStateChange.bind(this);
	
	// tell the callback we're about to load...
	this.status = K_AJAX_LOADING;
	eval(this.callbackFunction)(this, K_AJAX_LOADING);
	
	try
	{
		// are we POST'ing or GET'ing?
		if(this.postParams && this.postParams.length > 0)
		{
			this.request.open('POST', this.remoteURL, this.async);
			this.request.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
			this.request.send(this.postParams);
		}
		else
		{
			// simple GET
			this.request.open('GET', this.remoteURL, this.async);
			this.request.send(null);
		}
	}
	catch(exception)
	{
		// uh oh!
		this.exception = exception;
		this.status = K_AJAX_COMPLETE_FAILURE;
		eval(this.callbackFunction)(this, K_AJAX_COMPLETE_FAILURE);
	}
}

/**
* Convenience method to return the response text from the request
* or the exception text if there was a problem.
*/
k_AJAXRequest.prototype.getResponseText = function()
{
	if(this.exception)
		return this.exception;
	
	return this.request.responseText;
}

// ***** end simple AJAX mechanism ***** 
/**
* Stop event proprogation
*
*@access public
*@param Event
*@return void
*/
function bubblePop(event)
{
	event = (event) ? event : window.event;
	event.cancelBubble = true;
	if (event.stopPropagation) 
		event.stopPropagation();
	
}



