/**
 * help_bubble.js Created 10/6/2009 by NYU Division of Libraries LITS/Web Services
 * This is a javascript library that allows users to create popup dialog bubbles with help content.
 **/




/***********
 * GLOBALS *
 ***********/

var b_url = 'http://library.nyu.edu/common/retrieve_file_contents.php'; //default script for retrieving AJAX responses 
var replace_urls = new RegExp(/^((http:\/\/stage.library.nyu.edu:8000\/info)|(http:\/\/library.nyu.edu\/info)|(\/info\/))/); //RegEx to specify which anchor HREFs to replace
var new_el;
var x_text = "[x]";




/******************
 * MAIN FUNCTIONS *
 ******************/


/**
 * Initializes links to help pages by wrapping them in a <span> on page load and setting their mouse events
 *
 * @param wrapper_url                  the URL to the script that retrieves external URLs via AJAX call
 * @param default_event                set all unset bubble popup links on the page to have this event, either onclick|onmousover
 * @param optional_class [optional]    css class to key off of for info links
 * @return                             nothing
 **/
function initHelpBubble(wrapper_url,default_event,optional_class) {
        //If there is not wrapper_url, use the default 
        if (wrapper_url && wrapper_url != "") 
             b_url = wrapper_url;

        if (optional_class && optional_class != "") 
             anchors = 'a.' + optional_class
        else 
             anchors = 'a'

	var new_el, s;
	for (var i=0;i<$$(anchors).length;i++) {
	 if (replace_urls.test($$(anchors)[i])) {
		s = ($$(anchors)[i]).cloneNode(true);
		new_el = document.createElement('span');
		
		if (default_event) {
			if (!s.hasAttribute("onmouseover") && !s.hasAttribute("onclick")) {
				s.setAttribute(default_event, "javascript:showHelpBubble(this);return false;");
			}
		} else {
			if (!s.hasAttribute("onclick")) {
				s.setAttribute("onclick", "javascript:showHelpBubble(this);return false;");
			}
		}
        
		new_el.appendChild(s);
		($$(anchors)[i]).parentNode.replaceChild( new_el, ($$(anchors)[i]) ); 
	 } 
	}
}


/**
 * Generates help bubble
 *
 * @param el                      the anchor element that is calling this function
 * @param helpHtml [optional]     content for the help bubble manually passed in as HTML
 * @return                        nothing
 **/
function showHelpBubble(el,helpHtml) {
        hideAllOpenBubbles(); //hide open bubbles on each new call

	//Get document width
	var docwidth = getDocDim('width');

	//Generate the help bubble element and insert it into the DOM
        //This function sets global var new_el
	createBubbleElements(el,helpHtml);
	
	//Recreate the anchor's events
	//If function was called with onclick, make next onclick call hideBubble()
	if (el.hasAttribute("onclick") && !el.hasAttribute("onmouseover")) { 
		 el.removeAttribute("onclick");
                 //Include helpHtml variable if present
		 if (helpHtml)
	 	   el.setAttribute("onclick", "javascript:hideHelpBubble(this,'"+helpHtml+"');return false;"); 
	 	 else
	 	   el.setAttribute("onclick", "javascript:hideHelpBubble(this);return false;"); 
	} 
	//If function was called by onmouseover, make onclick false
	else if (el.hasAttribute("onmouseover")) {
		 el.setAttribute("onclick","javascript:return false;");
	}

	//Get bubble's absolute offsets
	new_el_offset = getAbsoluteOffsets(new_el);

	//Reassign CSS class based on position of bubble element on page
	//If the element extends beyond the width of the page, show bubble to the left
	if ((new_el_offset[0] + new_el.getWidth()) > docwidth) {
	  //If element extends above the height of the screen, show bubble below
 	  if ((new_el_offset[1]) < 0) {
 	    new_el.removeAttribute('class','bubble-topright');
 	    new_el.setAttribute('class','bubble-bottomleft bubble-help');
 	  } 
 	  //Otherwise, show bubble to the top left
 	  else {
 	    new_el.removeAttribute('class','bubble-topright');
 	    new_el.setAttribute('class','bubble-topleft bubble-help');
 	  }
	} 
	//If the element extends above the height of the screen, show bubble below
	else if ((new_el_offset[1]) < 0) {
 	  new_el.removeAttribute('class','bubble-topright');
 	  new_el.setAttribute('class','bubble-bottomright bubble-help');
	} //Otherwise leave the bubble in the initial spot at topright

	return false;
	
}


/**
 * Creates DOM elements which comprise the help bubble
 *
 * @param el                      the anchor element that is calling this function's parent
 * @param helpHtml [optional]     content for the help bubble manually passed in as HTML
 * @return                        nothing
 **/
function createBubbleElements(el,helpHtml) {
	var parent = el.parentNode; //the containing span element
	
	/*BELOW: generate elements*/
	
	//Add bubble class to span container generated by initHelpBubble()
	parent.setAttribute("class","bubble-container");

	//Generate div bubble
	new_el = document.createElement('div');
	
	//Set default class so bubble has initial offsets 
	//and the script can decide which position to place the bubble in
	new_el.setAttribute("class","bubble-topright bubble-help");

        //Set opacity to 0 if non-Opera browser for elegant fade-in effect
        //Opera browsers simply show the bubble
	if (!window.opera) {
	  new_el.setAttribute("style","opacity:0.0;");
	}
	
        //Generate x-out block
	new_el_exit = document.createElement('div');
	new_el_exit.setAttribute("class","bubble-exit");
        new_el_exit.innerHTML = "<a href=\"javascript:;\" onclick=\"javascript:hideAllOpenBubbles();\">"+x_text+"</a>";

	//Generate footer
	new_el_footer = document.createElement('div');
	new_el_footer.setAttribute("class","bubble-footer");
	
	//Generate text content
	new_el_p = document.createElement('div');
	new_el_p.setAttribute('class','bubble-text');

	//If the string was passed in, set it
	if (helpHtml != null) {
		new_el_p.innerHTML = helpHtml;
		appendDOMChildren(parent,new_el,new_el_p,new_el_footer,new_el_exit);
	} 
	//If the str wasn't passed in, get the contents from the HREF attribute	
	else {
		getFileContents(el.href,new_el_p,false);
		appendDOMChildren(parent,new_el,new_el_p,new_el_footer,new_el_exit);
	}	
	
}


/**
 * Physically attach the new DOM elements to the document
 *
 * @param parent                                          container element for the bubble
 * @params new_el, new_el_p, new_el_fooer, new_el_exit    children to append to parent
 * @return                                                nothing
 **/
function appendDOMChildren(parent,new_el,new_el_p,new_el_footer,new_el_exit) {
	//Construct DOM heirarchy
	new_el.appendChild(new_el_p);
        new_el.appendChild(new_el_exit);
	new_el.appendChild(new_el_footer);
	parent.appendChild(new_el);
	$(new_el).appear({duration: 0.3});
	return false;
}


/**
 * Removes the bubble element from the page and changes onclick method, where set
 *
 * @param el                      the anchor element that is calling this function's parent
 * @param helpHtml [optional]     content for the help bubble manually passed in as HTML
 * @return                        nothing
 **/
function hideHelpBubble(el,helpHtml) {
	//Delete element
	(el.parentNode).removeChild(el.nextSibling);
	//If onclick is set, change onclick status back to original
	if (el.hasAttribute("onclick") && !el.hasAttribute("onmouseover")) { 
	 el.removeAttribute("onclick");
	 if (helpHtml) 
 	   el.setAttribute("onclick", "javascript:showHelpBubble(this,'"+helpHtml+"');return false;");
 	 else
 	   el.setAttribute("onclick", "javascript:showHelpBubble(this);return false;");
 	} 
}


/**
 * Removes all open bubble divs
 *
 * @return     nothing
 **/
function hideAllOpenBubbles() {
        //Loop through each
        $$('div.bubble-help').each(function(el, index) { 
	  //If onclick is set, change onclick status back to original
	  if ((el.previousSibling).hasAttribute("onclick") && !(el.previousSibling).hasAttribute("onmouseover")) { 
	   //el.removeAttribute("onclick");
           old_onclick = (el.previousSibling).getAttribute("onclick");
	   (el.previousSibling).setAttribute("onclick",old_onclick.replaceHideFunction());
          }
          //Delete element
          (el.parentNode).removeChild(el);
        });
}




/********************
 * HELPER FUNCTIONS *
 ********************/


/**
 * Retrieves contents of help from external URL
 *
 * @param url                    the URL to get the data from
 * @param el                     the element to update
 * @param textOnly [optional]    if true, strip all HTML from response text
 * @return                       nothing
 **/
function getFileContents(url,el,textOnly) { 
        var temp_el,new_text;
	var l_url = b_url + "?the_url="+url

	new Ajax.Request(l_url, {
 	 method: 'get',
 	 asynchronous: false,
 	 onSuccess: function(transport) {

    	   var notice = $(el);

           //Generate a hidden Div element to load in the external HTML and parse it
           temp_el = document.createElement('div');
           temp_el.setAttribute('style','visibility:hidden;');
           temp_el.innerHTML = (transport.responseText).stripScriptTags(); 
           document.body.appendChild(temp_el);

           //Update the div
           new_text = truncateString($('nyulibrary_info'),250,url);
           notice.update(new_text);

           //Delete the temporary child
           (temp_el.parentNode).removeChild(temp_el);

  	 }//end onSuccess function
	});//end Ajax.Request
}


/**
 * Retrieves the screen dimension passed in by dim
 *
 * @param dim     height|width
 * @return        the document's height or width
 **/
function getDocDim(dim) {
	//opera Netscape 6 Netscape 4x Mozilla 
	if (window.innerWidth || window.innerHeight) { 
	docwidth = window.innerWidth; 
	docheight = window.innerHeight; 
	} 
	//IE Mozilla 
	if (document.body.clientWidth || document.body.clientHeight) { 
	docwidth = document.body.clientWidth; 
	docheight = document.body.clientHeight; 
	} 
	if (dim == 'width')
	  return docwidth;
	else
	  return docheight;
}


/**
 * Handles browser differences in finding absolute offset of elements
 *
 * @param el     the element for which the offsets are requested
 * @return       an array containing the offsets [left,top]
 **/
function getAbsoluteOffsets(el) {
        //Use prototype helper to get the offsets
	offsets = el.cumulativeOffset();
        
        //In non-Opera browser, this returns the correct offsets
	if (!window.opera) {
		return el.cumulativeOffset();
	} 
        //In Opera it returns the top left offset of the parent element, so calculation is necessary
        else {
		offset_left = offsets['left'] + (el.parentNode).getWidth() + el.getWidth();
		offset_top = offsets['top'] - el.getHeight();
		return [offset_left, offset_top];
	}
}


/**
 * Truncate text to given length and link out to full page.
 *
 * @param len     length of new string
 * @param url     the URL to link out to for 'Read more'
 **/
function truncateString(el,len,url) {

  var p = el;
  if (p) {

   var trunc = p.innerHTML;
   if (trunc.length > len) {

    /* Truncate the content of the P, then go back to the end of the
       previous word to ensure that we don't truncate in the middle of
       a word */
       trunc = trunc.stripHTMLComments();
       trunc = trunc.substring(0, len);
       trunc = trunc.replace(/\w+$/, '');
       trunc += '...';
       trunc = closeHtmlTags(trunc);
       trunc += '<a href="'+url+'" target="_blank">Read more<\/a>';
       return trunc;
   }
  }
}


/**
 * Make sure all opened tags are closed in HTML code
 *
 * @param txt     the text to properly close
 * @return        the input text with properly nested closing HTML tags
 **/
function closeHtmlTags(txt)  
{  
    var matchAllHtmlTags = /<(?:.|\s)*?>/gi;  
    var matchAllClosingTags = /<\/(?:.|\s)*?>/;
    var matchBeginningClosingTags = /<\/\w+/;
    var matchBeginningOpeningTags = /<\w+/;
    var m = txt.match(matchAllHtmlTags);
    var opened = [];
    var closed = [];
    
    m.each(function(tag, index) { 
      if (!matchAllClosingTags.test(tag)) {
       tag_name = String(tag.match(matchBeginningOpeningTags));
       opened.push(tag_name.substring(1));
      } else if (matchAllClosingTags.test(tag)) {
       tag_name = String(tag.match(matchBeginningClosingTags));
       closed.push(tag_name.substring(2));
       if (tag_name.substring(2) == opened[opened.length-1])
         opened.pop();
      }
    });
    if (opened.length > 0) {
      for (var i=opened.length-1;i>=0;i--) {
        txt += "</"+opened[i]+">";
        opened.pop();
      }
    }
    return txt; 
}; 


/**
 * Strips all HTML tags from object
 **/
String.prototype.stripHTML = function()  
{  
    var matchTag = /<(?:.|\s)*?>/g;  
    return this.replace(matchTag, "");  
}; 


/**
 * Strips all HTML comment tags from object
 **/
String.prototype.stripHTMLComments = function()  
{  
    var matchTag = /<!--(?:.|\s)*?-->/g;  
    return this.replace(matchTag, "");  
}; 


/**
 * Strips all HTML script tags from object
 **/
String.prototype.stripScriptTags = function()  
{  
    var matchTag = /<(\/)?script(?:.|\s)*?>/g;  
    return this.replace(matchTag, "");  
};


/** 
 * Strips the OmniUpdate link from HTML
 **/
String.prototype.stripOULink = function() {
  var matchTag = /<a class=\"cmslink\" (?:.|\s)*?><img (?:.|\s)*?><\/a>/g;
  return this.replace(matchTag, "");
};


/**
 * Replaces the text 'hidehelpBubble' with 'showHelpBubble'
 **/
String.prototype.replaceHideFunction = function() {
  var matchTag = /hideHelpBubble/g;
  return this.replace(matchTag, "showHelpBubble");
};
