// ---------------------------------------------------------------------------
// asynch.js: provides javascript functions for asynch fetching
// ---------------------------------------------------------------------------
// Part of the Juniper website rendering framework, 
// http://immortalcookie.com/juniper
// Copyright (C) 2006 Eric Miller
// eric@immortalcookie.com
// 
// ---------------------------------------------------------------------------
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// Lesser GPL license text:
// http://www.gnu.org/licenses/lgpl.txt
//
// The Creative Commons GNU-LGPL Summary:
// http://creativecommons.org/licenses/LGPL/2.1/
// ---------------------------------------------------------------------------

// asynch.js
// depends on 
//      prototype package
//      asynch.php
//		js_base.js
//
// Glue between prototype's AJAX functions and Juniper's rendering engine.
// Allows asynch rendering on a per-element basis.

juniper.asynch = new Object();

with( juniper ){
	
	// maps elements waiting for asynch response -> indicator elements
	asynch.indicatorMap = new juniper.Map();
	
	// maps indicator elements -> request stacks
	asynch.requestMap = new juniper.Map();



	/* juniper.asynch.swapContentIn

	    Swap the content of a DOM element with the content of a Juniper 
		element as fetched and rendered in the current page's lineage and 
		context.
    
	    iDOMElement: id of a DOM object, the object itself or a prototype
		success/failure object. See prototype docs for more info.
    
	    iReplacementName: the name of a juniper element visible from the 
		current page's lineage and context.
    
	    iArgs: (optional) a string of juniper-formatted attribute-value 
		arguments for element iReplacementName, eg:
	        "time='morning' day='Tuesday' activity='whittling'"

		iIndicatorDOMElement: (optional, id | "auto" ) the string id of a 
		dom element in which you wish a "working" indicator <img> to appear, 
		or "auto" if you would like an indicator prepended to iDOMElement. 
		Consecutive requests specifying the same id are supported; the 
		indicator won't go away until the last request has finished.

		iLineage: override the current lineage base by passing a full fs path.
		
		iInsertion: a prototype insertion object.

	*/
	
	asynch.swapContentIn = function(	iDOMElementID, iReplacementName, 
										iArgs, iIndicatorDOMElementID, 
										iLineageBase, iInsertion )
	{
	    if( iLineageBase == undefined )
			iLineageBase = juniper.asynch.altLineageBase;
	
	    var params = 	"juniper-asynch-request=yes&juniper-replace-element=yes&alt-lineage-base=" + 
						iLineageBase + "&juniper-element=" + iReplacementName + 
						"&attributes=" + encodeURIComponent( iArgs );

		var domElement = $( iDOMElementID );

		var completionFunc;
		if( iIndicatorDOMElementID != undefined && iIndicatorDOMElementID != "" )
			completionFunc = function(){ asynch.hideIndicatorForDOMObject( domElement ) };
		else
			completionFunc = function(){};
				
	    var updater = new Ajax.Updater( domElement, asynch.endpointURL,
	                                    {
	                                        method: 'get',
	                                        parameters: params,
											insertion: iInsertion,
											onFailure: asynch.updateFailureHandler,
											onException: asynch.updateExceptionHandler,
											onSuccess: asynch.updateSuccessHandler,
											onComplete: completionFunc,
											evalScripts: true
	                                    } );

		asynch.showIndicatorFor( domElement, iIndicatorDOMElementID );

	}

	asynch.showIndicatorFor = function( iDOMElement, iIndicatorDOMElementID )
	{
	
		if( iIndicatorDOMElementID == "auto" )
		{	
			// user didn't supply an element for us. we make one up and insert it
			// before the element that will be modified by the asynch fetch
			var indicator = document.createElement( 'img' );
	
			indicator.style.textAlign = "center";
			indicator.style.verticalAlign = "middle";

			indicator.setAttribute( "src", asynch.indicatorURL );
			indicator.setAttribute( "remove", true );
		
			iDOMElement.parentNode.insertBefore( indicator, iDOMElement );
		
			asynch.requestMap.add( indicator, { requests: 1 } );
			asynch.indicatorMap.add( iDOMElement, indicator );
		}
		else if( iIndicatorDOMElementID != undefined && iIndicatorDOMElementID != "" )
		{
			// user supplied an element for us.
			var indicator = asynch.indicatorMap.valueForKey( iDOMElement );
		
			if( indicator == null )
			{
				// but the indicator img is not already present
				var indicatorContainer = $( iIndicatorDOMElementID );
				var imgs = indicatorContainer.getElementsByTagName( "img" );
				var indicator = null;
			
				if( imgs.length > 0 )
					indicator = asynch.indicatorMap.add( iDOMElement, imgs[ 0 ] );
				
				// this indicator may already exist and be running 
				if( asynch.requestMap.valueForKey( indicator ) == null )
				{
					// it is not already mapped in.
					indicator = document.createElement( 'img' );
					indicator.setAttribute( "src", asynch.indicatorURL );

					indicatorContainer.appendChild( indicator );
					asynch.indicatorMap.add( iDOMElement, indicator );				
					asynch.requestMap.add( indicator, { requests: 1 } );
				}
				else
				{
					// the indicator element should already have its img.
					// we just bump its request stack.
					asynch.requestMap.valueForKey( indicator ).requests++;
				}
			}
			else
			{	
				asynch.requestMap.valueForKey( indicator ).requests++;
			}
		}
	}

	asynch.hideIndicatorForDOMObject = function( iDOMObject )
	{
		var stackObj;
		var indicator = asynch.indicatorMap.valueForKey( iDOMObject );
		if( indicator )
			stackObj = asynch.requestMap.valueForKey( indicator );
		
		if( stackObj )
			stackObj.requests--;
		else
			throw { message:"map is not in an expected state." };

		if( stackObj.requests == 0 )
		{
			// it may no longer be in the document.
			if( typeof( indicator.parentNode ) != undefined &&
				indicator.parentNode != null )
				Element.remove( indicator );

			asynch.requestMap.erase( indicator );
			asynch.indicatorMap.erase( iDOMObject );
		}
		else
		{
			// while the indicator stack is not empty, nonetheless this waiting
			// element has completed so we should remove it from the map.
			asynch.indicatorMap.erase( iDOMObject );
		}
	}
	
	asynch.updateSuccessHandler = function( iRequest, iResponse )
	{
	}

	asynch.updateFailureHandler = function( iRequest, iResponse )
	{
		alert( "Asynch fetch server error: " + iResponse )
	}

	asynch.updateExceptionHandler = function( iRequest, iException )
	{
		alert( "Asynch fetch failed with exception: " + iException.message )
	}


	/* juniper_insertBefore
	    Like above, but inserts before the passed DOM element. 
*/
	asynch.insertBefore = function( iDOMElementID, iInsertionName, iArgs, iIndicatorDOMElementID, iLineageBase )
	{
		asynch.swapContentIn( iDOMElementID, iInsertionName, iArgs, iIndicatorDOMElementID, iLineageBase, Insertion.Before );
	}

	/* juniper_insertAfter
	    Like above, but inserts after the passed DOM element. 
*/
	asynch.insertAfter = function( iDOMElementID, iInsertionName, iArgs, iIndicatorDOMElementID, iLineageBase )
	{
		asynch.swapContentIn( iDOMElementID, iInsertionName, iArgs, iIndicatorDOMElementID, iLineageBase, Insertion.After );
	}

	/* juniper_insertTop
	    Like above, but inserts as first child of the passed DOM element. 
*/
	asynch.insertTop = function( iDOMElementID, iInsertionName, iArgs, iIndicatorDOMElementID, iLineageBase )
	{

		asynch.swapContentIn( iDOMElementID, iInsertionName, iArgs, iIndicatorDOMElementID, iLineageBase, Insertion.Top );
	}

	/* juniper_insertBottom
	    Like above, but inserts as last child of the passed DOM element. 
*/
	asynch.insertBottom = function( iDOMElementID, iInsertionName, iArgs, iIndicatorDOMElementID, iLineageBase )
	{

		asynch.swapContentIn( iDOMElementID, iInsertionName, iArgs, iIndicatorDOMElementID, iLineageBase, Insertion.Bottom  );
	}
};