/*
* bubbletip
*
* Copyright (c) 2009-2010, UhLeeKa.
* Version:
* 1.0.6
* Licensed under the GNU Lesser General Public License:
* http://www.gnu.org/licenses/lgpl-3.0.html
* Author Website:
* http://www.uhleeka.com
* Project Hosting on Google Code:
* http://code.google.com/p/bubbletip/
*/
; (function($) {
var bindIndex = 0;
$.fn.extend({
bubbletip: function(tip, options) {
// check to see if the tip is a descendant of
// a table.bubbletip element and therefore
// has already been instantiated as a bubbletip
if ($('table.bubbletip #' + $(tip).get(0).id).length > 0) {
return this;
}
var _this, _tip, _options, _calc, _timeoutAnimate, _timeoutRefresh, _isActive, _isHiding, _wrapper, _bindIndex;
// hack for IE6,IE7
var _windowWidth, _windowHeight;
_this = $(this);
_tip = $(tip);
_bindIndex = bindIndex++; // for window.resize namespace binding
_options = {
positionAt: 'element', // element | body | mouse
positionAtElement: _this,
offsetTop: 0,
offsetLeft: 0,
deltaPosition: 30,
deltaDirection: 'up', // direction: up | down | left | right
animationDuration: 250,
animationEasing: 'swing', // linear | swing
bindShow: 'mouseover', // mouseover | focus | click | etc.
bindHide: 'mouseout', // mouseout | blur | etc.
delayShow: 0,
delayHide: 500,
calculateOnShow: false
};
if (options) {
_options = $.extend(_options, options);
}
// calculated values
_calc = {
top: 0,
left: 0,
delta: 0,
mouseTop: 0,
mouseLeft: 0,
tipHeight: 0,
bindShow: (_options.bindShow + ' ').replace(/ +/g, '.bubbletip' + _bindIndex),
bindHide: (_options.bindHide + ' ').replace(/ +/g, '.bubbletip' + _bindIndex)
};
_timeoutAnimate = null;
_timeoutRefresh = null;
_isActive = false;
_isHiding = false;
// store the tip id for removeBubbletip
if (!_this.data('bubbletip_tips')) {
_this.data('bubbletip_tips', [[_tip.get(0).id, _bindIndex]]);
} else {
_this.data('bubbletip_tips', $.merge(_this.data('bubbletip_tips'), [[_tip.get(0).id, _bindIndex]]));
}
// validate _options
if (!_options.positionAt.match(/^element|body|mouse$/i)) {
_options.positionAt = 'element';
}
if (!_options.deltaDirection.match(/^up|down|left|right$/i)) {
_options.deltaDirection = 'up';
}
// create the wrapper table element
if (_options.deltaDirection.match(/^up$/i)) {
_wrapper = $('
');
} else if (_options.deltaDirection.match(/^down$/i)) {
_wrapper = $('');
} else if (_options.deltaDirection.match(/^left$/i)) {
_wrapper = $('');
} else if (_options.deltaDirection.match(/^right$/i)) {
_wrapper = $('');
}
// append the wrapper to the document body
_wrapper.appendTo('body');
// apply IE filters to _wrapper elements
if ((/msie/.test(navigator.userAgent.toLowerCase())) && (!/opera/.test(navigator.userAgent.toLowerCase()))) {
$('*', _wrapper).each(function() {
var image = $(this).css('background-image');
if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) {
image = RegExp.$1;
$(this).css({
'backgroundImage': 'none',
'filter': 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=' + ($(this).css('backgroundRepeat') == 'no-repeat' ? 'crop' : 'scale') + ', src=\'' + image + '\')'
}).each(function() {
var position = $(this).css('position');
if (position != 'absolute' && position != 'relative')
$(this).css('position', 'relative');
});
}
});
}
// move the tip element into the content section of the wrapper
$('.bt-content', _wrapper).append(_tip);
// show the tip (in case it is hidden) so that we can calculate its dimensions
_tip.show();
// handle left|right delta
if (_options.deltaDirection.match(/^left|right$/i)) {
// tail is 40px, so divide height by two and subtract 20px;
_calc.tipHeight = parseInt(_tip.height() / 2);
// handle odd integer height
if ((_tip.height() % 2) == 1) {
_calc.tipHeight++;
}
_calc.tipHeight = (_calc.tipHeight < 20) ? 1 : _calc.tipHeight - 20;
if (_options.deltaDirection.match(/^left$/i)) {
$('div.bt-right', _wrapper).css('height', _calc.tipHeight + 'px');
} else {
$('div.bt-left', _wrapper).css('height', _calc.tipHeight + 'px');
}
}
// set the opacity of the wrapper to 0
_wrapper.css('opacity', 0);
// hack for FF 3.6
_wrapper.css({ 'width': _wrapper.width(), 'height': _wrapper.height() });
// execute initial calculations
_Calculate();
_wrapper.hide();
// handle window.resize
$(window).bind('resize.bubbletip' + _bindIndex, function() {
var w = $(window).width();
var h = $(window).height();
if ((w === _windowWidth) && (h === _windowHeight)) {
return;
}
_windowWidth = w;
_windowHeight = h;
if (_timeoutRefresh) {
clearTimeout(_timeoutRefresh);
}
_timeoutRefresh = setTimeout(function() {
_Calculate();
}, 250);
});
// handle mouseover and mouseout events
$([_wrapper.get(0), this.get(0)]).bind(_calc.bindShow, function() {
if (_timeoutAnimate) {
clearTimeout(_timeoutAnimate);
}
if (_options.delayShow === 0) {
_Show();
} else {
_timeoutAnimate = setTimeout(function() {
_Show();
}, _options.delayShow);
}
return false;
}).bind(_calc.bindHide, function() {
if (_timeoutAnimate) {
clearTimeout(_timeoutAnimate);
}
if (_options.delayHide === 0) {
_Hide();
} else {
_timeoutAnimate = setTimeout(function() {
_Hide();
}, _options.delayHide);
}
return false;
});
function _Show() {
var animation;
if (_isActive) { // the tip is currently showing; do nothing
return;
}
_isActive = true;
if (_isHiding) { // the tip is currently hiding; interrupt and start showing again
_wrapper.stop(true, false);
}
if (_options.calculateOnShow) {
_Calculate();
}
if (_options.positionAt.match(/^element|body$/i)) {
if (_options.deltaDirection.match(/^up|down$/i)) {
if (!_isHiding) {
_wrapper.css('top', parseInt(_calc.top + _calc.delta) + 'px');
}
animation = { 'top': _calc.top + 'px' };
} else {
if (!_isHiding) {
_wrapper.css('left', parseInt(_calc.left + _calc.delta) + 'px');
}
animation = { 'left': _calc.left + 'px' };
}
} else {
if (_options.deltaDirection.match(/^up|down$/i)) {
if (!_isHiding) {
_calc.mouseTop = e.pageY + _calc.top;
_wrapper.css({ 'top': parseInt(_calc.mouseTop + _calc.delta) + 'px', 'left': parseInt(e.pageX - (_wrapper.width() / 2)) + 'px' });
}
animation = { 'top': _calc.mouseTop + 'px' };
} else {
if (!_isHiding) {
_calc.mouseLeft = e.pageX + _calc.left;
_wrapper.css({ 'left': parseInt(_calc.mouseLeft + _calc.delta) + 'px', 'top': parseInt(e.pageY - (_wrapper.height() / 2)) + 'px' });
}
animation = { 'left': _calc.left + 'px' };
}
}
_isHiding = false;
_wrapper.show();
animation = $.extend(animation, { 'opacity': 1 });
_wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() {
_wrapper.css('opacity', '');
_isActive = true;
});
};
function _Hide() {
var animation;
_isActive = false;
_isHiding = true;
if (_options.positionAt.match(/^element|body$/i)) {
if (_options.deltaDirection.match(/^up|down$/i)) {
animation = { 'top': parseInt(_calc.top - _calc.delta) + 'px' };
} else {
animation = { 'left': parseInt(_calc.left - _calc.delta) + 'px' };
}
} else {
if (_options.deltaDirection.match(/^up|down$/i)) {
animation = { 'top': parseInt(_calc.mouseTop - _calc.delta) + 'px' };
} else {
animation = { 'left': parseInt(_calc.mouseLeft - _calc.delta) + 'px' };
}
}
animation = $.extend(animation, { 'opacity': 0 });
_wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() {
_wrapper.hide();
_isHiding = false;
});
};
function _Calculate() {
// calculate values
if (_options.positionAt.match(/^element$/i)) {
var offset = _options.positionAtElement.offset();
if (_options.deltaDirection.match(/^up$/i)) {
_calc.top = offset.top + _options.offsetTop - _wrapper.outerHeight();
_calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.outerWidth() - _wrapper.outerWidth()) / 2);
_calc.delta = _options.deltaPosition;
} else if (_options.deltaDirection.match(/^down$/i)) {
_calc.top = offset.top + _options.positionAtElement.outerHeight() + _options.offsetTop;
_calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.outerWidth() - _wrapper.outerWidth()) / 2);
_calc.delta = -_options.deltaPosition;
} else if (_options.deltaDirection.match(/^left$/i)) {
_calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.outerHeight() - _wrapper.outerHeight()) / 2);
_calc.left = offset.left + _options.offsetLeft - _wrapper.outerWidth();
_calc.delta = _options.deltaPosition;
} else if (_options.deltaDirection.match(/^right$/i)) {
_calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.outerHeight() - _wrapper.outerHeight()) / 2);
_calc.left = offset.left + _options.positionAtElement.outerWidth() + _options.offsetLeft;
_calc.delta = -_options.deltaPosition;
}
} else if (_options.positionAt.match(/^body$/i)) {
if (_options.deltaDirection.match(/^up|left$/i)) {
_calc.top = _options.offsetTop;
_calc.left = _options.offsetLeft;
// up or left
_calc.delta = _options.deltaPosition;
} else {
if (_options.deltaDirection.match(/^down$/i)) {
_calc.top = parseInt(_options.offsetTop + _wrapper.outerHeight());
_calc.left = _options.offsetLeft;
} else {
_calc.top = _options.offsetTop;
_calc.left = parseInt(_options.offsetLeft + _wrapper.outerWidth());
}
// down or right
_calc.delta = -_options.deltaPosition;
}
} else if (_options.positionAt.match(/^mouse$/i)) {
if (_options.deltaDirection.match(/^up|left$/i)) {
if (_options.deltaDirection.match(/^up$/i)) {
_calc.top = -(_options.offsetTop + _wrapper.outerHeight());
_calc.left = _options.offsetLeft;
} else if (_options.deltaDirection.match(/^left$/i)) {
_calc.top = _options.offsetTop;
_calc.left = -(_options.offsetLeft + _wrapper.outerWidth());
}
// up or left
_calc.delta = _options.deltaPosition;
} else {
_calc.top = _options.offsetTop;
_calc.left = _options.offsetLeft;
// down or right
_calc.delta = -_options.deltaPosition;
}
}
// handle the wrapper (element|body) positioning
if (_options.positionAt.match(/^element|body$/i)) {
_wrapper.css({
'position': 'absolute',
'top': _calc.top + 'px',
'left': _calc.left + 'px'
});
}
};
return this;
},
removeBubbletip: function(tips) {
var tipsActive;
var tipsToRemove = new Array();
var tipsActiveAdjusted = new Array();
var arr, i, ix;
var elem;
tipsActive = $.makeArray($(this).data('bubbletip_tips'));
// convert the parameter array of tip id's or elements to id's
arr = $.makeArray(tips);
for (i = 0; i < arr.length; i++) {
tipsToRemove.push($(arr[i]).get(0).id);
}
for (i = 0; i < tipsActive.length; i++) {
ix = null;
if ((tipsToRemove.length == 0) || ((ix = $.inArray(tipsActive[i][0], tipsToRemove)) >= 0)) {
// remove all tips if there are none specified
// otherwise, remove only specified tips
// find the surrounding table.bubbletip
elem = $('#' + tipsActive[i][0]).get(0).parentNode;
while (elem.tagName.toLowerCase() != 'table') {
elem = elem.parentNode;
}
// attach the tip element to body and hide
$('#' + tipsActive[i][0]).appendTo('body').hide();
// remove the surrounding table.bubbletip
$(elem).remove();
// unbind show/hide events
$(this).unbind('.bubbletip' + tipsActive[i][1]);
// unbind window.resize event
$(window).unbind('.bubbletip' + tipsActive[i][1]);
} else {
// tip is not being removed, so add it to the adjusted array
tipsActiveAdjusted.push(tipsActive[i]);
}
}
$(this).data('bubbletip_tips', tipsActiveAdjusted);
return this;
}
});
})(jQuery);