/*##############################################################################
#    ____________________________________________________________________
#   /                                                                    \
#  |               ____  __      ___          _____  /     ___    ___     |
#  |     ____       /  \/  \  ' /   \      / /      /__   /   \  /   \    |
#  |    / _  \     /   /   / / /    /  ___/  \__   /     /____/ /    /    |
#  |   / |_  /    /   /   / / /    / /   /      \ /     /      /____/     |
#  |   \____/    /   /    \/_/    /  \__/  _____/ \__/  \___/ /           |
#  |                                                         /            |
#  |                                                                      |
#  |   Copyright (c) 1999-2007                        MindStep SCOP SARL  |
#  |   Herve Masson                                                       |
#  |                                                                      |
#  |      www.mindstep.com                              www.mjslib.com    |
#  |   info-oss@mindstep.com                           mjslib@mjslib.com  |
#   \____________________________________________________________________/
#
#  Version: $Id: fieldgroup.js 3416 2007-08-06 16:52:31Z herve $
#
#  [This product is distributed under a BSD-like license]
#  
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions
#  are met:
#  
#     1. Redistributions of source code must retain the above copyright
#        notice, this list of conditions and the following disclaimer.
#  
#     2. Redistributions in binary form must reproduce the above copyright
#        notice, this list of conditions and the following disclaimer in
#        the documentation and/or other materials provided with the
#        distribution. 
#  
#  THIS SOFTWARE IS PROVIDED BY THE MINDSTEP CORP PROJECT ``AS IS'' AND
#  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
#  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MINDSTEP CORP OR CONTRIBUTORS
#  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
#  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
#  OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
#  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
#  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
#  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#  
#  The views and conclusions contained in the software and documentation
#  are those of the authors and should not be interpreted as representing
#  official policies, either expressed or implied, of MindStep Corp.
#  
##############################################################################*/


var gFGR_disClassDefault  = undefined;
var gFGR_groupList        = [];
var gFGR_groupTable       = {};
var gFGR_groupCtrlTable   = {};
var gFGR_uniquegid        = 1;
var gFGR_radioGroups      = {};

var gFGR_ctrlTable;


//--------------------------------------------------------------------------------
//	
//	Event handling - algorithm is a little obscure (a tradeoff with concision...)
//	
//	Behavior "enable": we maintain a counter for every element, which keeps
//	track of how many controllers asked to disable this element. When this
//	counter reaches 0, we can safely enable the element. The trick is to
//	get this counter initialized, this is where it gets a little scabrous...
//	
//--------------------------------------------------------------------------------

function _fgr_runEnableMode(ctrl)
{
	var id=mjs_allocateElementID(ctrl);
	var list=gFGR_ctrlTable[id];
	var value=ctrl.checked;
	var oldval,positive=0;
	var behav=ctrl._fgr_behav;

	if(!list || !behav['enable'])
	{
		return true;
	}

	if(mjs_valued(oldval=ctrl._fgr_oldstate))
	{
		if(oldval==value)
		{
			// Nothing changed => ignore (this is not only cosmetic,
			// if we don't do that, our counters will be messed up)
			return;
		}
		positive=1;
	}

	for(var i=0;i<list.length;i++)
	{
		// For each group it controls...
		var members=list[i]['members'];

		for(var j=0;j<members.length;j++)
		{
			// For each element of this group
			var oldval;
			var el=members[j];
			var ocount=el._fgr_counter;
			var css=mjs_inheritAttrDfl(el,"mjsdisabledclass",gFGR_disClassDefault);

			// Alter counter according to controller state
			var count=(value)?ocount+positive:ocount-1;
			if((count>=0) && (ocount<0))
			{
				// We reach zero ? => good, we can enable the field
				el.disabled=false;
				mjs_rmElementClass(el,css);
				if(behav['clear'])
				{
					mjs_setFieldValue(el,el._fgr_oldval);
				}
			}
			else if((count==-1) && (ocount==0))
			{
				// Just lose editability
				el.disabled=true;
				mjs_addElementClass(el,css);
				if(behav['clear'])
				{
					el._fgr_oldval=mjs_getFieldValue(el);
					mjs_emptyFieldValue(el);
				}
			}
			el._fgr_counter=count;
		}
	}
	ctrl._fgr_oldstate=value;
	return true;
}

function _fgr_runRadioEnableMode(ctrl,ev,radiomembers)
{
	// Radio buttons are a little more tricky since we also need to 
	// apply state change for other sibling fields (they will not
	// get the "onchange" callback invoked)

	for(var i=0;i<radiomembers.length;i++)
	{
		_fgr_runEnableMode(radiomembers[i]);
	}
	return true;
}

function _fgr_applyBehavior(ctrl)
{
	var id=mjs_allocateElementID(ctrl);
	var list=gFGR_ctrlTable[id];
	var behav=ctrl._fgr_behav;

	for(var i=0;i<list.length;i++)
	{
		// For each group it controls...
		var members=list[i]['members'];
		var code;

		for(var j=0;j<members.length;j++)
		{
			// For each element of this group
			var el=members[j];

			if(behav['copy'])
			{
				mjs_setFieldValue(el,mjs_getFieldValue(ctrl));
			}
			if(behav['clear'])
			{
				mjs_emptyFieldValue(el);
			}
			if(behav['check'])
			{
				el.checked=true;
			}
			if(behav['toggle'])
			{
				el.checked=!el.checked;
			}
			if(code=behav['code'])
			{
				code(el);
			}
		}
	}

	return true;
}


//--------------------------------------------------------------------------------
//	
//	Field groups initialization
//	
//--------------------------------------------------------------------------------

function _fgr_initGroups()
{
	gFGR_ctrlTable={};

	for(var i=0;i<gFGR_groupList.length;i++)
	{
		_fgr_initGroup(gFGR_groupList[i]);
	}

	var ids = gFGR_ctrlTable.keys();
	for(var i=0;i<ids.length;i++)
	{
		var ctrl=document.getElementById(ids[i]);
		_fgr_runEnableMode(ctrl);
	}
}

function _fgr_initGroup(group)
{
	var i,j,el;

	var ctrlList=group['controllers'];
	var members=group['members'];

	for(i=0;i<members.length;i++)
	{
		el=members[i];
		el._fgr_counter=0;
	}

	for(i=0;i<ctrlList.length;i++)
	{
		var ctrl=ctrlList[i];
		var id=mjs_allocateElementID(ctrl);

		if(!mjs_valued(ctrl._fgr_behav))
		{
			mjs_attachFieldGroupBehavior(ctrl,"enable");
		}
		var behav=ctrl._fgr_behav;

		if(!gFGR_ctrlTable[id])
		{
			gFGR_ctrlTable[id]=[group];
		}
		else
		{
			// The controller belongs to more than one group
			gFGR_ctrlTable[id].push(group);
		}
		ctrl._fgr_oldstate=undefined;

		if(behav['enable'])
		{
			if(mjs_isRadioButton(ctrl))
			{
				// Radio buttons are special:
				// we need to know about notification for every radio of the same radio-group
				// because the event model is a little fucked up

				var rgname=_fgr_radioGroupID(ctrl);
				var list=gFGR_radioGroups[rgname];

				for(var j=0;j<list.length;j++)
				{
					mjs_setOnClickCallback("fglupdate",list[j],_fgr_runRadioEnableMode,list);
				}
				continue;
			}
			else if(mjs_isCheckBox(ctrl))
			{
				mjs_setOnClickCallback("fglupdate",ctrl,_fgr_runEnableMode);
				continue;
			}
		}
		else if(behav['copy'] || behav['toggle'] || behav['check'] || behav['clear'] || behav['code'])
		{
			if(ctrl.tagName=="A")
			{
				if(mjs_empty(ctrl.href))
				{
					ctrl.href="javascript:void(0);";
				}
				mjs_setOnClickCallback("fglupdate",ctrl,_fgr_applyBehavior);
				continue;
			}
			if(mjs_isRadioButton(ctrl) || mjs_isCheckBox(ctrl) || mjs_isButton(ctrl))
			{
				mjs_setOnClickCallback("fglupdate",ctrl,_fgr_applyBehavior);
				continue;
			}
		}
		LOGERROR("controller %s of unsupported type <%s> - ignored",id,ctrl.tagName);
	}
}

//--------------------------------------------------------------------------------
//	
//	This function creates a unique ID for the radio group
//	
//--------------------------------------------------------------------------------

function _fgr_radioGroupID(ctrl)
{
	var str="",form,name;

	if(mjs_valued(form=ctrl.form))
	{
		str += mjs_allocateElementID(form) + "_";
	}
	return str+ctrl.name;
}

//--------------------------------------------------------------------------------
//	
//	Because of the event model associated with radio buttons, we need
//	to register a callback for *every* radio elements within a group,
//	so that notification go through properly. Too bad that the "onchange"
//	event does not work as it should by firing the callback when the radio
//	get turned off because another were selected ! 
//	
//--------------------------------------------------------------------------------
 
function _fgr_registerRadioButton(field)
{
	var rgname=_fgr_radioGroupID(field);
	var list=gFGR_radioGroups[rgname];

	if(!mjs_valued(list))
	{
		LOGTRACE("registering new radio-button group '%s'",rgname);
		gFGR_radioGroups[rgname]=list=new Array;
	}
	list.push(field);
}


//--------------------------------------------------------------------------------
//	
//	Parses the DOM tree and extract the field group information
//	
//--------------------------------------------------------------------------------

function _fgr_addControlToGroup(el,groupName)
{
	var id=mjs_allocateElementID(el);
	var key=id+":"+groupName;

	if(gFGR_groupCtrlTable[key])
	{
		// do not add the controller as group members
		return;
	}
	if(!gFGR_groupTable[groupName])
	{
		var grp={ name: groupName, members: [el], controllers: [] };
		gFGR_groupList.push(grp);
		gFGR_groupTable[groupName]=grp;
		return;
	}
	gFGR_groupTable[groupName]['members'].push(el);
}

function _fgr_addToGroup(el,groupName)
{
	var	controls=mjs_lookupEditableElements(el);

	for(var i=0;i<controls.length;i++)
	{
		_fgr_addControlToGroup(controls[i],groupName);
	}
}

function _fgr_addToGroups(el/*,groups*/)
{
	for(var i=1;i<arguments.length;i++)
	{
		_fgr_addToGroup(el,arguments[i]);
	}
}

function _fgr_addCtrlToGroups(ctrl/*,groupNames*/)
{
	var id=mjs_allocateElementID(ctrl);

	for(var i=1;i<arguments.length;i++)
	{
		var groupName=arguments[i];
		if(!gFGR_groupTable[groupName])
		{
			var grp={ name: groupName, members: [], controllers: [ctrl] };
			gFGR_groupList.push(grp);
			gFGR_groupTable[groupName]=grp;
		}
		else
		{
			gFGR_groupTable[groupName]['controllers'].push(ctrl);
		}
		var key=id+":"+groupName;
		gFGR_groupCtrlTable[key]=true;
	}
}


function _fgr_createFromMembers(/* members */)
{
	var i,id,el;
	var groupName="__fieldgroup"+gFGR_uniquegid++;

	for(var i=0;i<arguments.length;i++)
	{
		id=arguments[i];
		if(!(el=document.getElementById(id)))
		{
			LOGERROR("cannot add element #%s to group %s - no such element",id,groupName);
			continue;
		}
		_fgr_addToGroup(el,groupName);
	}
	return groupName;
}


function _fgr_initialize()
{
	var stack=[];

	function _fgr_parse(el)
	{
		var i,j,l=el.childNodes,l2,attr;

		for(i=0;i<l.length;i++)
		{
			var child=l[i];

			if(child.getAttribute != void(0))
			{
				var doMgsgroup=true;

				if(attr=child.getAttribute("mjstype"))
				{
					if(!strcasecmp("fieldgroup",attr))
					{
						id=mjs_allocateElementID(child);
						stack.push(id);
						_fgr_parse(child);
						stack.length--;
						continue;
					}

					if(!strcasecmp("fieldgroupcontroller",attr))
					{
						doMgsgroup=false;
						if(attr=child.getAttribute("mjsmembers"))
						{
							// Creates and populate a new group
							var grpName=_fgr_createFromMembers(attr.split("&"));
							_fgr_addCtrlToGroups(child,grpName);
						}
						else if(mjs_valued(attr=mjs_attr(child,"mjsgroup")))
						{
							// This elements join groups as controller
							_fgr_addCtrlToGroups(child,attr.split("&"));
						}
						else
						{
							// No members and no group associated ?
							// => We need to find a controller in its ancestors
							if(stack.length==0)
							{
								LOGERROR("field group controller <%s> has no fieldgroup ancestor - ignored",
									mjs_allocateElementID(child));
								continue;
							}
							_fgr_addCtrlToGroups(child,stack[stack.length-1]);
						}
						if(attr=child.getAttribute("mjsgroupmode"))
						{
							mjs_attachFieldGroupBehavior(child,attr);
						}
					}
				}

				switch(child.tagName)
				{
				case "INPUT":
					if(mjs_isRadioButton(child))
					{
						// Radiobuttons require extra attention
						// we need to keep a register of their grouping
						_fgr_registerRadioButton(child);
					}
					if(child.type == "hidden")
					{
						// Do not disable hidden fields
						break;
					}

				case "SELECT":
				case "TEXTAREA":
					for(j=0;j<stack.length;j++)
					{
						_fgr_addToGroup(child,stack[j]);
					}
				}

				if(doMgsgroup && (attr=child.getAttribute("mjsgroup")))
				{
					// This elements join a group as simple member
					_fgr_addToGroups(child,attr.split("&"));
				}
			}
			_fgr_parse(child);
		}
	}

	_fgr_parse(document.body);
	_fgr_initGroups();
}

mjs_registerModule("fieldgroup",_fgr_initialize);


//--------------------------------------------------------------------------------
//
//	Public javascript API 
//
//--------------------------------------------------------------------------------

function mjs_setDisabledClass(className)
{
	gFGR_disClassDefault=className;
}

function mjs_attachFieldGroupBehavior(child,attr)
{
	var list=attr.split("&");
	var obj={};

	for(var i=0;i<list.length;i++)
	{
		var a=list[i].split("=",2);
		if(a.length>1)
		{
			obj[mjs_lc(a[0])]=a[1];
		}
		else
		{
			obj[mjs_lc(list[i])]=true;
		}
	}

	var str;
	if(str=obj['code'])
	{
		eval("obj['code']=function(obj){ "+str.replace(/this/g,"obj")+"}");
	}

	child._fgr_behav=obj;
	return true;
}


