init.js

/** 
 * @module init
 * @desc The init module provides functions to initialize the fxToolTip environment
 * upon startup, and to remove it upon shutdown.
 */

import {windowResized} from './utils.js';
import {getRule, getRuleIndex} from './style.js';
import {targetTimer} from './fxTip.js';

/**
 * The CSS stylesheet used to store and alter the fxToolTip div elements' styles.
 * @type CSSStyleSheet
 * @global
 */
export let sheet;

/**
 * A collection of all of the CSS rules in the document's stylesheet.
 * @type Array
 * @global
 */
export let rules;

/**
 * The HTML div element inserted into the DOM which is used to display
 * all tooltips. This element is assigned the *fxToolTip* class.
 * @type DOM.element
 * @global
 */
export let ttDiv;

/**
 * The HTML div element inserted into the DOM which is used to display
 * all tooltip content. Child element of *ttDiv*.
 * @type DOM.Element
 * @global
 */
export let ttContainer;

/**
 * CSS rule associated with the *.fxToolTip* class.
 * @type CSSRule
 * @global
 */
export let beforeRule;

/**
 * CSS rule associated with the *.fxToolTip::after* pseudo class.
 * Used to style the tooltip arrow.
 * @type CSSRule
 * @global
 */
export let afterRule;

/**
 * CSS rule associated with the *.fxToolTipTarget* class. This class
 * is appended to all DOM elements associated with a tooltip. Used to
 * style the target element's cursor on hover.
 * @type CSSRule
 * @global
 */
export let targetRule;

/**
 * Represents the state of the fxToolTip environment.
 * @type boolean
 * @global
 */
export let set = false;

/**
 * @function setup
 * @desc The setup function is called upon initialization of the fxToolTip environment.
 * First, it sets a listener for the window *resize* event which invokes the
 * [windowResized]{@link module:utils~windowResized} function. Second, it determines if there
 * is a current stylesheet associated with the document, and if not inserts one. Next, it
 * inserts four class styles (*.fxToolTip*, *.fxContainer*, *.fxToolTip::after*, and *.fxTooltipTarget*).
 * *.fxToolTip* contains all of the rules that style the tooltip. *.fxToolTip::after* contains all of
 * the rules that style the tooltip arrow. *.fxContainer* contains rules that style the tooltip content.
 * *.fxToolTipTarget* contains one rule that styles the cursor of the target element on hover.
 * The setup function also appends one div element to the document body element created with the class
 * name '.fxToolTip' and styles all tooltips in the document.
 *
 * *note*:  The classes and div elements are created only once and shared by all of the tooltips.
 */
export function setUp() {

    if (set) { return; };
    
    if (window.addEventListener) {
        window.addEventListener('resize', windowResized);
    } else if (window.attachEvent) {
        window.attachEvent('onresize', windowResized);
    } else {
        window.onresize = windowResized;
    };
    windowResized();

    if (document.styleSheets.length == 0) {
        var head = document.getElementsByTagName("head")[0];
        sheet = document.createElement("style")
        sheet.type = "text/css";
        sheet.rel = 'stylesheet';
        sheet.media = 'screen';
        sheet.title = 'fxToolTip';
        sheet = head.appendChild(sheet).sheet;
    };
    
    sheet = document.styleSheets[0];
    rules = sheet.cssRules ? sheet.cssRules: sheet.rules;
    
    const fxToolTipRule = `.fxToolTip {
        opacity: 0;
        -moz-opacity: 0;
        -khtml-opacity: 0;
        position: fixed;
        visibility: hidden;
        z-index: 100;
        pointer-events: none;
        display: inline-block;
        box-sizing: border-box;
        -moz-box-sizing: border-box;
        -webkit-box-sizing: border-box}`

    const fxContainerRule = `.fxContainer {
        width: 100%;
        height: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        -ms-text-overflow: ellipsis;
        -o-text-overflow: ellipsis}`

    const fxToolTipAfterRule = `.fxToolTip::after{
        content: "";
        position: absolute;
        border-style: solid;
        pointer-events: none;}`

    if (sheet.insertRule) {
        sheet.insertRule(fxToolTipRule, rules.length);
        sheet.insertRule(fxContainerRule, rules.length);
        sheet.insertRule(fxToolTipAfterRule, rules.length);
        sheet.insertRule('.fxToolTipTarget {cursor: help;}', rules.length);
    } else {
        sheet.addRule(fxToolTipRule, rules.length);
        sheet.addRule(fxContainerRule, rules.length);
        sheet.addRule(fxToolTipAfterRule, rules.length);
        sheet.addRule('.fxToolTipTarget', '{cursor: help;}', rules.length);
    };
    
    beforeRule = getRule('.fxToolTip').style;
    afterRule = getRule('.fxToolTip::after').style;
    targetRule = getRule('.fxToolTipTarget').style;
    
    ttDiv = document.createElement('div');
    ttDiv.className = 'fxToolTip';
    document.body.insertBefore(ttDiv, document.body.firstChild);

    ttContainer = document.createElement('div');
    ttContainer.className = 'fxContainer';
    ttDiv.appendChild(ttContainer);
    
    set = true;
}

/**
 * @function closeDown
 * @desc The closeDown function is called after the last tooltip is removed from the stack.
 * It removes all event listeners, div elements, and CSS styles and rules.
 */
export function closeDown() {
    
    if (window.removeEventListener) {
        window.removeEventListener('resize', windowResized);
    } else if (window.detachEvent) {
        window.detachEvent('onresize', windowResized);
    } else {
        window.onresize = '';
    };

    if (sheet.deleteRule) {
        sheet.deleteRule(getRuleIndex('.fxToolTip'));   
        sheet.deleteRule(getRuleIndex('.fxToolTip::after'));
        sheet.deleteRule(getRuleIndex('.fxToolTipTarget'))
    } else {
        sheet.removeRule(getRuleIndex('.fxToolTip'));
        sheet.removeRule(getRuleIndex('.fxToolTip::after'));
        sheet.removeRule(getRuleIndex('.fxToolTipTarget'))
    };
    
    ttDiv.parentNode.removeChild(ttDiv);
    pseudoDiv.parentNode.removeChild(pseudoDiv);
    
    sheet = undefined;
    rules = undefined;
    
    window.clearInterval(targetTimer);

    set = false;
}