[data] Use correct syntax to unset CSS attributes (closes #8602446)
'default' is actually a recognized CSS cursor name (the regular
pointer). Using this value doesn't revert to whatever cursor DOM
elements have by default ('pointer' on links, 'text' on text, etc).
The proper way to unset a CSS attribute on DOM elements is to set it to
an empty string.
http://stackoverflow.com/questions/2027935/how-to-remove-css-property-using-javascript
/*!
* jquery.qtip. The jQuery tooltip plugin
*
* Copyright (c) 2009 Craig Thompson
* http://craigsworks.com
*
* Licensed under MIT
* http://www.opensource.org/licenses/mit-license.php
*
* Launch : February 2009
* Version : 1.0.0-rc3
* Released: Tuesday 12th May, 2009 - 00:00
* Debug: jquery.qtip.debug.js
*/
(function($)
{
// Implementation
$.fn.qtip = function(options, blanket)
{
var i, id, interfaces, opts, obj, command, config, api;
// Return API / Interfaces if requested
if(typeof options == 'string')
{
// Make sure API data exists if requested
if(typeof $(this).data('qtip') !== 'object')
$.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.NO_TOOLTIP_PRESENT, false);
// Return requested object
if(options == 'api')
return $(this).data('qtip').interfaces[ $(this).data('qtip').current ];
else if(options == 'interfaces')
return $(this).data('qtip').interfaces;
}
// Validate provided options
else
{
// Set null options object if no options are provided
if(!options) options = {};
// Sanitize option data
if(typeof options.content !== 'object' || (options.content.jquery && options.content.length > 0)) options.content = { text: options.content };
if(typeof options.content.title !== 'object') options.content.title = { text: options.content.title };
if(typeof options.position !== 'object') options.position = { corner: options.position };
if(typeof options.position.corner !== 'object') options.position.corner = { target: options.position.corner, tooltip: options.position.corner };
if(typeof options.show !== 'object') options.show = { when: options.show };
if(typeof options.show.when !== 'object') options.show.when = { event: options.show.when };
if(typeof options.show.effect !== 'object') options.show.effect = { type: options.show.effect };
if(typeof options.hide !== 'object') options.hide = { when: options.hide };
if(typeof options.hide.when !== 'object') options.hide.when = { event: options.hide.when };
if(typeof options.hide.effect !== 'object') options.hide.effect = { type: options.hide.effect };
if(typeof options.style !== 'object') options.style = { name: options.style };
options.style = sanitizeStyle(options.style);
// Build main options object
opts = $.extend(true, {}, $.fn.qtip.defaults, options);
// Inherit all style properties into one syle object and include original options
opts.style = buildStyle.call({ options: opts }, opts.style);
opts.user = $.extend(true, {}, options);
};
// Iterate each matched element
return $(this).each(function() // Return original elements as per jQuery guidelines
{
// Check for API commands
if(typeof options == 'string')
{
command = options.toLowerCase();
interfaces = $(this).qtip('interfaces');
// Make sure API data exists$('.qtip').qtip('destroy')
if(typeof interfaces == 'object')
{
// Check if API call is a BLANKET DESTROY command
if(blanket === true && command == 'destroy')
while(interfaces.length > 0) interfaces[interfaces.length-1].destroy();
// API call is not a BLANKET DESTROY command
else
{
// Check if supplied command effects this tooltip only (NOT BLANKET)
if(blanket !== true) interfaces = [ $(this).qtip('api') ];
// Execute command on chosen qTips
for(i = 0; i < interfaces.length; i++)
{
// Destroy command doesn't require tooltip to be rendered
if(command == 'destroy') interfaces[i].destroy();
// Only call API if tooltip is rendered and it wasn't a destroy call
else if(interfaces[i].status.rendered === true)
{
if(command == 'show') interfaces[i].show();
else if(command == 'hide') interfaces[i].hide();
else if(command == 'focus') interfaces[i].focus();
else if(command == 'disable') interfaces[i].disable(true);
else if(command == 'enable') interfaces[i].disable(false);
};
};
};
};
}
// No API commands, continue with qTip creation
else
{
// Create unique configuration object
config = $.extend(true, {}, opts);
config.hide.effect.length = opts.hide.effect.length;
config.show.effect.length = opts.show.effect.length;
// Sanitize target options
if(config.position.container === false) config.position.container = $(document.body);
if(config.position.target === false) config.position.target = $(this);
if(config.show.when.target === false) config.show.when.target = $(this);
if(config.hide.when.target === false) config.hide.when.target = $(this);
// Determine tooltip ID (Reuse array slots if possible)
id = $.fn.qtip.interfaces.length;
for(i = 0; i < id; i++)
{
if(typeof $.fn.qtip.interfaces[i] == 'undefined'){ id = i; break; };
};
// Instantiate the tooltip
obj = new qTip($(this), config, id);
// Add API references
$.fn.qtip.interfaces[id] = obj;
// Check if element already has qTip data assigned
if(typeof $(this).data('qtip') == 'object')
{
// Set new current interface id
if(typeof $(this).attr('qtip') === 'undefined')
$(this).data('qtip').current = $(this).data('qtip').interfaces.length;
// Push new API interface onto interfaces array
$(this).data('qtip').interfaces.push(obj);
}
// No qTip data is present, create now
else $(this).data('qtip', { current: 0, interfaces: [obj] });
// If prerendering is disabled, create tooltip on showEvent
if(config.content.prerender === false && config.show.when.event !== false && config.show.ready !== true)
{
config.show.when.target.bind(config.show.when.event+'.qtip-'+id+'-create', { qtip: id }, function(event)
{
// Retrieve API interface via passed qTip Id
api = $.fn.qtip.interfaces[ event.data.qtip ];
// Unbind show event and cache mouse coords
api.options.show.when.target.unbind(api.options.show.when.event+'.qtip-'+event.data.qtip+'-create');
api.cache.mouse = { x: event.pageX, y: event.pageY };
// Render tooltip and start the event sequence
construct.call( api );
api.options.show.when.target.trigger(api.options.show.when.event);
});
}
// Prerendering is enabled, create tooltip now
else
{
// Set mouse position cache to top left of the element
obj.cache.mouse = {
x: config.show.when.target.offset().left,
y: config.show.when.target.offset().top
};
// Construct the tooltip
construct.call(obj);
}
};
});
};
// Instantiator
function qTip(target, options, id)
{
// Declare this reference
var self = this;
// Setup class attributes
self.id = id;
self.options = options;
self.status = {
animated: false,
rendered: false,
disabled: false,
focused: false
};
self.elements = {
target: target.addClass(self.options.style.classes.target),
tooltip: null,
wrapper: null,
content: null,
contentWrapper: null,
title: null,
button: null,
tip: null,
bgiframe: null
};
self.cache = {
mouse: {},
position: {},
toggle: 0
};
self.timers = {};
// Define exposed API methods
$.extend(self, self.options.api,
{
show: function(event)
{
var returned, solo;
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'show');
// Only continue if element is visible
if(self.elements.tooltip.css('display') !== 'none') return self;
// Clear animation queue
self.elements.tooltip.stop(true, false);
// Call API method and if return value is false, halt
returned = self.beforeShow.call(self, event);
if(returned === false) return self;
// Define afterShow callback method
function afterShow()
{
// Call API method and focus if it isn't static
if(self.options.position.type !== 'static') self.focus();
self.onShow.call(self, event);
// Prevent antialias from disappearing in IE7 by removing filter attribute
if($.browser.msie) self.elements.tooltip.get(0).style.removeAttribute('filter');
};
// Maintain toggle functionality if enabled
self.cache.toggle = 1;
// Update tooltip position if it isn't static
if(self.options.position.type !== 'static')
self.updatePosition(event, (self.options.show.effect.length > 0));
// Hide other tooltips if tooltip is solo
if(typeof self.options.show.solo == 'object') solo = $(self.options.show.solo);
else if(self.options.show.solo === true) solo = $('div.qtip').not(self.elements.tooltip);
if(solo) solo.each(function(){ if($(this).qtip('api').status.rendered === true) $(this).qtip('api').hide(); });
// Show tooltip
if(typeof self.options.show.effect.type == 'function')
{
self.options.show.effect.type.call(self.elements.tooltip, self.options.show.effect.length);
self.elements.tooltip.queue(function(){ afterShow(); $(this).dequeue(); });
}
else
{
switch(self.options.show.effect.type.toLowerCase())
{
case 'fade':
self.elements.tooltip.fadeIn(self.options.show.effect.length, afterShow);
break;
case 'slide':
self.elements.tooltip.slideDown(self.options.show.effect.length, function()
{
afterShow();
if(self.options.position.type !== 'static') self.updatePosition(event, true);
});
break;
case 'grow':
self.elements.tooltip.show(self.options.show.effect.length, afterShow);
break;
default:
self.elements.tooltip.show(null, afterShow);
break;
};
// Add active class to tooltip
self.elements.tooltip.addClass(self.options.style.classes.active);
};
// Log event and return
return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_SHOWN, 'show');
},
hide: function(event)
{
var returned;
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'hide');
// Only continue if element is visible
else if(self.elements.tooltip.css('display') === 'none') return self;
// Stop show timer and animation queue
clearTimeout(self.timers.show);
self.elements.tooltip.stop(true, false);
// Call API method and if return value is false, halt
returned = self.beforeHide.call(self, event);
if(returned === false) return self;
// Define afterHide callback method
function afterHide(){ self.onHide.call(self, event); };
// Maintain toggle functionality if enabled
self.cache.toggle = 0;
// Hide tooltip
if(typeof self.options.hide.effect.type == 'function')
{
self.options.hide.effect.type.call(self.elements.tooltip, self.options.hide.effect.length);
self.elements.tooltip.queue(function(){ afterHide(); $(this).dequeue(); });
}
else
{
switch(self.options.hide.effect.type.toLowerCase())
{
case 'fade':
self.elements.tooltip.fadeOut(self.options.hide.effect.length, afterHide);
break;
case 'slide':
self.elements.tooltip.slideUp(self.options.hide.effect.length, afterHide);
break;
case 'grow':
self.elements.tooltip.hide(self.options.hide.effect.length, afterHide);
break;
default:
self.elements.tooltip.hide(null, afterHide);
break;
};
// Remove active class to tooltip
self.elements.tooltip.removeClass(self.options.style.classes.active);
};
// Log event and return
return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_HIDDEN, 'hide');
},
updatePosition: function(event, animate)
{
var i, target, tooltip, coords, mapName, imagePos, newPosition, ieAdjust, ie6Adjust, borderAdjust, mouseAdjust, offset, curPosition, returned
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updatePosition');
// If tooltip is static, return
else if(self.options.position.type == 'static')
return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.CANNOT_POSITION_STATIC, 'updatePosition');
// Define property objects
target = {
position: { left: 0, top: 0 },
dimensions: { height: 0, width: 0 },
corner: self.options.position.corner.target
};
tooltip = {
position: self.getPosition(),
dimensions: self.getDimensions(),
corner: self.options.position.corner.tooltip
};
// Target is an HTML element
if(self.options.position.target !== 'mouse')
{
// If the HTML element is AREA, calculate position manually
if(self.options.position.target.get(0).nodeName.toLowerCase() == 'area')
{
// Retrieve coordinates from coords attribute and parse into integers
coords = self.options.position.target.attr('coords').split(',');
for(i = 0; i < coords.length; i++) coords[i] = parseInt(coords[i]);
// Setup target position object
mapName = self.options.position.target.parent('map').attr('name');
imagePos = $('img[usemap="#'+mapName+'"]:first').offset();
target.position = {
left: Math.floor(imagePos.left + coords[0]),
top: Math.floor(imagePos.top + coords[1])
};
// Determine width and height of the area
switch(self.options.position.target.attr('shape').toLowerCase())
{
case 'rect':
target.dimensions = {
width: Math.ceil(Math.abs(coords[2] - coords[0])),
height: Math.ceil(Math.abs(coords[3] - coords[1]))
};
break;
case 'circle':
target.dimensions = {
width: coords[2] + 1,
height: coords[2] + 1
};
break;
case 'poly':
target.dimensions = {
width: coords[0],
height: coords[1]
};
for(i = 0; i < coords.length; i++)
{
if(i % 2 == 0)
{
if(coords[i] > target.dimensions.width)
target.dimensions.width = coords[i];
if(coords[i] < coords[0])
target.position.left = Math.floor(imagePos.left + coords[i]);
}
else
{
if(coords[i] > target.dimensions.height)
target.dimensions.height = coords[i];
if(coords[i] < coords[1])
target.position.top = Math.floor(imagePos.top + coords[i]);
};
};
target.dimensions.width = target.dimensions.width - (target.position.left - imagePos.left);
target.dimensions.height = target.dimensions.height - (target.position.top - imagePos.top);
break;
default:
return $.fn.qtip.log.error.call(self, 4, $.fn.qtip.constants.INVALID_AREA_SHAPE, 'updatePosition');
break;
};
// Adjust position by 2 pixels (Positioning bug?)
target.dimensions.width -= 2; target.dimensions.height -= 2;
}
// Target is the document
else if(self.options.position.target.add(document.body).length === 1)
{
target.position = { left: $(document).scrollLeft(), top: $(document).scrollTop() };
target.dimensions = { height: $(window).height(), width: $(window).width() };
}
// Target is a regular HTML element, find position normally
else
{
// Check if the target is another tooltip. If its animated, retrieve position from newPosition data
if(typeof self.options.position.target.attr('qtip') !== 'undefined')
target.position = self.options.position.target.qtip('api').cache.position;
else
target.position = self.options.position.target.offset();
// Setup dimensions objects
target.dimensions = {
height: self.options.position.target.outerHeight(),
width: self.options.position.target.outerWidth()
};
};
// Calculate correct target corner position
newPosition = $.extend({}, target.position);
if(target.corner.search(/right/i) !== -1)
newPosition.left += target.dimensions.width;
if(target.corner.search(/bottom/i) !== -1)
newPosition.top += target.dimensions.height;
if(target.corner.search(/((top|bottom)Middle)|center/) !== -1)
newPosition.left += (target.dimensions.width / 2);
if(target.corner.search(/((left|right)Middle)|center/) !== -1)
newPosition.top += (target.dimensions.height / 2);
}
// Mouse is the target, set position to current mouse coordinates
else
{
// Setup target position and dimensions objects
target.position = newPosition = { left: self.cache.mouse.x, top: self.cache.mouse.y };
target.dimensions = { height: 1, width: 1 };
};
// Calculate correct target corner position
if(tooltip.corner.search(/right/i) !== -1)
newPosition.left -= tooltip.dimensions.width;
if(tooltip.corner.search(/bottom/i) !== -1)
newPosition.top -= tooltip.dimensions.height;
if(tooltip.corner.search(/((top|bottom)Middle)|center/) !== -1)
newPosition.left -= (tooltip.dimensions.width / 2);
if(tooltip.corner.search(/((left|right)Middle)|center/) !== -1)
newPosition.top -= (tooltip.dimensions.height / 2);
// Setup IE adjustment variables (Pixel gap bugs)
ieAdjust = ($.browser.msie) ? 1 : 0; // And this is why I hate IE...
ie6Adjust = ($.browser.msie && parseInt($.browser.version.charAt(0)) === 6) ? 1 : 0; // ...and even more so IE6!
// Adjust for border radius
if(self.options.style.border.radius > 0)
{
if(tooltip.corner.search(/Left/) !== -1)
newPosition.left -= self.options.style.border.radius;
else if(tooltip.corner.search(/Right/) !== -1)
newPosition.left += self.options.style.border.radius;
if(tooltip.corner.search(/Top/) !== -1)
newPosition.top -= self.options.style.border.radius;
else if(tooltip.corner.search(/Bottom/) !== -1)
newPosition.top += self.options.style.border.radius;
};
// IE only adjustments (Pixel perfect!)
if(ieAdjust)
{
if(tooltip.corner.search(/top/) !== -1)
newPosition.top -= ieAdjust
else if(tooltip.corner.search(/bottom/) !== -1)
newPosition.top += ieAdjust
if(tooltip.corner.search(/left/) !== -1)
newPosition.left -= ieAdjust
else if(tooltip.corner.search(/right/) !== -1)
newPosition.left += ieAdjust
if(tooltip.corner.search(/leftMiddle|rightMiddle/) !== -1)
newPosition.top -= 1
};
// If screen adjustment is enabled, apply adjustments
if(self.options.position.adjust.screen === true)
newPosition = screenAdjust.call(self, newPosition, target, tooltip);
// If mouse is the target, prevent tooltip appearing directly under the mouse
if(self.options.position.target === 'mouse' && self.options.position.adjust.mouse === true)
{
if(self.options.position.adjust.screen === true && self.elements.tip)
mouseAdjust = self.elements.tip.attr('rel');
else
mouseAdjust = self.options.position.corner.tooltip;
newPosition.left += (mouseAdjust.search(/right/i) !== -1) ? -6 : 6;
newPosition.top += (mouseAdjust.search(/bottom/i) !== -1) ? -6 : 6;
}
// Initiate bgiframe plugin in IE6 if tooltip overlaps a select box or object element
if(!self.elements.bgiframe && $.browser.msie && parseInt($.browser.version.charAt(0)) == 6)
{
$('select, object').each(function()
{
offset = $(this).offset();
offset.bottom = offset.top + $(this).height();
offset.right = offset.left + $(this).width();
if(newPosition.top + tooltip.dimensions.height >= offset.top
&& newPosition.left + tooltip.dimensions.width >= offset.left)
bgiframe.call(self);
});
};
// Add user xy adjustments
newPosition.left += self.options.position.adjust.x;
newPosition.top += self.options.position.adjust.y;
// Set new tooltip position if its moved, animate if enabled
curPosition = self.getPosition();
if(newPosition.left != curPosition.left || newPosition.top != curPosition.top)
{
// Call API method and if return value is false, halt
returned = self.beforePositionUpdate.call(self, event);
if(returned === false) return self;
// Cache new position
self.cache.position = newPosition;
// Check if animation is enabled
if(animate === true)
{
// Set animated status
self.status.animated = true;
// Animate and reset animated status on animation end
self.elements.tooltip.animate(newPosition, 200, 'swing', function(){ self.status.animated = false });
}
// Set new position via CSS
else self.elements.tooltip.css(newPosition);
// Call API method and log event if its not a mouse move
self.onPositionUpdate.call(self, event);
if(typeof event !== 'undefined' && event.type && event.type !== 'mousemove')
$.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_POSITION_UPDATED, 'updatePosition');
};
return self;
},
updateWidth: function(newWidth)
{
var hidden;
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateWidth');
// Make sure supplied width is a number and if not, return
else if(newWidth && typeof newWidth !== 'number')
return $.fn.qtip.log.error.call(self, 2, 'newWidth must be of type number', 'updateWidth');
// Setup elements which must be hidden during width update
hidden = self.elements.contentWrapper.siblings().add(self.elements.tip).add(self.elements.button);
// Calculate the new width if one is not supplied
if(!newWidth)
{
// Explicit width is set
if(typeof self.options.style.width.value == 'number')
newWidth = self.options.style.width.value;
// No width is set, proceed with auto detection
else
{
// Set width to auto initally to determine new width and hide other elements
self.elements.tooltip.css({ width: 'auto' });
hidden.hide();
// Set position and zoom to defaults to prevent IE hasLayout bug
if($.browser.msie)
self.elements.wrapper.add(self.elements.contentWrapper.children()).css({ zoom: 'normal' });
// Set the new width
newWidth = self.getDimensions().width + 1;
// Make sure its within the maximum and minimum width boundries
if(!self.options.style.width.value)
{
if(newWidth > self.options.style.width.max) newWidth = self.options.style.width.max
if(newWidth < self.options.style.width.min) newWidth = self.options.style.width.min
};
};
};
// Adjust newWidth by 1px if width is odd (IE6 rounding bug fix)
if(newWidth % 2 !== 0) newWidth -= 1;
// Set the new calculated width and unhide other elements
self.elements.tooltip.width(newWidth);
hidden.show();
// Set the border width, if enabled
if(self.options.style.border.radius)
{
self.elements.tooltip.find('.qtip-betweenCorners').each(function(i)
{
$(this).width(newWidth - (self.options.style.border.radius * 2));
})
};
// IE only adjustments
if($.browser.msie)
{
// Reset position and zoom to give the wrapper layout (IE hasLayout bug)
self.elements.wrapper.add(self.elements.contentWrapper.children()).css({ zoom: '1' });
// Set the new width
self.elements.wrapper.width(newWidth);
// Adjust BGIframe height and width if enabled
if(self.elements.bgiframe) self.elements.bgiframe.width(newWidth).height(self.getDimensions.height);
};
// Log event and return
return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_WIDTH_UPDATED, 'updateWidth');
},
updateStyle: function(name)
{
var tip, borders, context, corner, coordinates;
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateStyle');
// Return if style is not defined or name is not a string
else if(typeof name !== 'string' || !$.fn.qtip.styles[name])
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.STYLE_NOT_DEFINED, 'updateStyle');
// Set the new style object
self.options.style = buildStyle.call(self, $.fn.qtip.styles[name], self.options.user.style);
// Update initial styles of content and title elements
self.elements.content.css( jQueryStyle(self.options.style) );
if(self.options.content.title.text !== false)
self.elements.title.css( jQueryStyle(self.options.style.title, true) );
// Update CSS border colour
self.elements.contentWrapper.css({ borderColor: self.options.style.border.color });
// Update tip color if enabled
if(self.options.style.tip.corner !== false)
{
if($('<canvas>').get(0).getContext)
{
// Retrieve canvas context and clear
tip = self.elements.tooltip.find('.qtip-tip canvas:first');
context = tip.get(0).getContext('2d');
context.clearRect(0,0,300,300);
// Draw new tip
corner = tip.parent('div[rel]:first').attr('rel');
coordinates = calculateTip(corner, self.options.style.tip.size.width, self.options.style.tip.size.height);
drawTip.call(self, tip, coordinates, self.options.style.tip.color || self.options.style.border.color);
}
else if($.browser.msie)
{
// Set new fillcolor attribute
tip = self.elements.tooltip.find('.qtip-tip [nodeName="shape"]');
tip.attr('fillcolor', self.options.style.tip.color || self.options.style.border.color);
};
};
// Update border colors if enabled
if(self.options.style.border.radius > 0)
{
self.elements.tooltip.find('.qtip-betweenCorners').css({ backgroundColor: self.options.style.border.color });
if($('<canvas>').get(0).getContext)
{
borders = calculateBorders(self.options.style.border.radius)
self.elements.tooltip.find('.qtip-wrapper canvas').each(function()
{
// Retrieve canvas context and clear
context = $(this).get(0).getContext('2d');
context.clearRect(0,0,300,300);
// Draw new border
corner = $(this).parent('div[rel]:first').attr('rel')
drawBorder.call(self, $(this), borders[corner],
self.options.style.border.radius, self.options.style.border.color);
});
}
else if($.browser.msie)
{
// Set new fillcolor attribute on each border corner
self.elements.tooltip.find('.qtip-wrapper [nodeName="arc"]').each(function()
{
$(this).attr('fillcolor', self.options.style.border.color)
});
};
};
// Log event and return
return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_STYLE_UPDATED, 'updateStyle');
},
updateContent: function(content, reposition)
{
var parsedContent, images, loadedImages;
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateContent');
// Make sure content is defined before update
else if(!content)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.NO_CONTENT_PROVIDED, 'updateContent');
// Call API method and set new content if a string is returned
parsedContent = self.beforeContentUpdate.call(self, content);
if(typeof parsedContent == 'string') content = parsedContent;
else if(parsedContent === false) return;
// Set position and zoom to defaults to prevent IE hasLayout bug
if($.browser.msie) self.elements.contentWrapper.children().css({ zoom: 'normal' });
// Append new content if its a DOM array and show it if hidden
if(content.jquery && content.length > 0)
content.clone(true).appendTo(self.elements.content).show();
// Content is a regular string, insert the new content
else self.elements.content.html(content);
// Check if images need to be loaded before position is updated to prevent mis-positioning
images = self.elements.content.find('img[complete=false]');
if(images.length > 0)
{
loadedImages = 0;
images.each(function(i)
{
$('<img src="'+ $(this).attr('src') +'" />')
.load(function(){ if(++loadedImages == images.length) afterLoad(); });
});
}
else afterLoad();
function afterLoad()
{
// Update the tooltip width
self.updateWidth();
// If repositioning is enabled, update positions
if(reposition !== false)
{
// Update position if tooltip isn't static
if(self.options.position.type !== 'static')
self.updatePosition(self.elements.tooltip.is(':visible'), true);
// Reposition the tip if enabled
if(self.options.style.tip.corner !== false)
positionTip.call(self);
};
};
// Call API method and log event
self.onContentUpdate.call(self);
return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_CONTENT_UPDATED, 'loadContent');
},
loadContent: function(url, data, method)
{
var returned;
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'loadContent');
// Call API method and if return value is false, halt
returned = self.beforeContentLoad.call(self);
if(returned === false) return self;
// Load content using specified request type
if(method == 'post')
$.post(url, data, setupContent);
else
$.get(url, data, setupContent);
function setupContent(content)
{
// Call API method and log event
self.onContentLoad.call(self);
$.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_CONTENT_LOADED, 'loadContent');
// Update the content
self.updateContent(content);
};
return self;
},
updateTitle: function(content)
{
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateTitle');
// Make sure content is defined before update
else if(!content)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.NO_CONTENT_PROVIDED, 'updateTitle');
// Call API method and if return value is false, halt
returned = self.beforeTitleUpdate.call(self);
if(returned === false) return self;
// Set the new content and reappend the button if enabled
if(self.elements.button) self.elements.button = self.elements.button.clone(true);
self.elements.title.html(content)
if(self.elements.button) self.elements.title.prepend(self.elements.button);
// Call API method and log event
self.onTitleUpdate.call(self);
return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_TITLE_UPDATED, 'updateTitle');
},
focus: function(event)
{
var curIndex, newIndex, elemIndex, returned;
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'focus');
else if(self.options.position.type == 'static')
return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.CANNOT_FOCUS_STATIC, 'focus');
// Set z-index variables
curIndex = parseInt( self.elements.tooltip.css('z-index') );
newIndex = 6000 + $('div.qtip[qtip]').length - 1;
// Only update the z-index if it has changed and tooltip is not already focused
if(!self.status.focused && curIndex !== newIndex)
{
// Call API method and if return value is false, halt
returned = self.beforeFocus.call(self, event);
if(returned === false) return self;
// Loop through all other tooltips
$('div.qtip[qtip]').not(self.elements.tooltip).each(function()
{
if($(this).qtip('api').status.rendered === true)
{
elemIndex = parseInt($(this).css('z-index'));
// Reduce all other tooltip z-index by 1
if(typeof elemIndex == 'number' && elemIndex > -1)
$(this).css({ zIndex: parseInt( $(this).css('z-index') ) - 1 });
// Set focused status to false
$(this).qtip('api').status.focused = false;
}
})
// Set the new z-index and set focus status to true
self.elements.tooltip.css({ zIndex: newIndex });
self.status.focused = true;
// Call API method and log event
self.onFocus.call(self, event);
$.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_FOCUSED, 'focus');
};
return self;
},
disable: function(state)
{
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'disable');
if(state)
{
// Tooltip is not already disabled, proceed
if(!self.status.disabled)
{
// Set the disabled flag and log event
self.status.disabled = true;
$.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_DISABLED, 'disable');
}
// Tooltip is already disabled, inform user via log
else $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.TOOLTIP_ALREADY_DISABLED, 'disable');
}
else
{
// Tooltip is not already enabled, proceed
if(self.status.disabled)
{
// Reassign events, set disable status and log
self.status.disabled = false;
$.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_ENABLED, 'disable');
}
// Tooltip is already enabled, inform the user via log
else $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.TOOLTIP_ALREADY_ENABLED, 'disable');
};
return self;
},
destroy: function()
{
var i, returned, interfaces;
// Call API method and if return value is false, halt
returned = self.beforeDestroy.call(self);
if(returned === false) return self;
// Check if tooltip is rendered
if(self.status.rendered)
{
// Remove event handlers and remove element
self.options.show.when.target.unbind('mousemove.qtip', self.updatePosition);
self.options.show.when.target.unbind('mouseout.qtip', self.hide);
self.options.show.when.target.unbind(self.options.show.when.event + '.qtip');
self.options.hide.when.target.unbind(self.options.hide.when.event + '.qtip');
self.elements.tooltip.unbind(self.options.hide.when.event + '.qtip');
self.elements.tooltip.unbind('mouseover.qtip', self.focus);
self.elements.tooltip.remove();
}
// Tooltip isn't yet rendered, remove render event
else self.options.show.when.target.unbind(self.options.show.when.event+'.qtip-create');
// Check to make sure qTip data is present on target element
if(typeof self.elements.target.data('qtip') == 'object')
{
// Remove API references from interfaces object
interfaces = self.elements.target.data('qtip').interfaces;
if(typeof interfaces == 'object' && interfaces.length > 0)
{
// Remove API from interfaces array
for(i = 0; i < interfaces.length - 1; i++)
if(interfaces[i].id == self.id) interfaces.splice(i, 1)
}
}
delete $.fn.qtip.interfaces[self.id];
// Set qTip current id to previous tooltips API if available
if(typeof interfaces == 'object' && interfaces.length > 0)
self.elements.target.data('qtip').current = interfaces.length -1;
else
self.elements.target.removeData('qtip');
// Call API method and log destroy
self.onDestroy.call(self);
$.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_DESTROYED, 'destroy');
return self.elements.target
},
getPosition: function()
{
var show, offset;
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'getPosition');
show = (self.elements.tooltip.css('display') !== 'none') ? false : true;
// Show and hide tooltip to make sure coordinates are returned
if(show) self.elements.tooltip.css({ visiblity: 'hidden' }).show();
offset = self.elements.tooltip.offset();
if(show) self.elements.tooltip.css({ visiblity: 'visible' }).hide();
return offset;
},
getDimensions: function()
{
var show, dimensions;
// Make sure tooltip is rendered and if not, return
if(!self.status.rendered)
return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'getDimensions');
show = (!self.elements.tooltip.is(':visible')) ? true : false;
// Show and hide tooltip to make sure dimensions are returned
if(show) self.elements.tooltip.css({ visiblity: 'hidden' }).show();
dimensions = {
height: self.elements.tooltip.outerHeight(),
width: self.elements.tooltip.outerWidth()
};
if(show) self.elements.tooltip.css({ visiblity: 'visible' }).hide();
return dimensions;
}
});
};
// Define priamry construct function
function construct()
{
var self, adjust, content, url, data, method, tempLength;
self = this;
// Call API method
self.beforeRender.call(self);
// Set rendered status to true
self.status.rendered = true;
// Create initial tooltip elements
self.elements.tooltip = '<div qtip="'+self.id+'" ' +
'class="qtip '+(self.options.style.classes.tooltip || self.options.style)+'"' +
'style="display:none; -moz-border-radius:0; -webkit-border-radius:0; border-radius:0;' +
'position:'+self.options.position.type+';">' +
' <div class="qtip-wrapper" style="position:relative; overflow:hidden; text-align:left;">' +
' <div class="qtip-contentWrapper" style="overflow:hidden;">' +
' <div class="qtip-content '+self.options.style.classes.content+'"></div>' +
'</div></div></div>';
// Append to container element
self.elements.tooltip = $(self.elements.tooltip);
self.elements.tooltip.appendTo(self.options.position.container)
// Setup tooltip qTip data
self.elements.tooltip.data('qtip', { current: 0, interfaces: [self] });
// Setup element references
self.elements.wrapper = self.elements.tooltip.children('div:first');
self.elements.contentWrapper = self.elements.wrapper.children('div:first').css({ background: self.options.style.background });
self.elements.content = self.elements.contentWrapper.children('div:first').css( jQueryStyle(self.options.style) );
// Apply IE hasLayout fix to wrapper and content elements
if($.browser.msie) self.elements.wrapper.add(self.elements.content).css({ zoom: 1 });
// Setup tooltip attributes
if(self.options.hide.when.event == 'unfocus') self.elements.tooltip.attr('unfocus', true);
// If an explicit width is set, updateWidth prior to setting content to prevent dirty rendering
if(typeof self.options.style.width.value == 'number') self.updateWidth();
// Create borders and tips if supported by the browser
if($('<canvas>').get(0).getContext || $.browser.msie)
{
// Create border
if(self.options.style.border.radius > 0)
createBorder.call(self);
else
self.elements.contentWrapper.css({ border: self.options.style.border.width+'px solid '+self.options.style.border.color });
// Create tip if enabled
if(self.options.style.tip.corner !== false)
createTip.call(self);
}
// Neither canvas or VML is supported, tips and borders cannot be drawn!
else
{
// Set defined border width
self.elements.contentWrapper.css({ border: self.options.style.border.width+'px solid '+self.options.style.border.color });
// Reset border radius and tip
self.options.style.border.radius = 0;
self.options.style.tip.corner = false;
// Inform via log
$.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.CANVAS_VML_NOT_SUPPORTED, 'render');
};
// Use the provided content string or DOM array
if((typeof self.options.content.text == 'string' && self.options.content.text.length > 0)
|| (self.options.content.text.jquery && self.options.content.text.length > 0))
content = self.options.content.text;
// Use title string for content if present
else if(typeof self.elements.target.attr('title') == 'string' && self.elements.target.attr('title').length > 0)
{
content = self.elements.target.attr('title').replace("\\n", '<br />');
self.elements.target.attr('title', ''); // Remove title attribute to prevent default tooltip showing
}
// No title is present, use alt attribute instead
else if(typeof self.elements.target.attr('alt') == 'string' && self.elements.target.attr('alt').length > 0)
{
content = self.elements.target.attr('alt').replace("\\n", '<br />');
self.elements.target.attr('alt', ''); // Remove alt attribute to prevent default tooltip showing
}
// No valid content was provided, inform via log
else
{
content = ' ';
$.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.NO_VALID_CONTENT, 'render');
};
// Set the tooltips content and create title if enabled
if(self.options.content.title.text !== false) createTitle.call(self);
self.updateContent(content);
// Assign events and toggle tooltip with focus
assignEvents.call(self);
if(self.options.show.ready === true) self.show();
// Retrieve ajax content if provided
if(self.options.content.url !== false)
{
url = self.options.content.url;
data = self.options.content.data;
method = self.options.content.method || 'get';
self.loadContent(url, data, method);
};
// Call API method and log event
self.onRender.call(self);
$.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_RENDERED, 'render');
};
// Create borders using canvas and VML
function createBorder()
{
var self, i, width, radius, color, coordinates, containers, size, betweenWidth, betweenCorners, borderTop, borderBottom, borderCoord, sideWidth, vertWidth;
self = this;
// Destroy previous border elements, if present
self.elements.wrapper.find('.qtip-borderBottom, .qtip-borderTop').remove();
// Setup local variables
width = self.options.style.border.width;
radius = self.options.style.border.radius;
color = self.options.style.border.color || self.options.style.tip.color;
// Calculate border coordinates
coordinates = calculateBorders(radius);
// Create containers for the border shapes
containers = {};
for(i in coordinates)
{
// Create shape container
containers[i] = '<div rel="'+i+'" style="'+((i.search(/Left/) !== -1) ? 'left' : 'right') + ':0; ' +
'position:absolute; height:'+radius+'px; width:'+radius+'px; overflow:hidden; line-height:0.1px; font-size:1px">';
// Canvas is supported
if($('<canvas>').get(0).getContext)
containers[i] += '<canvas height="'+radius+'" width="'+radius+'" style="vertical-align: top"></canvas>';
// No canvas, but if it's IE use VML
else if($.browser.msie)
{
size = radius * 2 + 3;
containers[i] += '<v:arc stroked="false" fillcolor="'+color+'" startangle="'+coordinates[i][0]+'" endangle="'+coordinates[i][1]+'" ' +
'style="width:'+size+'px; height:'+size+'px; margin-top:'+((i.search(/bottom/) !== -1) ? -2 : -1)+'px; ' +
'margin-left:'+((i.search(/Right/) !== -1) ? coordinates[i][2] - 3.5 : -1)+'px; ' +
'vertical-align:top; display:inline-block; behavior:url(#default#VML)"></v:arc>';
};
containers[i] += '</div>';
};
// Create between corners elements
betweenWidth = self.getDimensions().width - (Math.max(width, radius) * 2);
betweenCorners = '<div class="qtip-betweenCorners" style="height:'+radius+'px; width:'+betweenWidth+'px; ' +
'overflow:hidden; background-color:'+color+'; line-height:0.1px; font-size:1px;">';
// Create top border container
borderTop = '<div class="qtip-borderTop" dir="ltr" style="height:'+radius+'px; ' +
'margin-left:'+radius+'px; line-height:0.1px; font-size:1px; padding:0;">' +
containers['topLeft'] + containers['topRight'] + betweenCorners;
self.elements.wrapper.prepend(borderTop);
// Create bottom border container
borderBottom = '<div class="qtip-borderBottom" dir="ltr" style="height:'+radius+'px; ' +
'margin-left:'+radius+'px; line-height:0.1px; font-size:1px; padding:0;">' +
containers['bottomLeft'] + containers['bottomRight'] + betweenCorners;
self.elements.wrapper.append(borderBottom);
// Draw the borders if canvas were used (Delayed til after DOM creation)
if($('<canvas>').get(0).getContext)
{
self.elements.wrapper.find('canvas').each(function()
{
borderCoord = coordinates[ $(this).parent('[rel]:first').attr('rel') ];
drawBorder.call(self, $(this), borderCoord, radius, color);
})
}
// Create a phantom VML element (IE won't show the last created VML element otherwise)
else if($.browser.msie) self.elements.tooltip.append('<v:image style="behavior:url(#default#VML);"></v:image>');
// Setup contentWrapper border
sideWidth = Math.max(radius, (radius + (width - radius)) )
vertWidth = Math.max(width - radius, 0);
self.elements.contentWrapper.css({
border: '0px solid ' + color,
borderWidth: vertWidth + 'px ' + sideWidth + 'px'
})
};
// Border canvas draw method
function drawBorder(canvas, coordinates, radius, color)
{
// Create corner
var context = canvas.get(0).getContext('2d');
context.fillStyle = color;
context.beginPath();
context.arc(coordinates[0], coordinates[1], radius, 0, Math.PI * 2, false);
context.fill();
};
// Create tip using canvas and VML
function createTip(corner)
{
var self, color, coordinates, coordsize, path;
self = this;
// Destroy previous tip, if there is one
if(self.elements.tip !== null) self.elements.tip.remove();
// Setup color and corner values
color = self.options.style.tip.color || self.options.style.border.color;
if(self.options.style.tip.corner === false) return;
else if(!corner) corner = self.options.style.tip.corner;
// Calculate tip coordinates
coordinates = calculateTip(corner, self.options.style.tip.size.width, self.options.style.tip.size.height);
// Create tip element
self.elements.tip = '<div class="'+self.options.style.classes.tip+'" dir="ltr" rel="'+corner+'" style="position:absolute; ' +
'height:'+self.options.style.tip.size.height+'px; width:'+self.options.style.tip.size.width+'px; ' +
'margin:0 auto; line-height:0.1px; font-size:1px;">';
// Use canvas element if supported
if($('<canvas>').get(0).getContext)
self.elements.tip += '<canvas height="'+self.options.style.tip.size.height+'" width="'+self.options.style.tip.size.width+'"></canvas>';
// Canvas not supported - Use VML (IE)
else if($.browser.msie)
{
// Create coordize and tip path using tip coordinates
coordsize = self.options.style.tip.size.width + ',' + self.options.style.tip.size.height;
path = 'm' + coordinates[0][0] + ',' + coordinates[0][1];
path += ' l' + coordinates[1][0] + ',' + coordinates[1][1];
path += ' ' + coordinates[2][0] + ',' + coordinates[2][1];
path += ' xe';
// Create VML element
self.elements.tip += '<v:shape fillcolor="'+color+'" stroked="false" filled="true" path="'+path+'" coordsize="'+coordsize+'" ' +
'style="width:'+self.options.style.tip.size.width+'px; height:'+self.options.style.tip.size.height+'px; ' +
'line-height:0.1px; display:inline-block; behavior:url(#default#VML); ' +
'vertical-align:'+((corner.search(/top/) !== -1) ? 'bottom' : 'top')+'"></v:shape>';
// Create a phantom VML element (IE won't show the last created VML element otherwise)
self.elements.tip += '<v:image style="behavior:url(#default#VML);"></v:image>';
// Prevent tooltip appearing above the content (IE z-index bug)
self.elements.contentWrapper.css('position', 'relative');
};
// Attach new tip to tooltip element
self.elements.tooltip.prepend(self.elements.tip + '</div>');
// Create element reference and draw the canvas tip (Delayed til after DOM creation)
self.elements.tip = self.elements.tooltip.find('.'+self.options.style.classes.tip).eq(0);
if($('<canvas>').get(0).getContext)
drawTip.call(self, self.elements.tip.find('canvas:first'), coordinates, color);
// Fix IE small tip bug
if(corner.search(/top/) !== -1 && $.browser.msie && parseInt($.browser.version.charAt(0)) === 6)
self.elements.tip.css({ marginTop: -4 });
// Set the tip position
positionTip.call(self, corner);
};
// Canvas tip drawing method
function drawTip(canvas, coordinates, color)
{
// Setup properties
var context = canvas.get(0).getContext('2d');
context.fillStyle = color;
// Create tip
context.beginPath();
context.moveTo(coordinates[0][0], coordinates[0][1]);
context.lineTo(coordinates[1][0], coordinates[1][1]);
context.lineTo(coordinates[2][0], coordinates[2][1]);
context.fill();
};
function positionTip(corner)
{
var self, ieAdjust, paddingCorner, paddingSize, newMargin;
self = this;
// Return if tips are disabled or tip is not yet rendered
if(self.options.style.tip.corner === false || !self.elements.tip) return;
if(!corner) corner = self.elements.tip.attr('rel');
// Setup adjustment variables
ieAdjust = positionAdjust = ($.browser.msie) ? 1 : 0;
// Set initial position
self.elements.tip.css(corner.match(/left|right|top|bottom/)[0], 0);
// Set position of tip to correct side
if(corner.search(/top|bottom/) !== -1)
{
// Adjustments for IE6 - 0.5px border gap bug
if($.browser.msie)
{
if(parseInt($.browser.version.charAt(0)) === 6)
positionAdjust = (corner.search(/top/) !== -1) ? -3 : 1;
else
positionAdjust = (corner.search(/top/) !== -1) ? 1 : 2;
};
if(corner.search(/Middle/) !== -1)
self.elements.tip.css({ left: '50%', marginLeft: -(self.options.style.tip.size.width / 2) });
else if(corner.search(/Left/) !== -1)
self.elements.tip.css({ left: self.options.style.border.radius - ieAdjust });
else if(corner.search(/Right/) !== -1)
self.elements.tip.css({ right: self.options.style.border.radius + ieAdjust });
if(corner.search(/top/) !== -1)
self.elements.tip.css({ top: -positionAdjust });
else
self.elements.tip.css({ bottom: positionAdjust });
}
else if(corner.search(/left|right/) !== -1)
{
// Adjustments for IE6 - 0.5px border gap bug
if($.browser.msie)
positionAdjust = (parseInt($.browser.version.charAt(0)) === 6) ? 1 : ((corner.search(/left/) !== -1) ? 1 : 2);
if(corner.search(/Middle/) !== -1)
self.elements.tip.css({ top: '50%', marginTop: -(self.options.style.tip.size.height / 2) });
else if(corner.search(/Top/) !== -1)
self.elements.tip.css({ top: self.options.style.border.radius - ieAdjust });
else if(corner.search(/Bottom/) !== -1)
self.elements.tip.css({ bottom: self.options.style.border.radius + ieAdjust });
if(corner.search(/left/) !== -1)
self.elements.tip.css({ left: -positionAdjust });
else
self.elements.tip.css({ right: positionAdjust });
};
// Adjust tooltip padding to compensate for tip
paddingCorner = 'padding-' + corner.match(/left|right|top|bottom/)[0];
paddingSize = self.options.style.tip.size[ (paddingCorner.search(/left|right/) !== -1) ? 'width' : 'height' ];
self.elements.tooltip.css('padding', 0);
self.elements.tooltip.css(paddingCorner, paddingSize);
// Match content margin to prevent gap bug in IE6 ONLY
if($.browser.msie && parseInt($.browser.version.charAt(0)) == 6)
{
newMargin = parseInt(self.elements.tip.css('margin-top')) || 0;
newMargin += parseInt(self.elements.content.css('margin-top')) || 0;
self.elements.tip.css({ marginTop: newMargin });
};
};
// Create title bar for content
function createTitle()
{
var self = this;
// Destroy previous title element, if present
if(self.elements.title !== null) self.elements.title.remove();
// Create title element
self.elements.title = $('<div class="'+self.options.style.classes.title+'">')
.css( jQueryStyle(self.options.style.title, true) )
.css({ zoom: ($.browser.msie) ? 1 : 0 })
.prependTo(self.elements.contentWrapper);
// Update title with contents if enabled
if(self.options.content.title.text) self.updateTitle.call(self, self.options.content.title.text);
// Create title close buttons if enabled
if(self.options.content.title.button !== false
&& typeof self.options.content.title.button == 'string')
{
self.elements.button = $('<a class="'+self.options.style.classes.button+'" style="float:right; position: relative"></a>')
.css( jQueryStyle(self.options.style.button, true) )
.html(self.options.content.title.button)
.prependTo(self.elements.title)
.click(function(event){ if(!self.status.disabled) self.hide(event) });
};
};
// Assign hide and show events
function assignEvents()
{
var self, showTarget, hideTarget, inactiveEvents;
self = this;
// Setup event target variables
showTarget = self.options.show.when.target;
hideTarget = self.options.hide.when.target;
// Add tooltip as a hideTarget is its fixed
if(self.options.hide.fixed) hideTarget = hideTarget.add(self.elements.tooltip);
// Check if the hide event is special 'inactive' type
if(self.options.hide.when.event == 'inactive')
{
// Define events which reset the 'inactive' event handler
inactiveEvents = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
'mouseout', 'mouseenter', 'mouseleave', 'mouseover' ];
// Define 'inactive' event timer method
function inactiveMethod(event)
{
if(self.status.disabled === true) return;
//Clear and reset the timer
clearTimeout(self.timers.inactive);
self.timers.inactive = setTimeout(function()
{
// Unassign 'inactive' events
$(inactiveEvents).each(function()
{
hideTarget.unbind(this+'.qtip-inactive');
self.elements.content.unbind(this+'.qtip-inactive');
});
// Hide the tooltip
self.hide(event);
}
, self.options.hide.delay);
};
}
// Check if the tooltip is 'fixed'
else if(self.options.hide.fixed === true)
{
self.elements.tooltip.bind('mouseover.qtip', function()
{
if(self.status.disabled === true) return;
// Reset the hide timer
clearTimeout(self.timers.hide);
});
};
// Define show event method
function showMethod(event)
{
if(self.status.disabled === true) return;
// If set, hide tooltip when inactive for delay period
if(self.options.hide.when.event == 'inactive')
{
// Assign each reset event
$(inactiveEvents).each(function()
{
hideTarget.bind(this+'.qtip-inactive', inactiveMethod);
self.elements.content.bind(this+'.qtip-inactive', inactiveMethod);
});
// Start the inactive timer
inactiveMethod();
};
// Clear hide timers
clearTimeout(self.timers.show);
clearTimeout(self.timers.hide);
// Start show timer
self.timers.show = setTimeout(function(){ self.show(event); }, self.options.show.delay);
};
// Define hide event method
function hideMethod(event)
{
if(self.status.disabled === true) return;
// Prevent hiding if tooltip is fixed and event target is the tooltip
if(self.options.hide.fixed === true
&& self.options.hide.when.event.search(/mouse(out|leave)/i) !== -1
&& $(event.relatedTarget).parents('div.qtip[qtip]').length > 0)
{
// Prevent default and popagation
event.stopPropagation();
event.preventDefault();
// Reset the hide timer
clearTimeout(self.timers.hide);
return false;
};
// Clear timers and stop animation queue
clearTimeout(self.timers.show);
clearTimeout(self.timers.hide);
self.elements.tooltip.stop(true, true);
// If tooltip has displayed, start hide timer
self.timers.hide = setTimeout(function(){ self.hide(event); }, self.options.hide.delay);
};
// Both events and targets are identical, apply events using a toggle
if((self.options.show.when.target.add(self.options.hide.when.target).length === 1
&& self.options.show.when.event == self.options.hide.when.event
&& self.options.hide.when.event !== 'inactive')
|| self.options.hide.when.event == 'unfocus')
{
self.cache.toggle = 0;
// Use a toggle to prevent hide/show conflicts
showTarget.bind(self.options.show.when.event + '.qtip', function(event)
{
if(self.cache.toggle == 0) showMethod(event);
else hideMethod(event);
});
}
// Events are not identical, bind normally
else
{
showTarget.bind(self.options.show.when.event + '.qtip', showMethod);
// If the hide event is not 'inactive', bind the hide method
if(self.options.hide.when.event !== 'inactive')
hideTarget.bind(self.options.hide.when.event + '.qtip', hideMethod);
};
// Focus the tooltip on mouseover
if(self.options.position.type.search(/(fixed|absolute)/) !== -1)
self.elements.tooltip.bind('mouseover.qtip', self.focus);
// If mouse is the target, update tooltip position on mousemove
if(self.options.position.target === 'mouse' && self.options.position.type !== 'static')
{
showTarget.bind('mousemove.qtip', function(event)
{
// Set the new mouse positions if adjustment is enabled
self.cache.mouse = { x: event.pageX, y: event.pageY };
// Update the tooltip position only if the tooltip is visible and adjustment is enabled
if(self.status.disabled === false
&& self.options.position.adjust.mouse === true
&& self.options.position.type !== 'static'
&& self.elements.tooltip.css('display') !== 'none')
self.updatePosition(event);
});
};
};
// Screen position adjustment
function screenAdjust(position, target, tooltip)
{
var self, adjustedPosition, adjust, newCorner, overflow, corner;
self = this;
// Setup corner and adjustment variable
if(tooltip.corner == 'center') return target.position // TODO: 'center' corner adjustment
adjustedPosition = $.extend({}, position);
newCorner = { x: false, y: false };
// Define overflow properties
overflow = {
left: (adjustedPosition.left < $.fn.qtip.cache.screen.scroll.left),
right: (adjustedPosition.left + tooltip.dimensions.width + 2 >= $.fn.qtip.cache.screen.width + $.fn.qtip.cache.screen.scroll.left),
top: (adjustedPosition.top < $.fn.qtip.cache.screen.scroll.top),
bottom: (adjustedPosition.top + tooltip.dimensions.height + 2 >= $.fn.qtip.cache.screen.height + $.fn.qtip.cache.screen.scroll.top)
};
// Determine new positioning properties
adjust = {
left: (overflow.left && (tooltip.corner.search(/right/i) != -1 || (tooltip.corner.search(/right/i) == -1 && !overflow.right))),
right: (overflow.right && (tooltip.corner.search(/left/i) != -1 || (tooltip.corner.search(/left/i) == -1 && !overflow.left))),
top: (overflow.top && tooltip.corner.search(/top/i) == -1),
bottom: (overflow.bottom && tooltip.corner.search(/bottom/i) == -1)
};
// Tooltip overflows off the left side of the screen
if(adjust.left)
{
if(self.options.position.target !== 'mouse')
adjustedPosition.left = target.position.left + target.dimensions.width;
else
adjustedPosition.left = self.cache.mouse.x
newCorner.x = 'Left';
}
// Tooltip overflows off the right side of the screen
else if(adjust.right)
{
if(self.options.position.target !== 'mouse')
adjustedPosition.left = target.position.left - tooltip.dimensions.width;
else
adjustedPosition.left = self.cache.mouse.x - tooltip.dimensions.width;
newCorner.x = 'Right';
};
// Tooltip overflows off the top of the screen
if(adjust.top)
{
if(self.options.position.target !== 'mouse')
adjustedPosition.top = target.position.top + target.dimensions.height;
else
adjustedPosition.top = self.cache.mouse.y
newCorner.y = 'top';
}
// Tooltip overflows off the bottom of the screen
else if(adjust.bottom)
{
if(self.options.position.target !== 'mouse')
adjustedPosition.top = target.position.top - tooltip.dimensions.height;
else
adjustedPosition.top = self.cache.mouse.y - tooltip.dimensions.height;
newCorner.y = 'bottom';
};
// Don't adjust if resulting position is negative
if(adjustedPosition.left < 0)
{
adjustedPosition.left = position.left;
newCorner.x = false;
};
if(adjustedPosition.top < 0)
{
adjustedPosition.top = position.top;
newCorner.y = false;
};
// Change tip corner if positioning has changed and tips are enabled
if(self.options.style.tip.corner !== false)
{
// Determine new corner properties
adjustedPosition.corner = new String(tooltip.corner);
if(newCorner.x !== false) adjustedPosition.corner = adjustedPosition.corner.replace(/Left|Right|Middle/, newCorner.x);
if(newCorner.y !== false) adjustedPosition.corner = adjustedPosition.corner.replace(/top|bottom/, newCorner.y);
// Adjust tip if position has changed and tips are enabled
if(adjustedPosition.corner !== self.elements.tip.attr('rel'))
createTip.call(self, adjustedPosition.corner);
};
return adjustedPosition;
};
// Build a jQuery style object from supplied style object
function jQueryStyle(style, sub)
{
var styleObj, i;
styleObj = $.extend(true, {}, style);
for(i in styleObj)
{
if(sub === true && i.search(/(tip|classes)/i) !== -1)
delete styleObj[i];
else if(!sub && i.search(/(width|border|tip|title|classes|user)/i) !== -1)
delete styleObj[i];
};
return styleObj;
};
// Sanitize styles
function sanitizeStyle(style)
{
if(typeof style.tip !== 'object') style.tip = { corner: style.tip };
if(typeof style.tip.size !== 'object') style.tip.size = { width: style.tip.size, height: style.tip.size };
if(typeof style.border !== 'object') style.border = { width: style.border };
if(typeof style.width !== 'object') style.width = { value: style.width };
if(typeof style.width.max == 'string') style.width.max = parseInt(style.width.max.replace(/([0-9]+)/i, "$1"));
if(typeof style.width.min == 'string') style.width.min = parseInt(style.width.min.replace(/([0-9]+)/i, "$1"));
// Convert deprecated x and y tip values to width/height
if(typeof style.tip.size.x == 'number')
{
style.tip.size.width = style.tip.size.x;
delete style.tip.size.x;
};
if(typeof style.tip.size.y == 'number')
{
style.tip.size.height = style.tip.size.y;
delete style.tip.size.y;
};
return style;
};
// Build styles recursively with inheritance
function buildStyle()
{
var self, i, styleArray, styleExtend, finalStyle, ieAdjust;
self = this;
// Build style options from supplied arguments
styleArray = [true, {}];
for(i = 0; i < arguments.length; i++)
styleArray.push(arguments[i]);
styleExtend = [ $.extend.apply($, styleArray) ];
// Loop through each named style inheritance
while(typeof styleExtend[0].name == 'string')
{
// Sanitize style data and append to extend array
styleExtend.unshift( sanitizeStyle($.fn.qtip.styles[ styleExtend[0].name ]) );
};
// Make sure resulting tooltip className represents final style
styleExtend.unshift(true, {classes:{ tooltip: 'qtip-' + (arguments[0].name || 'defaults') }}, $.fn.qtip.styles.defaults);
// Extend into a single style object
finalStyle = $.extend.apply($, styleExtend);
// Adjust tip size if needed (IE 1px adjustment bug fix)
ieAdjust = ($.browser.msie) ? 1 : 0;
finalStyle.tip.size.width += ieAdjust;
finalStyle.tip.size.height += ieAdjust;
// Force even numbers for pixel precision
if(finalStyle.tip.size.width % 2 > 0) finalStyle.tip.size.width += 1;
if(finalStyle.tip.size.height % 2 > 0) finalStyle.tip.size.height += 1;
// Sanitize final styles tip corner value
if(finalStyle.tip.corner === true)
finalStyle.tip.corner = (self.options.position.corner.tooltip === 'center') ? false : self.options.position.corner.tooltip;
return finalStyle;
};
// Tip coordinates calculator
function calculateTip(corner, width, height)
{
// Define tip coordinates in terms of height and width values
var tips = {
bottomRight: [[0,0], [width,height], [width,0]],
bottomLeft: [[0,0], [width,0], [0,height]],
topRight: [[0,height], [width,0], [width,height]],
topLeft: [[0,0], [0,height], [width,height]],
topMiddle: [[0,height], [width / 2,0], [width,height]],
bottomMiddle: [[0,0], [width,0], [width / 2,height]],
rightMiddle: [[0,0], [width,height / 2], [0,height]],
leftMiddle: [[width,0], [width,height], [0,height / 2]]
};
tips.leftTop = tips.bottomRight;
tips.rightTop = tips.bottomLeft;
tips.leftBottom = tips.topRight;
tips.rightBottom = tips.topLeft;
return tips[corner];
};
// Border coordinates calculator
function calculateBorders(radius)
{
var borders;
// Use canvas element if supported
if($('<canvas>').get(0).getContext)
{
borders = {
topLeft: [radius,radius], topRight: [0,radius],
bottomLeft: [radius,0], bottomRight: [0,0]
};
}
// Canvas not supported - Use VML (IE)
else if($.browser.msie)
{
borders = {
topLeft: [-90,90,0], topRight: [-90,90,-radius],
bottomLeft: [90,270,0], bottomRight: [90, 270,-radius]
};
};
return borders;
};
// BGIFRAME JQUERY PLUGIN ADAPTION
// Special thanks to Brandon Aaron for this plugin
// http://plugins.jquery.com/project/bgiframe
function bgiframe()
{
var self, html, dimensions;
self = this;
dimensions = self.getDimensions();
// Setup iframe HTML string
html = '<iframe class="qtip-bgiframe" frameborder="0" tabindex="-1" src="javascript:false" '+
'style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=\'0\'); border: 1px solid red; ' +
'height:'+dimensions.height+'px; width:'+dimensions.width+'px" />';
// Append the new HTML and setup element reference
self.elements.bgiframe = self.elements.wrapper.prepend(html).children('.qtip-bgiframe:first');
};
// Assign cache and event initialisation on document load
$(document).ready(function()
{
// Setup library cache with window scroll and dimensions of document
$.fn.qtip.cache = {
screen: {
scroll: { left: $(window).scrollLeft(), top: $(window).scrollTop() },
width: $(window).width(),
height: $(window).height()
}
};
// Adjust positions of the tooltips on window resize or scroll if enabled
var adjustTimer;
$(window).bind('resize scroll', function(event)
{
clearTimeout(adjustTimer);
adjustTimer = setTimeout(function()
{
// Readjust cached screen values
if(event.type === 'scroll')
$.fn.qtip.cache.screen.scroll = { left: $(window).scrollLeft(), top: $(window).scrollTop() };
else
{
$.fn.qtip.cache.screen.width = $(window).width();
$.fn.qtip.cache.screen.height = $(window).height();
};
for(i = 0; i < $.fn.qtip.interfaces.length; i++)
{
// Access current elements API
var api = $.fn.qtip.interfaces[i];
// Update position if resize or scroll adjustments are enabled
if(api.status.rendered === true
&& (api.options.position.type !== 'static'
|| api.options.position.adjust.scroll && event.type === 'scroll'
|| api.options.position.adjust.resize && event.type === 'resize'))
{
// Queue the animation so positions are updated correctly
api.updatePosition(event, true);
}
};
}
, 100);
})
// Hide unfocus toolipts on document mousedown
$(document).bind('mousedown.qtip', function(event)
{
if($(event.target).parents('div.qtip').length === 0)
{
$('.qtip[unfocus]').each(function()
{
var api = $(this).qtip("api");
// Only hide if its visible and not the tooltips target
if($(this).is(':visible') && !api.status.disabled
&& $(event.target).add(api.elements.target).length > 1)
api.hide(event);
})
};
})
});
// Define qTip API interfaces array
$.fn.qtip.interfaces = []
// Define log and constant place holders
$.fn.qtip.log = { error: function(){ return this; } };
$.fn.qtip.constants = {};
// Define configuration defaults
$.fn.qtip.defaults = {
// Content
content: {
prerender: false,
text: false,
url: false,
data: null,
title: {
text: false,
button: false
}
},
// Position
position: {
target: false,
corner: {
target: 'bottomRight',
tooltip: 'topLeft'
},
adjust: {
x: 0, y: 0,
mouse: true,
screen: false,
scroll: true,
resize: true
},
type: 'absolute',
container: false
},
// Effects
show: {
when: {
target: false,
event: 'mouseover'
},
effect: {
type: 'fade',
length: 100
},
delay: 140,
solo: false,
ready: false
},
hide: {
when: {
target: false,
event: 'mouseout'
},
effect: {
type: 'fade',
length: 100
},
delay: 0,
fixed: false
},
// Callbacks
api: {
beforeRender: function(){},
onRender: function(){},
beforePositionUpdate: function(){},
onPositionUpdate: function(){},
beforeShow: function(){},
onShow: function(){},
beforeHide: function(){},
onHide: function(){},
beforeContentUpdate: function(){},
onContentUpdate: function(){},
beforeContentLoad: function(){},
onContentLoad: function(){},
beforeTitleUpdate: function(){},
onTitleUpdate: function(){},
beforeDestroy: function(){},
onDestroy: function(){},
beforeFocus: function(){},
onFocus: function(){}
}
};
$.fn.qtip.styles = {
defaults: {
background: 'white',
color: '#111',
overflow: 'hidden',
textAlign: 'left',
width: {
min: 0,
max: 250
},
padding: '5px 9px',
border: {
width: 1,
radius: 0,
color: '#d3d3d3'
},
tip: {
corner: false,
color: false,
size: { width: 13, height: 13 },
opacity: 1
},
title: {
background: '#e1e1e1',
fontWeight: 'bold',
padding: '7px 12px'
},
button: {
cursor: 'pointer'
},
classes: {
target: '',
tip: 'qtip-tip',
title: 'qtip-title',
button: 'qtip-button',
content: 'qtip-content',
active: 'qtip-active'
}
},
cream: {
border: {
width: 3,
radius: 0,
color: '#F9E98E'
},
title: {
background: '#F0DE7D',
color: '#A27D35'
},
background: '#FBF7AA',
color: '#A27D35',
classes: { tooltip: 'qtip-cream' }
},
light: {
border: {
width: 3,
radius: 0,
color: '#E2E2E2'
},
title: {
background: '#f1f1f1',
color: '#454545'
},
background: 'white',
color: '#454545',
classes: { tooltip: 'qtip-light' }
},
dark: {
border: {
width: 3,
radius: 0,
color: '#303030'
},
title: {
background: '#404040',
color: '#f3f3f3'
},
background: '#505050',
color: '#f3f3f3',
classes: { tooltip: 'qtip-dark' }
},
red: {
border: {
width: 3,
radius: 0,
color: '#CE6F6F'
},
title: {
background: '#f28279',
color: '#9C2F2F'
},
background: '#F79992',
color: '#9C2F2F',
classes: { tooltip: 'qtip-red' }
},
green: {
border: {
width: 3,
radius: 0,
color: '#A9DB66'
},
title: {
background: '#b9db8c',
color: '#58792E'
},
background: '#CDE6AC',
color: '#58792E',
classes: { tooltip: 'qtip-green' }
},
blue: {
border: {
width: 3,
radius: 0,
color: '#ADD9ED'
},
title: {
background: '#D0E9F5',
color: '#5E99BD'
},
background: '#E5F6FE',
color: '#4D9FBF',
classes: { tooltip: 'qtip-blue' }
}
};
})(jQuery);