/* uccs-nav.js

 *

 * Written by Joseph A LaConte on August 30, 2007 

 *

 * JavaScript Navigation to interact with scriptaculous effects

 * Requires scriptaculous.js (for Effects functions and $ function)

 *   - could use other library for effects.

 * Requires navigation to be nested in an element with id = uccsnav.navContainerID

 * Requires Category to be the HTML element right before group of category links

 *   - links must be grouped (<div>, <ul>, etc) following the category element

 *

 * Notes:

 * $(id) === document.getElmentById(id) <- except $() should be browser independant

 */

uccsnav = {

	//Boolean to determine if UCCS navigation has been initialized

	initialized: false,

	usingScriptaculous: true,

	

	//ID of element containing the navigation

	navContainerID: 'uccs_nav', 

	

	/* Category Container 

	 * - if the categories are sub-nested below the navcontainer 

	 * ie. categories are children of the category container and not the navcontainer */ 

	categoryContainerTag: null,

	categoryContainerClass: null,

	

	// categoryTag - Element tag used to define (nest) category

	categoryTag: 'span',

	// categoryClass - class of ALL elements within navContainer that will act as a category (elements below will be shown/hidden upon event) 

	categoryClass: 'uccs_trigger', 

	// categoryClassOpen - class used in conjuction with categoryClass to specify that the default behavior for element below is shown (open/expanded)

	categoryClassOpen: 'uccs_trigger_open',

	categoryClassClose: 'uccs_trigger_close',

	// Alt tags for opened or closed category elements

	categoryAltOpen: 'Close Category',

	categoryAltClose: 'Open Category',

	categoryArray: new Array(),

	categoryArrayKeys: new Array(),

    categoryActiveEffect: new Array(),

    categoryOpenArray: new Array(),


    linkHighlight: 'uccs_active_link',

	

	//initializeCategoryClose: 'uccsnav.CloseCategory',

    initializeCategoryClose: 'uccsnav.CloseCategoryNoEffect',

	

    // class of the element that is the subject of the effect

	//fxSubjectClass: 'uccs_nav_fx', 



    effectOpen: 'Effect.BlindDown',

	effectOpenParameters: {duration:0.3},

	effectClose: 'Effect.BlindUp',

	effectCloseParameters: {duration:0.5},



	/* Initialize - find categories */

    Initialize: function() {

	  //alert("initializing");	

	

	  // only process the first time - allows other functions to call/ensure initialization

	  // before processing

	  if (uccsnav.initialized) {

		return;  

	  }

	  

	  if (! $(uccsnav.navContainerID) ) {

		  //alert("ERROR");

		  uccsnav.ErrorHandler("UCCS Navigation requires a navigation container (usually a div) whose id is " + uccsnav.navContainerID);

		  return; // may not be necissary depending on functionality in ErrorHandler

	  }

	  //alert ('Found navContainerID');

	  

	  // start search at first child of nav container

	  var categoryNode = $(uccsnav.navContainerID).firstChild;



      // Ensure we are using a good element before entering loop & starting search

	  // want non-null and ELEMENT_NODEs - cycle through blanks, etc

	  while (categoryNode && categoryNode.nodeType!=1) {

		categoryNode = categoryNode.nextSibling;

	  }

	  

	  // Find all category nodes

	  while (categoryNode) {

	    categoryNode = uccsnav.FindNextCategory(categoryNode);

		

	    if (categoryNode) {

		  // store nodes and their keys

	      uccsnav.categoryArray[categoryNode.innerHTML] = categoryNode;

		  uccsnav.categoryArrayKeys[uccsnav.categoryArrayKeys.length] = categoryNode.innerHTML;

		  

		  // default effect

		  uccsnav.InitializeCategory(categoryNode);

		  

		  // add trigger

		  categoryNode.onclick = function() {

			  uccsnav.ToggleCategory(this);

		  }

		  // only move to next node if current node is not null

		  categoryNode = categoryNode.nextSibling;

		}		



        // cycle through to next good element - or null (end)

	    while (categoryNode && categoryNode.nodeType!=1) {

		  categoryNode = categoryNode.nextSibling;

	    }

	  }

	  

	  



	  

	  /*

	  for (var i in uccsnav.categoryArray) {

		var alertString = 'Found: ' + i;

	    alert(alertString);

      }*/

	  /*

	  for (var i=0; i<uccsnav.categoryArrayKeys.length; ++i) {

	    alert(uccsnav.categoryArrayKeys[i]);

		var currentCategoryName = uccsnav.categoryArrayKeys[i];

		var currentNode = uccsnav.categoryArray[currentCategoryName];

		currentNode = currentNode.nextSibling;

		while (currentNode && currentNode.nodeType!=1) {

		  currentNode = currentNode.nextSibling;

	    } 

		Effect.Squish(currentNode);

		//currentNode.className = 'uccs_hidden';

	  }*/

	  

	  uccsnav.initialized = true;

	  uccsnav.categoryActiveEffect= new Array();

	},

	

	InitializeCategory: function(categoryNode) {

	  if ( uccsnav.ContainsClass(categoryNode, uccsnav.categoryClassOpen) ) {
	    uccsnav.OpenCategory(categoryNode);	  
	  }
      else if (  in_array(categoryNode.innerHTML, uccsnav.categoryOpenArray) ) {
		uccsnav.AddClass(categoryNode, uccsnav.categoryClassOpen);
		uccsnav.OpenCategory(categoryNode);  
	  }
	  else {
	    //uccsnav.CloseCategory(categoryNode);
		eval(uccsnav.initializeCategoryClose+'(categoryNode)');
	  }

	},

	

	/* FindNextCategoryNode 

	 * - Exits if finds category node or iterates through all children.

     */

	FindNextCategory: function(currentNode) {



	  //alert ('starting loop');

	  

	  // Ensure we are using a good element before searching it for category match

	  // want non-null and ELEMENT_NODEs - cycle through blanks, etc

	  while (currentNode && currentNode.nodeType!=1) {

		currentNode = currentNode.nextSibling;

	  }

	  

	  // cycle through each node until match or no more (null is sibling)

	  while (currentNode) {

		

		// find category node if matches class

	    if ( uccsnav.IsCategory(currentNode) ) {

		  return currentNode;

	    }



        // iterate to next ELEMENT_NODE unless no more

		currentNode = currentNode.nextSibling;

		

		while (currentNode && currentNode.nodeType!=1) {

		  currentNode = currentNode.nextSibling;

	    } 

	  }

	  return null;

	},

	

	IsCategory: function(element) {

	  return uccsnav.ContainsClass(element, uccsnav.categoryClass);

	},

	

	ToggleCategory: function(element) {

       if ( uccsnav.ContainsClass(element, uccsnav.categoryClassOpen) ) {

         uccsnav.CloseCategory(element);   

	   }

	   else {

		 uccsnav.OpenCategory(element);   

	   }

	},



	ToggleCategoryByName: function(categoryName) {

	  if ( uccsnav.categoryArray[categoryName] ) {

        uccsnav.ToggleCategory(uccsnav.categoryArray[categoryName]);

	  }

	  else {

		uccsnav.ErrorHandler('Failed to toggle category by name: '+categoryName); 

	  }

	},



    OpenAllCategories: function() {

      for (var i=0; i<uccsnav.categoryArrayKeys.length; ++i) {

		var currentCategoryName = uccsnav.categoryArrayKeys[i];

        uccsnav.OpenCategory(uccsnav.categoryArray[currentCategoryName]);

	  }

	},

	

	CloseAllCategories: function() {

      for (var i=0; i<uccsnav.categoryArrayKeys.length; ++i) {

		var currentCategoryName = uccsnav.categoryArrayKeys[i];

        uccsnav.CloseCategory(uccsnav.categoryArray[currentCategoryName]);

	  }      

	},



	CloseCategory: function(element) {

		// do not activate code if an effect is active for this category

		if (uccsnav.categoryActiveEffect[element.innerHTML]) {

		  return;

		}

		

		uccsnav._CloseCategory(element);



		var currentNode = uccsnav.CategoryLinks(element);

		//Effect.Squish(currentNode);

		if (!uccsnav.usingScriptaculous) {

		  eval(uccsnav.effectClose+'(currentNode'+uccsnav.effectCloseParameters+')');

		}

		else {	  

          //Toggle active effect for category ON

		  uccsnav.categoryActiveEffect[element.innerHTML] = true;



          // add queue info to parameters - NOT NEEDED

		  uccsnav.effectCloseParameters.queue = { position:'end', scope: element.innerHTML, limit: 1 };

		  

		  // add event 'afterFinish' info to parameters - Reset active toggle to OFF

          uccsnav.effectCloseParameters.afterFinish = function(effect) { uccsnav.categoryActiveEffect[effect.options.queue.scope]=false; };



          // activate effect

          eval(uccsnav.effectClose+'(currentNode, uccsnav.effectCloseParameters)');

          

		}

	},

	

	CloseCategoryNoEffect: function(element) {

	    uccsnav._CloseCategory(element);

		uccsnav.CategoryLinks(element).style.display='none';

	},

	

	_CloseCategory: function(element) {

		// if open class, remove

		if ( uccsnav.ContainsClass(element, uccsnav.categoryClassOpen) ) {

			uccsnav.ReplaceClass(element, uccsnav.categoryClassOpen, '');

		}

		// if not close class, add

		if (! uccsnav.ContainsClass(element, uccsnav.categoryClassClose) ) {

			uccsnav.AddClass(element, uccsnav.categoryClassClose);

		}

		

		element.setAttribute('alt', uccsnav.categoryAltClose);

	},

	

	OpenCategory: function(element) {

		// do not activate code if an effect is active for this category

		if (uccsnav.categoryActiveEffect[element.innerHTML]) {

		  return;

		}

		element.setAttribute('alt', uccsnav.categoryAltOpen);

		

		// if close class, remove

		if ( uccsnav.ContainsClass(element, uccsnav.categoryClassClose) ) {

			uccsnav.ReplaceClass(element, uccsnav.categoryClassClose, '');

		}

		// if not open class, add

		if (! uccsnav.ContainsClass(element, uccsnav.categoryClassOpen) ) {

			uccsnav.AddClass(element, uccsnav.categoryClassOpen);

			

			// add effects if not already open

			var currentNode = uccsnav.CategoryLinks(element);

		    //Effect.Appear(currentNode);

			//eval(uccsnav.effectOpen+'(currentNode'+uccsnav.effectOpenParameters+')');

			

			if (!uccsnav.usingScriptaculous) {

		      eval(uccsnav.effectOpen+'(currentNode'+uccsnav.effectOpenParameters+')');

		    }

		    else {



              //Toggle active effect for category ON

			  uccsnav.categoryActiveEffect[element.innerHTML] = true;



              // add queue info to parameters - NOT NEEDED

			  uccsnav.effectOpenParameters.queue = { position:'end', scope: element.innerHTML, limit: 1 };

			  

			  // add event 'afterFinish' info to parameters - Reset active toggle to OFF

              uccsnav.effectOpenParameters.afterFinish = function(effect) { uccsnav.categoryActiveEffect[effect.options.queue.scope]=false; };

              // activate effect

			  eval(uccsnav.effectOpen+'(currentNode, uccsnav.effectOpenParameters)');

		    }

		}		

		

	},



	/* JOEY: FindActiveLink - [highlightURL] written by Joseph A LaConte on August 23, 2007

	 *       highlights a link in the navigation system if it is active */

	FindActiveLink:function(){

	  

	

	    var anchorElements;

		var matchingElement = null;

		// JOEY: Exit if the following functionality does not exist

		if(!document.getElementById){return;}

		if(!uccsnav.navContainerID){return;} 

		else if(document.getElementById(uccsnav.navContainerID)){

		/* JOEY: If parentElementId is specified attempt to get all elements that 

		 *       are specified (in dc object) as triggers within that parent element */

			anchorElements=document.getElementById(uccsnav.navContainerID).getElementsByTagName('a');

		}else{

		//JOEY: If parentElementId is specified but does not exist end.

			return;

		}

		

		

        //JOEY: may only be fully browser compatible with gloabl (full) URLs instead of relative

        var httpRegExp = new RegExp(/^http/);

		var testString;

		for (var i=0; i<anchorElements.length; ++i) {

		  //JOEY: http[s] is only protocol we care about - displayed in browser

		  if (! httpRegExp.test(anchorElements[i].href)) {

		    continue;	

		  }

		  // JOEY: link must be a substring of current URL

		  if (anchorElements[i].href.length > document.URL.length) {

			continue;  

		  }

		  // JOEY: break the strings down into matching length for comparison

		  testString = document.URL.substring(0, anchorElements[i].href.length);

		  if (testString == anchorElements[i].href) {

			//alert(anchorElements[i].href); 

			//JOEY: keep the one with greatest length - since most specific (least general)

			if (matchingElement == null) {

			  matchingElement = anchorElements[i];

			}

			else {

			  if (matchingElement.href.length < anchorElements[i].href.length) {

				matchingElement = anchorElements[i];  

			  }

			}

		  }

		}

		//alert(matchingElement.href);

		

		// only need to expand category(ies) if a match is found

		if ( matchingElement) {

		  // only highlight link if exact match, but open category

		  if (matchingElement.href.length == document.URL.length) {

		    //JOEY: replace existing class and add custom class to matching element

		    //matchingElement.className = uccsnav.linkHighlight;

		    uccsnav.AddClass(matchingElement, uccsnav.linkHighlight);

		  }

		

          //JOEY: call open (expand event) on parent if part is trigger element

          //var parentElement = matchingElement.parentNode;

		

          //JOEY: already checked that parentElementId is defined and exists

          //JOEY: cycle up node tree until we hit parentElement (upper level container for nav)

          var parentElement = matchingElement.parentNode;

          while (parentElement.id != uccsnav.navContainerID) { 

            //alert(parentElement.tagName);



		    /*** JOEY: tested for cycling backwards */

            var sibling = parentElement.previousSibling;

            while(sibling && sibling.nodeType!=1)

            {

              sibling=sibling.previousSibling;

            }

            if ( uccsnav.IsCategory(sibling) ) {

              uccsnav.AddClass(sibling, uccsnav.categoryClassOpen);

            }

		  

            parentElement = parentElement.parentNode;

          }

	    }

        if (! uccsnav.initialized) {

          uccsnav.Initialize();

	    }

	},


    /* AddOpenCategory
	 *
	 * Specify the name(s) of category(ies) to leave open on load (check after open class is checked)
	 */
    AddOpenCategory: function(categoryName) {
		uccsnav.categoryOpenArray[categoryName] = categoryName;
	},


	/* CategoryLinks

	 *   category - category node

	 *   return the container of links associated with this category */

	CategoryLinks: function(element) {

      var currentNode = element.nextSibling;

		while (currentNode && currentNode.nodeType!=1) {

		  currentNode = currentNode.nextSibling;

	  }



	  return currentNode;

	},



	ContainsClass: function(element, myClass) {

	  /* check for class name between anchors or spaces

	    - anchors: first class, last class, only class 

		- spaces: adjacent to another class */

		return new RegExp("(^|\\s)" + myClass + "(\\s|$)").test(element.className);

	},

	

	ReplaceClass: function(element, findClass, replaceClass) {

		// explicit

		//element.setAttribute( 'className', element.className.replace(findClass, replaceClass) );

		element.className = element.className.replace(findClass, replaceClass);

	},

	

	AddClass: function(element, newClass) {

		// check to see if has any class assigned

		if (element.className != '') {

		  // only add if does not already have

		  if (! uccsnav.ContainsClass(element, newClass) ) {

			//element.setAttribute('className', element.className + ' ' + newClass);

		    element.className = element.className + ' ' + newClass;

		  }

		}

		else {

		  //element.setAttribute('className', newClass);

		  element.className = newClass;

		}

	},

	

	

	AddEvent: function(element, action, eventHandler) {

	  //alert('Adding Event');

      //http://developer.mozilla.org/en/docs/DOM:element.addEventListener

      if (element.addEventListener){

        element.addEventListener(action, eventHandler, false); 

      } else if (element.attachEvent){

        element.attachEvent('on'+action, eventHandler);

      }

	  else {

		element['on' + action] = eventHandler;  

	  }

	},

	

	ErrorHandler: function(errorMsg) {

	  throw(errorMsg);

	}

	

};







//may be an easier way to do this by converting to string then checking string, etc
function in_array(needle, searchArray) {
  for (value in searchArray) {
	if (value == needle) {
	  return true;	
	}
  }
  return false;
}






//uccsnav.AddEvent(window, 'load', uccsnav.Initialize);

uccsnav.AddEvent(window, 'load', uccsnav.FindActiveLink);