/*
 Copyright INRA/CNRS

 Emmanuel.Courcelle@toulouse.inra.fr
 Jerome.Gouzy@toulouse.inra.fr

 This software is a perl module whose purpose is to help you writing
 your own scripts

 This software is governed by the CeCILL license under French law and
 abiding by the rules of distribution of free software.  You can  use,
 modify and/ or redistribute the software under the terms of the CeCILL
 license as circulated by CEA, CNRS and INRIA at the following URL
 "http://www.cecill.info".

 As a counterpart to the access to the source code and  rights to copy,
 modify and redistribute granted by the license, users are provided only
 with a limited warranty  and the software's author,  the holder of the
 economic rights,  and the successive licensors  have only  limited
 liability.

 In this respect, the user's attention is drawn to the risks associated
 with loading,  using,  modifying and/or developing or reproducing the
 software by the user in light of its specific status of free software,
 that may mean  that it is complicated to manipulate,  and  that  also
 therefore means  that it is reserved for developers  and  experienced
 professionals having in-depth computer knowledge. Users are therefore
 encouraged to load and test the software's suitability as regards their
 requirements in conditions enabling the security of their systems and/or
 data to be ensured and,  more generally, to use and operate it in the
 same conditions as regards security.

 The fact that you are presently reading this means that you have had
 knowledge of the CeCILL license and that you accept its terms.

*/

/* 

=pod

=head1 NAME
 Tools.js - Some useful tools

=head1 SYNOPSIS

 FindAssociateElement Utility function used by some classes
 ExpandableClass      Expand/collapse some html elements
 Helpclass            Open/close a little div for help
 
=cut
*/

/*
=pod

=head2 Function FindAssociateElement

 Title        : FindAssociateElement
 Prerequisite : The element must exist, together with another html element:
                    - A div or a span just after img, or rank after img
                    - A div or a span just after the img's parent, or rank after the img's parent
                    - Any element whose Id is passed
 Usage        : var associate = FindAssociateElement(element,rank)
 Function     : find an element that could be "associated" to the element passed by parameter, using the following algorithm:
                If rank is a number:
                   -we look for the "rank" next div or span, taking element as a start point
                   -if not found, we look for the "rank" next div or span of element's parent
                If rank is a string:
                   -the string is taken as an Id
 Arguments    : element                  The element to be associated
                rank[optional default 0] The rank, see upper comment
                OR
                id                       The Id
 Globals      : none

=cut
*/

function FindAssociateElement(elt,rank)
{
	var rank = rank || 0;
	var ass = undefined;
	
	// If rank is a string, it should be an Id
	if (Object.isString(rank))
	{
		ass = $(rank);
		if (ass==null)
		{
			throw ("ERROR in FindAssociateElement - There is no element whose id is " + rank);
		}
	}
	
	// rank is a number
	else if (Object.isNumber(rank))
	{
		// The div or span may be a sibling
		ass = elt.next('div',rank);
		if (ass==undefined)
		{
			ass = elt.next('span',rank);
		}
		
		// try with a sibling's parent, useful if the image is inside a <h1></h1>
		if (ass==undefined)
		{
			ass = elt.up().next('div',rank);
		}
		if (ass==undefined)
		{
			ass = elt.up().next('span',rank);
		}
		if (ass==null)
		{
			throw ("ERROR in FindAssociateElement - Could not find any element with rank " + rank);
		}
	}

	// If rank is an element, we do not have anything to do
	else if (Object.isElement(rank))
	{
		ass = rank;
	}
	
	// Else, we do not know what to do !
	else
	{
		throw ("ERROR in FindAssociateElement - What is the type of rank (" + rank + ") ?");
	}
	
	// return the found (or not found) element
	return ass;
}

/*
=pod

=head1 class ExpBaseClass

 Title        : ExpBaseClass
 Prerequisite : an img and another elt must have been created
 Usage        : new ExpBaseClass(img, rank, shown)
                new ExpBaseClass(img, "some_ID", shown);
                img is an image, We use FindAssociateElement to find elt
                The img click event is observed, and when clicked:
                   -img toggles between IMG_COLLAPSE and IMG_EXPAND sources
                   -elt toggles between show/hidden status
 Constructor args: img                            The img             
                   rank/id [optional default 0]   (cf. FindAssociateElement for the details)
                   shown [optional default false] (if true, the elt is shown at start)
 Globals      : none

=cut
*/

ExpBaseClass = Class.create({
	initialize: function(elt,rank,shown)
	{
		this._elt = $(elt);
		if (this._elt==null)
		{
			throw "ERROR in ExpBaseClas - Cannot create the object because the clickable element does not exist";
		}
		var shown = shown || false;
		this.__shown_at_init = shown;
		
		this.__behaviours = new Array;
		this.__behaviours['one_expanded'] = false;
		this.__a_expand_hooks = $A();
		this.__a_collapse_hooks = $A();

		// If it is a LipmPopup, just record it
		if (rank != undefined && rank.Display != undefined && rank.Close != undefined)
		{
			this.__toggle_elt = rank;
		}
		else
		{
			this.__toggle_elt = FindAssociateElement(this._elt,rank);
		}
		
		// If still undefined, we give up
		if (this.__toggle_elt == undefined)
		{
			throw "ERROR in ExpBaseClass - Cannot create the object because the toggle element does not exist";
		};
		
		// Observe the click on elt and change the cursor
		Event.observe(elt,'click',this.ToggleElt.bindAsEventListener(this));
		this._elt.style.cursor = 'pointer';

		// Record the _ChangeElementAtXXX functions as Expand/Collapse hooks (NOTE - they should be redefined by the child class)
		this.AddExpandHook(this._ChangeElementAtExpand.bind(this));
		this.AddCollapseHook(this._ChangeElementAtCollapse.bind(this));

		// Keep track of this object
		ExpBaseClass.__a_objects.push(this);
	},
	
/*
=pod

=head1 procedure __InitializeAtEnd

 Title        : __InitializeAtEnd
 Usage        : CALL THIS PROCEDURE AT END OF INITILIZATION IN YOUR DERIVED CLASSES !!!
 Globals      : none

=cut
*/	
    _InitializeAtEnd: function()
	{
		if (this.__shown_at_init==true)
		{
			this.ShowElt();
		}
		else
		{
			this.HideElt();
		}
	},
	
/*
=pod

=head1 procedures _ChangeElementAtExpand,_ChangeElementAtCollapse

 Title        : _ChangeElementAtExpand,_ChangeElementAtCollapse
 Usage        : REDEFINED THOSES PROCEDURES IN YOUR DERIVED CLASSES
                They are recorded as the first Expand/Collapse hook
				The derived procedure must change the appearance of the element
 Globals      : none

=cut
*/
    _ChangeElementAtExpand: function(){throw('ExpBaseClass is an abstract class');},
	_ChangeElementAtCollapse: function(){throw('ExpBaseClass is an abstract class');},

/*
=pod

=head2 GetElt,GetImg

 Title    : GetElt,GetImg
 Usage    : var img = o_exp.GetElt()
 Function : Return the clickable element
 DEPRECATED - GetImg is deprecated - Use GetElt instead !

=cut
*/

    GetElt: function() { return this._elt;},
	GetImg: function() { return this.GetElt();},

/*
=pod

=head2 AddExpandHook, AddCollapseHook

 Title        : addExpandHook, addCollapseHook
 Usage        : Add a hook which will be called when the object is expanded/collapsed
                The hook is called with this as parameter
 Procedure    :  

=cut
*/
    AddExpandHook: function(hook) {this.__a_expand_hooks.push(hook);},
	AddCollapseHook:function(hook) {this.__a_collapse_hooks.push(hook);},

/*
=pod

=head2 SetBehaviour,UnsetBehaviour

 Title        : SetBehaviour,UnsetBehaviour
 Usage        : obj.SetBehaviour('one_expanded'), obj.UnsetBehaviour('one_expanded')
 Procedure    : If the behaviour is set, ONLY ONE object of this class can be expanded at a given time 

=cut
*/
	
	SetBehaviour: function(behaviour)
	{
		this.__behaviours[behaviour] = true;
	},
	UnsetBehaviour: function(behaviour)
	{
		this.__behaviours[behaviour] = false;
	},
/*
=pod

=head2 ShowElt, HideElt, ToggleElt

 Title        : ShowElt, HideElt, ToggleElt
 Usage        : obj.ShowElt();
 Procedure    : Show the element, hide the element, of toggle show/hide
                If the behaviour one_expanded is set, when ShowElt() is called on one object HideElt() is called on all other objects of same class

=cut
*/	
	ShowElt: function()
	{
		// If the behaviour is set, hide every other object of this class
		if (this.__behaviours['one_expanded']==true)
		{
			for (var i=0; i<ExpBaseClass.__a_objects.length;i++)
			{
				var o = ExpBaseClass.__a_objects[i];
				try
				{
					o.HideElt();
				}
				catch(e){};
			}
		}
		
		// Call the expand hooks and show the toggle element
		this.__a_expand_hooks.each(function(h){h.call();});
		this.__toggle_elt.show();
	},
	HideElt: function()
	{
		// Call the collapse hooks and hide the toggle element
		this.__a_collapse_hooks.each(function(h){h.call();});
		this.__toggle_elt.hide();
	},
	ToggleElt: function(event)
	{
		if (this.__toggle_elt.visible())
		{
			this.HideElt(event);
		}
		else
		{
			this.ShowElt(event);
		}
	}
});

ExpBaseClass.__a_objects = $A();

/*
=pod

=head1 class ExpImgClass

 Title        : ExpImgClass
 Derives from : ExpBaseClass
 Prerequisite : an img and another elt must have been created
 Usage        : new ExpImgClass(img, rank, shown)
                new ExpImgClass(img, "some_ID", shown);
                img is an image, We use FindAssociateElement to find elt
                The img click event is observed, and when clicked:
                   -img toggles between IMG_COLLAPSE and IMG_EXPAND sources
                   -elt toggles between show/hidden status
 Constructor args: img                            The img             
                   rank/id [optional default 0]   (cf. FindAssociateElement for the details)
                   shown [optional default false] (if true, the elt is shown at start)
 Globals      : none

=cut
*/

ExpImgClass = Class.create(ExpBaseClass,{
	initialize: function($super,elt,rank,shown)
	{
		$super(elt,rank,shown);
		if (window.IMG_COLLAPSE==undefined)
		{
			var img_path = this._elt.src.sub('[^/]*$','');
			this.__img_collapse = img_path + 'minus.png';
			this.__img_expand   = img_path + 'plus.png';
		}
		else
		{
			this.__img_collapse = IMG_COLLAPSE;
			this.__img_expand   = IMG_EXPAND;
		}
		this._InitializeAtEnd();
	},
	_ChangeElementAtExpand: function()
	{
		this._elt.alt = 'collapse';
		this._elt.src = this.__img_collapse;
	},
	_ChangeElementAtCollapse: function()
	{
		this._elt.alt = 'expand';
		this._elt.src = this.__img_expand;
	}
});


/*
=pod

=head1 class ExpTxtClass

 Title        : ExpTxtClass
 Derives from : ExpBaseClass
 Prerequisite : an <a>some_text</a> or <span<some_text</span> AND another elt must have been created
 Usage        : new ExpTxtClass(elt, rank, shown)
                new ExpTxtClass(elt, "some_ID", shown);
                elt is an element which should contain texte: a, p, etc.
                The elt click event is observed, and when clicked:
                   -the text toggles between the TxtCollapse and TxtExpand class
                   -toggle_elt toggles between show/hidden status
 Constructor args: elt                            The elt             
                   rank/id [optional default 0]   (cf. FindAssociateElement for the details)
                   shown [optional default false] (if true, the elt is shown at start)
 Globals      : none

=cut
*/

ExpTxtClass = Class.create(ExpBaseClass,{
	initialize: function($super,elt,rank,shown)
	{
		$super(elt,rank,shown);
		this._InitializeAtEnd();
	},
	_ChangeElementAtExpand: function()
	{
		this._elt.addClassName('TxtCollapse');
		this._elt.removeClassName('TxtExpand');
	},
	_ChangeElementAtCollapse: function()
	{
		this._elt.addClassName('TxtExpand');
		this._elt.removeClassName('TxtCollapse');
	}
});


/*
=pod

=head1 class ExpandableClass

 Title        : ExpandableClass
 Has a class derived from ExpBaseClass
 Prerequisite : an img, a, or other element (see the complete list in the variable tags_to_class), AND another elt (toggle_elt) must have been created
 Usage        : new ExpandableClass(elt, rank, shown)
                new ExpandableClass(elt, "some_ID", shown);
                img is an html element, We use FindAssociateElement to find elt
                The elt click event is observed, and when clicked:
                   -elt has its apparance changed
                   -toggle_elt toggles between show/hidden status
 Constructor args: elt                            The html clicked element             
                   rank/id [optional default 0]   (cf. FindAssociateElement for the details)
                   shown [optional default false] (if true, the toggle_elt is shown at start)
 Globals      : none

=cut
*/
ExpandableClass = Class.create({
	initialize: function(elt,rank,shown)
	{
		var tags_to_class = { 
			img: ExpImgClass,
			a: ExpTxtClass,
			span: ExpTxtClass
		};
		
		var tag     = $(elt).tagName.toLowerCase();
		var o_class = tags_to_class[tag];
		if ( o_class != undefined)
		{
			this.__o_exp = new o_class(elt,rank,shown);
		}
		else
		{
			throw "ERROR - The tag " + tag + " misses an Exp" + tag.capitalize() + "Class - Do you want to write it ?";
		}
	},
	GetImg: function()                  { return this.__o_exp.GetImg(); },
    AddExpandHook: function(hook)       {this.__o_exp.AddExpandHook(hook);},
	AddCollapseHook:function(hook)      {this.__o_exp.AddCollapseHook(hook);},
	SetBehaviour: function(behaviour)   {this.__o_exp.SetBehaviour(behaviour);},
	UnsetBehaviour: function(behaviour) {this.__o_exp.UnsetBehaviour(behaviour);},
	ShowElt: function ShowElt()         {this.__o_exp.ShowElt();},
	HideElt: function HideElt()         {this.__o_exp.HideElt();},
	ToggleElt: function ToggleElt()     {this.__o_exp.HideElt();}
});

/*
=pod

=head1 class HelpClass

 Title        : HelpClass
 Prerequisite : an img element, and a SIBLING div OR a parent's SIBLING div, or another element with an ID should have been created
                If not, nothing happens (no error)
 Usage        : new HelpClass(img)
                new HelpClass(img, rank)
				new HelpClass(img, "some_id")
                img is the name of an image element. The element should represent a question mark
				We use FindAssociateElement to find elt
                The img mousedown and mouseup events are observed:
				   -when the mousedown event is received, elt is displayed
				   -When the mouseup event is received, elt is hidden
 Constructor arguments:
                 img                            The img
                 rank/id [optional default 0]   (cf. FindAssociateElement for the details)
 Globals      : none

=cut
 */
 
HelpClass = Class.create({
	initialize: function(img,rank)
	{
		this.__img  = $(img);
		if (this.__img==null)
		{
			return;
		};
		
		this.elt = FindAssociateElement(this.__img,rank);
		
		// If still undefined, give up
		if (this.elt != undefined)
		{
			Event.observe(this.__img,'mousedown',this.Show.bindAsEventListener(this));
			Event.observe(this.__img,'mouseup',this.Hide.bindAsEventListener(this));
			this.Hide();
		}
	},

/*
=pod

=head2 Show,Hide

 Title    : Show or Hide
 Usage    : o_hlp.Show();
            o_hlp.Hide();
 Procedure: Show or hide the help div - should not be useful as the user has just to click/release the help img to show/hide the help

=cut
*/
	Show: function(evt){ this.elt.show();},
	Hide: function(evt){ this.elt.hide();}
	
});


/*
=pod

=head1 COPYRIGHT NOTICE

This software is governed by the CeCILL license under French law - www.cecill.info

=cut
*/



