
  // --------------------------------------------------
  //  DOCUMENTATION
  // --------------------------------------------------
  // 
  //  Create a Menu:
  //  <ul class='ctxMenu cached click' id='ctxMenuDemo' style='display: none;' onclick="return CtxMenuManager.ajaxCallback('PublicationCtxMenu.getCtxMenu');">
  //  <ul class='ctxTooltip cached click idle' id='ctxMenuDemo2' style='display: none;' onclick="return CtxMenuManager.handleTooltip();">
  //
  //  - Class ctxMenu:    Indicate this is a Contextual Menu
  //  - Class ctxTooltip: Indicate this is a Contextual Tooltip
  //  - Class cached:     Indicate to cache ajax request
  //  - Class click:      Indicate to show on click (Values: click, rightclick, idle)
  //  - Class aligned:    Indicate to align the menu with the bottom left corner of the target element
  //  - Set an Id used by links
  //  - Set display to none
  //  - Set onclick with CtxMenuManager.ajaxCallback function
  //  --- First param is JSON callback (Java Method)
  //  --- Second param is parameters to call with JSON  method 
  //  
  //  - ttCard will retrieve content like an ajax-refresh request
  //  - click in a <div class='clickBypass'></div> will bypass the ttCard close 
  //
  //  Bind it with multiple links, 
  //  <input type='button' value='Click Me !' class="ctxMenuDemo ID_c_6222" />
  //  <a href='index.jsp'><img src='icon.gif' class="ctxTooltipDemo" title="Hello World" /></a>
  //
  //  - Class ctxMenuDemo is the Id of te menu to use
  //  - Class ID_c_6222 is the id of the current targeted JCMS Object
  //  -> first parameter of the call back will be the id c_6222
  // 
  //  IE Leaks: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp
  //

  // --------------------------------------------------
  //  EVENT OBSERVER
  // --------------------------------------------------


  Event.observe(window, 'load'  , function() { CtxMenuManager.initCtxtMenu.defer();   });
  
  
  // --------------------------------------------------
  //  CONTEXTUAL MENU MANAGER
  // --------------------------------------------------

  if (!window.CtxMenuManager) {
    var CtxMenuManager = new Object();
  }
  
  Object.extend(CtxMenuManager,{
    
    latestElement: null,
    initDone : false,
    
   // -------------------------------------
   //  Functions
   // -------------------------------------
    
    /**
     * Create a ContextMenu for each UL.ctxMenu
     * Bind CtxMenuManager.openCtxtMenu() as Event Observer on contextmenu event
     */
    initCtxtMenu: function(){
      if (CtxMenuManager.initDone){
        return;
      }
      var t0 = new Date().getTime();
      
      CtxMenuManager.initDone = true;
      CtxMenuManager.ctxmenus = new Object();
      
      // Bind right-click event
      Event.observe(document, 'contextmenu' , CtxMenuManager.openCtxtRightClickMenu.bindAsEventListener(this));
      
      // Bind click event
      Util.observeDocument('click', CtxMenuManager.openCtxtLeftClickMenu.bindAsEventListener(this));
      
      // Bind idle event
      new Notifier(1000,'ctxmnu', true, ['ctxTooltipCard','ctxTooltipMenu']);
      Event.observe(document, 'ctxmnu:idle' ,  CtxMenuManager.openCtxtIdleMenu.bindAsEventListener(this));
      Event.observe(document, 'ctxmnu:active', CtxMenuManager.prepareTooltip.bindAsEventListener(this));
      
      var t1 = new Date().getTime();
      JcmsLogger.info('CtxMenuManager', 'Init Context Menu Manager', ' in '+(t1-t0)+' ms');
    },
    
    /**
     * Display a context menu on left click
     * Respond for  document.contextmenu event
     * 
     * @param event the event
     * @param kind the event kind
     */
    _openCtxtClickMenu: function(event, kind){
      var isClick = kind != 'idle';
      var link = event.memo ? event.memo.target : Event.element(event);
      
      JcmsLogger.debug("CtxMenuManager"," kind: ",kind," type: ",event.type," which: ",event.which, " button: ",event.button, " detail: ",event.detail," link: ",link);
      
      if (!link && isClick){
        CtxMenuManager.hideAllCtxtMenus();
        return;
      }
      
      link = $(link);
      if (!link) {
        return;
      }

      // When we want to provide a JSP tooltip on a link
      // which does not have any visible image => use an
      // invisible image inside the link so we can provide
      // the jsp inside the longdesc attribute
      // <a ...><img ... class='ctxTooltip' longdesc='...jsp...'/>Idle on me</a>
      if (!isClick) {
        var ahref = link.fastUp('A', null, true, 6);
        if (ahref) {
          document.fire('ctxmenu:idle', { // Warning ! Not the same than internal event 'ctxmnu:idle'
            elm:  link.identify(), // The hovered element 
            link: ahref.identify()     // The wrapper link  
          });
          
          var tooltipImg = ahref.down('IMG.ctxTooltipMenu');
          if (tooltipImg) {
            link = tooltipImg;
          }
          
          if (ahref.hasClassName('ctxTooltipCard')){
            link = ahref;
          }
        }
        
        // Do not trigger if we are in a tooltip
        if (link.fastUp('UL', 'ctxTooltip', false, 6)){
          return;
        }
      }
      // Otherwise do not allow click on a tooltip which is a link
      else if(link.tagName == 'A' && (link.hasClassName('ctxTooltipMenu')
                                   ||  link.hasClassName('ctxTooltipCard'))) { // Skip links on tooltips Tooltips
        return;
      }
      
      // 1. Look on EXACT element class name (do not move up)
      if (!link._ctxmenu){ // Dynamically bind Menu to Element
        link._ctxmenu = CtxMenuManager.findCtxMenu(link,kind);
      }
      
      // 2. If find and wrapped by click  and is click skip
      if (link._ctxmenu && kind == 'click' && link.fastUp('A')){
          return;
      }
      
      // 3. Then move UP for ONLY for A on CLICK
      if (!link._ctxmenu && kind == "click"){
        for (var i = 0 ; i < 6 && link && link.tagName != "A"; i++){
          if (link.className && link.className.indexOf("clickBypass") >= 0){ return; } /* A way to escape */
          link = link.parentNode;
        }
        
        link = $(link)
        if (!link || link.tagName != "A" || link.className.indexOf('ctxTooltipMenu') >= 0){
          CtxMenuManager.hideAllCtxtMenus();
          return;
        }

        link._ctxmenu = CtxMenuManager.findCtxMenu(link,kind);
      }
      
      // 4. Show the menu if found
      if (link._ctxmenu && $(link._ctxmenu.id).hasClassName(kind)){
        JcmsLogger.debug('CtxMenuManager','_openCtxtClickMenu',"isClick: ",isClick,link);
        link._ctxmenu.showMenuEvent(event,link);
        $(document.body).addClassName('contextual-menu-displayed');
        CtxMenuManager.latestElement = link.identify();
        Event.stop(event);
        return;
      }
      
      if (isClick){
        if (link && link.fastUp(false,'clickBypass',10)){ return; }
        CtxMenuManager.hideAllCtxtMenus();
      }
    },
    
    /**
     * Display a context menu on left click
     * Respond for  document.contextmenu event
     * 
     * @param event the event
     */
    openCtxtLeftClickMenu: function(event){
      if (Util.isLeftClick(event)){ // Strange prototype issue
        CtxMenuManager._openCtxtClickMenu(event,'click');
      }
    },
    
    /**
     * Display a context menu on right click
     * Respond for  document.contextmenu event
     * 
     * @param event the event
     */
    openCtxtRightClickMenu: function(event){
      CtxMenuManager._openCtxtClickMenu(event,'rightclick');
    },
    
    /**
     * Display a context menu on idle
     * Respond for  Notifier event
     * 
     * @param event the event
     */
    openCtxtIdleMenu: function(event){
      CtxMenuManager._openCtxtClickMenu(event,'idle');
    },
    
    
    /**
     * Hide all contextual menu
     */
    hideAllCtxtMenus: function(){
      $H(CtxMenuManager.ctxmenus).each(function(entry,idx){
        entry.value.hideMenu($(entry.value.id));
      });
      $(document.body).removeClassName('contextual-menu-displayed');
    },
    
    /**
     * Returns the CtxMenu bind to the given element or null.
     * 
     * @param elm the element to work with
     * @param event then event kind to look for
     */
    findCtxMenu: function(elm, kind){
      
      if (!elm.className){
        return;
      }
      var entry = $H(CtxMenuManager.ctxmenus).find(function(entry,idx){

        // Check Id
        if (elm.className.indexOf(entry.value.id) < 0){ 
          return false;
        }
        
        // Check Event
        var ctxMenuElm = $(entry.value.id);
        if (kind && ctxMenuElm.className.indexOf(kind) < 0){
          return false;
        }
        return true;
      });
      
      if (entry){
        return entry.value;
      }
      
      var elm = $(elm);
      if (!elm.classNames){ return; }
      
      // Lazy retrieve of the UL.ctxMenu with an id matching one of the class of elm
      var ctxMenuULid = elm.classNames().find(function(classname){
        var curElm = $(classname);
        return (curElm && (curElm.hasClassName('ctxMenu') || curElm.hasClassName('ctxTooltip')) && curElm.hasClassName(kind));
      });
      
      var ctxMenuUL = $(ctxMenuULid);
      if (!ctxMenuUL) {
        return;
      }
      
      // Lazy initialize : fill CtxMenu the hash map
      ctxMenuUL.parentNode.removeChild(ctxMenuUL);
      document.body.appendChild(ctxMenuUL);
      var ctxMenuInst = new CtxMenu(ctxMenuUL);
      CtxMenuManager.ctxmenus[ctxMenuUL.id] = ctxMenuInst;

      return ctxMenuInst;
    },
    

    checkTrigger: function(trigger){
      var trigger = $(trigger);
      if (!trigger) return true;
      if (!trigger.parentNode) return false;
      if (!trigger.fastVisible() && trigger.tagName != 'IMG') return false;
      return true;
    },
    
    /**
     * Callback of active tootlip notifier used to remove the title attribute
     * for tooltip elements 
     */
    prepareTooltip: function(event){
      var memo = event.memo;
      if (!memo || !memo.target || !memo.target.title){ return; }
      
      memo.target._title = memo.target.title;
      memo.target.title  = '';
    },
    
    /**
     * Convenient callback for tooltip menu. Retrieve tooltip info from
     * title, nexSibling, longdesc, ...
     */
    handleTooltip: function(){
      var callback = function(link,menu){
        JcmsLogger.debug('CtxMenuManager','handleTooltip():',link,menu);
        
        if (!link || !link.className){
          return;
        }
        
        // Set wait icon
        menu.clearMenu(); // Very important (TODO: dig why it is so important)
        
        if (!this.isTTCard){ // Do not show Wait menu for ttCard
          var elm = $(this.id);
          elm.appendChild(menu.getWaitMenu());
          elm.ctxmenu.showMenu(elm);
        }
        
        // Retrieve content
        var link = $(link);
        var returnValue = "";
        if (link.longDesc){
          var url = link.longDesc;
          new Ajax.Request(url, {
            method: 'get',
            onSuccess: function(transport) { menu.initMenu(link, "<li class='tt'>"+transport.responseText+"</li>"); }
          });
        }
        else if (this.isTTCard){
          var jcmsId = link.getJcmsId();
          var params  = link.className.replace("ID_"+jcmsId,"").replace("ctxTooltipCard","").replace(/^\s+|\s+$/g,"").replace(/\s+/g,"|");
          var url = JcmsJsContext.getBaseUrl()+'jcore/tooltip/ttCard.jsp?ttId='+jcmsId+'&ttContext='+params;
          
          /* new Ajax.Request(url, {
            method: 'get',
            onSuccess: function(transport) { menu.initMenu(link, "<li class='tt'>"+transport.responseText+"</li>"); }
          }); */

          var elm = $(this.id); 
          elm.innerHTML = "<li class='tt'></li>";
          
          var li = elm.firstChild;
          JCMS.ajax.Refresh._request(li, url, { 'history' : false, 'noscroll': true, 'callback': function(refreshDiv){
            menu.initMenu(link, true, true);
          }});
          
        }
        else if (link.title){ // Default on title
          link._title = link.title; // Backup and remove
          link.title  = '';
          menu.initMenu(link, "<li class='tt'>"+link._title+"</li>");
        }
        else if (link._title){ // Use backuped title
          menu.initMenu(link, "<li class='tt'>"+link._title+"</li>");
        }
        else { // Default on inline data 
          var next = $(link).next();
          if (next && $(next).hasClassName('ctxTooltip')){
            menu.initMenu(link, "<li class='tt'>"+next.innerHTML+"</li>");
            next.innerHTML = "";
          } else{
            return;
          }
        }
      }
      return callback;
    },
    
    /**
     * Return a Callback function that will:
     * 
     * @param jcmsrpc Call an RPC JSON Func
     * @param params for the json request
     * @param hook Javascript func called to update rpc params (return false to stop process)
     */
    ajaxCallback: function(jcmsrpc, params, hook){
 
      JcmsLogger.debug('CtxMenuManager','ajaxCallback():',jcmsrpc,params,hook);
      
      var callback = function(link,menu){
        
        // Check Link
        if (!link || !link.className){
          return;
        }
        
        // Init Eval String for RPC
        var evalrpc = "JcmsJsContext.getJsonRPC()."+jcmsrpc+"(";
        
        // Append json callback (must be first parameter)
        evalrpc += "function(value){jsonRequest.asyncJsonCallBack(value);}";
        
        // Add JCMS Id to RPC
        var jcmsid = $(link).getJcmsId();
        if (jcmsid){
          evalrpc += ",'"+jcmsid+"'";
        }
        
        // Add custom parameters
        if (params){
          evalrpc += ","+params;
        }
        
        // Add hook params
        if (hook){
          var tmp = hook(link,menu);
          if (!tmp){ return; } // Hook ask to stop
          evalrpc += tmp ? ",'"+tmp+"'" : "";
        }
        
        // Close parenthesis
        evalrpc += ");";
        
        
        // Init JsonRequest
        var jsonRequest = new JcmsJsonRequest($(menu.id)); 
        
        
        // Init Effect with jsonRequest callback
        var functEffect = function(){ 
          
          menu.clearMenu(); // Very important !
          var elm = $(this.id);

          elm.appendChild(menu.getWaitMenu());
          jsonRequest.asyncEffectCallBack({});
        }.bind(menu);
        
        // Init RPC with jsonRequest callback
        var funcRPC = function(){
          eval(evalrpc);
        };
        
        // Init CallBack
        var funcCallBack = function(returnValue, returnEffect){ 
          menu.initMenu(link, returnValue);
        };
        
        // Run JSON Request
        jsonRequest.effect   = functEffect;
        jsonRequest.rpc      = funcRPC;
        jsonRequest.callback = funcCallBack;
        jsonRequest.asyncJsonCall();
      }
      return callback;
    },
    
    /**
     * Returns classes of the clicked link to be append
     * to JSON Request for server side actions
     * 
     * @param link 
     * @param menu
     * @return String the link classes
     */
    fillElmClassesHook: function(link,menu){
      if (!link.className){
        return;
      }
      return link.className;
    }
  });
  
  
  // --------------------------------------------------
  //  CONTEXTUAL MENU
  // --------------------------------------------------

  CtxMenu = Class.create();
	CtxMenu.prototype = {
	  
	  // --------------------
	  //  CONSTRUCTOR
	  // --------------------
	  
	  /**
	   * Initialise the ContextualMenu for the given UL element quickly.
	   * The second part of the initialisation is done by _initializeLazy()
	   * 
	   * @param elm the UL element
	   */
	  initialize: function(elm) {
	    this.id  = elm.id;
	    this.timeout     = 1000;
	    this.isCached    = elm.hasClassName('cached');
	    this.isAligned   = elm.hasClassName('aligned');
	    this.isIE        = /MSIE/.test(navigator.userAgent);
	    this.useIframe   = this.isIE;
	    this.isTooltip   = elm.hasClassName('ctxTooltip');
	    this.isTTCard    = this.id == 'ctxTooltipCard';
	    
	    elm.ctxmenu = this;
	    elm.hide();
	    elm.cleanWhitespace();
	  },
	  
	  _initLazy: false,
	  
	  /**
	   * Second part of the Initialisation
	   */
	  _initializeLazy: function(){
	    if (this._initLazy){
	      return;
	    }
	    
	    JcmsLogger.info("CtxMenu","Init Context Menu: ",this.id," isTooltip: ",this.isTooltip);
	    this._initLazy = true;
	    
	    if (!this.isTooltip){
        this._initHover();           // Setup "hover" behavior on LI
        this._initImages();          // Setup "mnuicon" behavior on IMG
        this._initSubMenu();         // Setup submenu behavior on UL
        this._initLinks();           // Wrap all <A> into a function checking parent LI is not disabled
	    }
	    
	    this._initIFrame($(this.id)); // Setup IFrame hack
	    this._initCallBack();          // Setup callback
	  },
	  
	  // --------------------
	  //  INTERNAL
	  // --------------------
	  
	  /**
	   * NB: Couldn't make a function because code is slightly different with display 
	   * menu in viewport (not the same behavior).
	   * 
	   * @param link the link to align with
	   */
	  _initMenuPosition: function(link){
      var elm = $(this.id);
      
       // Dedieu's trick
	    elm.removeClassName('ctxSmall');
	    elm.removeClassName('ctxTiny'); 
	    
	    elm.show(); // Cannot use style visible because it do not wwork with IE6
	    Position.prepare(); 
	    var pageScroll = [Position.deltaX, Position.deltaY];
	    var pageBounds = Util.getViewportBounds();
	    var menuPos    = Position.cumulativeOffset(elm);  
	    var menuDim    = elm.getDimensions();
	    var linkDim    = link.getDimensions();

      var menuPosWidth    = menuPos[0]+menuDim.width;
      var pageScrollBound = pageScroll[0]+pageBounds.width-20;
      var overflow        = menuPosWidth-pageScrollBound;
      var popBottom       = false;
      
      if (JcmsLogger.isDebug && JcmsLogger.CtxMenuTrace){
        JcmsLogger.debug('CtxMenuTrace','--- _initMenuPosition -------------------------- ');
	      JcmsLogger.debug('CtxMenuTrace','Pos: '   ,menuPos[0],menuPos[1]);
	      JcmsLogger.debug('CtxMenuTrace','Bound: ' ,menuDim.width,menuDim.height);
	      JcmsLogger.debug('CtxMenuTrace','Window: ',(pageScroll[0]+pageBounds.width));
	      JcmsLogger.debug('CtxMenuTrace','overflow: ',overflow);
	      JcmsLogger.debug('CtxMenuTrace','isAligned: ',this.isAligned);
      }

      // Check pop right
	    if (overflow > 0){ 
	      // Dedieu's trick  (Cannot set it dynamically)
	      if (this.isTooltip && !this.isTTCard && overflow < 150){
	        JcmsLogger.debug('CtxMenuTrace','ctxSmall');
          elm.addClassName('ctxSmall');
        } 
	      else if (this.isTooltip && !this.isTTCard && overflow < 250){
          JcmsLogger.debug('CtxMenuTrace','ctxTiny');
	        elm.addClassName('ctxTiny');
        } 
        else {
	        elm.style.left = this.isAligned ? menuPos[0]-overflow+'px' : menuPos[0]-Math.min(menuDim.width,menuPos[0])+'px';
          JcmsLogger.debug('CtxMenuTrace','left:', elm.style.left);
        }
	    }

	    // Check pop bottom
	    if ((menuPos[1]+menuDim.height > pageScroll[1]+pageBounds.height-20) && (menuPos[1]-menuDim.height > 0)){
	      var top = this.isAligned ? menuPos[1]-menuDim.height-linkDim.height : menuPos[1]-Math.min(menuDim.height,menuPos[1]);
	      elm.style.top  = (top-20)+'px';
        popBottom = true;
        JcmsLogger.debug('CtxMenuTrace','top:', elm.style.top);
	    }
	    
	    // Setup arrow
	    
      if (this.arrow){
        var arrow = $(this.arrow);
        var nudge = 30;
        arrow.removeClassName('arrow-down');
        
        if (popBottom){
          nudge = 48;
          arrow.addClassName('arrow-down');
        }
        
        arrow.style.left = (linkDim.width/2 - 10 + (overflow>0?overflow:0))+'px';
        elm.style.top =  (elm.offsetTop - nudge)+'px'; 
      }
	    
      JcmsLogger.debug('CtxMenuTrace','done positioning');
	  },
	  
	  /**
	   * Inits all LI, bind onmouseover and onmouseout events
	   */
	  _initHover: function(){  
      var elm = $(this.id);
	    $A(elm.getElementsByTagName('LI')).each(function(li,idx){
	     var li = $(li);
	     
	     if (li.hasClassName('hr'))
	       return;
	       
	     li.ctxmenu = this;
	     
	     // Fix mouse over/out function
	     Event.observe(li,'mouseover', this._eventShowSubMenu.bindAsEventListener(li));
	     Event.observe(li,'mouseout' , this._eventHideSubMenu.bindAsEventListener(li));
       
	    }.bind(this));
	  },
	  
	  /**
	   * Wrap all <A> with onclick function checking if parent node LI 
	   * has classname disabled.
	   */
	  _initLinks: function(){
      var elm = $(this.id);
	    $A(elm.getElementsByTagName('A')).each(function(ahref,idx){
	      
	      if (!Element.hasClassName(ahref.parentNode,'disabled')){
	        return;
	      }
	      
	      // ahref._onclick = ahref.onclick; // We never enable it
	      ahref.onclick  = function(){	          
	        return false;
	      }
	      
	    });
	    
	  },
	  
	  /**
	   * Inits all IMG, wrap DIV
	   */
	  _initImages: function(){
      var elm = $(this.id);
	    $A(elm.getElementsByTagName('IMG')).each(function(elm,idx){
	      
	      var img = $(elm);

        // Update imgage src if disabled
        if ($(img.parentNode.parentNode).hasClassName('disabled')){
          var path = img.src; 
          var pos  = path.lastIndexOf('.');
          img.src = "s.gif"; // path.substring(0,pos)+'_gray'+path.substring(pos);
          img.width = 16;
          img.height = 16;
        }
	      
	    }.bind(this));
	  },
	  
	  /**
	   * Inits all sub menus, hide bind 
	   */
	  _initSubMenu: function(){
      var elm = $(this.id);
      
	    $A(elm.getElementsByTagName('UL')).each(function(ul,idx){
	      var ul = $(ul);
	      
	      // Set node
	      if (!Element.hasClassName(ul.parentNode,'node'))
  	      Element.addClassName(ul.parentNode,'node');
	      
	      ul.parentNode.timeout = this.timeout;
	      ul.parentNode.submenu = ul;
	      ul.ctxmenu = this;
	      ul.cleanWhitespace();
	      
	      // IFrame
	      this._initIFrame(ul);
	      
	      // Hide submenu
	      ul.hide();
	      
	    }.bind(this));
	  },
	  
	  /**
	   * Use the onclick function of UL as callback to call 
	   * while displaying the menu.
	   */
	  _initCallBack: function(){
	    
      var elm = $(this.id);
	    if (!elm.onclick){
	      return;
	    }
	    
	    this.callback = elm.onclick(); // Retrieve the callback function from onclick
	    elm.onclick = null;
	  },
	  
	  /**
	   * IFrame hack under the menu for IE
	   */
	  _initIFrame: function(elm){
  	  
  	  if (!elm.style.zIndex){ // Set the zIndex
  	    elm.style.zIndex = 20000;
  	  }
  	  
  	  if (!this.useIframe){
  	    JcmsLogger.debug('CtxMenuTrace','No IFrame');
  	    return;
  	  }
  	  
  	  var bodyNode = document.getElementsByTagName('body')[0];
  	  if (!bodyNode){ return; }
  	  
  	  if (!elm.iframe){
  	    elm.iframe = $(document.createElement('IFRAME'));
  	    elm.iframe.src='s.gif';
  	    elm.iframe.style.position = 'absolute';
  	    elm.iframe.style.display = 'none';
  	    elm.iframe.style.zIndex = elm.style.zIndex-1;
	      elm.iframe.frameBorder = 'no';
	      elm.iframe.scrolling = 'no';
	      bodyNode.appendChild(elm.iframe);
  	  }
	  },
	  
	  /**
	   * Display IFrame with the size of the elm
	   */
	  _setupIFrame: function(elm){
	    
	    if (!elm.iframe)
	      return;
	    
	    var elm = $(elm); 
	    var pos = Position.cumulativeOffset(elm);
	    
	    elm.iframe.style.left   = pos[0]+1+'px';
	    elm.iframe.style.top    = pos[1]+1+'px';
	    elm.iframe.style.width  = elm.offsetWidth-2+'px';
	    elm.iframe.style.height = elm.offsetHeight-2+'px';
	    elm.iframe.show();
	  },
	  
	  /**
	   * Hide IFrame
	   */
	  _hideIFrame: function(elm){
	    if (!elm.iframe)
	      return;
	      
	    elm.iframe.hide();
	    
	    // Hide sub iframes
	    $(elm).select('UL').each(function(elm,idx){
        if (!elm.iframe) return;
        elm.iframe.hide();
      });
	  },
	  
	  /**
	   * Convenient function used to handle event to show sub menus. 
	   * If LI is a node then call hideSubMenu otherwise use simple code
	   * Should be called with bindAsEventListener()
	   */
	  _eventHideSubMenu: function(event){
	    //var func = function(event){ 
	      
	      Event.stop(event);
	      var item    = this;
	      var cxtmenu = item.ctxmenu;
	      
	      // Delete timer and create new one
	      if (item.activeTimeout)    clearTimeout(item.activeTimeout);
	      if (cxtmenu.activeTimeout) clearTimeout(cxtmenu.activeTimeout);
	      
	      if (item.timeout || cxtmenu.activeTimeout){
	        item.activeTimeout    = setTimeout( function(){this.ctxmenu.hideSubMenu($(this))}.bind(item),item.timeout);   // Wait mouse come back or open a submenu
	        cxtmenu.activeTimeout = setTimeout( function(){this.ctxmenu.hideMenu($(this.ctxmenu.id))}.bind(item),cxtmenu.timeout); // Wait mouse come back or open a menu
	      } else {
	        cxtmenu.hideSubMenu(item);
	        cxtmenu.activeTimeout = setTimeout( function(){this.ctxmenu.hideMenu($(this.ctxmenu.id))}.bind(item),cxtmenu.timeout); // Wait mouse come back or open a menu
	      }
	    //};
	    //return func;
	  },
	  
	  /**
	   * Convenient function used to handle event to show sub menus.
	   * Should be called with bindAsEventListener()
	   * @param event the event
	   */
	  _eventShowSubMenu: function(event){
	    this.ctxmenu.showSubMenu(this);
	  },
	  
	  // --------------------
	  //  FUNCTIONS
	  // --------------------
	  
	  initMenu: function(link, innerHTML){
              
	    var elm = $(this.id);
	    
	    if (!CtxMenuManager.checkTrigger(link)){ elm.hide(); return; }
	    
	    // Ugly hack to setup CSS on menu calculated from link
	    document.fire('ctxmenu:css', {
        menu:  elm.identify(),  // menu 
        link: link.identify()   // link  
      });
	    
	    if ((!link || !link.ctxmenuCache) && (innerHTML)){
        JcmsLogger.debug('CtxMenu','InitMenu: ','Set innerHTML');
        elm.hide();
        if (innerHTML !== true){
          elm.innerHTML = innerHTML;
        }
        
  	    elm.cleanWhitespace();
  	    
  	    if (this.isAligned && !$(this.arrow)){
  	      var arrow = new Element('LI', { 'class': 'arrow' });
  	      elm.appendChild(arrow);
  	      this.arrow = arrow.identify();
  	    }
  	    
  	    this._initHover();    // Setup "hover" behavior on LI // RFE:GJ:Allow tooltip to disapear
  	    if (!this.isTooltip){
  	      this._initImages();   // Setup "mnuicon" behavior on IMG
  	      this._initSubMenu();  // Setup submenu behavior on UL
  	      this._initLinks();    // Wrap all <A> into a function checking parent LI is not disabled
  	    }
  	    this._initMenuPosition(link); // Display menu in viewport
  	    elm.ctxmenu.showMenu(elm); // Show the menu
  	    
  	    if (!this.isCached){
          return;
        }
        
        link.ctxmenuCache = $A(elm.childNodes).clone();
      }
      else if (link.ctxmenuCache){
        JcmsLogger.debug('CtxMenu','InitMenu: ','Use cached DOM');
        
        // Remove previous elements
        this.clearMenu();
        
        // Append new children
        link.ctxmenuCache.each(function(node, idx){
          elm.appendChild(node);
        }.bind(this));
        
        //if (!this.isTooltip){ // RFE:GJ:Allow tooltip to disapear
          this._initHover();  // Reset observer (removed by clearMenu())
        //}
        
        this._initMenuPosition(link); // Display menu in viewport
        elm.ctxmenu.showMenu(elm); // Show the menu
      }
      else{
	      alert(I18N.glp('warn.json.sessiontimeout'));
	      elm.hide();
	      return;
      }        
	  },
	   
	  /**
	   * Remove all child from current elmenet
	   */
	  clearMenu: function(){
      var elm = $(this.id);
	    Util.cleanDOMElements(elm,false);
	  },
	  
	  /**
	   * Returns a <li><img/></li> DOM elements to wait ajax request
	   * Build is lazy and cached in menu.
	   * TODO: Should it removed from previous parent ?
	   */
	  getWaitMenu: function(){
	    if (this.waitmenu){
	      return this.waitmenu;
	    }
	    
	    var li  = document.createElement('li');
      li.className = 'wait';
      
      var img = document.createElement('img');
      img.src = 'images/jalios/icons/waitsmall.gif';
      li.appendChild(img);
      
      this.waitmenu = li;
      return li;
	  },
	  
	  /**
	   * Wrap into a funcion  the onclick of target elements
	   */
	  showMenuEvent: function(event, link, eX, eY){ 

		  //if (!eX) {
	    //  Event.stop(event);
	    //}

	    var link     = link ? link : this;
	    var ctxmenu  = link._ctxmenu;
      var elm      = $(ctxmenu.id);
      
      // Lazy Initialise
      ctxmenu._initializeLazy();
      if (elm.style.display == '' && link == ctxmenu.lastLink && !event.memo){
        ctxmenu.hideMenu(elm);
        return false;
      } 
      else {
        CtxMenuManager.hideAllCtxtMenus();
        
        // Align menu with opener link
        if (ctxmenu.isAligned){
        Position.prepare(); 
          var pageScroll = [Position.deltaX, Position.deltaY];
    	  var linkPos = link.viewportOffset();
    	  var linkDim = link.getDimensions();
          elm.style.left = linkPos[0] + pageScroll[0] + 'px';
          elm.style.top  = linkPos[1] + linkDim.height + pageScroll[1] +'px' ;
        }
        // Align on mouse click 
        else {
          elm.style.left = (eX || (event.memo && event.memo.eX) || Event.pointerX(event))+1+'px';
          elm.style.top  = (eY || (event.memo && event.memo.eY) || Event.pointerY(event))+1+'px';
        }
        
        // Backup last opened element
        ctxmenu.lastLink = link;
        
        // Use cached version of the menu
        if (link.ctxmenuCache){
          ctxmenu.initMenu(link);
        }
        else if (ctxmenu.callback){ 
          ctxmenu.callback(link, ctxmenu);
        }
        else if (CtxMenuManager.checkTrigger(link)){
          ctxmenu._initMenuPosition(link);
          ctxmenu.showMenu(elm);
        }
      }
      
      return false;
	  },
	  
	  showMenu: function(elm){
	    elm.show();
      elm.ctxmenu._setupIFrame(elm);
	  },
	  
	  /**
	   * Show a submenu. 
	   * A boolean "active" indicate if mouse come back on LI (this limit job)
	   * Set the right position of the submenu
	   */
	  showSubMenu: function(li){
	    // Remove runing timeout
	    if (li.activeTimeout)         clearTimeout(li.activeTimeout);         // If mouse go back on LI
	    if (li.ctxmenu.activeTimeout) clearTimeout(li.ctxmenu.activeTimeout); // If mouse go back on menu
	    
	    // Do not open if disabled
	    if (li.hasClassName('disabled'))
	      return;
	      
	    // Do not do job twice
	    if (li.cxtshow)
	      return;
	      
	    li.cxtshow = true;
	    li.cxthide = false;

	    // Hide all sibling LI
	    var firstChild = $(li.parentNode).fastChild('LI','hover');
	    while (firstChild){
	      firstChild.ctxmenu.hideSubMenu(firstChild);
	      firstChild = firstChild.fastNext('LI','hover');
	    }
	    
	    
	    li.addClassName('hover');
	    if (!li.submenu){
	      return;
	    }
	      
	    // Compute position
	    //var ul = li.parentNode;
	    li.makePositioned();
	    var liPos = Position.positionedOffset(li);
	    var liDim = li.getDimensions();
	    li.undoPositioned();
	    
	    JcmsLogger.debug('CtxMenuTrace','liPos: '+liPos[0]+','+liPos[1]);
	    JcmsLogger.debug('CtxMenuTrace','liDim: '+liDim.width+','+liDim.height);
	    
	    // Set position
	    li.submenu.style.left = liPos[0]+liDim.width+'px';
	    li.submenu.style.top  = liPos[1]+'px';
	    
	    
	    // Start to display even if call showMenu at the end
	    li.submenu.show();
	    
	    // Display submenu in viewport
	    // NB: Couldn't make a function because code is slightly different with display 
	    //     menu in viewport (not the smae behavior).
	    
	    Position.prepare();
	    var pageScroll = [Position.deltaX, Position.deltaY];
	    var pageBounds = Util.getViewportBounds();
	    var menuPos    = Position.cumulativeOffset(li.submenu);
	    var menuDim    = li.submenu.getDimensions();
	    
	    if (JcmsLogger.isDebug && JcmsLogger.CtxMenuTrace){
	      JcmsLogger.debug('CtxMenuTrace','--- showSubMenu -------------------------- ');
	      JcmsLogger.debug('CtxMenuTrace','pageScroll: ',pageScroll[0],pageScroll[1]);
	      JcmsLogger.debug('CtxMenuTrace','pageBounds: ',pageBounds.width,pageBounds.height);
	      JcmsLogger.debug('CtxMenuTrace','menuPos: '   ,menuPos[0],menuPos[1]);
	      JcmsLogger.debug('CtxMenuTrace','menuDim: '   ,menuDim.width,menuDim.height);
      }
	    
	    // Display on left if there is enough bounds
	    if (menuPos[0]+menuDim.width > pageScroll[0]+pageBounds.width-20){
	      if (menuPos[0] < 2*menuDim.width-40){ // No rooms at left (2 level only)
          li.submenu.style.left = liPos[0]+18-(menuPos[0]-menuDim.width)+'px';
          li.submenu.style.top  = liPos[1]+8+'px';
        } else {
	        li.submenu.style.left = liPos[0]-menuDim.width+'px';
	      }
	    }
	    
	    // Display on top
	    var tmp = (pageScroll[1]+pageBounds.height-20) - (menuPos[1]+menuDim.height);
	    if ((menuPos[1]+menuDim.height > pageScroll[1]+pageBounds.height-20) && liPos[1] > tmp){
	      li.submenu.style.top  = liPos[1]+tmp+'px';
	    }
	    
	    
	    JcmsLogger.debug('CtxMenuTrace','SubMenu2: '+li.submenu.style.left+' , '+li.submenu.style.top);
	    
	    // Show menu
	    li.ctxmenu.showMenu(li.submenu);
	  },
	  
	  /**
	   * Hide a submenu.
	   */
	  hideSubMenu: function(li){ 
	    
	    // Remove runing timeout
	    if (li.activeTimeout){
	      clearTimeout(li.activeTimeout);
	    }
	    
	    // Do not do job twice
	    if (li.cxthide)
	      return;
	    li.cxthide = true;
	    li.cxtshow = false;
	    
	    
	    li.removeClassName('hover');
	    
	    if (li.submenu){
	      li.ctxmenu.hideMenu(li.submenu);
	    }
	  },
	  
	  /**
	   * Hide menu, sub iframe, ...
	   */
	  hideMenu: function(elm){
	    JcmsLogger.debug('CtxMenu','hideMenu('+elm.id+')');
	    
	    var ctxmenu = elm.ctxmenu;
	    
	    // Menu already closed
	    if (elm.style.display == 'none'){
	      return;
	    }
	    
	    // Main menu is closed
      var ctxMenuElm = $(ctxmenu.id);
	    if (elm == ctxMenuElm){
	      // Clear timeout to do stuff once
	      if (elm.ctxmenu.activeTimeout){
	        clearTimeout(elm.ctxmenu.activeTimeout);
	      }
	    }
	    	    
	    var elm = $(elm);
	    elm.hide();
	    elm.ctxmenu._hideIFrame(elm);
	  }
	}
	
	
