2 * jquery.qtip. The jQuery tooltip plugin
4 * Copyright (c) 2009 Craig Thompson
5 * http://craigsworks.com
8 * http://www.opensource.org/licenses/mit-license.php
10 * Launch : February 2009
12 * Released: Tuesday 12th May, 2009 - 00:00
13 * Debug: jquery.qtip.debug.js
16 "use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
17 /*jslint browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, bitwise: true, regexp: true, strict: true, newcap: true, immed: true */
19 /*global window: false, jQuery: false */
21 // Assign cache and event initialisation on document load
22 $(document
).ready(function () {
23 // Adjust positions of the tooltips on window resize or scroll if enabled
25 $(window
).bind('resize scroll', function (event
) {
26 for (i
= 0; i
< $.fn
.qtip
.interfaces
.length
; i
++) {
27 // Access current elements API
28 var api
= $.fn
.qtip
.interfaces
[i
];
30 // Update position if resize or scroll adjustments are enabled
31 if(api
&& api
.status
&& api
.status
.rendered
&& api
.options
.position
.type
!== 'static' && api
.elements
.tooltip
.is(':visible') &&
32 (api
.options
.position
.adjust
.scroll
&& event
.type
=== 'scroll' || api
.options
.position
.adjust
.resize
&& event
.type
=== 'resize')) {
33 // Queue the animation so positions are updated correctly
34 api
.updatePosition(event
, true);
39 // Hide unfocus toolipts on document mousedown
40 $(document
).bind('mouseenter.qtip', function (event
) {
41 if($(event
.target
).parents('div.qtip').length
=== 0) {
42 var tooltip
= $('.qtipSelector'),
43 api
= tooltip
.qtip('api');
45 // Only hide if its visible and not the tooltips target
46 if(tooltip
.is(':visible') && api
&& api
.status
&& !api
.status
.disabled
&& $(event
.target
).add(api
.elements
.target
).length
> 1) { api
.hide(event
); }
51 // Corner object parser
52 function Corner(corner
) {
53 if(!corner
){ return false; }
55 this.x
= String(corner
).replace(/middle/i, 'center').match(/left|right|center/i)[0].toLowerCase();
56 this.y
= String(corner
).replace(/middle/i, 'center').match(/top|bottom|center/i)[0].toLowerCase();
57 this.offset
= { left
: 0, top
: 0 };
58 this.precedance
= (corner
.charAt(0).search(/^(t|b)/) > -1) ? 'y' : 'x';
59 this.string = function(){ return (this.precedance
=== 'y') ? this.y
+this.x
: this.x
+this.y
; };
62 // Tip coordinates calculator
63 function calculateTip(corner
, width
, height
) {
64 // Define tip coordinates in terms of height and width values
66 bottomright
: [[0, 0], [width
, height
], [width
, 0]],
67 bottomleft
: [[0, 0], [width
, 0], [0, height
]],
68 topright
: [[0, height
], [width
, 0], [width
, height
]],
69 topleft
: [[0, 0], [0, height
], [width
, height
]],
70 topcenter
: [[0, height
], [width
/ 2, 0], [width
, height
]],
71 bottomcenter
: [[0, 0], [width
, 0], [width
/ 2, height
]],
72 rightcenter
: [[0, 0], [width
, height
/ 2], [0, height
]],
73 leftcenter
: [[width
, 0], [width
, height
], [0, height
/ 2]]
75 tips
.lefttop
= tips
.bottomright
;
76 tips
.righttop
= tips
.bottomleft
;
77 tips
.leftbottom
= tips
.topright
;
78 tips
.rightbottom
= tips
.topleft
;
83 // Border coordinates calculator
84 function calculateBorders(radius
) {
87 // Use canvas element if supported
88 if($('<canvas />').get(0).getContext
) {
90 topLeft
: [radius
, radius
],
91 topRight
: [0, radius
],
92 bottomLeft
: [radius
, 0],
97 // Canvas not supported - Use VML (IE)
98 else if($.browser
.msie
) {
100 topLeft
: [-90, 90, 0],
101 topRight
: [-90, 90, -radius
],
102 bottomLeft
: [90, 270, 0],
103 bottomRight
: [90, 270, -radius
]
111 // Build a jQuery style object from supplied style object
112 function jQueryStyle(style
, sub
) {
115 styleObj
= $.extend(true, {}, style
);
116 for (i
in styleObj
) {
117 if(sub
=== true && (/(tip|classes)/i).test(i
)) { delete styleObj
[i
]; }
118 else if(!sub
&& (/(width|border|tip|title|classes|user)/i).test(i
)) { delete styleObj
[i
]; }
125 function sanitizeStyle(style
) {
126 if(typeof style
.tip
!== 'object') {
127 style
.tip
= { corner
: style
.tip
};
129 if(typeof style
.tip
.size
!== 'object') {
131 width
: style
.tip
.size
,
132 height
: style
.tip
.size
135 if(typeof style
.border
!== 'object') {
140 if(typeof style
.width
!== 'object') {
145 if(typeof style
.width
.max
=== 'string') { style
.width
.max
= parseInt(style
.width
.max
.replace(/([0-9]+)/i, "$1"), 10); }
146 if(typeof style
.width
.min
=== 'string') { style
.width
.min
= parseInt(style
.width
.min
.replace(/([0-9]+)/i, "$1"), 10); }
148 // Convert deprecated x and y tip values to width/height
149 if(typeof style
.tip
.size
.x
=== 'number') {
150 style
.tip
.size
.width
= style
.tip
.size
.x
;
151 delete style
.tip
.size
.x
;
153 if(typeof style
.tip
.size
.y
=== 'number') {
154 style
.tip
.size
.height
= style
.tip
.size
.y
;
155 delete style
.tip
.size
.y
;
161 // Build styles recursively with inheritance
162 function buildStyle() {
163 var self
, i
, styleArray
, styleExtend
, finalStyle
, ieAdjust
;
166 // Build style options from supplied arguments
167 styleArray
= [true, {}];
168 for(i
= 0; i
< arguments
.length
; i
++){ styleArray
.push(arguments
[i
]); }
169 styleExtend
= [$.extend
.apply($, styleArray
)];
171 // Loop through each named style inheritance
172 while(typeof styleExtend
[0].name
=== 'string') {
173 // Sanitize style data and append to extend array
174 styleExtend
.unshift(sanitizeStyle($.fn
.qtip
.styles
[styleExtend
[0].name
]));
177 // Make sure resulting tooltip className represents final style
178 styleExtend
.unshift(true, {
180 tooltip
: 'qtip-' + (arguments
[0].name
|| 'defaults')
182 }, $.fn
.qtip
.styles
.defaults
);
184 // Extend into a single style object
185 finalStyle
= $.extend
.apply($, styleExtend
);
187 // Adjust tip size if needed (IE 1px adjustment bug fix)
188 ieAdjust
= ($.browser
.msie
) ? 1 : 0;
189 finalStyle
.tip
.size
.width
+= ieAdjust
;
190 finalStyle
.tip
.size
.height
+= ieAdjust
;
192 // Force even numbers for pixel precision
193 if(finalStyle
.tip
.size
.width
% 2 > 0) { finalStyle
.tip
.size
.width
+= 1; }
194 if(finalStyle
.tip
.size
.height
% 2 > 0) { finalStyle
.tip
.size
.height
+= 1; }
196 // Sanitize final styles tip corner value
197 if(finalStyle
.tip
.corner
=== true) {
198 if(self
.options
.position
.corner
.tooltip
=== 'center' && self
.options
.position
.corner
.target
=== 'center') {
199 finalStyle
.tip
.corner
= false;
202 finalStyle
.tip
.corner
= self
.options
.position
.corner
.tooltip
;
209 // Border canvas draw method
210 function drawBorder(canvas
, coordinates
, radius
, color
) {
212 var context
= canvas
.get(0).getContext('2d');
213 context
.fillStyle
= color
;
215 context
.arc(coordinates
[0], coordinates
[1], radius
, 0, Math
.PI
* 2, false);
219 // Create borders using canvas and VML
220 function createBorder() {
221 var self
, i
, width
, radius
, color
, coordinates
, containers
, size
, betweenWidth
, betweenCorners
, borderTop
, borderBottom
, borderCoord
, sideWidth
, vertWidth
;
224 // Destroy previous border elements, if present
225 self
.elements
.wrapper
.find('.qtip-borderBottom, .qtip-borderTop').remove();
227 // Setup local variables
228 width
= self
.options
.style
.border
.width
;
229 radius
= self
.options
.style
.border
.radius
;
230 color
= self
.options
.style
.border
.color
|| self
.options
.style
.tip
.color
;
232 // Calculate border coordinates
233 coordinates
= calculateBorders(radius
);
235 // Create containers for the border shapes
237 for (i
in coordinates
) {
238 // Create shape container
239 containers
[i
] = '<div rel="' + i
+ '" style="' + ((/Left/).test(i
) ? 'left' : 'right') + ':0; ' + 'position:absolute; height:' + radius
+ 'px; width:' + radius
+ 'px; overflow:hidden; line-height:0.1px; font-size:1px">';
241 // Canvas is supported
242 if($('<canvas />').get(0).getContext
) { containers
[i
] += '<canvas height="' + radius
+ '" width="' + radius
+ '" style="vertical-align: top"></canvas>'; }
244 // No canvas, but if it's IE use VML
245 else if($.browser
.msie
) {
246 size
= radius
* 2 + 3;
247 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:' + ((/bottom/).test(i
) ? -2 : -1) + 'px; ' + 'margin-left:' + ((/Right/).test(i
) ? coordinates
[i
][2] - 3.5 : -1) + 'px; ' + 'vertical-align:top; display:inline-block; behavior:url(#default#VML)"></v:arc>';
251 containers
[i
] += '</div>';
254 // Create between corners elements
255 betweenWidth
= self
.getDimensions().width
- (Math
.max(width
, radius
) * 2);
256 betweenCorners
= '<div class="qtip-betweenCorners" style="height:' + radius
+ 'px; width:' + betweenWidth
+ 'px; ' + 'overflow:hidden; background-color:' + color
+ '; line-height:0.1px; font-size:1px;">';
258 // Create top border container
259 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
;
260 self
.elements
.wrapper
.prepend(borderTop
);
262 // Create bottom border container
263 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
;
264 self
.elements
.wrapper
.append(borderBottom
);
266 // Draw the borders if canvas were used (Delayed til after DOM creation)
267 if($('<canvas />').get(0).getContext
) {
268 self
.elements
.wrapper
.find('canvas').each(function () {
269 borderCoord
= coordinates
[$(this).parent('[rel]:first').attr('rel')];
270 drawBorder
.call(self
, $(this), borderCoord
, radius
, color
);
274 // Create a phantom VML element (IE won't show the last created VML element otherwise)
275 else if($.browser
.msie
) { self
.elements
.tooltip
.append('<v:image style="behavior:url(#default#VML);"></v:image>'); }
277 // Setup contentWrapper border
278 sideWidth
= Math
.max(radius
, (radius
+ (width
- radius
)));
279 vertWidth
= Math
.max(width
- radius
, 0);
280 self
.elements
.contentWrapper
.css({
281 border
: '0px solid ' + color
,
282 borderWidth
: vertWidth
+ 'px ' + sideWidth
+ 'px'
286 // Canvas tip drawing method
287 function drawTip(canvas
, coordinates
, color
) {
289 var context
= canvas
.get(0).getContext('2d');
290 context
.fillStyle
= color
;
294 context
.moveTo(coordinates
[0][0], coordinates
[0][1]);
295 context
.lineTo(coordinates
[1][0], coordinates
[1][1]);
296 context
.lineTo(coordinates
[2][0], coordinates
[2][1]);
300 function positionTip(corner
) {
301 var self
, ieAdjust
, positionAdjust
, paddingCorner
, paddingSize
, newMargin
;
304 // Return if tips are disabled or tip is not yet rendered
305 if(self
.options
.style
.tip
.corner
=== false || !self
.elements
.tip
) { return; }
306 if(!corner
) { corner
= new Corner(self
.elements
.tip
.attr('rel')); }
308 // Setup adjustment variables
309 ieAdjust
= positionAdjust
= ($.browser
.msie
) ? 1 : 0;
311 // Set initial position
312 self
.elements
.tip
.css(corner
[corner
.precedance
], 0);
314 // Set position of tip to correct side
315 if(corner
.precedance
=== 'y') {
316 // Adjustments for IE6 - 0.5px border gap bug
318 if(parseInt($.browser
.version
.charAt(0), 10) === 6) { positionAdjust
= corner
.y
=== 'top' ? -3 : 1; }
319 else { positionAdjust
= corner
.y
=== 'top' ? 1 : 2; }
322 if(corner
.x
=== 'center') {
323 self
.elements
.tip
.css({
325 marginLeft
: -(self
.options
.style
.tip
.size
.width
/ 2)
328 else if(corner
.x
=== 'left') {
329 self
.elements
.tip
.css({
330 left
: self
.options
.style
.border
.radius
- ieAdjust
334 self
.elements
.tip
.css({
335 right
: self
.options
.style
.border
.radius
+ ieAdjust
339 if(corner
.y
=== 'top') {
340 self
.elements
.tip
.css({
345 self
.elements
.tip
.css({
346 bottom
: positionAdjust
352 // Adjustments for IE6 - 0.5px border gap bug
354 positionAdjust
= (parseInt($.browser
.version
.charAt(0), 10) === 6) ? 1 : (corner
.x
=== 'left' ? 1 : 2);
357 if(corner
.y
=== 'center') {
358 self
.elements
.tip
.css({
360 marginTop
: -(self
.options
.style
.tip
.size
.height
/ 2)
363 else if(corner
.y
=== 'top') {
364 self
.elements
.tip
.css({
365 top
: self
.options
.style
.border
.radius
- ieAdjust
369 self
.elements
.tip
.css({
370 bottom
: self
.options
.style
.border
.radius
+ ieAdjust
374 if(corner
.x
=== 'left') {
375 self
.elements
.tip
.css({
376 left
: -positionAdjust
380 self
.elements
.tip
.css({
381 right
: positionAdjust
386 // Adjust tooltip padding to compensate for tip
387 paddingCorner
= 'padding-' + corner
[corner
.precedance
];
388 paddingSize
= self
.options
.style
.tip
.size
[corner
.precedance
=== 'x' ? 'width' : 'height'];
389 self
.elements
.tooltip
.css('padding', 0).css(paddingCorner
, paddingSize
);
391 // Match content margin to prevent gap bug in IE6 ONLY
392 if($.browser
.msie
&& parseInt($.browser
.version
.charAt(0), 6) === 6) {
393 newMargin
= parseInt(self
.elements
.tip
.css('margin-top'), 10) || 0;
394 newMargin
+= parseInt(self
.elements
.content
.css('margin-top'), 10) || 0;
396 self
.elements
.tip
.css({ marginTop
: newMargin
});
400 // Create tip using canvas and VML
401 function createTip(corner
) {
402 var self
, color
, coordinates
, coordsize
, path
, tip
;
405 // Destroy previous tip, if there is one
406 if(self
.elements
.tip
!== null) { self
.elements
.tip
.remove(); }
408 // Setup color and corner values
409 color
= self
.options
.style
.tip
.color
|| self
.options
.style
.border
.color
;
410 if(self
.options
.style
.tip
.corner
=== false) { return; }
411 else if(!corner
) { corner
= new Corner(self
.options
.style
.tip
.corner
); }
413 // Calculate tip coordinates
414 coordinates
= calculateTip(corner
.string(), self
.options
.style
.tip
.size
.width
, self
.options
.style
.tip
.size
.height
);
416 // Create tip element
417 self
.elements
.tip
= '<div class="' + self
.options
.style
.classes
.tip
+ '" dir="ltr" rel="' + corner
.string() + '" 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;"></div>';
419 // Attach new tip to tooltip element
420 self
.elements
.tooltip
.prepend(self
.elements
.tip
);
422 // Use canvas element if supported
423 if($('<canvas />').get(0).getContext
) { tip
= '<canvas height="' + self
.options
.style
.tip
.size
.height
+ '" width="' + self
.options
.style
.tip
.size
.width
+ '"></canvas>'; }
425 // Canvas not supported - Use VML (IE)
426 else if($.browser
.msie
) {
427 // Create coordize and tip path using tip coordinates
428 coordsize
= self
.options
.style
.tip
.size
.width
+ ',' + self
.options
.style
.tip
.size
.height
;
429 path
= 'm' + coordinates
[0][0] + ',' + coordinates
[0][1];
430 path
+= ' l' + coordinates
[1][0] + ',' + coordinates
[1][1];
431 path
+= ' ' + coordinates
[2][0] + ',' + coordinates
[2][1];
434 // Create VML element
435 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
.y
=== 'top' ? 'bottom' : 'top') + '"></v:shape>';
437 // Create a phantom VML element (IE won't show the last created VML element otherwise)
438 tip
+= '<v:image style="behavior:url(#default#VML);"></v:image>';
440 // Prevent tooltip appearing above the content (IE z-index bug)
441 self
.elements
.contentWrapper
.css('position', 'relative');
444 // Create element reference and append vml/canvas
445 self
.elements
.tip
= self
.elements
.tooltip
.find('.' + self
.options
.style
.classes
.tip
).eq(0);
446 self
.elements
.tip
.html(tip
);
448 // Draw the canvas tip (Delayed til after DOM creation)
449 if($('<canvas />').get(0).getContext
) { drawTip
.call(self
, self
.elements
.tip
.find('canvas:first'), coordinates
, color
); }
451 // Fix IE small tip bug
452 if(corner
.y
=== 'top' && $.browser
.msie
&& parseInt($.browser
.version
.charAt(0), 10) === 6) {
453 self
.elements
.tip
.css({
458 // Set the tip position
459 positionTip
.call(self
, corner
);
462 // Create title bar for content
463 function createTitle() {
466 // Destroy previous title element, if present
467 if(self
.elements
.title
!== null) { self
.elements
.title
.remove(); }
469 // Append new ARIA attribute to tooltip
470 self
.elements
.tooltip
.attr('aria-labelledby', 'qtip-' + self
.id
+ '-title');
472 // Create title element
473 self
.elements
.title
= $('<div id="qtip-' + self
.id
+ '-title" class="' + self
.options
.style
.classes
.title
+ '"></div>').css(jQueryStyle(self
.options
.style
.title
, true)).css({
474 zoom
: ($.browser
.msie
) ? 1 : 0
475 }).prependTo(self
.elements
.contentWrapper
);
477 // Update title with contents if enabled
478 if(self
.options
.content
.title
.text
) { self
.updateTitle
.call(self
, self
.options
.content
.title
.text
); }
480 // Create title close buttons if enabled
481 if(self
.options
.content
.title
.button
!== false && typeof self
.options
.content
.title
.button
=== 'string') {
482 self
.elements
.button
= $('<a class="' + self
.options
.style
.classes
.button
+ '" role="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
) {
483 if(!self
.status
.disabled
) { self
.hide(event
); }
488 // Assign hide and show events
489 function assignEvents() {
490 var self
, showTarget
, hideTarget
, inactiveEvents
;
493 // Setup event target variables
494 showTarget
= self
.options
.show
.when
.target
;
495 hideTarget
= self
.options
.hide
.when
.target
;
497 // Add tooltip as a hideTarget is its fixed
498 if(self
.options
.hide
.fixed
) { hideTarget
= hideTarget
.add(self
.elements
.tooltip
); }
500 // Define events which reset the 'inactive' event handler
501 inactiveEvents
= ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
502 'mouseout', 'mouseenter', 'mouseleave', 'mouseover'];
504 // Define 'inactive' event timer method
505 function inactiveMethod(event
) {
506 if(self
.status
.disabled
=== true) { return; }
508 //Clear and reset the timer
509 clearTimeout(self
.timers
.inactive
);
510 self
.timers
.inactive
= setTimeout(function () {
511 // Unassign 'inactive' events
512 $(inactiveEvents
).each(function () {
513 hideTarget
.unbind(this + '.qtip-inactive');
514 self
.elements
.content
.unbind(this + '.qtip-inactive');
519 }, self
.options
.hide
.delay
);
522 // Check if the tooltip is 'fixed'
523 if(self
.options
.hide
.fixed
=== true) {
524 self
.elements
.tooltip
.bind('mouseover.qtip', function () {
525 if(self
.status
.disabled
=== true) { return; }
527 // Reset the hide timer
528 clearTimeout(self
.timers
.hide
);
532 // Define show event method
533 function showMethod(event
) {
534 if(self
.status
.disabled
=== true) { return; }
536 // If set, hide tooltip when inactive for delay period
537 if(self
.options
.hide
.when
.event
=== 'inactive') {
538 // Assign each reset event
539 $(inactiveEvents
).each(function () {
540 hideTarget
.bind(this + '.qtip-inactive', inactiveMethod
);
541 self
.elements
.content
.bind(this + '.qtip-inactive', inactiveMethod
);
544 // Start the inactive timer
549 clearTimeout(self
.timers
.show
);
550 clearTimeout(self
.timers
.hide
);
553 if(self
.options
.show
.delay
> 0) {
554 self
.timers
.show
= setTimeout(function () {
556 }, self
.options
.show
.delay
);
563 // Define hide event method
564 function hideMethod(event
) {
565 if(self
.status
.disabled
=== true) { return; }
567 // Prevent hiding if tooltip is fixed and event target is the tooltip
568 if(self
.options
.hide
.fixed
=== true && (/mouse(out|leave)/i).test(self
.options
.hide
.when
.event
) && $(event
.relatedTarget
).parents('div.qtip[id^="qtip"]').length
> 0) {
569 // Prevent default and popagation
570 event
.stopPropagation();
571 event
.preventDefault();
573 // Reset the hide timer
574 clearTimeout(self
.timers
.hide
);
578 // Clear timers and stop animation queue
579 clearTimeout(self
.timers
.show
);
580 clearTimeout(self
.timers
.hide
);
581 self
.elements
.tooltip
.stop(true, true);
583 // If tooltip has displayed, start hide timer
584 self
.timers
.hide
= setTimeout(function () {
586 }, self
.options
.hide
.delay
);
589 // If mouse is the target, update tooltip position on mousemove
590 if(self
.options
.position
.target
=== 'mouse' && self
.options
.position
.type
!== 'static') {
591 showTarget
.bind('mousemove.qtip', function (event
) {
592 // Set the new mouse positions if adjustment is enabled
598 // Update the tooltip position only if the tooltip is visible and adjustment is enabled
599 if(self
.status
.disabled
=== false && self
.options
.position
.adjust
.mouse
=== true && self
.options
.position
.type
!== 'static' && self
.elements
.tooltip
.css('display') !== 'none') {
600 self
.updatePosition(event
);
605 // Both events and targets are identical, apply events using a toggle
606 if((self
.options
.show
.when
.target
.add(self
.options
.hide
.when
.target
).length
=== 1 &&
607 self
.options
.show
.when
.event
=== self
.options
.hide
.when
.event
&& self
.options
.hide
.when
.event
!== 'inactive') ||
608 self
.options
.hide
.when
.event
=== 'unfocus') {
609 self
.cache
.toggle
= 0;
610 // Use a toggle to prevent hide/show conflicts
611 showTarget
.bind(self
.options
.show
.when
.event
+ '.qtip', function (event
) {
612 if(self
.cache
.toggle
=== 0) { showMethod(event
); }
613 else { hideMethod(event
); }
617 // Events are not identical, bind normally
619 showTarget
.bind(self
.options
.show
.when
.event
+ '.qtip', showMethod
);
621 // If the hide event is not 'inactive', bind the hide method
622 if(self
.options
.hide
.when
.event
!== 'inactive') { hideTarget
.bind(self
.options
.hide
.when
.event
+ '.qtip', hideMethod
); }
625 // Focus the tooltip on mouseover
626 if((/(fixed|absolute)/).test(self
.options
.position
.type
)) { self
.elements
.tooltip
.bind('mouseover.qtip', self
.focus
); }
629 // BGIFRAME JQUERY PLUGIN ADAPTION
630 // Special thanks to Brandon Aaron for this plugin
631 // http://plugins.jquery.com/project/bgiframe
632 function bgiframe() {
633 var self
, html
, dimensions
;
635 dimensions
= self
.getDimensions();
637 // Setup iframe HTML string
638 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" />';
640 // Append the new HTML and setup element reference
641 self
.elements
.bgiframe
= self
.elements
.wrapper
.prepend(html
).children('.qtip-bgiframe:first');
644 // Define primary construct function
645 function construct() {
646 var self
, content
, url
, data
, method
;
650 self
.beforeRender
.call(self
);
652 // Set rendered status to true
653 self
.status
.rendered
= 2;
655 // Create initial tooltip elements
656 self
.elements
.tooltip
= '<div qtip="' + self
.id
+ '" id="qtip-' + self
.id
+ '" role="tooltip" ' + 'aria-describedby="qtip-' + self
.id
+ '-content" 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 id="qtip-' + self
.id
+ '-content" class="qtip-content ' + self
.options
.style
.classes
.content
+ '"></div> ' + '</div></div></div>';
658 // Append to container element
659 self
.elements
.tooltip
= $(self
.elements
.tooltip
);
660 self
.elements
.tooltip
.appendTo(self
.options
.position
.container
);
662 // Setup tooltip qTip data
663 self
.elements
.tooltip
.data('qtip', {
668 // Setup element references
669 self
.elements
.wrapper
= self
.elements
.tooltip
.children('div:first');
670 self
.elements
.contentWrapper
= self
.elements
.wrapper
.children('div:first');
671 self
.elements
.content
= self
.elements
.contentWrapper
.children('div:first').css(jQueryStyle(self
.options
.style
));
673 // Apply IE hasLayout fix to wrapper and content elements
674 if($.browser
.msie
) { self
.elements
.wrapper
.add(self
.elements
.content
).css({ zoom
: 1 }); }
676 // Setup tooltip attributes
677 if(self
.options
.hide
.when
.event
=== 'unfocus') { self
.elements
.tooltip
.attr('unfocus', true); }
679 // If an explicit width is set, updateWidth prior to setting content to prevent dirty rendering
680 if(typeof self
.options
.style
.width
.value
=== 'number') { self
.updateWidth(); }
682 // Create borders and tips if supported by the browser
683 if($('<canvas />').get(0).getContext
|| $.browser
.msie
) {
685 if(self
.options
.style
.border
.radius
> 0) { createBorder
.call(self
); }
687 self
.elements
.contentWrapper
.css({
688 border
: self
.options
.style
.border
.width
+ 'px solid ' + self
.options
.style
.border
.color
692 // Create tip if enabled
693 if(self
.options
.style
.tip
.corner
!== false) { createTip
.call(self
); }
696 // Neither canvas or VML is supported, tips and borders cannot be drawn!
698 // Set defined border width
699 self
.elements
.contentWrapper
.css({
700 border
: self
.options
.style
.border
.width
+ 'px solid ' + self
.options
.style
.border
.color
703 // Reset border radius and tip
704 self
.options
.style
.border
.radius
= 0;
705 self
.options
.style
.tip
.corner
= false;
708 // Use the provided content string or DOM array
709 if((typeof self
.options
.content
.text
=== 'string' && self
.options
.content
.text
.length
> 0) || (self
.options
.content
.text
.jquery
&& self
.options
.content
.text
.length
> 0)) {
710 content
= self
.options
.content
.text
;
713 // Check for valid title and alt attributes
714 else { content
= ' '; }
716 // Set the tooltips content and create title if enabled
717 if(self
.options
.content
.title
.text
!== false) { createTitle
.call(self
); }
718 self
.updateContent(content
, false);
720 // Assign events and toggle tooltip with focus
721 assignEvents
.call(self
);
722 if(self
.options
.show
.ready
=== true) { self
.show(); }
724 // Retrieve ajax content if provided
725 if(self
.options
.content
.url
!== false) {
726 url
= self
.options
.content
.url
;
727 data
= self
.options
.content
.data
;
728 method
= self
.options
.content
.method
|| 'get';
729 self
.loadContent(url
, data
, method
);
732 // Call API method and log event
733 self
.status
.rendered
= true;
734 self
.onRender
.call(self
);
738 function QTip(target
, options
, id
) {
739 // Declare this reference
742 // Setup class attributes
744 self
.options
= options
;
752 target
: target
.addClass(self
.options
.style
.classes
.target
),
756 contentWrapper
: null,
766 overflow
: { left
: false, top
: false }
770 // Define exposed API methods
771 $.extend(self
, self
.options
.api
, {
772 show: function (event
) {
775 // Make sure tooltip is rendered and if not, return
776 if(!self
.status
.rendered
) { return false; }
778 // Only continue if element is visible
779 if(self
.elements
.tooltip
.css('display') !== 'none') { return self
; }
781 // Clear animation queue
782 self
.elements
.tooltip
.stop(true, false);
784 // Call API method and if return value is false, halt
785 returned
= self
.beforeShow
.call(self
, event
);
786 if(returned
=== false) { return self
; }
788 // Define afterShow callback method
789 function afterShow() {
790 // Set ARIA hidden status attribute
791 self
.elements
.tooltip
.attr('aria-hidden', true);
793 // Call API method and focus if it isn't static
794 if(self
.options
.position
.type
!== 'static') { self
.focus(); }
795 self
.onShow
.call(self
, event
);
797 // Prevent antialias from disappearing in IE7 by removing filter and opacity attribute
799 var ieStyle
= self
.elements
.tooltip
.get(0).style
;
800 ieStyle
.removeAttribute('filter');
801 ieStyle
.removeAttribute('opacity');
804 self
.elements
.tooltip
.css({ opacity
: '' });
808 // Maintain toggle functionality if enabled
809 self
.cache
.toggle
= 1;
811 // Update tooltip position if it isn't static
812 if(self
.options
.position
.type
!== 'static') {
813 self
.updatePosition(event
, (self
.options
.show
.effect
.length
> 0 && self
.rendered
!== 2));
816 // Hide other tooltips if tooltip is solo
817 if(typeof self
.options
.show
.solo
=== 'object') {
818 solo
= $(self
.options
.show
.solo
);
820 else if(self
.options
.show
.solo
=== true) {
821 solo
= $('div.qtip').not(self
.elements
.tooltip
);
824 solo
.each(function () {
825 if($(this).qtip('api').status
.rendered
=== true) { $(this).qtip('api').hide(); }
830 if(typeof self
.options
.show
.effect
.type
=== 'function') {
831 self
.options
.show
.effect
.type
.call(self
.elements
.tooltip
, self
.options
.show
.effect
.length
);
832 self
.elements
.tooltip
.queue(function () {
838 switch (self
.options
.show
.effect
.type
.toLowerCase()) {
840 self
.elements
.tooltip
.fadeIn(self
.options
.show
.effect
.length
, afterShow
);
844 self
.elements
.tooltip
.slideDown(self
.options
.show
.effect
.length
, function () {
846 if(self
.options
.position
.type
!== 'static') { self
.updatePosition(event
, true); }
851 self
.elements
.tooltip
.show(self
.options
.show
.effect
.length
, afterShow
);
855 self
.elements
.tooltip
.show(null, afterShow
);
859 // Add active class to tooltip
860 self
.elements
.tooltip
.addClass(self
.options
.style
.classes
.active
);
863 // Log event and return
867 hide: function (event
) {
870 // Make sure tooltip is rendered and if not, return
871 if(!self
.status
.rendered
) { return false; }
873 // Only continue if element is visible
874 else if(self
.elements
.tooltip
.css('display') === 'none') { return self
; }
876 // Stop show timer and animation queue
877 clearTimeout(self
.timers
.show
);
878 self
.elements
.tooltip
.stop(true, false);
880 // Call API method and if return value is false, halt
881 returned
= self
.beforeHide
.call(self
, event
);
882 if(returned
=== false) { return self
; }
884 // Define afterHide callback method
885 function afterHide() {
886 // Set ARIA hidden status attribute
887 self
.elements
.tooltip
.attr('aria-hidden', true);
889 // Remove opacity attribute
891 self
.elements
.tooltip
.get(0).style
.removeAttribute('opacity');
894 self
.elements
.tooltip
.css({ opacity
: '' });
898 self
.onHide
.call(self
, event
);
901 // Maintain toggle functionality if enabled
902 self
.cache
.toggle
= 0;
905 if(typeof self
.options
.hide
.effect
.type
=== 'function') {
906 self
.options
.hide
.effect
.type
.call(self
.elements
.tooltip
, self
.options
.hide
.effect
.length
);
907 self
.elements
.tooltip
.queue(function () {
913 switch (self
.options
.hide
.effect
.type
.toLowerCase()) {
915 self
.elements
.tooltip
.fadeOut(self
.options
.hide
.effect
.length
, afterHide
);
919 self
.elements
.tooltip
.slideUp(self
.options
.hide
.effect
.length
, afterHide
);
923 self
.elements
.tooltip
.hide(self
.options
.hide
.effect
.length
, afterHide
);
927 self
.elements
.tooltip
.hide(null, afterHide
);
931 // Remove active class to tooltip
932 self
.elements
.tooltip
.removeClass(self
.options
.style
.classes
.active
);
935 // Log event and return
939 toggle: function (event
, state
) {
940 var condition
= /boolean|number/.test(typeof state
) ? state
: !self
.elements
.tooltip
.is(':visible');
942 self
[condition
? 'show' : 'hide'](event
);
947 updatePosition: function (event
, animate
) {
948 if(!self
.status
.rendered
) {
952 var posOptions
= options
.position
,
953 target
= $(posOptions
.target
),
954 elemWidth
= self
.elements
.tooltip
.outerWidth(),
955 elemHeight
= self
.elements
.tooltip
.outerHeight(),
956 targetWidth
, targetHeight
, position
,
957 my
= posOptions
.corner
.tooltip
,
958 at
= posOptions
.corner
.target
,
960 coords
, i
, mapName
, imagePos
,
963 var leftEdge
= $(window
).scrollLeft(),
964 rightEdge
= $(window
).width() + $(window
).scrollLeft(),
965 myOffset
= my
.x
=== 'center' ? elemWidth
/2 : elemWidth
,
966 atOffset
= my
.x
=== 'center' ? targetWidth
/2 : targetWidth
,
967 borderAdjust
= (my
.x
=== 'center' ? 1 : 2) * self
.options
.style
.border
.radius
,
968 offset
= -2 * posOptions
.adjust
.x
,
969 pRight
= position
.left
+ elemWidth
,
972 // Cut off by right side of window
973 if(pRight
> rightEdge
) {
974 adj
= offset
- myOffset
- atOffset
+ borderAdjust
;
976 // Shifting to the left will make whole qTip visible, or will minimize how much is cut off
977 if(position
.left
+ adj
> leftEdge
|| leftEdge
- (position
.left
+ adj
) < pRight
- rightEdge
) {
978 return { adjust
: adj
, tip
: 'right' };
981 // Cut off by left side of window
982 if(position
.left
< leftEdge
) {
983 adj
= offset
+ myOffset
+ atOffset
- borderAdjust
;
985 // Shifting to the right will make whole qTip visible, or will minimize how much is cut off
986 if(pRight
+ adj
< rightEdge
|| pRight
+ adj
- rightEdge
< leftEdge
- position
.left
) {
987 return { adjust
: adj
, tip
: 'left' };
991 return { adjust
: 0, tip
: my
.x
};
994 var topEdge
= $(window
).scrollTop(),
995 bottomEdge
= $(window
).height() + $(window
).scrollTop(),
996 myOffset
= my
.y
=== 'center' ? elemHeight
/2 : elemHeight
,
997 atOffset
= my
.y
=== 'center' ? targetHeight
/2 : targetHeight
,
998 borderAdjust
= (my
.y
=== 'center' ? 1 : 2) * self
.options
.style
.border
.radius
,
999 offset
= -2 * posOptions
.adjust
.y
,
1000 pBottom
= position
.top
+ elemHeight
,
1003 // Cut off by bottom of window
1004 if(pBottom
> bottomEdge
) {
1005 adj
= offset
- myOffset
- atOffset
+ borderAdjust
;
1007 // Shifting to the top will make whole qTip visible, or will minimize how much is cut off
1008 if(position
.top
+ adj
> topEdge
|| topEdge
- (position
.top
+ adj
) < pBottom
- bottomEdge
) {
1009 return { adjust
: adj
, tip
: 'bottom' };
1012 // Cut off by top of window
1013 if(position
.top
< topEdge
) {
1014 adj
= offset
+ myOffset
+ atOffset
- borderAdjust
;
1016 // Shifting to the top will make whole qTip visible, or will minimize how much is cut off
1017 if(pBottom
+ adj
< bottomEdge
|| pBottom
+ adj
- bottomEdge
< topEdge
- position
.top
) {
1018 return { adjust
: adj
, tip
: 'top' };
1022 return { adjust
: 0, tip
: my
.y
};
1026 if(event
&& options
.position
.target
=== 'mouse') {
1027 // Force left top to allow flipping
1028 at
= { x
: 'left', y
: 'top' };
1029 targetWidth
= targetHeight
= 0;
1031 // Use cached mouse coordiantes if not available
1033 position
= self
.cache
.mouse
;
1043 if(target
[0] === document
) {
1044 targetWidth
= target
.width();
1045 targetHeight
= target
.height();
1046 position
= { top
: 0, left
: 0 };
1048 else if(target
[0] === window
) {
1049 targetWidth
= target
.width();
1050 targetHeight
= target
.height();
1052 top
: target
.scrollTop(),
1053 left
: target
.scrollLeft()
1056 else if(target
.is('area')) {
1057 // Retrieve coordinates from coords attribute and parse into integers
1058 coords
= self
.options
.position
.target
.attr('coords').split(',');
1059 for(i
= 0; i
< coords
.length
; i
++) { coords
[i
] = parseInt(coords
[i
], 10); }
1061 // Setup target position object
1062 mapName
= self
.options
.position
.target
.parent('map').attr('name');
1063 imagePos
= $('img[usemap="#' + mapName
+ '"]:first').offset();
1065 left
: Math
.floor(imagePos
.left
+ coords
[0]),
1066 top
: Math
.floor(imagePos
.top
+ coords
[1])
1069 // Determine width and height of the area
1070 switch (self
.options
.position
.target
.attr('shape').toLowerCase()) {
1072 targetWidth
= Math
.ceil(Math
.abs(coords
[2] - coords
[0]));
1073 targetHeight
= Math
.ceil(Math
.abs(coords
[3] - coords
[1]));
1077 targetWidth
= coords
[2] + 1;
1078 targetHeight
= coords
[2] + 1;
1082 targetWidth
= coords
[0];
1083 targetHeight
= coords
[1];
1085 for (i
= 0; i
< coords
.length
; i
++) {
1087 if(coords
[i
] > targetWidth
) { targetWidth
= coords
[i
]; }
1088 if(coords
[i
] < coords
[0]) { position
.left
= Math
.floor(imagePos
.left
+ coords
[i
]); }
1091 if(coords
[i
] > targetHeight
) { targetHeight
= coords
[i
]; }
1092 if(coords
[i
] < coords
[1]) { position
.top
= Math
.floor(imagePos
.top
+ coords
[i
]); }
1096 targetWidth
= targetWidth
- (position
.left
- imagePos
.left
);
1097 targetHeight
= targetHeight
- (position
.top
- imagePos
.top
);
1101 // Adjust position by 2 pixels (Positioning bug?)
1106 targetWidth
= target
.outerWidth();
1107 targetHeight
= target
.outerHeight();
1109 if(!self
.elements
.tooltip
.is(':visible')) {
1110 self
.elements
.tooltip
.css({ left
: '-10000000em' }).show();
1113 // Account for tooltips offset parent if necessary
1114 if(self
.elements
.tooltip
.offsetParent()[0] === document
.body
) {
1115 position
= target
.offset();
1118 // Account for offset parent and it's scroll positions
1119 position
= target
.position();
1120 position
.top
+= target
.offsetParent().scrollTop();
1121 position
.left
+= target
.offsetParent().scrollLeft();
1125 // Adjust position relative to target
1126 position
.left
+= at
.x
=== 'right' ? targetWidth
: at
.x
=== 'center' ? targetWidth
/ 2 : 0;
1127 position
.top
+= at
.y
=== 'bottom' ? targetHeight
: at
.y
=== 'center' ? targetHeight
/ 2 : 0;
1130 // Adjust position relative to tooltip
1131 position
.left
+= posOptions
.adjust
.x
+ (my
.x
=== 'right' ? -elemWidth
: my
.x
=== 'center' ? -elemWidth
/ 2 : 0);
1132 position
.top
+= posOptions
.adjust
.y
+ (my
.y
=== 'bottom' ? -elemHeight
: my
.y
=== 'center' ? -elemHeight
/ 2 : 0);
1134 // Adjust for border radius
1135 if(self
.options
.style
.border
.radius
> 0) {
1136 if(my
.x
=== 'left') { position
.left
-= self
.options
.style
.border
.radius
; }
1137 else if(my
.x
=== 'right') { position
.left
+= self
.options
.style
.border
.radius
; }
1139 if(my
.y
=== 'top') { position
.top
-= self
.options
.style
.border
.radius
; }
1140 else if(my
.y
=== 'bottom') { position
.top
+= self
.options
.style
.border
.radius
; }
1143 // Adjust tooltip position if screen adjustment is enabled
1144 if(posOptions
.adjust
.screen
) {
1146 var adjusted
= { x
: 0, y
: 0 },
1147 adapted
= { x
: adapt
.left(), y
: adapt
.top() },
1148 tip
= new Corner(options
.style
.tip
.corner
);
1150 if(self
.elements
.tip
&& tip
) {
1151 // Adjust position according to adjustment that took place
1152 if(adapted
.y
.adjust
!== 0) {
1153 position
.top
+= adapted
.y
.adjust
;
1154 tip
.y
= adjusted
.y
= adapted
.y
.tip
;
1156 if(adapted
.x
.adjust
!== 0) {
1157 position
.left
+= adapted
.x
.adjust
;
1158 tip
.x
= adjusted
.x
= adapted
.x
.tip
;
1161 // Update overflow cache
1162 self
.cache
.overflow
= {
1163 left
: adjusted
.x
=== false,
1164 top
: adjusted
.y
=== false
1167 // Update and redraw the tip
1168 if(self
.elements
.tip
.attr('rel') !== tip
.string()) { createTip
.call(self
, tip
); }
1173 // Initiate bgiframe plugin in IE6 if tooltip overlaps a select box or object element
1174 if(!self
.elements
.bgiframe
&& $.browser
.msie
&& parseInt($.browser
.version
.charAt(0), 10) === 6) {
1175 bgiframe
.call(self
);
1178 // Call API method and if return value is false, halt
1179 returned
= self
.beforePositionUpdate
.call(self
, event
);
1180 if(returned
=== false) { return self
; }
1182 // Check if animation is enabled
1183 if(options
.position
.target
!== 'mouse' && animate
=== true) {
1184 // Set animated status
1185 self
.status
.animated
= true;
1187 // Animate and reset animated status on animation end
1188 self
.elements
.tooltip
.stop().animate(position
, 200, 'swing', function () {
1189 self
.status
.animated
= false;
1193 // Set new position via CSS
1194 else { self
.elements
.tooltip
.css(position
); }
1196 // Call API method and log event if its not a mouse move
1197 self
.onPositionUpdate
.call(self
, event
);
1202 updateWidth: function (newWidth
) {
1203 // Make sure tooltip is rendered and width is a number
1204 if(!self
.status
.rendered
|| (newWidth
&& typeof newWidth
!== 'number')) { return false; }
1206 // Setup elements which must be hidden during width update
1207 var hidden
= self
.elements
.contentWrapper
.siblings().add(self
.elements
.tip
).add(self
.elements
.button
),
1208 zoom
= self
.elements
.wrapper
.add(self
.elements
.contentWrapper
.children()),
1209 tooltip
= self
.elements
.tooltip
,
1210 max
= self
.options
.style
.width
.max
,
1211 min
= self
.options
.style
.width
.min
;
1213 // Calculate the new width if one is not supplied
1215 // Explicit width is set
1216 if(typeof self
.options
.style
.width
.value
=== 'number') {
1217 newWidth
= self
.options
.style
.width
.value
;
1220 // No width is set, proceed with auto detection
1222 // Set width to auto initally to determine new width and hide other elements
1223 self
.elements
.tooltip
.css({ width
: 'auto' });
1226 // Set the new calculated width and if width has not numerical, grab new pixel width
1227 tooltip
.width(newWidth
);
1229 // Set position and zoom to defaults to prevent IE hasLayout bug
1230 if($.browser
.msie
) {
1231 zoom
.css({ zoom
: '' });
1234 // Set the new width
1235 newWidth
= self
.getDimensions().width
;
1237 // Make sure its within the maximum and minimum width boundries
1238 if(!self
.options
.style
.width
.value
) {
1239 newWidth
= Math
.min(Math
.max(newWidth
, min
), max
);
1244 // Adjust newWidth by 1px if width is odd (IE6 rounding bug fix)
1245 if(newWidth
% 2) { newWidth
+= 1; }
1247 // Set the new calculated width and unhide other elements
1248 self
.elements
.tooltip
.width(newWidth
);
1251 // Set the border width, if enabled
1252 if(self
.options
.style
.border
.radius
) {
1253 self
.elements
.tooltip
.find('.qtip-betweenCorners').each(function (i
) {
1254 $(this).width(newWidth
- (self
.options
.style
.border
.radius
* 2));
1258 // IE only adjustments
1259 if($.browser
.msie
) {
1260 // Reset position and zoom to give the wrapper layout (IE hasLayout bug)
1261 zoom
.css({ zoom
: 1 });
1263 // Set the new width
1264 self
.elements
.wrapper
.width(newWidth
);
1266 // Adjust BGIframe height and width if enabled
1267 if(self
.elements
.bgiframe
) { self
.elements
.bgiframe
.width(newWidth
).height(self
.getDimensions
.height
); }
1270 // Log event and return
1274 updateStyle: function (name
) {
1275 var tip
, borders
, context
, corner
, coordinates
;
1277 // Make sure tooltip is rendered and style is defined
1278 if(!self
.status
.rendered
|| typeof name
!== 'string' || !$.fn
.qtip
.styles
[name
]) { return false; }
1280 // Set the new style object
1281 self
.options
.style
= buildStyle
.call(self
, $.fn
.qtip
.styles
[name
], self
.options
.user
.style
);
1283 // Update initial styles of content and title elements
1284 self
.elements
.content
.css(jQueryStyle(self
.options
.style
));
1285 if(self
.options
.content
.title
.text
!== false) { self
.elements
.title
.css(jQueryStyle(self
.options
.style
.title
, true)); }
1287 // Update CSS border colour
1288 self
.elements
.contentWrapper
.css({
1289 borderColor
: self
.options
.style
.border
.color
1292 // Update tip color if enabled
1293 if(self
.options
.style
.tip
.corner
!== false) {
1294 if($('<canvas />').get(0).getContext
) {
1295 // Retrieve canvas context and clear
1296 tip
= self
.elements
.tooltip
.find('.qtip-tip canvas:first');
1297 context
= tip
.get(0).getContext('2d');
1298 context
.clearRect(0, 0, 300, 300);
1301 corner
= tip
.parent('div[rel]:first').attr('rel');
1302 coordinates
= calculateTip(corner
, self
.options
.style
.tip
.size
.width
, self
.options
.style
.tip
.size
.height
);
1303 drawTip
.call(self
, tip
, coordinates
, self
.options
.style
.tip
.color
|| self
.options
.style
.border
.color
);
1305 else if($.browser
.msie
) {
1306 // Set new fillcolor attribute
1307 tip
= self
.elements
.tooltip
.find('.qtip-tip [nodeName="shape"]');
1308 tip
.attr('fillcolor', self
.options
.style
.tip
.color
|| self
.options
.style
.border
.color
);
1312 // Update border colors if enabled
1313 if(self
.options
.style
.border
.radius
> 0) {
1314 self
.elements
.tooltip
.find('.qtip-betweenCorners').css({
1315 backgroundColor
: self
.options
.style
.border
.color
1318 if($('<canvas />').get(0).getContext
) {
1319 borders
= calculateBorders(self
.options
.style
.border
.radius
);
1320 self
.elements
.tooltip
.find('.qtip-wrapper canvas').each(function () {
1321 // Retrieve canvas context and clear
1322 context
= $(this).get(0).getContext('2d');
1323 context
.clearRect(0, 0, 300, 300);
1326 corner
= $(this).parent('div[rel]:first').attr('rel');
1327 drawBorder
.call(self
, $(this), borders
[corner
], self
.options
.style
.border
.radius
, self
.options
.style
.border
.color
);
1330 else if($.browser
.msie
) {
1331 // Set new fillcolor attribute on each border corner
1332 self
.elements
.tooltip
.find('.qtip-wrapper [nodeName="arc"]').each(function () {
1333 $(this).attr('fillcolor', self
.options
.style
.border
.color
);
1338 // Log event and return
1342 updateContent: function (content
, reposition
) {
1343 var parsedContent
, images
, loadedImages
;
1345 function afterLoad() {
1346 // Update the tooltip width
1349 // If repositioning is enabled, update positions
1350 if(reposition
!== false) {
1351 // Update position if tooltip isn't static
1352 if(self
.options
.position
.type
!== 'static') { self
.updatePosition(self
.elements
.tooltip
.is(':visible'), true); }
1354 // Reposition the tip if enabled
1355 if(self
.options
.style
.tip
.corner
!== false) { positionTip
.call(self
); }
1359 // Make sure content is defined if not, return
1360 if(!content
) { return false; }
1362 // Call API method and set new content if a string is returned
1363 parsedContent
= self
.beforeContentUpdate
.call(self
, content
);
1364 if(typeof parsedContent
=== 'string') { content
= parsedContent
; }
1365 else if(parsedContent
=== false) { return; }
1367 // Continue normally if rendered, but if not set options.content.text instead
1368 if(self
.status
.rendered
) {
1369 // Set position and zoom to defaults to prevent IE hasLayout bug
1370 if($.browser
.msie
) {
1371 self
.elements
.contentWrapper
.children().css({
1376 // Append new content if its a DOM array and show it if hidden
1377 if(content
.jquery
&& content
.length
> 0) { content
.clone(true).appendTo(self
.elements
.content
).show(); }
1379 // Content is a regular string, insert the new content
1380 else { self
.elements
.content
.html(content
); }
1382 // Check if images need to be loaded before position is updated to prevent mis-positioning
1383 images
= self
.elements
.content
.find('img[complete=false]');
1384 if(images
.length
> 0) {
1386 images
.each(function (i
) {
1387 $('<img src="' + $(this).attr('src') + '" />').load(function () {
1388 if(++loadedImages
=== images
.length
) { afterLoad(); }
1392 else { afterLoad(); }
1395 self
.options
.content
.text
= content
;
1398 // Call API method and log event
1399 self
.onContentUpdate
.call(self
);
1403 loadContent: function (url
, data
, method
) {
1406 function setupContent(content
) {
1407 // Call API method and log event
1408 self
.onContentLoad
.call(self
);
1410 // Update the content
1411 self
.updateContent(content
);
1414 // Make sure tooltip is rendered and if not, return
1415 if(!self
.status
.rendered
) { return false; }
1417 // Call API method and if return value is false, halt
1418 returned
= self
.beforeContentLoad
.call(self
);
1419 if(returned
=== false) { return self
; }
1421 // Load content using specified request type
1422 if(method
=== 'post') { $.post(url
, data
, setupContent
); }
1423 else { $.get(url
, data
, setupContent
); }
1428 updateTitle: function (content
) {
1431 // Make sure tooltip is rendered and content is defined
1432 if(!self
.status
.rendered
|| !content
) { return false; }
1434 // Call API method and if return value is false, halt
1435 returned
= self
.beforeTitleUpdate
.call(self
);
1436 if(returned
=== false) { return self
; }
1438 // Set the new content and reappend the button if enabled
1439 if(self
.elements
.button
) { self
.elements
.button
= self
.elements
.button
.clone(true); }
1440 self
.elements
.title
.html(content
);
1441 if(self
.elements
.button
) { self
.elements
.title
.prepend(self
.elements
.button
); }
1443 // Call API method and log event
1444 self
.onTitleUpdate
.call(self
);
1448 focus: function (event
) {
1449 var curIndex
, newIndex
, elemIndex
, returned
;
1451 // Make sure tooltip is rendered and if not, return
1452 if(!self
.status
.rendered
|| self
.options
.position
.type
=== 'static') { return false; }
1454 // Set z-index variables
1455 curIndex
= parseInt(self
.elements
.tooltip
.css('z-index'), 10);
1456 newIndex
= 15000 + $('div.qtip[id^="qtip"]').length
- 1;
1458 // Only update the z-index if it has changed and tooltip is not already focused
1459 if(!self
.status
.focused
&& curIndex
!== newIndex
) {
1460 // Call API method and if return value is false, halt
1461 returned
= self
.beforeFocus
.call(self
, event
);
1462 if(returned
=== false) { return self
; }
1464 // Loop through all other tooltips
1465 $('div.qtip[id^="qtip"]').not(self
.elements
.tooltip
).each(function () {
1466 if($(this).qtip('api').status
.rendered
=== true) {
1467 elemIndex
= parseInt($(this).css('z-index'), 10);
1469 // Reduce all other tooltip z-index by 1
1470 if(typeof elemIndex
=== 'number' && elemIndex
> -1) {
1471 $(this).css({ zIndex
: parseInt($(this).css('z-index'), 10) - 1 });
1474 // Set focused status to false
1475 $(this).qtip('api').status
.focused
= false;
1479 // Set the new z-index and set focus status to true
1480 self
.elements
.tooltip
.css({ zIndex
: newIndex
});
1481 self
.status
.focused
= true;
1483 // Call API method and log event
1484 self
.onFocus
.call(self
, event
);
1490 disable: function (state
) {
1491 self
.status
.disabled
= state
? true : false;
1495 destroy: function () {
1496 var i
, returned
, interfaces
,
1497 oldattr
= self
.elements
.target
.data('old'+self
.cache
.attr
[0]);
1499 // Call API method and if return value is false, halt
1500 returned
= self
.beforeDestroy
.call(self
);
1501 if(returned
=== false) { return self
; }
1503 // Check if tooltip is rendered
1504 if(self
.status
.rendered
) {
1505 // Remove event handlers and remove element
1506 self
.options
.show
.when
.target
.unbind('mousemove.qtip', self
.updatePosition
);
1507 self
.options
.show
.when
.target
.unbind('mouseout.qtip', self
.hide
);
1508 self
.options
.show
.when
.target
.unbind(self
.options
.show
.when
.event
+ '.qtip');
1509 self
.options
.hide
.when
.target
.unbind(self
.options
.hide
.when
.event
+ '.qtip');
1510 self
.elements
.tooltip
.unbind(self
.options
.hide
.when
.event
+ '.qtip');
1511 self
.elements
.tooltip
.unbind('mouseover.qtip', self
.focus
);
1512 self
.elements
.tooltip
.remove();
1515 // Tooltip isn't yet rendered, remove render event
1516 else { self
.options
.show
.when
.target
.unbind(self
.options
.show
.when
.event
+ '.qtip-' + self
.id
+ '-create'); }
1518 // Check to make sure qTip data is present on target element
1519 if(typeof self
.elements
.target
.data('qtip') === 'object') {
1520 // Remove API references from interfaces object
1521 interfaces
= self
.elements
.target
.data('qtip').interfaces
;
1522 if(typeof interfaces
=== 'object' && interfaces
.length
> 0) {
1523 // Remove API from interfaces array
1524 for(i
= 0; i
< interfaces
.length
- 1; i
++) {
1525 if(interfaces
[i
].id
=== self
.id
) { interfaces
.splice(i
, 1); }
1529 $.fn
.qtip
.interfaces
.splice(self
.id
, 1);
1531 // Set qTip current id to previous tooltips API if available
1532 if(typeof interfaces
=== 'object' && interfaces
.length
> 0) { self
.elements
.target
.data('qtip').current
= interfaces
.length
- 1; }
1533 else { self
.elements
.target
.removeData('qtip'); }
1535 // Reset old title attribute if removed
1537 self
.elements
.target
.attr(self
.cache
.attr
[0], oldattr
);
1540 // Call API method and log destroy
1541 self
.onDestroy
.call(self
);
1543 return self
.elements
.target
;
1546 getPosition: function () {
1549 // Make sure tooltip is rendered and if not, return
1550 if(!self
.status
.rendered
) { return false; }
1552 show
= (self
.elements
.tooltip
.css('display') !== 'none') ? false : true;
1554 // Show and hide tooltip to make sure coordinates are returned
1555 if(show
) { self
.elements
.tooltip
.css({ visiblity
: 'hidden' }).show(); }
1556 offset
= self
.elements
.tooltip
.offset();
1557 if(show
) { self
.elements
.tooltip
.css({ visiblity
: 'visible' }).hide(); }
1562 getDimensions: function () {
1563 var show
, dimensions
;
1565 // Make sure tooltip is rendered and if not, return
1566 if(!self
.status
.rendered
) { return false; }
1568 show
= (!self
.elements
.tooltip
.is(':visible')) ? true : false;
1570 // Show and hide tooltip to make sure dimensions are returned
1571 if(show
) { self
.elements
.tooltip
.css({ visiblity
: 'hidden' }).show(); }
1573 height
: self
.elements
.tooltip
.outerHeight(),
1574 width
: self
.elements
.tooltip
.outerWidth()
1576 if(show
) { self
.elements
.tooltip
.css({ visiblity
: 'visible' }).hide(); }
1584 $.fn
.qtip = function (options
, blanket
) {
1585 var i
, id
, interfaces
, opts
, obj
, command
, config
, api
;
1587 // Return API / Interfaces if requested
1588 if(typeof options
=== 'string') {
1589 if($(this).data('qtip')) {
1590 // Return requested object
1591 if(options
=== 'api') {
1592 return $(this).data('qtip').interfaces
[$(this).data('qtip').current
];
1594 else if(options
=== 'interfaces') {
1595 return $(this).data('qtip').interfaces
;
1603 // Validate provided options
1605 // Set null options object if no options are provided
1606 if(!options
) { options
= {}; }
1608 // Sanitize option data
1609 if(typeof options
.content
!== 'object' || (options
.content
.jquery
&& options
.content
.length
> 0)) {
1610 options
.content
= { text
: options
.content
};
1612 if(typeof options
.content
.title
!== 'object') {
1613 options
.content
.title
= { text
: options
.content
.title
};
1615 if(typeof options
.position
!== 'object') {
1616 options
.position
= { corner
: options
.position
};
1618 if(typeof options
.position
.corner
!== 'object') {
1619 options
.position
.corner
= {
1620 target
: options
.position
.corner
,
1621 tooltip
: options
.position
.corner
1624 if(typeof options
.show
!== 'object') {
1625 options
.show
= { when
: options
.show
};
1627 if(typeof options
.show
.when
!== 'object') {
1628 options
.show
.when
= { event
: options
.show
.when
};
1630 if(typeof options
.show
.effect
!== 'object') {
1631 options
.show
.effect
= { type
: options
.show
.effect
};
1633 if(typeof options
.hide
!== 'object') {
1634 options
.hide
= { when
: options
.hide
};
1636 if(typeof options
.hide
.when
!== 'object') {
1637 options
.hide
.when
= { event
: options
.hide
.when
};
1639 if(typeof options
.hide
.effect
!== 'object') {
1640 options
.hide
.effect
= { type
: options
.hide
.effect
};
1642 if(typeof options
.style
!== 'object') {
1643 options
.style
= { name
: options
.style
};
1646 // Sanitize option styles
1647 options
.style
= sanitizeStyle(options
.style
);
1649 // Build main options object
1650 opts
= $.extend(true, {}, $.fn
.qtip
.defaults
, options
);
1652 // Inherit all style properties into one syle object and include original options
1653 opts
.style
= buildStyle
.call({
1656 opts
.user
= $.extend(true, {}, options
);
1659 // Iterate each matched element
1660 return $(this).each(function () // Return original elements as per jQuery guidelines
1662 var self
= $(this), content
= false;
1664 // Check for API commands
1665 if(typeof options
=== 'string') {
1666 command
= options
.toLowerCase();
1667 interfaces
= $(this).qtip('interfaces');
1669 // Make sure API data exists
1670 if(typeof interfaces
=== 'object') {
1671 // Check if API call is a BLANKET DESTROY command
1672 if(blanket
=== true && command
=== 'destroy') {
1673 for(i
= interfaces
.length
- 1; i
> -1; i
--) {
1674 if('object' === typeof interfaces
[i
]) {
1675 interfaces
[i
].destroy();
1680 // API call is not a BLANKET DESTROY command
1682 // Check if supplied command effects this tooltip only (NOT BLANKET)
1683 if(blanket
!== true) { interfaces
= [$(this).qtip('api')]; }
1685 // Execute command on chosen qTips
1686 for (i
= 0; i
< interfaces
.length
; i
++) {
1687 // Destroy command doesn't require tooltip to be rendered
1688 if(command
=== 'destroy') { interfaces
[i
].destroy(); }
1690 // Only call API if tooltip is rendered and it wasn't a destroy call
1691 else if(interfaces
[i
].status
.rendered
=== true) {
1692 if(command
=== 'show') { interfaces
[i
].show(); }
1693 else if(command
=== 'hide') { interfaces
[i
].hide(); }
1694 else if(command
=== 'focus') { interfaces
[i
].focus(); }
1695 else if(command
=== 'disable') { interfaces
[i
].disable(true); }
1696 else if(command
=== 'enable') { interfaces
[i
].disable(false); }
1697 else if(command
=== 'update') { interfaces
[i
].updatePosition(); }
1704 // No API commands, continue with qTip creation
1706 // Create unique configuration object
1707 config
= $.extend(true, {}, opts
);
1708 config
.hide
.effect
.length
= opts
.hide
.effect
.length
;
1709 config
.show
.effect
.length
= opts
.show
.effect
.length
;
1711 // Sanitize target options
1712 if(config
.position
.container
=== false) { config
.position
.container
= $(document
.body
); }
1713 if(config
.position
.target
=== false) { config
.position
.target
= $(this); }
1714 if(config
.show
.when
.target
=== false) { config
.show
.when
.target
= $(this); }
1715 if(config
.hide
.when
.target
=== false) { config
.hide
.when
.target
= $(this); }
1717 // Parse corner options
1718 config
.position
.corner
.tooltip
= new Corner(config
.position
.corner
.tooltip
);
1719 config
.position
.corner
.target
= new Corner(config
.position
.corner
.target
);
1721 // If no content is provided, check title and alt attributes for fallback
1722 if(!config
.content
.text
.length
) {
1723 $(['title', 'alt']).each(function(i
, attr
) {
1724 var val
= self
.attr(attr
);
1725 if(val
&& val
.length
) {
1726 content
= [attr
, val
];
1727 self
.data('old'+attr
, val
).removeAttr(attr
);
1728 config
.content
.text
= val
.replace(/\n/gi, '<br />');
1734 // Determine tooltip ID (Reuse array slots if possible)
1735 id
= $.fn
.qtip
.interfaces
.length
;
1736 for (i
= 0; i
< id
; i
++) {
1737 if(typeof $.fn
.qtip
.interfaces
[i
] === 'undefined') {
1743 // Instantiate the tooltip
1744 obj
= new QTip($(this), config
, id
);
1746 // Add API references and cache content if present
1747 $.fn
.qtip
.interfaces
[id
] = obj
;
1748 obj
.cache
.attr
= content
;
1750 // Check if element already has qTip data assigned
1751 if(typeof $(this).data('qtip') === 'object' && $(this).data('qtip')) {
1752 // Set new current interface id
1753 if(typeof $(this).attr('qtip') === 'undefined') { $(this).data('qtip').current
= $(this).data('qtip').interfaces
.length
; }
1755 // Push new API interface onto interfaces array
1756 $(this).data('qtip').interfaces
.push(obj
);
1759 // No qTip data is present, create now
1761 $(this).data('qtip', {
1767 // If prerendering is disabled, create tooltip on showEvent
1768 if(config
.content
.prerender
=== false && config
.show
.when
.event
!== false && config
.show
.ready
!== true) {
1769 config
.show
.when
.target
.bind(config
.show
.when
.event
+ '.qtip-' + id
+ '-create', { qtip
: id
}, function (event
) {
1770 // Retrieve API interface via passed qTip Id
1771 api
= $.fn
.qtip
.interfaces
[event
.data
.qtip
];
1773 // Unbind show event and cache mouse coords
1774 api
.options
.show
.when
.target
.unbind(api
.options
.show
.when
.event
+ '.qtip-' + event
.data
.qtip
+ '-create');
1780 // Render tooltip and start the event sequence
1781 construct
.call(api
);
1782 api
.options
.show
.when
.target
.trigger(api
.options
.show
.when
.event
);
1786 // Prerendering is enabled, create tooltip now
1788 // Set mouse position cache to top left of the element
1790 left
: config
.show
.when
.target
.offset().left
,
1791 top
: config
.show
.when
.target
.offset().top
1794 // Construct the tooltip
1795 construct
.call(obj
);
1801 // Define qTip API interfaces array
1802 $.fn
.qtip
.interfaces
= [];
1804 /* Add intermediary method to the 'attr' class to allow other plugins to successfully
1805 retrieve the title of an element with a qTip applied */
1806 $.fn
.qtip
.fn
= { attr
: $.fn
.attr
};
1807 $.fn
.attr = function(attr
) {
1808 var api
= $(this).qtip('api');
1810 return (arguments
.length
=== 1 && (/title|alt/i).test(attr
) && api
.status
&& api
.status
.rendered
=== true)
1811 ? $(this).data('old' + api
.cache
.attr
[0])
1812 : $.fn
.qtip
.fn
.attr
.apply(this, arguments
);
1815 // Define configuration defaults
1816 $.fn
.qtip
.defaults
= {
1832 target
: 'bottomRight',
1874 beforeRender: function () {},
1875 onRender: function () {},
1876 beforePositionUpdate: function () {},
1877 onPositionUpdate: function () {},
1878 beforeShow: function () {},
1879 onShow: function () {},
1880 beforeHide: function () {},
1881 onHide: function () {},
1882 beforeContentUpdate: function () {},
1883 onContentUpdate: function () {},
1884 beforeContentLoad: function () {},
1885 onContentLoad: function () {},
1886 beforeTitleUpdate: function () {},
1887 onTitleUpdate: function () {},
1888 beforeDestroy: function () {},
1889 onDestroy: function () {},
1890 beforeFocus: function () {},
1891 onFocus: function () {}
1895 $.fn
.qtip
.styles
= {
1897 background
: 'white',
1921 background
: '#e1e1e1',
1931 title
: 'qtip-title',
1932 button
: 'qtip-button',
1933 content
: 'qtip-content',
1934 active
: 'qtip-active'
1944 background
: '#F0DE7D',
1947 background
: '#FBF7AA',
1951 tooltip
: 'qtip-cream'
1961 background
: '#f1f1f1',
1964 background
: 'white',
1968 tooltip
: 'qtip-light'
1978 background
: '#404040',
1981 background
: '#505050',
1985 tooltip
: 'qtip-dark'
1995 background
: '#f28279',
1998 background
: '#F79992',
2012 background
: '#b9db8c',
2015 background
: '#CDE6AC',
2019 tooltip
: 'qtip-green'
2029 background
: '#D0E9F5',
2032 background
: '#E5F6FE',
2036 tooltip
: 'qtip-blue'