(function($) {
//
// **Possible parameters:**
//
// _Event handler:_
//
// str showEvent Defines the event that activates the tooltip.
// Possible values: mouseover, focus, click, dblclick, change etc.
// Default: "mouseover"
// str hideEvent Defines the event that hides the tooltip.
// Possible values: mouseout, blur, click, dblclick, change etc.
// Default: "mouseout"
//
// _CSS ids and classes:_
//
// str ttClass Class for the tooltip for overriding and additional styling.
// Default: "tt_tip"
// str activeClass Class for the trigger element while tooltip is displayed.
// Default: "tt_active"
// str ttIdPrefix Prefix for the element ID that identifies the tooltip ID.
// Example: element with id="test" looks for tooltip container with id="tt_test"
// Default: "tt_"
//
// _Positioning:_
//
// str align: Preferred horizontal alignment of the tooltip.
// Values: "absCenter", "center", "right", "left", "flushRight", and "flushLeft".
// Default: "flushLeft".
// str vAlign: Preferred vertical alignment of the tooltip.
// Values: "absCenter", "center", "above" and "below".
// Default: "above"
// int windowMargin The tooltip's minimum margin from the window's edge.
// Default: 5
// int distanceX The tooltip's horizontal distance from the trigger element.
// Not used if align is flushLeft, flushRight or centered.
// Default: 0
// int distanceY The tooltip's vertical distance from the trigger element.
// Not used for if vAlign is centered.
// Default: 2
// int nudgeX Nudge along x axis. Use negative int to nudge left; positive int to nudge right.
// Default: 0
// int nudgeY Nudge along y axis. Use negative int to nudge up; positive int to nudge down.
// Default: 0
//
// _Timing:_
//
// int timeOut Time that tooltip is displayed after mouseout.
// Default: 1000
// int delay Delay before displaying tooltip.
// Default: 500
// int fadeOut Time it take for the tooltip to fade out.
// Default: 250
// int fadeIn Time it take for the tooltip to fade in.
// Default: 100
//
// _Styling:_
//
// obj css Object with CSS rules, applied in addition to default styles.
// As with jQuery CSS, use JavaScript CSS syntax, or quote properties.
// Example: "css: {textAlign: 'left'}" or "css: {'text-align': 'left'}"
// Default: Empty.
// bool zoom Use a zoom effect to animate the tooltip from the dimensions
// and point of origin of the target element.
// Special case: If target and tooltip are both images, the image will zoom as well
// Default: false
$.fn.tt = function(options) {
// build main options before element iteration
var opts = $.extend({},$.fn.tt.defaults,options);
return this.each(function() {
var $this = $(this),
$ttTooltip;
// Support for the meta plugin
var o = $.meta ? $.extend({},opts, $this.data()) : opts;
//Storage object for cached positioning values
$this.cache = {valid: false};
$this.isOn = false;
//Support for tooltips on the title attribute
//Either use the trigger element's title attr or the target element, if it exists.
if ($this.attr('id').length === 0 || !($('#' + o.ttIdPrefix + $this.attr('id'))[0])) {
if (!($this.attr('title')) || $this.attr('title').length === 0) return;
$this.ttTitle = $this.oldTitle = $this.attr('title');
$this.attr('title', '');
$ttTooltip = $('
').hide();
} else {
$ttTooltip = $('#' + o.ttIdPrefix + $this.attr('id')).hide();
//Marker for the initial position of the tooltip, so we can replace it on hide.
var orgPos = $('').insertAfter($ttTooltip).hide();
}
//Set initial styles: Define standard styles if no custom class is set.
var css = o.ttClass !== 'tt_tip' ? {position: 'absolute'} : {
position: 'absolute',
font: '11px "lucida grande", tahoma, helvetica, arial, sans-serif',
border: '1px solid #666',
background: '#ffd',
padding: '.5em',
'-webkit-border-radius': '5px',
'-webkit-box-shadow': '0 6px 15px rgba(0,0,0,.6)',
'-moz-border-radius': '5px',
'-moz-box-shadow': '0 6px 15px rgba(0,0,0,.6)'
};
//Extend with options css properties
css = $.extend({},css,o.css);
//Set initial styling and class names from options.
$ttTooltip.addClass(o.ttClass).css(css);
$this.bind(o.showEvent, delayShowTip);
//Make sure that we do not hide the tooltip when the mouse is over it.
$ttTooltip.bind('mouseover', function(e) {
clearTimeout($this.hideTimer);
$ttTooltip.one(o.hideEvent, hideTip);
});
if (o.visibleOnScroll) {
//On scroll, recalculate position so we don't go offsreen.
$(window).bind('scroll', function () {
//Don't do anything if the tooltip is not on!'
if ($this.isOn) {
$ttTooltip.css(getTooltipPosition());
}
});
}
//On resize, kill cached position data and recalculate.
$(window).bind('resize', function(){
$this.cache.valid = false;
//Don't reposition if tooltip isn't on!
if ($this.isOn) {
$ttTooltip.css(getTooltipPosition());
}
});
//
// private functions
//
//A wrapper for the showTip function in order to enable delaying it.
function delayShowTip() {
clearTimeout($this.delayTimer);
clearTimeout($this.hideTimer);
$this.delayTimer = setTimeout(showTip, o.delay);
$this.one(o.hideEvent, hideTip);
}
//Hide the Tooltip and do some cleanup.
function hideTip() {
clearTimeout($this.delayTimer);
clearTimeout($this.hideTimer);
$this.hideTimer = setTimeout(function() {
//TODO: Is there a better way to handle nested tooltips? With a global bool isNested?
//Don't hide tooltips that contain nested tooltips - wait for child element's activeClass to go away.
if ($ttTooltip.find('.' + o.activeClass)[0]) {
hideTip();
return;
}
$this.removeClass(o.activeClass);
$ttTooltip.fadeOut(o.fadeOut, function(){
$this.isOn = false;
//Cleanup: Put that content back where you found it!
if (orgPos){
$ttTooltip.insertBefore(orgPos);
}
if (o.onhide) {
o.onhide({
elm: $this,
tt: $ttTooltip,
opt: o
});
};
});
},
o.timeOut);
}
function showTip() {
//Move the tooltip to body to avoid issues with position and overflow CSS settings on the page.
$ttTooltip.appendTo('body');
$this.addClass(o.activeClass);
$this.isOn = true;
//Get target dimensions and position of the tooltip
var tipPosition = getTooltipPosition();
//Zoom!
if (o.zoom) {
var ratio = ($this.cache.elmDim.h * $this.cache.elmDim.w) / ($this.cache.ttInnerDim.h * $this.cache.ttInnerDim.w);
// Named CSS attributes to scale by this area ratio:
var cssAttrs = {'fontSize': ''};
var startCSS = {
opacity: 0,
left: $this.cache.elmOffset.left,
top: $this.cache.elmOffset.top,
width: $this.cache.elmDim.w,
height: $this.cache.elmDim.h
};
var endCSS = $.extend({}, tipPosition, {
width: $this.cache.ttInnerDim.w + 1,
height: $this.cache.ttInnerDim.h + 1,
opacity: 1
});
for (i in cssAttrs) {
cssAttrs[i] = endCSS[i] = $ttTooltip.css(i);
startCSS[i] = parseInt(cssAttrs[i].split('px')[0], 10) * ratio;
};
//Set initial dimensions and position to be equal to the target element's
$ttTooltip.css(startCSS);
//Temporarily unbind events
$ttTooltip.unbind(o.hideEvent, hideTip);
$this.unbind(o.showEvent, delayShowTip).unbind(o.hideEvent, hideTip);
$ttTooltip.addClass(o.ttClass).animate(endCSS, o.fadeIn, function() {
//Rebind events.
$ttTooltip.bind(o.hideEvent, hideTip);
//Bind the showEvent. The hideEvent is bound on show.
$this.bind(o.showEvent, delayShowTip);
});
} else {
$ttTooltip.addClass(o.ttClass).css(tipPosition).fadeIn(o.fadeIn, function() {
if (o.onshow) {
o.onshow({
elm: $this,
tt: $ttTooltip,
opt: o
});
};
});
}
}
function updateCache() {
if ($this.cache.valid) return;
$this.cache = {
elmOffset: $this.offset(),
elmDim: {
w: $this.outerWidth(),
h: $this.outerHeight()
},
ttDim: {
w: $ttTooltip.outerWidth(),
h: $ttTooltip.outerHeight()
},
ttInnerDim: {
w: $ttTooltip.width(),
h: $ttTooltip.height()
},
vp: {
w: $(window).width(),
h: $(window).height()
},
valid: true
};
}
function getTooltipPosition() {
updateCache();
//Save as copy of the original preferences.
var align = {
vert: o.vAlign,
hor: o.align
};
//Scroll position
var scroll = {
left: $(document.documentElement.body).scrollLeft(),
top: $(document.documentElement.body).scrollTop()
};
//All the possible positions for the tooltip
var pos = {
top: {
above: $this.cache.elmOffset.top - $this.cache.ttDim.h - o.distanceY + o.nudgeY,
below: $this.cache.elmOffset.top + $this.cache.elmDim.h + o.distanceY + o.nudgeY,
center: $this.cache.elmOffset.top - $this.cache.ttDim.h/2 + $this.cache.elmDim.h/2,
flushTop: $this.cache.elmOffset.top,
flushBottom: $this.cache.elmOffset.top + $this.cache.elmDim.h + $this.cache.ttDim.h,
absTop: scroll.top + o.windowMargin,
absBottom: $this.cache.vp.h + scroll.top - $this.cache.ttDim.h - o.windowMargin,
absCenter: scroll.top + $this.cache.vp.h/2 - $this.cache.ttDim.h/2
},
left: {
left: $this.cache.elmOffset.left - $this.cache.ttDim.w - o.distanceX + o.nudgeX,
right: $this.cache.elmOffset.left + $this.cache.elmDim.w + o.distanceX + o.nudgeX,
center: $this.cache.elmOffset.left - $this.cache.ttDim.w/2 + $this.cache.elmDim.w/2,
flushLeft: $this.cache.elmOffset.left,
flushRight: $this.cache.elmOffset.left + $this.cache.elmDim.w - $this.cache.ttDim.w,
absLeft: scroll.left + o.windowMargin,
absRight: $this.cache.vp.w + scroll.left - $this.cache.ttDim.w - o.windowMargin,
absCenter: scroll.left + $this.cache.vp.w/2 - $this.cache.ttDim.w/2
}
};
//Booleans for whether there is space for the tooltip in a variety of positions.
//Compares tooltip offset to the absolute top/left position keeping tooltip on-screen
var space = {
above: pos.top[align.vert] < pos.top.absTop ? false : true,
below: pos.top[align.vert] > pos.top.absBottom ? false : true,
left: pos.left[align.hor] < pos.left.absLeft ? false : true,
right: pos.left[align.hor] > pos.left.absRight ? false : true
};
//Move the tooltip around if there isn't space in the current position.
if ($this.cache.vp.h < $this.cache.ttDim.h) align.vert = 'absTop';
else if (!space.above && !space.below && align.vert == 'below') {
align.vert = 'absBottom';
} else if ((align.vert === 'above' || align.vert === 'center') && !space.above) {
align.vert = 'absTop';
} else if ((align.vert === 'below' || align.vert === 'center') && !space.below) {
align.vert = 'absBottom';
}
if (!space.left && !space.right) {
align.hor = 'absLeft';
} else if ((/^right|flushLeft|center$/i).test(align.hor) && !space.right) {
align.hor = 'absRight';
} else if ((/^left|flushRight|center$/i).test(align.hor) && !space.left) {
align.hor = 'absLeft';
}
return {
left: pos.left[align.hor],
top: pos.top[align.vert]
};
}
});
};
//
// private function for debugging
//
function cons(message) {
if (window.console && window.console.log) {
console.log(message);
}
};
//
// plugin defaults
//
$.fn.tt.defaults = {
showEvent: 'mouseover',
hideEvent: 'mouseout',
align: 'left',
vAlign: 'below',
visibleOnScroll: true,
windowMargin: 0,
distanceX: 0,
distanceY: 0,
nudgeX: 0,
nudgeY: 0,
ttClass: 'tt_tip',
activeClass: 'tt_active',
ttIdPrefix: 'tt_',
timeOut: 3000,
delay: 0,
fadeIn: 100,
fadeOut: 250,
zoom: false
};
//
// end of closure
//
})(jQuery);