• Jump To … +
    breakpoints.js date-parse.js jquery.breakpoints.js jquery.calcRestrainedPos.js jquery.clientRect.js jquery.contentSize.js jquery.cookies.js jquery.css.js jquery.customEvent.js jquery.delimitedString.js jquery.disableEvent.js jquery.hostIframe.js jquery.hoverDelay.js jquery.htmlEncode.js jquery.imageSize.js jquery.isVisibleKeyCode.js jquery.menu.js jquery-menu-exampleskin.js jquery.modalDialog.deviceFixes.js jquery.modalDialog.getSettings.js jquery.modalDialog.header.js jquery.modalDialog.history.js jquery.modalDialog.js jquery.modalDialog.unobtrusive.js jquery.modalDialog.userAgent.js jquery.modalDialogContent.header.js jquery.modalDialogContent.js jquery.msAjax.js jquery.ns.js jquery.partialLoad.js jquery.postMessage.js jquery.proxyAll.js jquery.queryString.js jquery.richTooltip.js jquery.scrollAnchor.js jquery.uncomment.js jquery.url.js pointy.gestures.js pointy.js
  • jquery.richTooltip.js

  • ¶

    / / / /

    (function ($, undefined) {
        var arrowDirections = {
            south: 'north',
            north: 'south',
            east: 'west',
            west: 'east'
        };
  • ¶

    commonly used array for looping over

        var directions = ['north', 'south', 'west', 'east'];
    
        var defaults = {
            pos: 'south',
            arrowDirection: null,
            arrowStyle: 'outset',
            closeOnWindowResize: true,
            closeOnDocumentClick: true
        };
    
        function Tooltip(context, options) {
            $.proxyAll(this, 'show', 'hide', 'toggle', 'pos', 'unhover', 'onWindowResize', 'onDocumentClick', 'onUiElementOpen');
  • ¶

    target context element that initalizes this tooltip

            this.context = $(context).addClass('rich-tooltip-context');
  • ¶

    store options

            this.options = $.extend({}, defaults, options || {});
  • ¶

    determine the content of the tooltip

            this.content = $(this.options.content).eq(0).addClass('rich-tooltip-content'); // class adds legacy browser support
  • ¶

    ensure we actually found the content

            if (this.content.length === 0) {
                throw new Error('jquery.richTooltip: failed to find desired tooltip');
            }
  • ¶

    add functional classes

            this.content.addClass('rich-tooltip-pos-' + this.options.pos);
  • ¶

    configure the context

            this.context.attr('data-rel', 'tooltip');
  • ¶

    setup the hover events

            if (this.options.action === 'hover') {
  • ¶

    soft dependency on hoverDelay

                if ($.fn.hoverDelay) {
  • ¶

    hover is desktop only, and does not support pointer events

                    this.context.hoverDelay(this.show, this.unhover, { delayOver: 200, delayOut: 500, addChildren: this.content });
  • ¶

    hover over the tooltip content should not hide the tooltip yet

                    this.content.hoverDelay(this.show, this.unhover, { delayOver: 200, delayOut: 500, addChildren: this.context });
                } else {
  • ¶

    hover is desktop only, and does not support pointer events

                    this.context.hover(this.show, this.unhover);
  • ¶

    hover over the tooltip content should not hide the tooltip yet

                    this.content.hover(this.show, this.unhover);
                }
  • ¶

    listen to pointer down and record the event to be used on click calling prevent default on pointerup/pointerdown does not prevent navigation, preventDefault must happen within click event

                this.context.on('press', $.proxy(function (event) {
  • ¶

    toggle visibility on touches

                    if (event.pointerType === 'touch') {
                        event.preventClick();
                        this.toggle(event);
                    }
                }, this));
            } else {
  • ¶

    when hover is off, we simply toggle on click event

                this.context.on('click', this.toggle);
            }
  • ¶

    find the container element

            this.container = this.options.container ? $(this.options.container) : null;
  • ¶

    look for exiting arrow

            this.arrow = this.content.find('.rich-tooltip-arrow');
  • ¶

    no arrow found, create one

            if (this.arrow.length === 0) {
                this.arrow = $('<div class="rich-tooltip-arrow"></div>').appendTo(this.content);
            }
  • ¶

    anything with [data-rel=”close”] can be used to close the tooltip

            this.content.on('click', '[data-rel="close"]', this.hide);
        }
    
        Tooltip.prototype.unhover = function () {
            this._clearHoverTimeout();
            this.hoverTimeout = setTimeout(this.hide, 250);
        };
    
        Tooltip.prototype._clearHoverTimeout = function () {
            if (this.hoverTimeout) {
                clearTimeout(this.hoverTimeout);
                this.hoverTimeout = null;
            }
        };
  • ¶

    Borrowed from the JQuery UI core method zIndex. See https://github.com/jquery/jquery-ui/blob/master/ui/core.js

        function findZIndex( elem ) {
            var pos;
            var zIndexVal;
            var maxZIndexVal = 0;
            if ( this.length ) {
                while ( elem.length && elem[ 0 ] !== document ) {
  • ¶

    Ignore z-index if position is set to a value where z-index is ignored by the browser This makes behavior of this function consistent across browsers WebKit always returns auto if the element is positioned

                    pos = elem.css( 'position' );
                    if ( pos === 'absolute' || pos === 'relative' || pos === 'fixed' ) {
  • ¶

    IE returns 0 when zIndex is not specified other browsers return a string we ignore the case of nested elements with an explicit value of 0

                        zIndexVal = parseInt( elem.css( 'zIndex' ), 10 );
                        if ( !isNaN( zIndexVal ) && zIndexVal !== 0 ) {
                            if (zIndexVal > maxZIndexVal) {
                                maxZIndexVal = zIndexVal;
                            }
                        }
                    }
                    elem = elem.parent();
                }
            }
    
            return maxZIndexVal;
        }
    
        Tooltip.prototype.show = function () {
            this._clearHoverTimeout();
    
            if (this.visible) {
                return;
            }
    
            var zIndex = findZIndex(this.content.parent());
  • ¶

    if the tooltip has a parent with z-index, set tooltip’s one higher. In case the tooltip being used inside a control like a modal dialog.

            if (zIndex > 0) {
                this.content.css('z-index', zIndex + 1);
            }
  • ¶

    move the content to the body to avoid positioning conflicts

            if (!this.content.parent().is('body')) { 
                this.content.appendTo('body');
            }
  • ¶

    tell other ui events we are opening, hopefully they all close themselves

            $(document).trigger('ui.element.open', this);
    
            this.content.show();
            this.pos();
  • ¶

    mark the tooltip as visible

            this.visible = true;
  • ¶

    indicate to the context the tooltip is open

            this.context.addClass('rich-tooltip-open').trigger('richTooltip:open');
    
            this.viewportSize = {
                height: $(window).height(),
                width: $(window).width()
            };
  • ¶

    hide the tooltip if the browser resizes, the user can open it back up easily

            if (this.options.closeOnWindowResize) {
                $(window).on('resize', this.onWindowResize);
            }
  • ¶

    hide the tooltip if user clicks outside of the tooltip

            if (this.options.closeOnDocumentClick) {
                $(document).on('click', this.onDocumentClick);
            }
  • ¶

    listen for other ui elements opening, and if one opens, close this tooltip

            $(document).one('ui.element.open', this.onUiElementOpen);
        };
    
        Tooltip.prototype.hide = function () {
            this._clearHoverTimeout();
    
            if (!this.visible) {
                return;
            }
    
            this.content.hide();
  • ¶

    mark the tooltip as not visible

            this.visible = false;
  • ¶

    indicate to the context the tooltip is now closed

            this.context.removeClass('rich-tooltip-open').trigger('richTooltip:close');
  • ¶

    remove event listeners

            if (this.options.closeOnWindowResize) {
                $(window).off('resize', this.onWindowResize);
            }
    
            if (this.options.closeOnDocumentClick) {
                $(document).off('click', this.onDocumentClick);
            }
  • ¶

    stop listening for other ui elements opening, since we no longer need to care

            $(document).off('ui.element.open', this.onUiElementOpen);
        };
    
        Tooltip.prototype.toggle = function (event) {
            if (event) {
                event.preventDefault();
  • ¶

    we need to call stop propagation or else this will trigger up to the document and result in closing the tooltip.

                event.stopPropagation();
            }
    
            if (this.visible) {
                this.hide();
            } else {
                this.show();
            }
        };
    
        Tooltip.prototype.onUiElementOpen = function (event, item) {
            if (item !== this) {
                this.hide();
            }
        };
  • ¶

    on window resize, confirm the window actually changed sizes IE9 and below triggers a resize on any element changing size, this includes elements becoming visible or hidding @see http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer

        Tooltip.prototype.onWindowResize = function () {
            if ($(window).height() != this.viewportSize.height || $(window).width() != this.viewportSize.width) {
                this.hide();
            }
        };
  • ¶

    on a document click event, we close the tooltip is the click was not to the tooltip or a child of the tooltip

        Tooltip.prototype.onDocumentClick = function (e) {
            var target = $(e.target);
            if (!target.is(this.content) && !target.isChildOf(this.content)) {
                this.hide();
            }
        };
  • ¶

    padding constant used during position calculations. TODO: compute these dynamically based on the control’s actual CSS

        var PADDING = 10;
        var ARROW_WIDTH = 15;
    
        Tooltip.prototype.pos = function () {
  • ¶

    find the size of the arrow .css-arrow elements do not have a size itself because it uses :before and :after

            var arrowSize = this.options.arrowStyle === 'inset' || this.arrow.css('display') == 'none' ? 0 : ARROW_WIDTH;
    
            var restrainedPos = $.calcRestrainedPos({
                giveMeSomething: true,
                direction: this.options.pos,
                content: this.content.css('max-width', '100%'),
                context: this.context,
                container: this.container,
                reset: {
                    margin: 0
                },
                offsets: {
                    viewport: PADDING,
                    vertical: arrowSize,
                    horizontal: arrowSize
                }
            });
    
            var pos = restrainedPos.pos;
    
            if (restrainedPos.direction !== this.options.pos) {
                this.content
                    .removeClass('rich-tooltip-pos-' + this.options.pos)
                    .addClass('rich-tooltip-pos-' + restrainedPos.direction);
            }
  • ¶

    determine the new arrow direction

            var arrowDirection = this.options.arrowDirection || arrowDirections[restrainedPos.direction];
    
            this.content.css(pos);
    
            this.arrow
  • ¶

    remove any previously added tooltip arrow direction class

                .removeClass(directions.join(' '))
  • ¶

    add the tooltip arrow direction class

                .addClass(arrowDirection);
        };
    
        $.fn.tooltip = $.fn.richTooltip = function jQueryTooltip(options) {
            var el = $(this);
            var tooltip = el.data('__tooltip');
    
            if (!tooltip) {
                tooltip = new Tooltip(el, options);
                el.data('__tooltip', tooltip);
            }
  • ¶

    allow this to be used to run methods on the tooltips

            if (typeof options === 'string' && options in tooltip) {
                tooltip[options]();
            }
    
            return this;
        };
    
        $.fn.isChildOf = function jQueryIsChildOf(filter_string) {
            var parents = $(this).parents();
    
            for (var j = 0; j < parents.length; j++) {
                if ($(parents[j]).is(filter_string)) {
                    return true;
                }
            }
    
            return false;
        };
  • ¶

    initialize all existing tooltips

        $(function () {
  • ¶

    translate data attributes to options

            function getOptionsFromData(context, content) {
                var data = context.data();
    
                if (!content) {
                    content = $(data.tooltip);
                }
    
                return {
                    content: content,
                    action: data.tooltipAction || 'click',
                    pos: data.tooltipPos || 'south',
                    container: data.tooltipContainer || undefined,
                    arrowDirection: data.tooltipArrowDirection || null,
                    arrowStyle: data.tooltipArrowStyle || null,
                    closeOnWindowResize: data.tooltipIgnoreWindowResize === undefined,
                    closeOnDocumentClick: data.tooltipIgnoreDocumentClick === undefined
                };
            }
  • ¶

    convert

    to tooltips

            $('[data-rel="tooltip"] + aside').each(function (i, el) {
                var content = $(el); // this is the aside
                var context = content.prev(); // element prior to aside
    
                context.richTooltip(getOptionsFromData(context, content));
            });
  • ¶

    allow instances without the

    directly after the context

            $('[data-tooltip]').each(function (i, context) {
                context = $(context);
                context.richTooltip(getOptionsFromData(context));
            });
        });
    })(jQuery);