// ===========================================================================
var ContextMenu = Class.create();
Object.extend(ContextMenu, {
// ContextMenu provides an easy to use dynamic context menu for your page.
// It injects all the necessary HTML and CSS into your document to display the
// menu, so it's easy to use.  You just give the menu a name, an array of 
// items to display in the menu, and apply the menu name as a class to any
// element you want the menu to active on.
//
// IE is of course brain dead and minimum styling is applied.  Firefox looks 
// much nicer.
//
// Any HTML should work fine in the menu items.  In Firefox the CSS will
// style <A\> tags to look like a regular context menu item in Windows.
//
// Overload the ContextMenu.prototype.modifyMenu() method to make it into a 
// dynamic menu.  You can also overload the ContextMenu.menu_style 
// attribute, or ContextMenu.menu_style_css() method to apply your own style. 
//
// Example: Simple static menu
//
//     <script type="text/javascript"\>
//     new ContextMenu('context_menu',
//        new Array(
//           "My context menu",
//           "<hr/\>",
//           "<a href=\"http://yahoo.com\"\>yahoo</a\>",
//           "<a href=\"http://prototypejs.org\"\>prototype</a\>"
//        )
//     );
//     </script\>
//     <div id="my_div" class="context_menu"\>Right click on me</div\>
//
// Example: Dynamic menu
//
//     <script type="text/javascript"\>
//     Object.extend(
//        new ContextMenu('dynamic_menu',
//           new Array(
//              "item_menu for ___replace___",
//              "<a href=\"http://prototypejs.org\"\>prototype</a\>"
//           )
//        ),
//        {
//           modifyMenu: function(context) {
//              // You can really put anything in here, AJAX if you want.
//              ContextMenu.gsub(/___replace___/,context.id);
//           }
//        }
//     );
//     </script\>
//     <div id="your_div" class="dynamic_menu"\>Right click on me</div\>
// ===========================================================================

   // ========================================================================
   // ContextMenu static class methods and attributes
   // ========================================================================
   context_menu_div: null,  // after init holds ref to menu div, so the DOM doesn't have to be search repeatedly
   menus:            new Array(), // staticly shared list of all menus in use on this page    

   // ----------------------------------------------------
   gsub: function (pattern, replacement) {
   // Convienience method to perform gsub on the menu div.
   // Useful in extensions to modifyMenu()
   // ----------------------------------------------------
	  ContextMenu.context_menu_div.innerHTML = ContextMenu.context_menu_div.innerHTML.gsub(pattern,replacement);
   },

   // --------------------------------------------------------------------------------
   onContextMenu: function(event) {
   // Handler that calls each menu object in menus array, seeing if one of those menus 
   // is going to handle the oncontextmenu event. The first one to handle it wins. 
   // Nested elements using unique menus be ware.
   //
   // This is registered as a global handler that reissues to all known menus, because
   // having each menu register it's own handler would cause them to clobber each
   // other because they share a common div for displaying their menus, and
   // oncontextmenu is a fussy thing.
   // --------------------------------------------------------------------------------
	  var ret = true;
	  if (ContextMenu.context_menu_div.visible()) {
		 // onMouseDownOrClick happens first, so a click outside the menu is already handled there.
		 // if the menu is still visible here, the onContextMenu happened inside the menu, just ignore it.
		 ret = false;
	  } else {
		 // loop until a menu catches and handles the event, then break.
		 // Or none catch, return true, and let the browser handle it.
		 ContextMenu.menus.each(function(m) {
			ret = m.showContextMenu(event);  // returning false means it handled it.
			if (ret == false) {
			   throw $break;
			}
		 });
	  }
	  return ret;
   },

   // ----------------------------------------------------
   onMouseDownOrClick: function(event) {
   // This handler hides the menu on the appropriate click
   // ----------------------------------------------------
	  var element = Event.element(event);
	  if (ContextMenu.context_menu_div.visible()) {
		 if ((!Position.within(ContextMenu.context_menu_div, Event.pointerX(event), Event.pointerY(event))) ||
			(event.type == 'click' && element.tagName == 'A')) {
			ContextMenu.context_menu_div.hide();
		 }
	  }
   },


   // ========================================================================
   // ContextMenu instance methods and attributes
   // ========================================================================
   prototype: {

	  // ------------------------------------------
	  initialize: function(menu_name, menu_items) {
	  // ------------------------------------------
		 this.menu_name  = menu_name;
		 this.menu_items = menu_items;
		 for (var idx = 0; idx < menu_items.size(); idx++) {
			// If the menu_item is not an <A> or <HR> item, wrap it inside a DIV tag
			// HACK: For IE wrap everything in a DIV, because it can't properly convert tags to block style via CSS.
			if (Prototype.Browser.IE || (this.menu_items[idx].search(/^ *<[a].+<\/[a]> *$|^ *<hr\/?> *$/i) == -1)) {
			   this.menu_items[idx] = '<div>' + this.menu_items[idx] + '</div>';
			}
		 }
		 if (ContextMenu.menus.size() == 0) {  // do initialization of static class members if this is the first menu
			// This code references elements in the DOM that must be loaded before being referenced,
			// so it is wrapped inside an event handler tied to window 'load'.
			Event.observe(window, 'load', function() {
			   // display:none must be done inline or javascript can't override the CSS on the div for some reason.
				new Insertion.Top(document.body, '<div id="__context_menu" style="display: none;"> text replaced by showContextMenu </div>');
			   ContextMenu.context_menu_div = $('__context_menu'); // cache the reference
			   // register event handlers
			   document.body.oncontextmenu  = ContextMenu.onContextMenu.bindAsEventListener();
			   Event.observe(document, 'mousedown', ContextMenu.onMouseDownOrClick.bindAsEventListener());
			   Event.observe(document, 'click', ContextMenu.onMouseDownOrClick.bindAsEventListener());
			});
		 }
		 ContextMenu.menus.push(this);  // add to static class list of menus, used by onContextMenu event handler
	  },

	  // -------------------------------------------------------------------
	  contextElement: function(event) {
	  // Returns the nearest element searching up the DOM that has the class
	  // of the menu name.  Returns undefined if no objects matched  
	  // -------------------------------------------------------------------
		 var element = Event.element(event);
		 if (!element.hasClassName(this.menu_name)) {
			element = element.up('.' + this.menu_name);
		 }
		 return element;
	  },

	  // --------------------------------------------------------------------
	  modifyMenu: function(element) {
	  // Extensions can put code here to manipulate the contents of the menu
	  // to make it dynamic rather than static.
	  // element: is the element in the DOM that this menu is being displayed
	  //          for.  It is not necessarily the element that was clicked on
	  //          (as could be in the case of nested elements).
	  // -------------------------------------------------------------------
	  },

	  // -------------------------------------------------------------------
	  showContextMenu: function(event) {
	  // Called by the onContextMenu event handler.  If this menu is
	  // to be displayed it does the following:
	  //   Fills the menu div with the menu_items.
	  //   Calls modifyMenu, to allow dynamic modifications
	  //   Positions the menu and makes it visible
	  // Returns false if it display the menu, true if it does not
	  // -------------------------------------------------------------------
		 var element = this.contextElement(event);
		 if (!element) { return true; } // event not generated from inside an element associated with this menu.
		 ContextMenu.context_menu_div.innerHTML = this.menu_items.join('');      
		 this.modifyMenu(element);
		 ContextMenu.context_menu_div.style.left = Event.pointerX(event) + 'px';
		 ContextMenu.context_menu_div.style.top  = Event.pointerY(event) + 'px';
		 ContextMenu.context_menu_div.show();
		 return false;
	  }
   }
});
