/* * jQuery Tippy * Version 1.3.1 * By Chris Roberts, chris@dailycross.net * http://croberts.me/ * */ /* * The MIT License (MIT) * * Copyright (c) 2013 Chris Roberts * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ (function($) { $.fn.tippy = function(options) { // Track data for each of our tooltips var tippy_state = {}; // Hold global position data var tippy_positions = {}; // Store id of last displayed tooltip var tippy_showing = false; var opts = $.extend({}, $.fn.tippy.defaults, options); // How many tooltips are out there? var countTips = 0; // Arbitrary counter for bringing tooltips to the front when clicked var topTipIndex = 100; // Loop through tooltips and set them up return this.each(function() { countTips++; var tipId = 'tippy_' + countTips; // Initialize the storage object for this tooltip and load all default or global options tippy_state[tipId] = {}; // Grab data values tippy_state[tipId].options = $.extend({}, opts, $(this).data()); // Make sure the data container is hidden $(this).hide(); // Set the id to our data container $(this).attr('id', tipId); // Create the link that will trigger the tooltip. var tippyLink; // See if we are attaching the tooltip to an already-existing anchor or if we're creating a new link if (typeof tippy_state[tipId].options.anchor != 'undefined') { tippyLink = $(tippy_state[tipId].options.anchor); } else { tippyLink = $(''); } tippyLink.addClass('tippy_link') .attr('id', tipId + '_link'); // Using [''] for class variable since .class breaks in ie8 if (typeof tippy_state[tipId].options['class'] != 'undefined') { tippyLink.addClass(tippy_state[tipId].options['class']) } if (typeof tippy_state[tipId].options.name != 'undefined') { tippyLink.attr('name', tippy_state[tipId].options.name) } if (tippy_state[tipId].options.showtitle) { tippyLink.attr('title', tippy_state[tipId].options.title); } if (typeof tippy_state[tipId].options.img != 'undefined') { tippyLink.css('display', 'inline-block').css('position', 'relative'); var tippyImg = $(''); tippyImg.attr('src', tippy_state[tipId].options.img); if (tippy_state[tipId].options.showtitle) { tippyImg.attr('alt', tippy_state[tipId].options.title); } tippyLink.append(tippyImg); tippy_state[tipId].img = tippyImg; tippy_state[tipId].imgsrc = tippy_state[tipId].options.img; // See if we have a swap image, go ahead and set it if (typeof tippy_state[tipId].options.swapimg != 'undefined') { var tippySwapImg = $('') .attr('src', tippy_state[tipId].options.swapimg) .addClass('tippy_swap') .css('display', 'none'); if (tippy_state[tipId].options.showtitle) { tippySwapImg.attr('alt', tippy_state[tipId].options.title); } tippyLink.append(tippySwapImg); tippy_state[tipId].swapimg = tippySwapImg; // tippy_state[tipId].swapimgsrc = tippy_state[tipId].options.swapimg; } } else if (typeof tippy_state[tipId].options.title != 'undefined') { tippyLink.html(tippy_state[tipId].options.title); } if (typeof tippy_state[tipId].options.title == 'undefined' && typeof tippy_state[tipId].options.headertitle == 'undefined') { tippy_state[tipId].options.showheader = false; } // Check for a link if (typeof tippy_state[tipId].options.href != 'undefined') { tippyLink.attr('href', tippy_state[tipId].options.href); if (typeof tippy_state[tipId].options.target != 'undefined') { tippyLink.attr('target', tippy_state[tipId].options.target); } } if (tippy_state[tipId].options.hoverpopup) { tippyLink.mouseover(function(event) { showTooltip(tipId, event); }); } else { tippyLink.click(function(event) { showTooltip(tipId, event); }); } if (tippy_state[tipId].options.autoclose) { tippyLink.mouseout(function() { hideTooltip(tipId); }); } if (typeof tippy_state[tipId].options.anchor == 'undefined') { $(this).before(tippyLink); } tippy_state[tipId].link = tippyLink; // See if we are autoshowing. If so, create the tip and show it. if (tippy_state[tipId].options.autoshow) { createTooltip(tipId); positionTip(tipId); doShowTooltip(tipId, true); } }); function createTooltip(tipId) { // Initialize container variables var tipBox, tipHeader, tipClose, tipBody; // Create our tooltip box for this tip. Store it so we can reuse it. tipBox = $('
') .hide() .css('height', 'auto') .css('display', 'none') .addClass('tippy_tip') .addClass('domTip_Tip') .attr('id', tipId + '_box') .mouseover(function() { freezeTooltip(tipId); }) .click(function() { $(this).css('z-index', topTipIndex); topTipIndex++; }); tippy_state[tipId].tipBox = tipBox; if (!tippy_state[tipId].options.container) { $('#' + tipId).before(tippy_state[tipId].tipBox); } else { $(tippy_state[tipId].options.container).append(tipBox); } // Using [''] for class variable since .class breaks in ie8 if (typeof tippy_state[tipId].options['class'] != 'undefined') { tipBox.addClass(tippy_state[tipId].options['class'] + '_tip') } switch (tippy_state[tipId].options.position) { case 'link': case 'mouse': tipBox.css('position', 'absolute'); break; default: tipBox.css('position', tippy_state[tipId].options.position); } // Start the close timer when mouse away, if specified if (tippy_state[tipId].options.autoclose) { tipBox.mouseout(function() { hideTooltip(tipId); }); } // Set up the header, if used if (tippy_state[tipId].options.showheader) { tipHeader = $('
') .css('height', 'auto') .addClass('tippy_header') .addClass('domTip_tipHeader'); var headerTitle; if (typeof tippy_state[tipId].options.headertitle != 'undefined') { headerTitle = tippy_state[tipId].options.headertitle; } else { headerTitle = tippy_state[tipId].options.title; } if (typeof tippy_state[tipId].options.headerhref != 'undefined') { var headerLink = $('') .attr('href', tippy_state[tipId].options.headerhref) .attr('title', headerTitle) .html(headerTitle); if (typeof tippy_state[tipId].options.target != 'undefined') { headerLink.attr('target', tippy_state[tipId].options.target); } tipHeader.append(headerLink); } else { tipHeader.html(headerTitle); } tipHeader.appendTo(tipBox); } // Set up the tooltip body tipBody = $('
') .css('height', 'auto') .addClass('tippy_body') .addClass('domTip_tipBody') .appendTo(tipBox); if (tippy_state[tipId].options.htmlentities == false) { // Move body content $('#' + tipId).appendTo(tipBody).show(); } if (tippy_state[tipId].options.hasnested == true) { tipBody.css('overflow', 'visible'); } if (tippy_state[tipId].options.height != false) { tipBody.css("height", tippy_state[tipId].options.height + "px"); tipBody.css("min-height", tippy_state[tipId].options.height + "px"); tipBody.css("max-height", tippy_state[tipId].options.height + "px"); } // Set up the close link, if used. Position depends on whether or not the header is displayed if (tippy_state[tipId].options.showclose) { tipClose = $('
') .addClass('tippy_closelink') .click(function() { doHideTooltip(tipId); }) .html(tippy_state[tipId].options.closetext); if (tippy_state[tipId].options.showheader) { tipHeader.append(tipClose); } else { tipBody.prepend(tipClose); } } if (tippy_state[tipId].options.width != false) { // Get difference if (tippy_state[tipId].options.showheader) { headerDiff = tipBox.width() - tipHeader.width(); tipHeader.css('width', (tippy_state[tipId].options.width - headerDiff) + 'px'); } bodyDiff = tipBox.width() - tipBody.width(); tipBox.css('width', tippy_state[tipId].options.width + 'px'); tipBody.css('width', (tippy_state[tipId].options.width - bodyDiff) + 'px'); } if (typeof tippy_state[tipId].options.bodystyle != 'undefined') { tipBody.attr('style', tipBody.attr('style') + ' ' + tippy_state[tipId].options.bodystyle); } if (tippy_state[tipId].options.draggable) { if (tippy_state[tipId].options.dragheader && tippy_state[tipId].options.showheader) { tipBox.draggable({ handle: '.tippy_header' }); tipHeader.addClass('tippy_draggable'); } else { tipBox.draggable(); tipBox.addClass('tippy_draggable'); } } } // Initialize all position data function getPositions(tipId, event) { if (!event) { event = window.event; } tippy_positions.scrollPageX = $(window).scrollLeft(); tippy_positions.scrollPageY = $(window).scrollTop(); tippy_positions.viewScreenX = $(window).width(); tippy_positions.viewScreenY = $(window).height(); tippy_positions.curPageX = event.clientX + tippy_positions.scrollPageX; tippy_positions.curPageY = event.clientY + tippy_positions.scrollPageY; tippy_positions.viewPageX = event.clientX; tippy_positions.viewPageY = event.clientY; tippy_positions.tipLinkHeight = $('#' + tipId + '_link').height(); // document: calculate the position relative to the document. // parent: calculate the position relative to the parent. // Most situations will want parent, but more advanced positioning techniques may want document. if (tippy_state[tipId].options.calcpos == 'document') { tippy_positions.tipLinkX = $('#' + tipId + '_link').offset().left; tippy_positions.tipLinkY = $('#' + tipId + '_link').offset().top; } else { tippy_positions.tipLinkX = $('#' + tipId + '_link').position().left; tippy_positions.tipLinkY = $('#' + tipId + '_link').position().top; } }; function positionTip(tipId, event) { // Grab the box with a shortcut name tipBox = tippy_state[tipId].tipBox; // Grab position settings getPositions(tipId, event); // Get the height and width of the tooltip container. Will use this when // calculating tooltip position. var tipHeight = tipBox.height(); var tipWidth = tipBox.width(); // Calculate where the tooltip should be located var tipHorSide = "left", tipVertSide = "top"; var tipXloc, tipYloc = 0; // tipXloc and tipYloc specify where the tooltip should appear. // By default, it is just below and to the right of the mouse pointer. if (tippy_state[tipId].options.position == 'link') { // Position below the link tipXloc = tippy_positions.tipLinkX; tipYloc = tippy_positions.tipLinkY + tippy_positions.tipLinkHeight; } else if (tippy_state[tipId].options.position == 'mouse') { // Position below the mouse cursor tipXloc = tippy_positions.curPageX; tipYloc = tippy_positions.curPageY; } else { // Check our top/bottom and left/right values; use 0 as a default if something is missing from each pair. if (typeof tippy_state[tipId].options.top == 'undefined' && typeof tippy_state[tipId].options.bottom == 'undefined') { tipYloc = 0; } else if (typeof tippy_state[tipId].options.top != 'undefined') { tipYloc = tippy_state[tipId].options.top; } else { tipVertSide = "bottom" tipYloc = tippy_state[tipId].options.bottom; } if (typeof tippy_state[tipId].options.left == 'undefined' && typeof tippy_state[tipId].options.right == 'undefined') { tipXloc = 0; } else if (typeof tippy_state[tipId].options.left != 'undefined') { tipXloc = tippy_state[tipId].options.left; } else { tipHorSide = "right" tipXloc = tippy_state[tipId].options.right; } } // Check offsets if position is link or mouse if (tippy_state[tipId].options.position == 'link' || tippy_state[tipId].options.position == 'mouse') { tipXloc += tippy_state[tipId].options.offsetx; tipYloc += tippy_state[tipId].options.offsety; } // Adjust position of tooltip to place it within window boundaries // If the tooltip extends off the right side, pull it over if ((tipXloc - tippy_positions.scrollPageX) + 5 + tipWidth > tippy_positions.viewScreenX) { pageXDiff = ((tipXloc - tippy_positions.scrollPageX) + 5 + tipWidth) - tippy_positions.viewScreenX; tipXloc -= pageXDiff; } // If the tooltip will extend off the bottom of the screen, pull it back up. if ((tipYloc - tippy_positions.scrollPageY) + 5 + tipHeight > tippy_positions.viewScreenY) { pageYDiff = ((tipYloc - tippy_positions.scrollPageY) + 5 + tipHeight - tippy_positions.viewScreenY); tipYloc -= pageYDiff; } // If the tooltip extends off the bottom and the top, line up the top of // the tooltip with the top of the page if (tipHeight > tippy_positions.viewScreenY) { tipYloc = tippy_positions.scrollPageY + 5; } // Set the position in pixels. tipBox.css(tipHorSide, tipXloc + "px"); tipBox.css(tipVertSide, tipYloc + "px"); } function showTooltip(tipId, event) { // See if state exists if (typeof tippy_state[tipId].tipBox == 'undefined') { createTooltip(tipId); } // Are we already showing the tooltip? Freeze it. if (tippy_state[tipId].state == 'showing' || tippy_state[tipId].state == 'displaying') { // Nothing to freeze if we're not autoclosing if (tippy_state[tipId].options.autoclose) { freezeTooltip(tipId); } } else { tippy_state[tipId].state = 'displaying'; positionTip(tipId, event); tippy_state[tipId].timer = setTimeout(function() { doShowTooltip(tipId, false); }, tippy_state[tipId].options.showdelay); } } function doShowTooltip(tipId, instashow) { tippy_state[tipId].state = 'showing'; // Check on a swapimg/swaptitle to use if img/title is set if (typeof tippy_state[tipId].options.swapimg != 'undefined' && typeof tippy_state[tipId].options.img != 'undefined') { // Fade out the primary image but don't set it display: none; This approach preserves // the image size for the swapimg. tippy_state[tipId].img.animate({ opacity: 0 }, 500); tippy_state[tipId].swapimg.fadeIn(500); } else if (typeof tippy_state[tipId].options.swaptitle != 'undefined' && typeof tippy_state[tipId].options.title != 'undefined') { tippy_state[tipId].link.html(tippy_state[tipId].options.swaptitle); } if (typeof tippy_state[tipId].options.swaptitle != 'undefined' && typeof tippy_state[tipId].options.title != 'undefined') { tippy_state[tipId].link.html(tippy_state[tipId].options.swaptitle); } if (!tippy_state[tipId].options.multitip && tippy_showing) { doHideTooltip(tippy_showing); } // See if we need to decode and pull in content if (tippy_state[tipId].options.htmlentities == true) { var convertMarkup = $("
").html($('#' + tipId).html()).text(); $('.tippy_body', tippy_state[tipId].tipBox).html(convertMarkup).show(); } if (instashow) { tippy_state[tipId].tipBox.show(); } else { tippy_state[tipId].tipBox.fadeIn(tippy_state[tipId].options.showspeed); } tippy_showing = tipId; // Load the MediaElement player on any media elements in the tooltip jQuery('video,audio', tippy_state[tipId].tipBox).mediaelementplayer().load(); } function showSwapImg(tipId, event) { // Check on a swapimg/swaptitle to use if img/title is set if (typeof tippy_state[tipId].options.swapimg != 'undefined' && typeof tippy_state[tipId].options.img != 'undefined') { // Fade out the primary image but don't set it display: none; This approach preserves // the image size for the swapimg. tippy_state[tipId].img.animate({ opacity: 0 }, 500); tippy_state[tipId].swapimg.fadeIn(500); } } function hideSwapImg(tipId) { // Check on a swaptitle to use if title is set if (typeof tippy_state[tipId].options.swapimg != 'undefined' && typeof tippy_state[tipId].options.img != 'undefined') { tippy_state[tipId].img.animate({ opacity: 1 }, 500); tippy_state[tipId].swapimg.fadeOut(500); } } // When the visitor mouses away from the link or the tooltip, start a timer that // will hide the tooltip after a set period of time. function hideTooltip(tipId) { if (tippy_state[tipId].state == 'displaying') { tippy_state[tipId].state = 'hidden'; clearTimeout(tippy_state[tipId].timer); } else { tippy_state[tipId].timer = setTimeout(function() { doHideTooltip(tipId); }, tippy_state[tipId].options.hidedelay); } } function doHideTooltip(tipId) { tippy_showing = false; tippy_state[tipId].state = 'hidden'; clearTimeout(tippy_state[tipId].timer); hideSwapImg(tipId); if (typeof tippy_state[tipId].options.swaptitle != 'undefined' && typeof tippy_state[tipId].options.title != 'undefined') { tippy_state[tipId].link.html(tippy_state[tipId].options.title); } tippy_state[tipId].tipBox.fadeOut(tippy_state[tipId].options.hidespeed, function() { // See if we need to clear content if (tippy_state[tipId].options.htmlentities == true) { $('.tippy_body', tippy_state[tipId].tipBox).html(''); } }); } // If the user mouses over the tooltip, make sure we don't hide it. The tooltip // should freeze when the mouse is over it. function freezeTooltip(tipId) { clearTimeout(tippy_state[tipId].timer); tippy_state[tipId].tipBox.stop(); tippy_state[tipId].tipBox.css("opacity", 100); } }; $.fn.tippy.defaults = { showtitle: false, // Should the browser's title attribute be used? Good for accessibility, bad for tooltip visibility. offsetx: 10, // When position is set to mouse or link, how far should the tooltip be offset left or right? Positive for right, negative for left. offsety: 10, // When position is set to mouse or link, how far should the tooltip be offset up or down? Positive for down, negative for up. hoverpopup: true, // Should the tooltip display on hover? If set to false, tooltip shows on click. multitip: false, // Should it be possible to have multiple tooltips onscreen at once? showdelay: 100, // When showing on hover, how long in ms should it delay before showing the tooltip? showspeed: 200, // How long in ms should it take the tooltip to fade in? hidedelay: 1000, // When auto hiding, how long in ms should the tooltip be visible before it starts to fade out? hidespeed: 200, // How long in ms should it take the tooltip to fade out? showheader: true, // Should the tooltip have a header? showclose: true, // Should the tooltip have a close link? Usefor for mobile devices or when autoClose is false. closetext: 'close', // When using a close link, what should it say? autoclose: true, // Should the tooltip auto close after a delay? container: false, // What should be the tooltip's parent element? Useful if you want to move it into another element. position: 'link', // fixed, absolute, relative, mouse, link height: false, // Specify a height for the tooltip width: false, // Specify a width for the tooltip draggable: false, // Should visitors be able to drag the tooltip around? (requires jQuery UI) dragheader: true, // If dragging is enabled should the visitor only be able to drag from the header? If false, user can move the tooltip from any part. autoshow: false, // Should tooltips automatically be displayed when the page is loaded? calcpos: 'parent', // Should the tooltip position be calculated relative to the parent or to the document? htmlentities: false, // If false, Tippy assumes the tooltip content is straight html. If true, assumes it is encoded as entities and needs to be decoded. hasnested: false // Whether or not this tooltip has a nested tooltip } }(jQuery));