Wiki - KEONHACAI COPA

Thành viên:MeigyokuThmn/Tooltips.js

// <source lang="javascript">

/*
  Cross-browser tooltip support for MediaWiki.
  
  Author: [[User:Lupo]], March 2008
  License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0)
  
  Choose whichever license of these you like best :-)

  Based on ideas gleaned from prototype.js and prototip.js.
  http://www.prototypejs.org/
  http://www.nickstakenburg.com/projects/prototip/
  However, since prototype is pretty large, and prototip had some
  problems in my tests, this stand-alone version was written.
  
  Note: The fancy effects from scriptaculous have not been rebuilt.
  http://script.aculo.us/
  
  See http://commons.wikimedia.org/wiki/MediaWiki_talk:Tooltips.js for
  more information including documentation and examples.
*/

var is_IE       = !!window.ActiveXObject;
var is_IE_pre_7 = is_IE 
               && (function(agent) {
                     var version = new RegExp('MSIE ([\\d.]+)').exec(agent);
                     return version ? (parseFloat(version[1]) < 7) : false;
                   })(navigator.userAgent);

var EvtHandler = {
  listen_to : function (object, node, evt, f)
  {
    var listener = EvtHandler.make_listener (object, f);
    EvtHandler.attach (node, evt, listener);
  },
  
  attach : function (node, evt, f)
  {
    if (node.attachEvent) node.attachEvent ('on' + evt, f);
    else if (node.addEventListener) node.addEventListener (evt, f, false);
    else node['on' + evt] = f;
  },
  
  remove : function (node, evt, f)
  {
    if (node.detachEvent) node.detachEvent ('on' + evt, f);
    else if (node.removeEventListener) node.removeEventListener (evt, f, false);
    else node['on' + evt] = null;
  },
  
  make_listener : function (obj, listener)
  {
    // Some hacking around to make sure 'this' is set correctly
    var object = obj, f = listener;
    return function (evt) { return f.apply (object, [evt || window.event]); }
  },

  mouse_offset : function ()
  {
    // IE does some strange things...
    // This is adapted from dojo 0.9.0, see http://dojotoolkit.org
    if (is_IE) {
      var doc_elem = document.documentElement;
      if (doc_elem) {
        if (typeof (doc_elem.getBoundingClientRect) == 'function') {
          var tmp = doc_elem.getBoundingClientRect ();
          return {x : tmp.left, y : tmp.top};
        } else {
          return {x : doc_elem.clientLeft, y : doc_elem.clientTop};
        }
      }
    }
    return null;
  },
  
  killEvt : function (evt)
  {
    if (typeof (evt.preventDefault) == 'function') {
      evt.stopPropagation ();
      evt.preventDefault (); // Don't follow the link
    } else if (typeof (evt.cancelBubble) != 'undefined') { // IE...
      evt.cancelBubble = true;
    }
    return false; // Don't follow the link (IE)
  }

} // end EvtHandler

var Buttons = {
  
  buttonClasses : {},
  
  createCSS : function (imgs, sep, id)
  {
    var width   = imgs[0].getAttribute ('width');
    var height  = imgs[0].getAttribute ('height');
    try {
      // The only way to set the :hover and :active properties through Javascript is by
      // injecting a piece of CSS. There is no direct access within JS to these properties.
      var sel1  = "a" + sep + id;
      var prop1 = "border:0; text-decoration:none; background-color:transparent; "
                + "width:" + width + "px; height:" + height + "px; "
                + "display:inline-block; "
                + "background-position:left; background-repeat:no-repeat; "
                + "background-image:url(" + imgs[0].src + ");";
      var sel2  = null, prop2 = null, sel3  = null, prop3 = null; // For IE...
      var css   = sel1 + ' {' + prop1 + '}\n';                    // For real browsers
      if (imgs.length > 1 && imgs[1]) {
        sel2  = "a" + sep + id + ":hover";
        prop2 = "background-image:url(" + imgs[1].src + ");";
        css   = css + sel2 + ' {' + prop2 + '}\n';
      }
      if (imgs.length > 2 && imgs[2]) {
        sel3  = "a" + sep + id + ":active"
        prop3 = "background-image:url(" + imgs[2].src + ");";
        css   = css + sel3 + ' {' + prop3 + '}\n';
      }
      // Now insert a style sheet with these properties into the document (or rather, its head).
      var styleElem = document.createElement( 'style' );
      styleElem.setAttribute ('type', 'text/css');
      try {
        styleElem.appendChild (document.createTextNode (css));
        document.getElementsByTagName ('head')[0].appendChild (styleElem);
      } catch (ie_bug) {
        // Oh boy, IE has a big problem here
        document.getElementsByTagName ('head')[0].appendChild (styleElem);
//        try {
          styleElem.styleSheet.cssText = css;
/*
        } catch (anything) {
          if (document.styleSheets) {
            var lastSheet = document.styleSheets[document.styleSheets.length - 1];          
            if (lastSheet && typeof (lastSheet.addRule) != 'undefined') {
              lastSheet.addRule (sel1, prop1);
              if (sel2) lastSheet.addRule (sel2, prop2);
              if (sel3) lastSheet.addRule (sel3, prop3);
            }
          }
        }
*/
      }
    } catch (ex) {
      return null;
    }
    if (sep == '.') {
      // It's a class: remember the first image
      Buttons.buttonClasses[id] = imgs[0];
    }
    return id;
  }, // end createCSS
  
  createClass : function (imgs, id)
  {
    return Buttons.createCSS (imgs, '.', id);
  },
  
  makeButton : function (imgs, id, handler, title)
  {
    var success     = false;
    var buttonClass = null;
    var content     = null;
    if (typeof (imgs) == 'string') {
      buttonClass = imgs;
      content     = Buttons.buttonClasses[imgs];
      success     = (content != null);
    } else {
      success     = (Buttons.createCSS (imgs, '#', id) != null);
      content     = imgs[0];
    }
    if (success) {
      var lk = document.createElement ('a');
      lk.setAttribute
        ('title', title || content.getAttribute ('alt') || content.getAttribute ('title') || "");
      lk.id = id;
      if (buttonClass) lk.className = buttonClass;
      if (typeof (handler) == 'string') {
        lk.href = handler;
      } else {
        lk.href = '#'; // Dummy, overridden by the onclick handler below.
        lk.onclick = function (evt)
          {
            var e = evt || window.event; // W3C, IE
            try {handler (e);} catch (ex) {};
            return EvtHandler.killEvt (e);
          };
      }
      content = content.cloneNode (true);
      content.style.visibility = 'hidden';
      lk.appendChild (content);
      return lk;
    } else {
      return null;
    }
  } // end makeButton
  
} // end Button

var Tooltips = {
  // Helper object to force quick closure of a tooltip if another one shows up.
  debug    : false,  
  top_tip  : null,
  nof_tips : 0,

  new_id : function ()
  {
    Tooltips.nof_tips++;
    return 'tooltip_' + Tooltips.nof_tips;
  },

  register : function (new_tip)
  {
    if (Tooltips.top_tip && Tooltips.top_tip != new_tip) Tooltips.top_tip.hide_now ();
    Tooltips.top_tip = new_tip;
  },
  
  deregister : function (tip)
  {
    if (Tooltips.top_tip == tip) Tooltips.top_tip = null;
  },

  close : function ()
  {
    if (Tooltips.top_tip) {
      Tooltips.top_tip.hide_now ();
      Tooltips.top_tip = null;
    }
  }
}

var Tooltip = function () {this.initialize.apply (this, arguments);}
// This is the Javascript way of creating a class. Methods are added below to Tooltip.prototype;
// one such method is 'initialize', and that will be called when a new instance is created.
// To create instances of this class, use var t = new Tooltip (...);

// Location constants
Tooltip.MOUSE             = 0; // Near the mouse pointer
Tooltip.TRACK             = 1; // Move tooltip when mouse pointer moves
Tooltip.FIXED             = 2; // Always use a fixed poition (anchor) relative to an element

// Anchors
Tooltip.TOP_LEFT          = 1;
Tooltip.TOP_RIGHT         = 2;
Tooltip.BOTTOM_LEFT       = 3;
Tooltip.BOTTOM_RIGHT      = 4;

// Activation constants
Tooltip.NONE              = -1; // You must show the tooltip explicitly in this case.
Tooltip.HOVER             =  1;
Tooltip.FOCUS             =  2; // always uses the FIXED position
Tooltip.CLICK             =  4;
Tooltip.ALL_ACTIVATIONS   =  7;

// Deactivation constants

Tooltip.MOUSE_LEAVE       =  1; // Mouse leaves target, alternate target, and tooltip
Tooltip.LOSE_FOCUS        =  2; // Focus changes away from target
Tooltip.CLICK_ELEM        =  4; // Target is clicked
Tooltip.CLICK_TIP         =  8; // Makes only sense if not tracked
Tooltip.ESCAPE            = 16;
Tooltip.ALL_DEACTIVATIONS = 31;
Tooltip.LEAVE             = Tooltip.MOUSE_LEAVE | Tooltip.LOSE_FOCUS;

// On IE, use the mouseleave/mouseenter events, which fire only when the boundaries of the
// element are left (but not when the element if left because the mouse moved over some
// contained element)

Tooltip.mouse_in    = (is_IE ? 'mouseenter' : 'mouseover');
Tooltip.mouse_out   = (is_IE ? 'mouseleave' : 'mouseout');

Tooltip.prototype =
{
  initialize : function (on_element, tt_content, opt, css)
  {
    if (!on_element || !tt_content) return;
    this.tip_id      = Tooltips.new_id ();
    // Registering event handlers via attacheEvent on IE is apparently a time-consuming
    // operation. When you add many tooltips to a page, this can add up to a noticeable delay.
    // We try to mitigate that by only adding those handlers we absolutely need when the tooltip
    // is created: those for showing the tooltip. The ones for hiding it again are added the
    // first time the tooltip is actually shown. We thus record which handlers are installed to
    // avoid installing them multiple times:
    //   event_state: -1 : nothing set, 0: activation set, 1: all set
    //   tracks:      true iff there is a mousemove handler for tracking installed.
    // This change bought us about half a second on IE (for 13 tooltips on one page). On FF, it
    // doesn't matter at all; in Firefoy, addEventListener is fast anyway.
    this.event_state = -1;
    this.tracks      = false;
    // We clone the node, wrap it, and re-add it at the very end of the
    // document to make sure we're not within some nested container with
    // position='relative', as this screws up all absolute positioning
    // (We always position in global document coordinates.)
    // In my tests, it appeared as if Nick Stakenburg's "prototip" has
    // this problem...
    if (typeof (tt_content) == 'function') {
      this.tip_creator = tt_content;
      this.css         = css;
      this.content     = null;
    } else {
      this.tip_creator = null;
      this.css         = null;
      if (tt_content.parentNode) {
        if (tt_content.ownerDocument != document)
          tt_content = document.importNode (tt_content, true);
        else
          tt_content = tt_content.cloneNode (true);
      }
      tt_content.id = this.tip_id;
      this.content  = tt_content;
    }
    // Wrap it
    var wrapper = document.createElement ('div');
    wrapper.className = 'tooltipContent';
    // On IE, 'relative' triggers lots of float:right bugs (floats become invisible or are
    // mispositioned).
    //if (!is_IE) wrapper.style.position = 'relative';
    if (this.content) wrapper.appendChild (this.content);
    this.popup = document.createElement ('div');
    this.popup.style.display = 'none';
    this.popup.style.position = 'absolute';
    this.popup.style.top = "0px";
    this.popup.style.left = "0px";
    this.popup.appendChild (wrapper);
    // Set the options
    this.options = {
       mode         : Tooltip.TRACK              // Where to display the tooltip.
      ,activate     : Tooltip.HOVER              // When to activate
      ,deactivate   : Tooltip.LEAVE | Tooltip.CLICK_ELEM | Tooltip.ESCAPE // When to deactivate
      ,mouse_offset : {x: 5, y: 5, dx: 1, dy: 1} // Pixel offsets and direction from mouse pointer
      ,fixed_offset : {x:10, y: 5, dx: 1, dy: 1} // Pixel offsets from anchor position
      ,anchor       : Tooltip.BOTTOM_LEFT        // Anchor for fixed position
      ,target       : null                       // Optional alternate target for fixed display.
      ,max_width    :    0.6         // Percent of window width (1.0 == 100%)
      ,max_pixels   :    0           // If > 0, maximum width in pixels
      ,z_index      : 1000           // On top of everything
      ,open_delay   :  500           // Millisecs, set to zero to open immediately
      ,hide_delay   : 1000           // Millisecs, set to zero to close immediately
      ,close_button : null           // Either a single image, or an array of up to three images
                                     // for the normal, hover, and active states, in that order
      ,onclose      : null           // Callback to be called when the tooltip is hidden. Should be
                                     // a function taking a single argument 'this' (this Tooltip)
                                     // an an optional second argument, the event.
      ,onopen       : null           // Ditto, called after opening.
    };
    // The lower of max_width and max_pixels limits the tooltip's width.
    if (opt) { // Merge in the options
      for (var option in opt) {
        if (option == 'mouse_offset' || option == 'fixed_offset') {
          try {
            for (var attr in opt[option]) {
              this.options[option][attr] = opt[option][attr];
            }
          } catch (ex) {
          }
        } else
          this.options[option] = opt[option];
      }
    }
    // Set up event handlers as appropriate
    this.eventShow   = EvtHandler.make_listener (this, this.show);
    this.eventToggle = EvtHandler.make_listener (this, this.toggle);
    this.eventFocus  = EvtHandler.make_listener (this, this.show_focus);
    this.eventClick  = EvtHandler.make_listener (this, this.show_click);
    this.eventHide   = EvtHandler.make_listener (this, this.hide);
    this.eventTrack  = EvtHandler.make_listener (this, this.track);
    this.eventClose  = EvtHandler.make_listener (this, this.hide_now);
    this.eventKey    = EvtHandler.make_listener (this, this.key_handler);

    this.close_button       = null;
    this.close_button_width = 0;
    if (this.options.close_button) {
      this.makeCloseButton ();
      if (this.close_button) {
        // Only a click on the close button will close the tip.
        this.options.deactivate = this.options.deactivate & ~Tooltip.CLICK_TIP;
        // And escape is always active if we have a close button
        this.options.deactivate = this.options.deactivate | Tooltip.ESCAPE;
        // Don't track, you'd have troubles ever getting to the close button.
        if (this.options.mode == Tooltip.TRACK) this.options.mode = Tooltip.MOUSE;
        this.has_links = true;
      }
    }
    if (this.options.activate == Tooltip.NONE) {
      this.options.activate = 0;
    } else {
      if ((this.options.activate & Tooltip.ALL_ACTIVATIONS) == 0) {
        if (on_element.nodeName.toLowerCase () == 'a')
          this.options.activate = Tooltip.CLICK;
        else
          this.options.activate = Tooltip.HOVER;
      }
    }
    if ((this.options.deactivate & Tooltip.ALL_DEACTIVATIONS) == 0 && !this.close_button)
      this.options.deactivate = Tooltip.LEAVE | Tooltip.CLICK_ELEM | Tooltip.ESCAPE;
    document.body.appendChild (this.popup);
    if (this.content) this.apply_styles (this.content, css); // After adding it to the document
    // Clickable links?
    if (this.content && this.options.mode == Tooltip.TRACK) {
      this.setHasLinks ();
      if (this.has_links) {
        // If you track a tooltip with links, you'll never be able to click the links
        this.options.mode = Tooltip.MOUSE;
      }
    }
    // No further option checks. If nonsense is passed, you'll get nonsense or an exception.
    this.popup.style.zIndex = "" + this.options.z_index;
    this.target             = on_element;
    this.open_timeout_id    = null;
    this.hide_timeout_id    = null;
    this.size               = {width : 0, height : 0};
    this.setupEvents (EvtHandler.attach, 0);
    this.ieFix = null;
    if (is_IE) {
      // Display an invisible IFrame of the same size as the popup beneath it to make popups
      // correctly cover "windowed controls" such as form input fields in IE. For IE >=5.5, but
      // who still uses older IEs?? The technique is also known as a "shim". A good
      // description is at http://dev2dev.bea.com/lpt/a/39
      this.ieFix = document.createElement ('iframe');
      this.ieFix.style.position = 'absolute';
      this.ieFix.style.border   = '0';
      this.ieFix.style.margin   = '0';
      this.ieFix.style.padding  = '0';
      this.ieFix.style.zIndex   = "" + (this.options.z_index - 1); // Below the popup
      this.ieFix.tabIndex       = -1;
      this.ieFix.frameBorder    = '0';
      this.ieFix.style.display  = 'none';
      document.body.appendChild (this.ieFix);
      this.ieFix.style.filter  = 'alpha(Opacity=0)'; // Ensure transparency
    } 
  },
  
  apply_styles : function (node, css)
  {
    if (css) {
      for (var styledef in css) node.style[styledef] = css[styledef];
    }
    if (this.close_button) node.style.opacity = "1.0"; // Bug workaround.
    // FF doesn't handle the close button at all if it is (partially) transparent...
    if (node.style.display == 'none') node.style.display = "";
  },

  setHasLinks : function ()
  {
    if (this.close_button) { this.has_links = true; return; }
    var lks = this.content.getElementsByTagName ('a');
    this.has_links = false;
    for (var i=0; i < lks.length; i++) {
      var href = lks[i].getAttribute ('href');
      if (href && href.length > 0) { this.has_links = true; return; }
    }
    // Check for form elements
    function check_for (within, names)
    {
      if (names) {
        for (var i=0; i < names.length; i++) {
          var elems = within.getElementsByTagName (names[i]);
          if (elems && elems.length > 0) return true;
        }
      }
      return false;
    }
    this.has_links = check_for (this.content, ['form', 'textarea', 'input', 'button', 'select']);
  },

  setupEvents : function (op, state)
  {
    if (state < 0 || state == 0 && this.event_state < state) {
      if (this.options.activate & Tooltip.HOVER)
        op (this.target, Tooltip.mouse_in, this.eventShow);
      if (this.options.activate & Tooltip.FOCUS)
        op (this.target, 'focus', this.eventFocus);
      if (   (this.options.activate & Tooltip.CLICK)
          && (this.options.deactivate & Tooltip.CLICK_ELEM)) {
        op (this.target, 'click', this.eventToggle);
      } else {
        if (this.options.activate & Tooltip.CLICK)
          op (this.target, 'click', this.eventClick);
        if (this.options.deactivate & Tooltip.CLICK_ELEM)
          op (this.target, 'click', this.eventClose);
      }
      this.event_state = state;
    }
    if (state < 0 || state == 1 && this.event_state < state) {
      if (this.options.deactivate & Tooltip.MOUSE_LEAVE) {
        op (this.target, Tooltip.mouse_out, this.eventHide);
        op (this.popup, Tooltip.mouse_out, this.eventHide);
        if (this.options.target) op (this.options.target, Tooltip.mouse_out, this.eventHide);
      }
      if (this.options.deactivate & Tooltip.LOSE_FOCUS)
        op (this.target, 'blur', this.eventHide);
      if (   (this.options.deactivate & Tooltip.CLICK_TIP)
          && (this.options.mode != Tooltip.TRACK))
        op (this.popup, 'click', this.eventClose);        
      
      // Some more event handling
      if (this.hide_delay > 0) {
        if (!(this.options.activate & Tooltip.HOVER))
          op (this.popup, Tooltip.mouse_in, this.eventShow);
        op (this.popup, 'mousemove', this.eventShow);
      }
      this.event_state = state;
    }
    if (state < 0 && this.tracks)
      op (this.target, 'mousemove', this.eventTrack);
  },
  
  remove: function ()
  {
    this.hide_now ();
    this.setupEvents (EvtHandler.remove, -1);
    this.tip_creator = null;
    document.body.removeElement (this.popup);
    if (this.ieFix) document.body.removeElement (this.ieFix);
  },
  
  show : function (evt)
  {
    this.show_tip (evt, true);
  },
  
  show_focus : function (evt) // Show on focus
  {
    this.show_tip (evt, false);
  },
  
  show_click : function (evt)
  {
    this.show_tip (evt, false);
    if (this.target.nodeName.toLowerCase () == 'a') return EvtHandler.killEvt (evt); else return false;
  },
  
  toggle : function (evt)
  {
    if (this.popup.style.display != 'none' && this.popup.style.display != null) {
      this.hide_now (evt);
    } else {
      this.show_tip (evt, true);
    }
    if (this.target.nodeName.toLowerCase () == 'a') return EvtHandler.killEvt (evt); else return false;
  },

  show_tip : function (evt, is_mouse_evt)
  {
    if (this.hide_timeout_id != null) window.clearTimeout (this.hide_timeout_id);
    this.hide_timeout_id = null;
    if (this.popup.style.display != 'none' && this.popup.style.display != null) return;
    if (this.tip_creator) {
      // Dynamically created tooltip.
      try {
        this.content = this.tip_creator (evt);
      } catch (ex) {
        // Oops! Indicate that something went wrong!
        var error_msg = document.createElement ('div');
        error_msg.appendChild (
          document.createElement ('b').appendChild (
            document.createTextNode ('Exception: ' + ex.name)));
        error_msg.appendChild(document.createElement ('br'));
        error_msg.appendChild (document.createTextNode (ex.message));
        if (typeof (ex.fileName) != 'undefined' &&
            typeof (ex.lineNumber) != 'undefined') {
          error_msg.appendChild(document.createElement ('br'));
          error_msg.appendChild (document.createTextNode ('File ' + ex.fileName));
          error_msg.appendChild(document.createElement ('br'));
          error_msg.appendChild (document.createTextNode ('Line ' + ex.lineNumber));
        }
        this.content = error_msg;
      }
      // Our wrapper has at most two children: the close button, and the content. Don't remove
      // the close button, if any.
      if (this.popup.firstChild.lastChild && this.popup.firstChild.lastChild != this.close_button)
        this.popup.firstChild.removeChild (this.popup.firstChild.lastChild);
      this.popup.firstChild.appendChild (this.content);
      this.apply_styles (this.content, this.css);
      if (this.options.mode == Tooltip.TRACK) this.setHasLinks ();
    }
    // Position it now. It must be positioned before the timeout below!
    this.position_tip (evt, is_mouse_evt);
    if (Tooltips.debug) {
      alert ('Position: x = ' + this.popup.style.left + ' y = ' + this.popup.style.top);
    }
    this.setupEvents (EvtHandler.attach, 1);
    if (this.options.mode == Tooltip.TRACK) {
      if (this.has_links) {
        if (this.tracks) EvtHandler.remove (this.target, 'mousemove', this.eventTrack);
        this.tracks = false;
      } else {
        if (!this.tracks) EvtHandler.attach (this.target, 'mousemove', this.eventTrack);
        this.tracks = true;
      }
    }
    if (this.options.open_delay > 0) {
      var obj = this;
      this.open_timout_id = 
        window.setTimeout (function () {obj.show_now (obj);}, this.options.open_delay);
    } else
      this.show_now (this);
  },
  
  show_now : function (elem)
  {
    if (elem.popup.style.display != 'none' && elem.popup.style.display != null) return;
    Tooltips.register (elem);
    if (elem.ieFix) {
      elem.ieFix.style.top     = elem.popup.style.top;
      elem.ieFix.style.left    = elem.popup.style.left;
      elem.ieFix.style.width   = elem.size.width + "px";
      elem.ieFix.style.height  = elem.size.height + "px";
      elem.ieFix.style.display = "";
    }
    elem.popup.style.display = ""; // Finally show it
    if (   (elem.options.deactivate & Tooltip.ESCAPE)
        && typeof (elem.popup.focus) == 'function') {
      // We need to attach this event globally.
      EvtHandler.attach (document, 'keydown', elem.eventKey);
    }
    elem.open_timeout_id = null;
    // Callback
    if (typeof (elem.options.onopen) == 'function') elem.options.onopen (elem);
  },
  
  track : function (evt)
  {
    this.position_tip (evt, true);
    // Also move the shim!
    if (this.ieFix) {
      this.ieFix.style.top     = this.popup.style.top;
      this.ieFix.style.left    = this.popup.style.left;
      this.ieFix.style.width   = this.size.width + "px";
      this.ieFix.style.height  = this.size.height + "px";
    }
  },
  
  size_change : function ()
  {
    // If your content is such that it changes, make sure this is called after each size change.
    // Unfortunately, I have found no way of monitoring size changes of this.popup and then doing
    // this automatically. See for instance the "toggle" example (the 12th) on the example page at
    // http://commons.wikimedia.org/wiki/MediaWiki:Tooltips.js/Documentation/Examples
    if (this.popup.style.display != 'none' && this.popup.style.display != null) {
      // We're visible. Make sure the shim gets resized, too!
      this.size = {width : this.popup.offsetWidth, height: this.popup.offsetHeight};
      if (this.ieFix) {
        this.ieFix.style.top     = this.popup.style.top;
        this.ieFix.style.left    = this.popup.style.left;
        this.ieFix.style.width   = this.size.width + "px";
        this.ieFix.style.height  = this.size.height + "px";
      }
    }
  },
    
  position_tip : function (evt, is_mouse_evt)
  {
    var view = {width  : this.viewport ('Width'),
                height : this.viewport ('Height')};
    var off  = {left   : this.scroll_offset ('Left'),
                top    : this.scroll_offset ('Top')};
    var x = 0, y = 0;
    var offset = null;
    // Calculate the position
    if (is_mouse_evt && this.options.mode != Tooltip.FIXED) {
      var mouse_delta = EvtHandler.mouse_offset ();
      if (Tooltips.debug && mouse_delta) {
        alert ("Mouse offsets: x = " + mouse_delta.x + ", y = " + mouse_delta.y);
      }
      x = (evt.pageX || (evt.clientX + off.left - (mouse_delta ? mouse_delta.x : 0)));
      y = (evt.pageY || (evt.clientY + off.top - (mouse_delta ? mouse_delta.y : 0)));
      offset = 'mouse_offset';
    } else {
      var tgt = this.options.target || this.target;
      var pos = this.position (tgt);
      switch (this.options.anchor) {
        default:
        case Tooltip.BOTTOM_LEFT:
          x = pos.x; y = pos.y + tgt.offsetHeight;
          break;
        case Tooltip.BOTTOM_RIGHT:
          x = pos.x + tgt.offsetWidth; y = pos.y + tgt.offsetHeight;
          break;
        case Tooltip.TOP_LEFT:
          x = pos.x; y = pos.y;
          break;
        case Tooltip.TOP_RIGHT:
          x = pos.x + tgt.offsetWidth; y = pos.y;
          break;
      }
      offset = 'fixed_offset';
    }
    
    x = x + this.options[offset].x * this.options[offset].dx;
    y = y + this.options[offset].y * this.options[offset].dy;

    this.size = this.calculate_dimension ();
    if (this.options[offset].dx < 0) x = x - this.size.width;
    if (this.options[offset].dy < 0) y = y - this.size.height;
    
    // Now make sure we're within the view.
    if (x + this.size.width > off.left + view.width) x = off.left + view.width - this.size.width;
    if (x < off.left) x = off.left;
    if (y + this.size.height > off.top + view.height) y = off.top + view.height - this.size.height;
    if (y < off.top) y = off.top;
    
    this.popup.style.top  = y + "px";
    this.popup.style.left = x + "px";
  },
  
  hide : function (evt)
  {
    if (this.popup.style.display == 'none') return;
    // Get mouse position
    var mouse_delta = EvtHandler.mouse_offset ();
    var x = evt.pageX
         || (evt.clientX + this.scroll_offset ('Left') - (mouse_delta ? mouse_delta.x : 0));
    var y = evt.pageY
         || (evt.clientY + this.scroll_offset ('Top') - (mouse_delta ? mouse_delta.y : 0));
    // We hide it if we're neither within this.target nor within this.content nor within the
    // alternate target, if one was given.
    if (Tooltips.debug) {
      var tp = this.position (this.target);
      var pp = this.position (this.popup);
      alert ("x = " + x + " y = " + y + '\n' +
             "t: " + tp.x + "/" + tp.y + "/" +
               this.target.offsetWidth + "/" + this.target.offsetHeight + '\n' +
             (tp.n ? "t.m = " + tp.n.nodeName + "/" + tp.n.getAttribute ('margin') + "/"
                     + tp.n.getAttribute ('marginTop')
                     + "/" + tp.n.getAttribute ('border') + '\n'
                   : "") +
             "p: " + pp.x + "/" + pp.y + "/" +
               this.popup.offsetWidth + "/" + this.popup.offsetHeight + '\n' +
             (pp.n ? "p.m = " + pp.n.nodeName + "/" + pp.n.getAttribute ('margin') + "/"
                     + pp.n.getAttribute ('marginTop')
                     + "/" + pp.n.getAttribute ('border') + '\n'
                   : "") +
             "e: " + evt.pageX + "/" + evt.pageY + " "
               + evt.clientX + "/" + this.scroll_offset ('Left') + " "
               + evt.clientY + "/" + this.scroll_offset ('Top') + '\n' +
             (mouse_delta ? "m : " + mouse_delta.x + "/" + mouse_delta.y + '\n' : "")
             );
    }
    if (   !this.within (this.target, x, y)
        && !this.within (this.popup, x, y)
        && (!this.options.target || !this.within (this.options.target, x, y))) {
      if (this.open_timeout_id != null) window.clearTimeout (this.open_timeout_id);
      this.open_timeout_id = null;
      var event_copy = evt;
      if (this.options.hide_delay > 0) {
        var obj = this;
        this.hide_timeout_id =
          window.setTimeout (
              function () {obj.hide_popup (obj, event_copy);}
            , this.options.hide_delay
          );
      } else
        this.hide_popup (this, event_copy);
    }
  },
  
  hide_popup : function (elem, event)
  {
    if (elem.popup.style.display == 'none') return; // Already hidden, recursion from onclose?
    elem.popup.style.display = 'none';
    if (elem.ieFix) elem.ieFix.style.display = 'none';
    elem.hide_timeout_id = null;
    Tooltips.deregister (elem);
    if (elem.options.deactivate & Tooltip.ESCAPE)
      EvtHandler.remove (document, 'keydown', elem.eventKey);
    // Callback
    if (typeof (elem.options.onclose) == 'function') elem.options.onclose (elem, event);
  },
  
  hide_now : function (evt)
  {
    if (this.open_timeout_id != null) window.clearTimeout (this.open_timeout_id);
    this.open_timeout_id = null;
    var event_copy = evt || null;
    this.hide_popup (this, event_copy);
    if (evt && this.target.nodeName.toLowerCase == 'a') return EvtHandler.killEvt (evt); else return false;
  },
  
  key_handler : function (evt)
  {
    if (Tooltips.debug) alert ('key evt ' + evt.keyCode);
    if (evt.DOM_VK_ESCAPE && evt.keyCode == evt.DOM_VK_ESCAPE || evt.keyCode == 27)
      this.hide_now (evt);
    return true;
  },

  setZIndex : function (z_index)
  {
    if (z_index === null || isNaN (z_index) || z_index < 2) return;
    z_index = Math.floor (z_index);
    if (z_index == this.options.z_index) return; // No change
    if (this.ieFix) {
      // Always keep the shim below the actual popup.
      if (z_index > this.options.z_index) {
        this.popup.style.zIndex = z_index;
        this.ieFix.style.zIndex = "" + (z_index - 1);
      } else {
        this.ieFix.style.zIndex = "" + (z_index - 1);
        this.popup.style.zIndex = z_index;
      }
    } else {
      this.popup.style.zIndex = z_index;
    }
    this.options.z_index = z_index;
  },

  makeCloseButton : function ()
  {
    this.close_button = null;
    if (!this.options.close_button) return;
    var imgs = null;
    if (typeof (this.options.close_button.length) != 'undefined')
      imgs = this.options.close_button; // Also if it's a string (name of previously created class)
    else
      imgs = [this.options.close_button];
    if (!imgs || imgs.length == 0) return; // Paranoia
    var lk = Buttons.makeButton (imgs, this.tip_id + '_button', this.eventClose); 

    if (lk) {
      var width = lk.firstChild.getAttribute ('width');
      if (!is_IE) {
        lk.style.cssFloat = 'right';
      } else {
        // IE is incredibly broken on right floats.
        var container = document.createElement ('div');
        container.style.display      = 'inline';
        container.style.styleFloat   = 'right';
        container.appendChild (lk);
        lk = container;
      }
      lk.style.paddingTop   = '2px';
      lk.style.paddingRight = '2px';
      this.popup.firstChild.insertBefore (lk, this.popup.firstChild.firstChild);
      this.close_button = lk;
      this.close_button_width = parseInt ("" + width, 10);
    }
  },

  within : function (node, x, y)
  {
    if (!node) return false;
    var pos = this.position (node);
    return    (x == null || x > pos.x && x < pos.x + node.offsetWidth)
           && (y == null || y > pos.y && y < pos.y + node.offsetHeight);
  },
  
  position : (function ()
  {
    // The following is the jQuery.offset implementation. We cannot use jQuery yet in globally
    // activated scripts (it has strange side effects for Opera 8 users who can't log in anymore,
    // and it breaks the search box for some users). Note that jQuery does not support Opera 8.
    // Until the WMF servers serve jQuery by default, this copy from the jQuery 1.3.2 sources is
    // needed here. If and when we have jQuery available officially, the whole thing here can be
    // replaced by "var tmp = jQuery (node).offset(); return {x:tmp.left, y:tmp.top};"
    // Kudos to the jQuery development team. Any errors in this adaptation are my own. (Lupo,
    // 2009-08-24).
    //   Note: I have virtually the same code also in LAPI.js, but I cannot import that here
    // because I know that at least one gadget at the French Wikipedia includes this script here
    // directly from here. I'd have to use importScriptURI instead of importScript to keep that
    // working, but I'd run the risk that including LAPI at the French Wikipedia might break
    // something there. I *hate* it when people hotlink scripts across projects!

    var data = null;

    function jQuery_init ()
    {
      data = {};
      // Capability check from jQuery.
      var body = document.body;
      var container = document.createElement('div');
      var html =
          '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;'
        + 'padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;'
        + 'top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" '
        + 'cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';
      var rules = { position: 'absolute', visibility: 'hidden'
                   ,top: 0, left: 0
                   ,margin: 0, border: 0
                   ,width: '1px', height: '1px'
                  };
      Object.merge (rules, container.style);

      container.innerHTML = html;
      body.insertBefore(container, body.firstChild);
      var innerDiv = container.firstChild;
      var checkDiv = innerDiv.firstChild;
      var td = innerDiv.nextSibling.firstChild.firstChild;

      data.doesNotAddBorder = (checkDiv.offsetTop !== 5);
      data.doesAddBorderForTableAndCells = (td.offsetTop === 5);

      innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative';
      data.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);

      var bodyMarginTop    = body.style.marginTop;
      body.style.marginTop = '1px';
      data.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0);
      body.style.marginTop = bodyMarginTop;

      body.removeChild(container);
    };

    function jQuery_offset (node)
    {
      if (node === node.ownerDocument.body) return jQuery_bodyOffset (node);
      if (node.getBoundingClientRect) {
        var box    = node.getBoundingClientRect ();
        var scroll = {x : this.scroll_offset ('Left'), y: this.scroll_offset ('Top')};
        return {x : (box.left + scroll.x), y : (box.top + scroll.y)};
      }
      if (!data) jQuery_init ();
      var elem              = node;
      var offsetParent      = elem.offsetParent;
      var prevOffsetParent  = elem;
      var doc               = elem.ownerDocument;
      var prevComputedStyle = doc.defaultView.getComputedStyle(elem, null);
      var computedStyle;

      var top  = elem.offsetTop;
      var left = elem.offsetLeft;

      while ( (elem = elem.parentNode) && elem !== doc.body && elem !== doc.documentElement ) {
        computedStyle = doc.defaultView.getComputedStyle(elem, null);
        top -= elem.scrollTop, left -= elem.scrollLeft;
        if ( elem === offsetParent ) {
          top += elem.offsetTop, left += elem.offsetLeft;
          if (   data.doesNotAddBorder
              && !(data.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName))
             )
          {
            top  += parseInt (computedStyle.borderTopWidth,  10) || 0;
            left += parseInt (computedStyle.borderLeftWidth, 10) || 0;
          }
          prevOffsetParent = offsetParent; offsetParent = elem.offsetParent;
        }
        if (data.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== 'visible')
        {
          top  += parseInt (computedStyle.borderTopWidth,  10) || 0;
          left += parseInt (computedStyle.borderLeftWidth, 10) || 0;
        }
        prevComputedStyle = computedStyle;
      }

      if (prevComputedStyle.position === 'relative' || prevComputedStyle.position === 'static') {
        top  += doc.body.offsetTop;
        left += doc.body.offsetLeft;
      }
      if (prevComputedStyle.position === 'fixed') {
        top  += Math.max (doc.documentElement.scrollTop, doc.body.scrollTop);
        left += Math.max (doc.documentElement.scrollLeft, doc.body.scrollLeft);
      }
      return {x: left, y: top};            
    }

    function jQuery_bodyOffset (body)
    {
      if (!data) jQuery_init();
      var top = body.offsetTop, left = body.offsetLeft;
      if (data.doesNotIncludeMarginInBodyOffset) {
        var styles;
        if (   body.ownerDocument.defaultView
            && body.ownerDocument.defaultView.getComputedStyle)
        { // Gecko etc.
          styles = body.ownerDocument.defaultView.getComputedStyle (body, null);
          top  += parseInt (style.getPropertyValue ('margin-top' ), 10) || 0;
          left += parseInt (style.getPropertyValue ('margin-left'), 10) || 0;
        } else {
          function to_px (element, val) {
            // Convert em etc. to pixels. Kudos to Dean Edwards; see
            // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
            if (!/^\d+(px)?$/i.test (val) && /^\d/.test (val) && body.runtimeStyle) {
              var style                 = element.style.left;
              var runtimeStyle          = element.runtimeStyle.left;
              element.runtimeStyle.left = element.currentStyle.left;
              element.style.left        = result || 0;
              val = elem.style.pixelLeft + "px";
              element.style.left        = style;
              element.runtimeStyle.left = runtimeStyle;
            }
            return val;
          }
          style = body.currentStyle || body.style;
          top  += parseInt (to_px (body, style.marginTop ), 10) || 0;
          left += parseInt (to_px (body, style.marginleft), 10) || 0;
        }
      }
      return {x: left, y: top};
    }

    return jQuery_offset;
  })(),

  scroll_offset : function (what)
  {
    var s = 'scroll' + what;
    return (document.documentElement ? document.documentElement[s] : 0)
           || document.body[s] || 0;
  },

  viewport : function (what)
  {
    var s = 'client' + what;
    return (document.documentElement ? document.documentElement[s] : 0) || document.body[s] || 0;
  },


  calculate_dimension : function ()
  {
    if (this.popup.style.display != 'none' && this.popup.style.display != null) {
      return {width : this.popup.offsetWidth, height : this.popup.offsetHeight};
    }
    // Must display it... but position = 'absolute' and visibility = 'hidden' means
    // the user won't notice it.
    var view_width = this.viewport ('Width');
    this.popup.style.top        = "0px";
    this.popup.style.left       = "0px";
    // Remove previous width as it may change with dynamic tooltips
    this.popup.style.width      = "";
    this.popup.style.maxWidth   = "";
    this.popup.style.overflow   = 'hidden';
    this.popup.style.visibility = 'hidden';
    // Remove the close button, otherwise the float will always extend the box to
    // the right edge.
    if (this.close_button)
      this.popup.firstChild.removeChild (this.close_button);
    this.popup.style.display = "";   // Display it. Now we should have a width
    var w = this.popup.offsetWidth;
    var h = this.popup.offsetHeight;
    var limit = Math.round (view_width * this.options.max_width);
    if (this.options.max_pixels > 0 && this.options.max_pixels < limit)
      limit = this.options.max_pixels;
    if (w > limit) {
      w = limit;
      this.popup.style.width    = "" + w + "px";
      this.popup.style.maxWidth = this.popup.style.width;
      if (this.close_button) {
        this.popup.firstChild.insertBefore
          (this.close_button, this.popup.firstChild.firstChild);
      }
    } else {
      this.popup.style.width    = "" + w + "px";
      this.popup.style.maxWidth = this.popup.style.width;
      if (this.close_button) {
        this.popup.firstChild.insertBefore
          (this.close_button, this.popup.firstChild.firstChild);
      }
      if (h != this.popup.offsetHeight) {
        w =  w + this.close_button_width;    
        this.popup.style.width    = "" + w + "px";
        this.popup.style.maxWidth = this.popup.style.width;
      }
    }
    var size = {width : this.popup.offsetWidth, height : this.popup.offsetHeight};
    this.popup.style.display = 'none';       // Hide it again
    this.popup.style.visibility = "";
    return size;
  }
    
} // end Tooltip

// </source>
Wiki - Keonhacai copa chuyên cung cấp kiến thức thể thao, keonhacai tỷ lệ kèo, bóng đá, khoa học, kiến thức hằng ngày được chúng tôi cập nhật mỗi ngày mà bạn có thể tìm kiếm tại đây có nguồn bài viết: https://vi.wikipedia.org/wiki/Th%C3%A0nh_vi%C3%AAn:MeigyokuThmn/Tooltips.js