Source: js/tooltips/tooltips.js

/*
# ***** BEGIN LICENSE BLOCK *****
# Assets Library - The open source PHP/JavaScript/CSS library of Les Ateliers Pierrot
# Copyleft (c) 2013-2014 Pierre Cassat and contributors
# <www.ateliers-pierrot.fr> - <contact@ateliers-pierrot.fr>
# License GPL-3.0 <http://www.opensource.org/licenses/gpl-3.0.html>
# Sources <http://github.com/atelierspierrot/assets-library>
#
# Ce programme est un logiciel libre distribué sous licence GNU/GPL.
#
# ***** END LICENSE BLOCK ***** */


/**
 * Build some tooltips on element with a specific class with the content of an element's attribute
 *
 * Internal private variables are prefixed by '_tltp'
 */
function TOOLTIP() {

    var self = {},                          // the whole object properties
        _ie = document.all ? true : false,  // are we in IE ?
        _tltp_id_attribute = 'tltpid',      // the ID attribute added to any tooltip element
        _tltp_wrapper_class = 'tooltip-wrapper-block',     // the ID attribute added to any tooltip element
        _tltp_content_class = 'tooltip-content-block',     // the ID attribute added to any tooltip element
        _tltp_wrapper,                      // the tooltip DOM wrapper
        _tltp_content,                      // the tooltip DOM content
        _tltp_cache,                        // the tooltip cache
        _tltp_currentel,                    // the current visible tooltip element
        registry;                           // the instances registry

// ------------------
// Private methods
// ------------------

// INIT

    // initialization of the tooltips objects	
    function domInit() {
        if (typeof window['_dbg_info'] == 'function')
            _dbg_info('entering domInit searching elements with class '+self._classname);
        var tooltipList = document.getElementsByClassName(self._classname);
        if (tooltipList) {
            for (var i=0, len=tooltipList.length; i<len; i++) {
                setInstance( tooltipList[i] );
                tooltipList[i].onmouseover 	= function() { show(this); };
                tooltipList[i].onmouseout 	= function() { hide(this); };
            }
        }
    };

    // all default properties
    function newInstance() {
        self._tltp_id           = null;
        self._classname 		= 'tooltip';
        self._attribute		    = 'title';
        self._content_class_attribute = null;
        self._wrapper_class 	= 'tooltip_wrapper';
        self._content_class	    = 'tooltip_content';
        self._wrapper_id_prefix = 'tooltip';
        self._content_id_prefix = 'tooltip_content';
        self._topleft_class 	= 'tooltip_tl';
        self._topright_class 	= 'tooltip_tr';
        self._bottomleft_class 	= 'tooltip_bl';
        self._bottomright_class = 'tooltip_br';
        self._top_class 	    = 'tooltip_t';
        self._bottom_class      = 'tooltip_b';
        self._left_class 	    = 'tooltip_l';
        self._right_class 	    = 'tooltip_r';
        self._tooltip_mask	    = null;
        self._fixed             = false
        self._auto_pos		    = true;
        self._top 		        = 3;
        self._left 		        = 3;
        self._bottom 		    = 6;
        self._right 		    = 6;
        self._max_width 		= 300;
        self._speed 		    = 10;
        self._timer 		    = 20;
        self._alpha 		    = 0;
        self._end_alpha 		= 95;
    };

// Registry

    // set an element instance
    function setInstance(el) {
        if (registry===undefined || registry===null) {
            registry = Registry();
        }
        if (self._tltp_id===null) {
            self._tltp_id = registry.add( self );
        }
        el.setAttribute( _tltp_id_attribute, self._tltp_id );
    };

    // get an element instance
    function getInstance(el) {
        var _tid = el.getAttribute( _tltp_id_attribute );
        return registry.get( _tid );
    };

    // get the current element instance
    function getCurrentInstance() {
        if (_tltp_currentel===null) return null;
        var _tid = _tltp_currentel.getAttribute( _tltp_id_attribute );
        return registry.get( _tid );
    };

// Tooltips

    // creation of the tooltip wrapper and append it to document
    function initTooltip(el) {
        var _self = getInstance(el);
        if (_tltp_wrapper===undefined || _tltp_wrapper===null)
        {
            // the wrapper
            _tltp_wrapper = document.createElement('div');
            _tltp_wrapper.setAttribute('id',self._wrapper_id_prefix+'_'+self._tltp_id);
            addClassName(_tltp_wrapper, _tltp_wrapper_class);
            addClassName(_tltp_wrapper, self._wrapper_class);
            // the content
            _tltp_content = document.createElement('div');
            _tltp_content.setAttribute('id',self._content_id_prefix+'_'+self._tltp_id);
            addClassName(_tltp_content, _tltp_content_class);
            addClassName(_tltp_content, self._content_class);
            if (_self._tooltip_mask!==undefined && _self._tooltip_mask!==null) {
                _patrn = new RegExp('\\$\\$content\\$\\$', 'ig');
                var str = _self._tooltip_mask.replace(_patrn, _tltp_content);
                if (typeof window['_dbg_info'] == 'function')
                    _dbg_info(_tooltip_mask);
                _tltp_wrapper.appendChild(str);
            }
            else {
                _tltp_wrapper.appendChild(_tltp_content);
            }
            document.body.appendChild(_tltp_wrapper);
            _tltp_wrapper.style.opacity = 0;
            _tltp_wrapper.style.filter = 'alpha(opacity=0)';
        }
    };

    // update of the tooltip wrapper and show it
    function showTooltip(el) {
        var _self = getInstance(el);
        _tltp_currentel = el;
        _tltp_cache = _tltp_currentel.getAttribute(_self._attribute);
        _tltp_currentel.setAttribute(_self._attribute, '');
        _tltp_content.innerHTML = _tltp_cache;
        _tltp_wrapper.style.display = 'block';
        _tltp_wrapper.style.width = 'auto';
        if (_ie) {
            _tltp_wrapper.style.width = _tltp_wrapper.offsetWidth;
        }
        if (_tltp_wrapper.offsetWidth > _self._max_width) {
            _tltp_wrapper.style.width = _self._max_width + 'px'
        }
        if (_self._content_class_attribute!==undefined && _self._content_class_attribute!==null) {
            var _cls = el.getAttribute(_self._content_class_attribute);
            if (_cls) {
                addClassName(_tltp_content, _cls);
            }
        }
        if (!_self._fixed) {
            if (_ie){
                document.attachEvent('onMouseMove', mouseFollow);
            } else {
                document.addEventListener('mousemove', mouseFollow, false);
            }
        } else {
            buildFixedPosition(el);
        }
        clearInterval(_tltp_wrapper.timer);
        _tltp_wrapper.timer = setInterval(function() { fade(1,el); },_self._timer);
    };

    // update of the tooltip wrapper and hide it
    function hideTooltip(el) {
        var _self = getInstance(el);
        clearInterval(_tltp_wrapper.timer);
        _tltp_wrapper.timer = setInterval(function() { fade(-1,el); },_self._timer);
    };

    // clear the tooltip
    function clearTooltip(el) {
        var _self = getInstance(el);
        _tltp_wrapper.style.display = 'none';
        clearInterval(_tltp_wrapper.timer);
        el.setAttribute(_self._attribute, _tltp_cache);
        if (_self._content_class_attribute!==undefined && _self._content_class_attribute!==null) {
            var _cls = el.getAttribute(_self._content_class_attribute);
            if (_cls) {
                removeClassName(_tltp_content, _cls);
            }
        }
        _tltp_currentel = null;
        if (!_self._fixed) {
            if (_ie){
                document.detachEvent('onMouseMove', mouseFollow);
            } else {
                document.removeEventListener('mousemove', mouseFollow, false);
            }
        }
    };

    // be sure the old tooltip is cleared before doing next one
    function stopPropagation() {
        if (_tltp_currentel!==undefined && _tltp_currentel!==null) {
            clearTooltip(_tltp_currentel);
        }
    };

// ------------------
// Events treatments
// ------------------

    // show a tooltip on element "el"
    var show = function(el) {
        initTooltip(el);
        stopPropagation();
        showTooltip(el);
    },

    // hide tooltip on element "el" with fade
    hide = function(el) {
        hideTooltip(el);
    },

// ------------------
// Utilities
// ------------------

    // position the tooltip in the center of the element
    buildFixedPosition = function(el) {
        var _self = getCurrentInstance();
        if (_self===null) return;
        var vattr=null, hattr=null, positions = { top: 'auto', bottom: 'auto', left: 'auto', right: 'auto' };
        if (hasClassName(_tltp_currentel, _self._bottom_class)) { hattr = 'bottom'; }
        else if (hasClassName(_tltp_currentel, _self._left_class)) { vattr = 'left'; }
        else if (hasClassName(_tltp_currentel, _self._right_class)) { vattr = 'right'; }
        else { hattr = 'top'; }

        element_positions = getOffset(el);

        // top or bottom or left or right ?
        var eh = parseInt(_tltp_wrapper.offsetHeight),
            ew = parseInt(_tltp_wrapper.offsetWidth),
            elw = parseInt(element_positions.right-element_positions.left),
            elh = parseInt(element_positions.bottom-element_positions.top);
        if (hattr==='bottom') {
            positions.top = parseInt(element_positions.bottom + _self._bottom) + 'px';
            positions.left = parseInt(element_positions.left + (elw / 2) - (ew / 2)) + 'px';
        }
        else if (vattr==='left') {
            positions.top = parseInt(element_positions.top + (elh / 2) - (eh / 2)) + 'px';
            positions.left = parseInt(element_positions.left - ew - _self._left) + 'px';
        }
        else if (vattr==='right') {
            positions.top = parseInt(element_positions.top + (elh / 2) - (eh / 2)) + 'px';
            positions.left = parseInt(element_positions.right + _self._right) + 'px';
        }
        else {
            positions.top = parseInt(element_positions.top - _self._top - eh) + 'px';
            positions.left = parseInt(element_positions.left + (elw / 2) - (ew / 2)) + 'px';
        }

        for (var pos in positions) {
            _tltp_wrapper.style[pos] = positions[pos];
        }
    },

    // follow mouse position according to the tooltip class, with automatic position if so
    mouseFollow = function(e) {
        var _self = getCurrentInstance();
        if (_self===null) return;
        var vattr=null, hattr=null, positions = { top: 'auto', bottom: 'auto', left: 'auto', right: 'auto' };
        if (hasClassName(_tltp_currentel, _self._bottomright_class)) { vattr = 'right'; hattr = 'bottom'; }
        else if (hasClassName(_tltp_currentel, _self._bottomleft_class)) { vattr = 'left'; hattr = 'bottom'; }
        else if (hasClassName(_tltp_currentel, _self._topright_class)) { vattr = 'right'; hattr = 'top'; }
        else { vattr = 'left'; hattr = 'top'; }

        positions = buildHorizontalPosition(e, positions, hattr);
        positions = buildVerticalPosition(e, positions, vattr);

        for(var pos in positions) {
            _tltp_wrapper.style[pos] = positions[pos];
        }
    },

    // searching the current horizontal position for the tooltip
    buildHorizontalPosition = function(e, positions, el_attr) {
        var _self = getCurrentInstance();
        var u=0, wh = parseInt(window.innerHeight), eh = parseInt(_tltp_wrapper.offsetHeight);
        // top or bottom ?
        if (el_attr==='bottom') {
            u = _ie ? e.clientY - document.documentElement.scrollBottom : window.innerHeight - e.pageY;
            if (_self._auto_pos && parseInt(eh + _self._bottom) > wh) {
                return buildHorizontalPosition(e, positions, 'top');
            }
            positions.bottom = parseInt(u - eh + _self._bottom) + 'px';
        }
        else {
            u = _ie ? e.clientY + document.documentElement.scrollTop : e.pageY;
            if (_self._auto_pos && parseInt(eh + _self._top) > parseInt(u)) {
                return buildHorizontalPosition(e, positions, 'bottom');
            }
            positions.top = parseInt(u - eh + _self._top) + 'px';
        }
        return positions;
    },

    // searching the current vertical position for the tooltip
    buildVerticalPosition = function(e, positions, el_attr) {
        var _self = getCurrentInstance();
        var l=0, ww = parseInt(window.innerWidth), ew = parseInt(_tltp_wrapper.offsetWidth);
        // left or right ?
        if (el_attr==='right') {
            l = _ie ? e.clientX + document.documentElement.scrollLeft : window.innerWidth - e.pageX;
            if (_self._auto_pos && parseInt(ew + _self._right) > parseInt(l)) {
                return buildVerticalPosition(e, positions, 'left');
            }
            positions.right = parseInt(l + _self._right) + 'px';
        }
        else {
            l = _ie ? e.clientX + document.documentElement.scrollLeft : e.pageX;
            if (_self._auto_pos && parseInt(ew + _self._left) > ww) {
                return buildVerticalPosition(e, positions, 'right');
            }
            positions.left = parseInt(l + _self._left) + 'px';
        }
        return positions;
    },

    // fading the tooltip before it really disappear
    fade = function(d,el) {
        var _self = getInstance(el);
        var a = _self._alpha;
        if ((a!==_self._end_alpha && d===1) || (a!==0 && d===-1)) {
            var i = _self._speed;
            if (_self._end_alpha - a < _self._speed && d===1) {
                i = _self._end_alpha - a;
            } else if (_self._alpha < _self._speed && d===-1) {
                i = a;
            }
            _self._alpha = a + (i * d);
            _tltp_wrapper.style.opacity = _self._alpha * .01;
            _tltp_wrapper.style.filter = 'alpha(opacity=' + _self._alpha + ')';
        } else {
            clearInterval(_tltp_wrapper.timer);
            if (d===-1) { clearTooltip(el); }
        }
    },

    // get an element position
    getOffset = function(el) {
        var _el=el, _x=0, _y=0;
        while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
            _x += el.offsetLeft - el.scrollLeft;
            _y += el.offsetTop - el.scrollTop;
            el = el.offsetParent;
        }
        return {
            top: _y, bottom: _y+_el.offsetHeight,
            left: _x, right: _x+_el.offsetWidth
        };
    };

// ------------------
// The object
// ------------------

    newInstance();
    if (arguments.length) { extend(self, arguments[0], '_%s'); }
    domInit();
    return this;
};

// Endfile