[PLUGINS] +set de base
[lhc/web/www.git] / www / plugins / spip-bonux-3 / javascript / jquery.qtip-1.0.0-rc3.js
1 /*!
2 * jquery.qtip. The jQuery tooltip plugin
3 *
4 * Copyright (c) 2009 Craig Thompson
5 * http://craigsworks.com
6 *
7 * Licensed under MIT
8 * http://www.opensource.org/licenses/mit-license.php
9 *
10 * Launch : February 2009
11 * Version : 1.0.0-rc3
12 * Released: Tuesday 12th May, 2009 - 00:00
13 * Debug: jquery.qtip.debug.js
14 */
15
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 */
18
19 /*global window: false, jQuery: false */
20 (function ($) {
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
24 var i;
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];
29
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);
35 }
36 }
37 });
38
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');
44
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); }
47 }
48 });
49 });
50
51 // Corner object parser
52 function Corner(corner) {
53 if(!corner){ return false; }
54
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; };
60 }
61
62 // Tip coordinates calculator
63 function calculateTip(corner, width, height) {
64 // Define tip coordinates in terms of height and width values
65 var tips = {
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]]
74 };
75 tips.lefttop = tips.bottomright;
76 tips.righttop = tips.bottomleft;
77 tips.leftbottom = tips.topright;
78 tips.rightbottom = tips.topleft;
79
80 return tips[corner];
81 }
82
83 // Border coordinates calculator
84 function calculateBorders(radius) {
85 var borders;
86
87 // Use canvas element if supported
88 if($('<canvas />').get(0).getContext) {
89 borders = {
90 topLeft: [radius, radius],
91 topRight: [0, radius],
92 bottomLeft: [radius, 0],
93 bottomRight: [0, 0]
94 };
95 }
96
97 // Canvas not supported - Use VML (IE)
98 else if($.browser.msie) {
99 borders = {
100 topLeft: [-90, 90, 0],
101 topRight: [-90, 90, -radius],
102 bottomLeft: [90, 270, 0],
103 bottomRight: [90, 270, -radius]
104 };
105 }
106
107 return borders;
108 }
109
110
111 // Build a jQuery style object from supplied style object
112 function jQueryStyle(style, sub) {
113 var styleObj, i;
114
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]; }
119 }
120
121 return styleObj;
122 }
123
124 // Sanitize styles
125 function sanitizeStyle(style) {
126 if(typeof style.tip !== 'object') {
127 style.tip = { corner: style.tip };
128 }
129 if(typeof style.tip.size !== 'object') {
130 style.tip.size = {
131 width: style.tip.size,
132 height: style.tip.size
133 };
134 }
135 if(typeof style.border !== 'object') {
136 style.border = {
137 width: style.border
138 };
139 }
140 if(typeof style.width !== 'object') {
141 style.width = {
142 value: style.width
143 };
144 }
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); }
147
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;
152 }
153 if(typeof style.tip.size.y === 'number') {
154 style.tip.size.height = style.tip.size.y;
155 delete style.tip.size.y;
156 }
157
158 return style;
159 }
160
161 // Build styles recursively with inheritance
162 function buildStyle() {
163 var self, i, styleArray, styleExtend, finalStyle, ieAdjust;
164 self = this;
165
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)];
170
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]));
175 }
176
177 // Make sure resulting tooltip className represents final style
178 styleExtend.unshift(true, {
179 classes: {
180 tooltip: 'qtip-' + (arguments[0].name || 'defaults')
181 }
182 }, $.fn.qtip.styles.defaults);
183
184 // Extend into a single style object
185 finalStyle = $.extend.apply($, styleExtend);
186
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;
191
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; }
195
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;
200 }
201 else {
202 finalStyle.tip.corner = self.options.position.corner.tooltip;
203 }
204 }
205
206 return finalStyle;
207 }
208
209 // Border canvas draw method
210 function drawBorder(canvas, coordinates, radius, color) {
211 // Create corner
212 var context = canvas.get(0).getContext('2d');
213 context.fillStyle = color;
214 context.beginPath();
215 context.arc(coordinates[0], coordinates[1], radius, 0, Math.PI * 2, false);
216 context.fill();
217 }
218
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;
222 self = this;
223
224 // Destroy previous border elements, if present
225 self.elements.wrapper.find('.qtip-borderBottom, .qtip-borderTop').remove();
226
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;
231
232 // Calculate border coordinates
233 coordinates = calculateBorders(radius);
234
235 // Create containers for the border shapes
236 containers = {};
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">';
240
241 // Canvas is supported
242 if($('<canvas />').get(0).getContext) { containers[i] += '<canvas height="' + radius + '" width="' + radius + '" style="vertical-align: top"></canvas>'; }
243
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>';
248
249 }
250
251 containers[i] += '</div>';
252 }
253
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;">';
257
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);
261
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);
265
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);
271 });
272 }
273
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>'); }
276
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'
283 });
284 }
285
286 // Canvas tip drawing method
287 function drawTip(canvas, coordinates, color) {
288 // Setup properties
289 var context = canvas.get(0).getContext('2d');
290 context.fillStyle = color;
291
292 // Create tip
293 context.beginPath();
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]);
297 context.fill();
298 }
299
300 function positionTip(corner) {
301 var self, ieAdjust, positionAdjust, paddingCorner, paddingSize, newMargin;
302 self = this;
303
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')); }
307
308 // Setup adjustment variables
309 ieAdjust = positionAdjust = ($.browser.msie) ? 1 : 0;
310
311 // Set initial position
312 self.elements.tip.css(corner[corner.precedance], 0);
313
314 // Set position of tip to correct side
315 if(corner.precedance === 'y') {
316 // Adjustments for IE6 - 0.5px border gap bug
317 if($.browser.msie) {
318 if(parseInt($.browser.version.charAt(0), 10) === 6) { positionAdjust = corner.y === 'top' ? -3 : 1; }
319 else { positionAdjust = corner.y === 'top' ? 1 : 2; }
320 }
321
322 if(corner.x === 'center') {
323 self.elements.tip.css({
324 left: '50%',
325 marginLeft: -(self.options.style.tip.size.width / 2)
326 });
327 }
328 else if(corner.x === 'left') {
329 self.elements.tip.css({
330 left: self.options.style.border.radius - ieAdjust
331 });
332 }
333 else {
334 self.elements.tip.css({
335 right: self.options.style.border.radius + ieAdjust
336 });
337 }
338
339 if(corner.y === 'top') {
340 self.elements.tip.css({
341 top: -positionAdjust
342 });
343 }
344 else {
345 self.elements.tip.css({
346 bottom: positionAdjust
347 });
348 }
349
350 }
351 else {
352 // Adjustments for IE6 - 0.5px border gap bug
353 if($.browser.msie) {
354 positionAdjust = (parseInt($.browser.version.charAt(0), 10) === 6) ? 1 : (corner.x === 'left' ? 1 : 2);
355 }
356
357 if(corner.y === 'center') {
358 self.elements.tip.css({
359 top: '50%',
360 marginTop: -(self.options.style.tip.size.height / 2)
361 });
362 }
363 else if(corner.y === 'top') {
364 self.elements.tip.css({
365 top: self.options.style.border.radius - ieAdjust
366 });
367 }
368 else {
369 self.elements.tip.css({
370 bottom: self.options.style.border.radius + ieAdjust
371 });
372 }
373
374 if(corner.x === 'left') {
375 self.elements.tip.css({
376 left: -positionAdjust
377 });
378 }
379 else {
380 self.elements.tip.css({
381 right: positionAdjust
382 });
383 }
384 }
385
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);
390
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;
395
396 self.elements.tip.css({ marginTop: newMargin });
397 }
398 }
399
400 // Create tip using canvas and VML
401 function createTip(corner) {
402 var self, color, coordinates, coordsize, path, tip;
403 self = this;
404
405 // Destroy previous tip, if there is one
406 if(self.elements.tip !== null) { self.elements.tip.remove(); }
407
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); }
412
413 // Calculate tip coordinates
414 coordinates = calculateTip(corner.string(), self.options.style.tip.size.width, self.options.style.tip.size.height);
415
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>';
418
419 // Attach new tip to tooltip element
420 self.elements.tooltip.prepend(self.elements.tip);
421
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>'; }
424
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];
432 path += ' xe';
433
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>';
436
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>';
439
440 // Prevent tooltip appearing above the content (IE z-index bug)
441 self.elements.contentWrapper.css('position', 'relative');
442 }
443
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);
447
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); }
450
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({
454 marginTop: -4
455 });
456 }
457
458 // Set the tip position
459 positionTip.call(self, corner);
460 }
461
462 // Create title bar for content
463 function createTitle() {
464 var self = this;
465
466 // Destroy previous title element, if present
467 if(self.elements.title !== null) { self.elements.title.remove(); }
468
469 // Append new ARIA attribute to tooltip
470 self.elements.tooltip.attr('aria-labelledby', 'qtip-' + self.id + '-title');
471
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);
476
477 // Update title with contents if enabled
478 if(self.options.content.title.text) { self.updateTitle.call(self, self.options.content.title.text); }
479
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); }
484 });
485 }
486 }
487
488 // Assign hide and show events
489 function assignEvents() {
490 var self, showTarget, hideTarget, inactiveEvents;
491 self = this;
492
493 // Setup event target variables
494 showTarget = self.options.show.when.target;
495 hideTarget = self.options.hide.when.target;
496
497 // Add tooltip as a hideTarget is its fixed
498 if(self.options.hide.fixed) { hideTarget = hideTarget.add(self.elements.tooltip); }
499
500 // Define events which reset the 'inactive' event handler
501 inactiveEvents = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
502 'mouseout', 'mouseenter', 'mouseleave', 'mouseover'];
503
504 // Define 'inactive' event timer method
505 function inactiveMethod(event) {
506 if(self.status.disabled === true) { return; }
507
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');
515 });
516
517 // Hide the tooltip
518 self.hide(event);
519 }, self.options.hide.delay);
520 }
521
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; }
526
527 // Reset the hide timer
528 clearTimeout(self.timers.hide);
529 });
530 }
531
532 // Define show event method
533 function showMethod(event) {
534 if(self.status.disabled === true) { return; }
535
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);
542 });
543
544 // Start the inactive timer
545 inactiveMethod();
546 }
547
548 // Clear hide timers
549 clearTimeout(self.timers.show);
550 clearTimeout(self.timers.hide);
551
552 // Start show timer
553 if(self.options.show.delay > 0) {
554 self.timers.show = setTimeout(function () {
555 self.show(event);
556 }, self.options.show.delay);
557 }
558 else {
559 self.show(event);
560 }
561 }
562
563 // Define hide event method
564 function hideMethod(event) {
565 if(self.status.disabled === true) { return; }
566
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();
572
573 // Reset the hide timer
574 clearTimeout(self.timers.hide);
575 return false;
576 }
577
578 // Clear timers and stop animation queue
579 clearTimeout(self.timers.show);
580 clearTimeout(self.timers.hide);
581 self.elements.tooltip.stop(true, true);
582
583 // If tooltip has displayed, start hide timer
584 self.timers.hide = setTimeout(function () {
585 self.hide(event);
586 }, self.options.hide.delay);
587 }
588
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
593 self.cache.mouse = {
594 left: event.pageX,
595 top: event.pageY
596 };
597
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);
601 }
602 });
603 }
604
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); }
614 });
615 }
616
617 // Events are not identical, bind normally
618 else {
619 showTarget.bind(self.options.show.when.event + '.qtip', showMethod);
620
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); }
623 }
624
625 // Focus the tooltip on mouseover
626 if((/(fixed|absolute)/).test(self.options.position.type)) { self.elements.tooltip.bind('mouseover.qtip', self.focus); }
627 }
628
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;
634 self = this;
635 dimensions = self.getDimensions();
636
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" />';
639
640 // Append the new HTML and setup element reference
641 self.elements.bgiframe = self.elements.wrapper.prepend(html).children('.qtip-bgiframe:first');
642 }
643
644 // Define primary construct function
645 function construct() {
646 var self, content, url, data, method;
647 self = this;
648
649 // Call API method
650 self.beforeRender.call(self);
651
652 // Set rendered status to true
653 self.status.rendered = 2;
654
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>';
657
658 // Append to container element
659 self.elements.tooltip = $(self.elements.tooltip);
660 self.elements.tooltip.appendTo(self.options.position.container);
661
662 // Setup tooltip qTip data
663 self.elements.tooltip.data('qtip', {
664 current: 0,
665 interfaces: [self]
666 });
667
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));
672
673 // Apply IE hasLayout fix to wrapper and content elements
674 if($.browser.msie) { self.elements.wrapper.add(self.elements.content).css({ zoom: 1 }); }
675
676 // Setup tooltip attributes
677 if(self.options.hide.when.event === 'unfocus') { self.elements.tooltip.attr('unfocus', true); }
678
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(); }
681
682 // Create borders and tips if supported by the browser
683 if($('<canvas />').get(0).getContext || $.browser.msie) {
684 // Create border
685 if(self.options.style.border.radius > 0) { createBorder.call(self); }
686 else {
687 self.elements.contentWrapper.css({
688 border: self.options.style.border.width + 'px solid ' + self.options.style.border.color
689 });
690 }
691
692 // Create tip if enabled
693 if(self.options.style.tip.corner !== false) { createTip.call(self); }
694 }
695
696 // Neither canvas or VML is supported, tips and borders cannot be drawn!
697 else {
698 // Set defined border width
699 self.elements.contentWrapper.css({
700 border: self.options.style.border.width + 'px solid ' + self.options.style.border.color
701 });
702
703 // Reset border radius and tip
704 self.options.style.border.radius = 0;
705 self.options.style.tip.corner = false;
706 }
707
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;
711 }
712
713 // Check for valid title and alt attributes
714 else { content = ' '; }
715
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);
719
720 // Assign events and toggle tooltip with focus
721 assignEvents.call(self);
722 if(self.options.show.ready === true) { self.show(); }
723
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);
730 }
731
732 // Call API method and log event
733 self.status.rendered = true;
734 self.onRender.call(self);
735 }
736
737 // Instantiator
738 function QTip(target, options, id) {
739 // Declare this reference
740 var self = this;
741
742 // Setup class attributes
743 self.id = id;
744 self.options = options;
745 self.status = {
746 animated: false,
747 rendered: false,
748 disabled: false,
749 focused: false
750 };
751 self.elements = {
752 target: target.addClass(self.options.style.classes.target),
753 tooltip: null,
754 wrapper: null,
755 content: null,
756 contentWrapper: null,
757 title: null,
758 button: null,
759 tip: null,
760 bgiframe: null
761 };
762 self.cache = {
763 attr: false,
764 mouse: {},
765 toggle: 0,
766 overflow: { left: false, top: false }
767 };
768 self.timers = {};
769
770 // Define exposed API methods
771 $.extend(self, self.options.api, {
772 show: function (event) {
773 var returned, solo;
774
775 // Make sure tooltip is rendered and if not, return
776 if(!self.status.rendered) { return false; }
777
778 // Only continue if element is visible
779 if(self.elements.tooltip.css('display') !== 'none') { return self; }
780
781 // Clear animation queue
782 self.elements.tooltip.stop(true, false);
783
784 // Call API method and if return value is false, halt
785 returned = self.beforeShow.call(self, event);
786 if(returned === false) { return self; }
787
788 // Define afterShow callback method
789 function afterShow() {
790 // Set ARIA hidden status attribute
791 self.elements.tooltip.attr('aria-hidden', true);
792
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);
796
797 // Prevent antialias from disappearing in IE7 by removing filter and opacity attribute
798 if($.browser.msie) {
799 var ieStyle = self.elements.tooltip.get(0).style;
800 ieStyle.removeAttribute('filter');
801 ieStyle.removeAttribute('opacity');
802 }
803 else {
804 self.elements.tooltip.css({ opacity: '' });
805 }
806 }
807
808 // Maintain toggle functionality if enabled
809 self.cache.toggle = 1;
810
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));
814 }
815
816 // Hide other tooltips if tooltip is solo
817 if(typeof self.options.show.solo === 'object') {
818 solo = $(self.options.show.solo);
819 }
820 else if(self.options.show.solo === true) {
821 solo = $('div.qtip').not(self.elements.tooltip);
822 }
823 if(solo) {
824 solo.each(function () {
825 if($(this).qtip('api').status.rendered === true) { $(this).qtip('api').hide(); }
826 });
827 }
828
829 // Show tooltip
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 () {
833 afterShow();
834 $(this).dequeue();
835 });
836 }
837 else {
838 switch (self.options.show.effect.type.toLowerCase()) {
839 case 'fade':
840 self.elements.tooltip.fadeIn(self.options.show.effect.length, afterShow);
841 break;
842
843 case 'slide':
844 self.elements.tooltip.slideDown(self.options.show.effect.length, function () {
845 afterShow();
846 if(self.options.position.type !== 'static') { self.updatePosition(event, true); }
847 });
848 break;
849
850 case 'grow':
851 self.elements.tooltip.show(self.options.show.effect.length, afterShow);
852 break;
853
854 default:
855 self.elements.tooltip.show(null, afterShow);
856 break;
857 }
858
859 // Add active class to tooltip
860 self.elements.tooltip.addClass(self.options.style.classes.active);
861 }
862
863 // Log event and return
864 return self;
865 },
866
867 hide: function (event) {
868 var returned;
869
870 // Make sure tooltip is rendered and if not, return
871 if(!self.status.rendered) { return false; }
872
873 // Only continue if element is visible
874 else if(self.elements.tooltip.css('display') === 'none') { return self; }
875
876 // Stop show timer and animation queue
877 clearTimeout(self.timers.show);
878 self.elements.tooltip.stop(true, false);
879
880 // Call API method and if return value is false, halt
881 returned = self.beforeHide.call(self, event);
882 if(returned === false) { return self; }
883
884 // Define afterHide callback method
885 function afterHide() {
886 // Set ARIA hidden status attribute
887 self.elements.tooltip.attr('aria-hidden', true);
888
889 // Remove opacity attribute
890 if($.browser.msie) {
891 self.elements.tooltip.get(0).style.removeAttribute('opacity');
892 }
893 else {
894 self.elements.tooltip.css({ opacity: '' });
895 }
896
897 // Call API callback
898 self.onHide.call(self, event);
899 }
900
901 // Maintain toggle functionality if enabled
902 self.cache.toggle = 0;
903
904 // Hide tooltip
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 () {
908 afterHide();
909 $(this).dequeue();
910 });
911 }
912 else {
913 switch (self.options.hide.effect.type.toLowerCase()) {
914 case 'fade':
915 self.elements.tooltip.fadeOut(self.options.hide.effect.length, afterHide);
916 break;
917
918 case 'slide':
919 self.elements.tooltip.slideUp(self.options.hide.effect.length, afterHide);
920 break;
921
922 case 'grow':
923 self.elements.tooltip.hide(self.options.hide.effect.length, afterHide);
924 break;
925
926 default:
927 self.elements.tooltip.hide(null, afterHide);
928 break;
929 }
930
931 // Remove active class to tooltip
932 self.elements.tooltip.removeClass(self.options.style.classes.active);
933 }
934
935 // Log event and return
936 return self;
937 },
938
939 toggle: function (event, state) {
940 var condition = /boolean|number/.test(typeof state) ? state : !self.elements.tooltip.is(':visible');
941
942 self[condition ? 'show' : 'hide'](event);
943
944 return self;
945 },
946
947 updatePosition: function (event, animate) {
948 if(!self.status.rendered) {
949 return false;
950 }
951
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,
959 returned,
960 coords, i, mapName, imagePos,
961 adapt = {
962 left: function () {
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,
970 adj;
971
972 // Cut off by right side of window
973 if(pRight > rightEdge) {
974 adj = offset - myOffset - atOffset + borderAdjust;
975
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' };
979 }
980 }
981 // Cut off by left side of window
982 if(position.left < leftEdge) {
983 adj = offset + myOffset + atOffset - borderAdjust;
984
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' };
988 }
989 }
990
991 return { adjust: 0, tip: my.x };
992 },
993 top: function () {
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,
1001 adj;
1002
1003 // Cut off by bottom of window
1004 if(pBottom > bottomEdge) {
1005 adj = offset - myOffset - atOffset + borderAdjust;
1006
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' };
1010 }
1011 }
1012 // Cut off by top of window
1013 if(position.top < topEdge) {
1014 adj = offset + myOffset + atOffset - borderAdjust;
1015
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' };
1019 }
1020 }
1021
1022 return { adjust: 0, tip: my.y };
1023 }
1024 };
1025
1026 if(event && options.position.target === 'mouse') {
1027 // Force left top to allow flipping
1028 at = { x: 'left', y: 'top' };
1029 targetWidth = targetHeight = 0;
1030
1031 // Use cached mouse coordiantes if not available
1032 if(!event.pageX) {
1033 position = self.cache.mouse;
1034 }
1035 else {
1036 position = {
1037 top: event.pageY,
1038 left: event.pageX
1039 };
1040 }
1041 }
1042 else {
1043 if(target[0] === document) {
1044 targetWidth = target.width();
1045 targetHeight = target.height();
1046 position = { top: 0, left: 0 };
1047 }
1048 else if(target[0] === window) {
1049 targetWidth = target.width();
1050 targetHeight = target.height();
1051 position = {
1052 top: target.scrollTop(),
1053 left: target.scrollLeft()
1054 };
1055 }
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); }
1060
1061 // Setup target position object
1062 mapName = self.options.position.target.parent('map').attr('name');
1063 imagePos = $('img[usemap="#' + mapName + '"]:first').offset();
1064 position = {
1065 left: Math.floor(imagePos.left + coords[0]),
1066 top: Math.floor(imagePos.top + coords[1])
1067 };
1068
1069 // Determine width and height of the area
1070 switch (self.options.position.target.attr('shape').toLowerCase()) {
1071 case 'rect':
1072 targetWidth = Math.ceil(Math.abs(coords[2] - coords[0]));
1073 targetHeight = Math.ceil(Math.abs(coords[3] - coords[1]));
1074 break;
1075
1076 case 'circle':
1077 targetWidth = coords[2] + 1;
1078 targetHeight = coords[2] + 1;
1079 break;
1080
1081 case 'poly':
1082 targetWidth = coords[0];
1083 targetHeight = coords[1];
1084
1085 for (i = 0; i < coords.length; i++) {
1086 if(i % 2 === 0) {
1087 if(coords[i] > targetWidth) { targetWidth = coords[i]; }
1088 if(coords[i] < coords[0]) { position.left = Math.floor(imagePos.left + coords[i]); }
1089 }
1090 else {
1091 if(coords[i] > targetHeight) { targetHeight = coords[i]; }
1092 if(coords[i] < coords[1]) { position.top = Math.floor(imagePos.top + coords[i]); }
1093 }
1094 }
1095
1096 targetWidth = targetWidth - (position.left - imagePos.left);
1097 targetHeight = targetHeight - (position.top - imagePos.top);
1098 break;
1099 }
1100
1101 // Adjust position by 2 pixels (Positioning bug?)
1102 targetWidth -= 2;
1103 targetHeight -= 2;
1104 }
1105 else {
1106 targetWidth = target.outerWidth();
1107 targetHeight = target.outerHeight();
1108
1109 if(!self.elements.tooltip.is(':visible')) {
1110 self.elements.tooltip.css({ left: '-10000000em' }).show();
1111 }
1112
1113 // Account for tooltips offset parent if necessary
1114 if(self.elements.tooltip.offsetParent()[0] === document.body) {
1115 position = target.offset();
1116 }
1117 else {
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();
1122 }
1123 }
1124
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;
1128 }
1129
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);
1133
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; }
1138
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; }
1141 }
1142
1143 // Adjust tooltip position if screen adjustment is enabled
1144 if(posOptions.adjust.screen) {
1145 (function() {
1146 var adjusted = { x: 0, y: 0 },
1147 adapted = { x: adapt.left(), y: adapt.top() },
1148 tip = new Corner(options.style.tip.corner);
1149
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;
1155 }
1156 if(adapted.x.adjust !== 0) {
1157 position.left += adapted.x.adjust;
1158 tip.x = adjusted.x = adapted.x.tip;
1159 }
1160
1161 // Update overflow cache
1162 self.cache.overflow = {
1163 left: adjusted.x === false,
1164 top: adjusted.y === false
1165 };
1166
1167 // Update and redraw the tip
1168 if(self.elements.tip.attr('rel') !== tip.string()) { createTip.call(self, tip); }
1169 }
1170 }());
1171 }
1172
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);
1176 }
1177
1178 // Call API method and if return value is false, halt
1179 returned = self.beforePositionUpdate.call(self, event);
1180 if(returned === false) { return self; }
1181
1182 // Check if animation is enabled
1183 if(options.position.target !== 'mouse' && animate === true) {
1184 // Set animated status
1185 self.status.animated = true;
1186
1187 // Animate and reset animated status on animation end
1188 self.elements.tooltip.stop().animate(position, 200, 'swing', function () {
1189 self.status.animated = false;
1190 });
1191 }
1192
1193 // Set new position via CSS
1194 else { self.elements.tooltip.css(position); }
1195
1196 // Call API method and log event if its not a mouse move
1197 self.onPositionUpdate.call(self, event);
1198
1199 return self;
1200 },
1201
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; }
1205
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;
1212
1213 // Calculate the new width if one is not supplied
1214 if(!newWidth) {
1215 // Explicit width is set
1216 if(typeof self.options.style.width.value === 'number') {
1217 newWidth = self.options.style.width.value;
1218 }
1219
1220 // No width is set, proceed with auto detection
1221 else {
1222 // Set width to auto initally to determine new width and hide other elements
1223 self.elements.tooltip.css({ width: 'auto' });
1224 hidden.hide();
1225
1226 // Set the new calculated width and if width has not numerical, grab new pixel width
1227 tooltip.width(newWidth);
1228
1229 // Set position and zoom to defaults to prevent IE hasLayout bug
1230 if($.browser.msie) {
1231 zoom.css({ zoom: '' });
1232 }
1233
1234 // Set the new width
1235 newWidth = self.getDimensions().width;
1236
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);
1240 }
1241 }
1242 }
1243
1244 // Adjust newWidth by 1px if width is odd (IE6 rounding bug fix)
1245 if(newWidth % 2) { newWidth += 1; }
1246
1247 // Set the new calculated width and unhide other elements
1248 self.elements.tooltip.width(newWidth);
1249 hidden.show();
1250
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));
1255 });
1256 }
1257
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 });
1262
1263 // Set the new width
1264 self.elements.wrapper.width(newWidth);
1265
1266 // Adjust BGIframe height and width if enabled
1267 if(self.elements.bgiframe) { self.elements.bgiframe.width(newWidth).height(self.getDimensions.height); }
1268 }
1269
1270 // Log event and return
1271 return self;
1272 },
1273
1274 updateStyle: function (name) {
1275 var tip, borders, context, corner, coordinates;
1276
1277 // Make sure tooltip is rendered and style is defined
1278 if(!self.status.rendered || typeof name !== 'string' || !$.fn.qtip.styles[name]) { return false; }
1279
1280 // Set the new style object
1281 self.options.style = buildStyle.call(self, $.fn.qtip.styles[name], self.options.user.style);
1282
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)); }
1286
1287 // Update CSS border colour
1288 self.elements.contentWrapper.css({
1289 borderColor: self.options.style.border.color
1290 });
1291
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);
1299
1300 // Draw new tip
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);
1304 }
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);
1309 }
1310 }
1311
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
1316 });
1317
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);
1324
1325 // Draw new border
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);
1328 });
1329 }
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);
1334 });
1335 }
1336 }
1337
1338 // Log event and return
1339 return self;
1340 },
1341
1342 updateContent: function (content, reposition) {
1343 var parsedContent, images, loadedImages;
1344
1345 function afterLoad() {
1346 // Update the tooltip width
1347 self.updateWidth();
1348
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); }
1353
1354 // Reposition the tip if enabled
1355 if(self.options.style.tip.corner !== false) { positionTip.call(self); }
1356 }
1357 }
1358
1359 // Make sure content is defined if not, return
1360 if(!content) { return false; }
1361
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; }
1366
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({
1372 zoom: 'normal'
1373 });
1374 }
1375
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(); }
1378
1379 // Content is a regular string, insert the new content
1380 else { self.elements.content.html(content); }
1381
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) {
1385 loadedImages = 0;
1386 images.each(function (i) {
1387 $('<img src="' + $(this).attr('src') + '" />').load(function () {
1388 if(++loadedImages === images.length) { afterLoad(); }
1389 });
1390 });
1391 }
1392 else { afterLoad(); }
1393 }
1394 else {
1395 self.options.content.text = content;
1396 }
1397
1398 // Call API method and log event
1399 self.onContentUpdate.call(self);
1400 return self;
1401 },
1402
1403 loadContent: function (url, data, method) {
1404 var returned;
1405
1406 function setupContent(content) {
1407 // Call API method and log event
1408 self.onContentLoad.call(self);
1409
1410 // Update the content
1411 self.updateContent(content);
1412 }
1413
1414 // Make sure tooltip is rendered and if not, return
1415 if(!self.status.rendered) { return false; }
1416
1417 // Call API method and if return value is false, halt
1418 returned = self.beforeContentLoad.call(self);
1419 if(returned === false) { return self; }
1420
1421 // Load content using specified request type
1422 if(method === 'post') { $.post(url, data, setupContent); }
1423 else { $.get(url, data, setupContent); }
1424
1425 return self;
1426 },
1427
1428 updateTitle: function (content) {
1429 var returned;
1430
1431 // Make sure tooltip is rendered and content is defined
1432 if(!self.status.rendered || !content) { return false; }
1433
1434 // Call API method and if return value is false, halt
1435 returned = self.beforeTitleUpdate.call(self);
1436 if(returned === false) { return self; }
1437
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); }
1442
1443 // Call API method and log event
1444 self.onTitleUpdate.call(self);
1445 return self;
1446 },
1447
1448 focus: function (event) {
1449 var curIndex, newIndex, elemIndex, returned;
1450
1451 // Make sure tooltip is rendered and if not, return
1452 if(!self.status.rendered || self.options.position.type === 'static') { return false; }
1453
1454 // Set z-index variables
1455 curIndex = parseInt(self.elements.tooltip.css('z-index'), 10);
1456 newIndex = 15000 + $('div.qtip[id^="qtip"]').length - 1;
1457
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; }
1463
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);
1468
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 });
1472 }
1473
1474 // Set focused status to false
1475 $(this).qtip('api').status.focused = false;
1476 }
1477 });
1478
1479 // Set the new z-index and set focus status to true
1480 self.elements.tooltip.css({ zIndex: newIndex });
1481 self.status.focused = true;
1482
1483 // Call API method and log event
1484 self.onFocus.call(self, event);
1485 }
1486
1487 return self;
1488 },
1489
1490 disable: function (state) {
1491 self.status.disabled = state ? true : false;
1492 return self;
1493 },
1494
1495 destroy: function () {
1496 var i, returned, interfaces,
1497 oldattr = self.elements.target.data('old'+self.cache.attr[0]);
1498
1499 // Call API method and if return value is false, halt
1500 returned = self.beforeDestroy.call(self);
1501 if(returned === false) { return self; }
1502
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();
1513 }
1514
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'); }
1517
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); }
1526 }
1527 }
1528 }
1529 $.fn.qtip.interfaces.splice(self.id, 1);
1530
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'); }
1534
1535 // Reset old title attribute if removed
1536 if(oldattr) {
1537 self.elements.target.attr(self.cache.attr[0], oldattr);
1538 }
1539
1540 // Call API method and log destroy
1541 self.onDestroy.call(self);
1542
1543 return self.elements.target;
1544 },
1545
1546 getPosition: function () {
1547 var show, offset;
1548
1549 // Make sure tooltip is rendered and if not, return
1550 if(!self.status.rendered) { return false; }
1551
1552 show = (self.elements.tooltip.css('display') !== 'none') ? false : true;
1553
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(); }
1558
1559 return offset;
1560 },
1561
1562 getDimensions: function () {
1563 var show, dimensions;
1564
1565 // Make sure tooltip is rendered and if not, return
1566 if(!self.status.rendered) { return false; }
1567
1568 show = (!self.elements.tooltip.is(':visible')) ? true : false;
1569
1570 // Show and hide tooltip to make sure dimensions are returned
1571 if(show) { self.elements.tooltip.css({ visiblity: 'hidden' }).show(); }
1572 dimensions = {
1573 height: self.elements.tooltip.outerHeight(),
1574 width: self.elements.tooltip.outerWidth()
1575 };
1576 if(show) { self.elements.tooltip.css({ visiblity: 'visible' }).hide(); }
1577
1578 return dimensions;
1579 }
1580 });
1581 }
1582
1583 // Implementation
1584 $.fn.qtip = function (options, blanket) {
1585 var i, id, interfaces, opts, obj, command, config, api;
1586
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];
1593 }
1594 else if(options === 'interfaces') {
1595 return $(this).data('qtip').interfaces;
1596 }
1597 }
1598 else {
1599 return $(this);
1600 }
1601 }
1602
1603 // Validate provided options
1604 else {
1605 // Set null options object if no options are provided
1606 if(!options) { options = {}; }
1607
1608 // Sanitize option data
1609 if(typeof options.content !== 'object' || (options.content.jquery && options.content.length > 0)) {
1610 options.content = { text: options.content };
1611 }
1612 if(typeof options.content.title !== 'object') {
1613 options.content.title = { text: options.content.title };
1614 }
1615 if(typeof options.position !== 'object') {
1616 options.position = { corner: options.position };
1617 }
1618 if(typeof options.position.corner !== 'object') {
1619 options.position.corner = {
1620 target: options.position.corner,
1621 tooltip: options.position.corner
1622 };
1623 }
1624 if(typeof options.show !== 'object') {
1625 options.show = { when: options.show };
1626 }
1627 if(typeof options.show.when !== 'object') {
1628 options.show.when = { event: options.show.when };
1629 }
1630 if(typeof options.show.effect !== 'object') {
1631 options.show.effect = { type: options.show.effect };
1632 }
1633 if(typeof options.hide !== 'object') {
1634 options.hide = { when: options.hide };
1635 }
1636 if(typeof options.hide.when !== 'object') {
1637 options.hide.when = { event: options.hide.when };
1638 }
1639 if(typeof options.hide.effect !== 'object') {
1640 options.hide.effect = { type: options.hide.effect };
1641 }
1642 if(typeof options.style !== 'object') {
1643 options.style = { name: options.style };
1644 }
1645
1646 // Sanitize option styles
1647 options.style = sanitizeStyle(options.style);
1648
1649 // Build main options object
1650 opts = $.extend(true, {}, $.fn.qtip.defaults, options);
1651
1652 // Inherit all style properties into one syle object and include original options
1653 opts.style = buildStyle.call({
1654 options: opts
1655 }, opts.style);
1656 opts.user = $.extend(true, {}, options);
1657 }
1658
1659 // Iterate each matched element
1660 return $(this).each(function () // Return original elements as per jQuery guidelines
1661 {
1662 var self = $(this), content = false;
1663
1664 // Check for API commands
1665 if(typeof options === 'string') {
1666 command = options.toLowerCase();
1667 interfaces = $(this).qtip('interfaces');
1668
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();
1676 }
1677 }
1678 }
1679
1680 // API call is not a BLANKET DESTROY command
1681 else {
1682 // Check if supplied command effects this tooltip only (NOT BLANKET)
1683 if(blanket !== true) { interfaces = [$(this).qtip('api')]; }
1684
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(); }
1689
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(); }
1698 }
1699 }
1700 }
1701 }
1702 }
1703
1704 // No API commands, continue with qTip creation
1705 else {
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;
1710
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); }
1716
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);
1720
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 />');
1729 return false;
1730 }
1731 });
1732 }
1733
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') {
1738 id = i;
1739 break;
1740 }
1741 }
1742
1743 // Instantiate the tooltip
1744 obj = new QTip($(this), config, id);
1745
1746 // Add API references and cache content if present
1747 $.fn.qtip.interfaces[id] = obj;
1748 obj.cache.attr = content;
1749
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; }
1754
1755 // Push new API interface onto interfaces array
1756 $(this).data('qtip').interfaces.push(obj);
1757 }
1758
1759 // No qTip data is present, create now
1760 else {
1761 $(this).data('qtip', {
1762 current: 0,
1763 interfaces: [obj]
1764 });
1765 }
1766
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];
1772
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');
1775 api.cache.mouse = {
1776 left: event.pageX,
1777 top: event.pageY
1778 };
1779
1780 // Render tooltip and start the event sequence
1781 construct.call(api);
1782 api.options.show.when.target.trigger(api.options.show.when.event);
1783 });
1784 }
1785
1786 // Prerendering is enabled, create tooltip now
1787 else {
1788 // Set mouse position cache to top left of the element
1789 obj.cache.mouse = {
1790 left: config.show.when.target.offset().left,
1791 top: config.show.when.target.offset().top
1792 };
1793
1794 // Construct the tooltip
1795 construct.call(obj);
1796 }
1797 }
1798 });
1799 };
1800
1801 // Define qTip API interfaces array
1802 $.fn.qtip.interfaces = [];
1803
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');
1809
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);
1813 };
1814
1815 // Define configuration defaults
1816 $.fn.qtip.defaults = {
1817 // Content
1818 content: {
1819 prerender: false,
1820 text: false,
1821 url: false,
1822 data: null,
1823 title: {
1824 text: false,
1825 button: false
1826 }
1827 },
1828 // Position
1829 position: {
1830 target: false,
1831 corner: {
1832 target: 'bottomRight',
1833 tooltip: 'topLeft'
1834 },
1835 adjust: {
1836 x: 0,
1837 y: 0,
1838 mouse: true,
1839 screen: false,
1840 scroll: true,
1841 resize: true
1842 },
1843 type: 'absolute',
1844 container: false
1845 },
1846 // Effects
1847 show: {
1848 when: {
1849 target: false,
1850 event: 'mouseover'
1851 },
1852 effect: {
1853 type: 'fade',
1854 length: 100
1855 },
1856 delay: 140,
1857 solo: false,
1858 ready: false
1859 },
1860 hide: {
1861 when: {
1862 target: false,
1863 event: 'mouseout'
1864 },
1865 effect: {
1866 type: 'fade',
1867 length: 100
1868 },
1869 delay: 0,
1870 fixed: false
1871 },
1872 // Callbacks
1873 api: {
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 () {}
1892 }
1893 };
1894
1895 $.fn.qtip.styles = {
1896 defaults: {
1897 background: 'white',
1898 color: '#111',
1899 overflow: 'hidden',
1900 textAlign: 'left',
1901 width: {
1902 min: 0,
1903 max: 250
1904 },
1905 padding: '5px 9px',
1906 border: {
1907 width: 1,
1908 radius: 0,
1909 color: '#d3d3d3'
1910 },
1911 tip: {
1912 corner: false,
1913 color: false,
1914 size: {
1915 width: 13,
1916 height: 13
1917 },
1918 opacity: 1
1919 },
1920 title: {
1921 background: '#e1e1e1',
1922 fontWeight: 'bold',
1923 padding: '7px 12px'
1924 },
1925 button: {
1926 cursor: 'pointer'
1927 },
1928 classes: {
1929 target: '',
1930 tip: 'qtip-tip',
1931 title: 'qtip-title',
1932 button: 'qtip-button',
1933 content: 'qtip-content',
1934 active: 'qtip-active'
1935 }
1936 },
1937 cream: {
1938 border: {
1939 width: 3,
1940 radius: 0,
1941 color: '#F9E98E'
1942 },
1943 title: {
1944 background: '#F0DE7D',
1945 color: '#A27D35'
1946 },
1947 background: '#FBF7AA',
1948 color: '#A27D35',
1949
1950 classes: {
1951 tooltip: 'qtip-cream'
1952 }
1953 },
1954 light: {
1955 border: {
1956 width: 3,
1957 radius: 0,
1958 color: '#E2E2E2'
1959 },
1960 title: {
1961 background: '#f1f1f1',
1962 color: '#454545'
1963 },
1964 background: 'white',
1965 color: '#454545',
1966
1967 classes: {
1968 tooltip: 'qtip-light'
1969 }
1970 },
1971 dark: {
1972 border: {
1973 width: 3,
1974 radius: 0,
1975 color: '#303030'
1976 },
1977 title: {
1978 background: '#404040',
1979 color: '#f3f3f3'
1980 },
1981 background: '#505050',
1982 color: '#f3f3f3',
1983
1984 classes: {
1985 tooltip: 'qtip-dark'
1986 }
1987 },
1988 red: {
1989 border: {
1990 width: 3,
1991 radius: 0,
1992 color: '#CE6F6F'
1993 },
1994 title: {
1995 background: '#f28279',
1996 color: '#9C2F2F'
1997 },
1998 background: '#F79992',
1999 color: '#9C2F2F',
2000
2001 classes: {
2002 tooltip: 'qtip-red'
2003 }
2004 },
2005 green: {
2006 border: {
2007 width: 3,
2008 radius: 0,
2009 color: '#A9DB66'
2010 },
2011 title: {
2012 background: '#b9db8c',
2013 color: '#58792E'
2014 },
2015 background: '#CDE6AC',
2016 color: '#58792E',
2017
2018 classes: {
2019 tooltip: 'qtip-green'
2020 }
2021 },
2022 blue: {
2023 border: {
2024 width: 3,
2025 radius: 0,
2026 color: '#ADD9ED'
2027 },
2028 title: {
2029 background: '#D0E9F5',
2030 color: '#5E99BD'
2031 },
2032 background: '#E5F6FE',
2033 color: '#4D9FBF',
2034
2035 classes: {
2036 tooltip: 'qtip-blue'
2037 }
2038 }
2039 };
2040 }(jQuery));