PluginProbe
Easy Footnotes / 1.0.1
Easy Footnotes v1.0.1
trunk 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.2 All 32 releases
easy-footnotes / assets / qtip / jquery.qtip.js

jquery.qtip.js in Easy Footnotes 1.0.1, at assets/qtip/jquery.qtip.js

3,452 lines 100.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 * qTip2 - Pretty powerful tooltips - v2.2.1
3 * http://qtip2.com
4 *
5 * Copyright (c) 2014
6 * Released under the MIT licenses
7 * http://jquery.org/license
8 *
9 * Date: Sat Sep 6 2014 06:25 EDT-0400
10 * Plugins: tips viewport imagemap svg modal ie6
11 * Styles: core basic css3
12 */
13 /*global window: false, jQuery: false, console: false, define: false */
14
15 /* Cache window, document, undefined */
16 (function( window, document, undefined ) {
17
18 // Uses AMD or browser globals to create a jQuery plugin.
19 (function( factory ) {
20 "use strict";
21 if(typeof define === 'function' && define.amd) {
22 define(['jquery'], factory);
23 }
24 else if(jQuery && !jQuery.fn.qtip) {
25 factory(jQuery);
26 }
27 }
28 (function($) {
29 "use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
30 ;// Munge the primitives - Paul Irish tip
31 var TRUE = true,
32 FALSE = false,
33 NULL = null,
34
35 // Common variables
36 X = 'x', Y = 'y',
37 WIDTH = 'width',
38 HEIGHT = 'height',
39
40 // Positioning sides
41 TOP = 'top',
42 LEFT = 'left',
43 BOTTOM = 'bottom',
44 RIGHT = 'right',
45 CENTER = 'center',
46
47 // Position adjustment types
48 FLIP = 'flip',
49 FLIPINVERT = 'flipinvert',
50 SHIFT = 'shift',
51
52 // Shortcut vars
53 QTIP, PROTOTYPE, CORNER, CHECKS,
54 PLUGINS = {},
55 NAMESPACE = 'qtip',
56 ATTR_HAS = 'data-hasqtip',
57 ATTR_ID = 'data-qtip-id',
58 WIDGET = ['ui-widget', 'ui-tooltip'],
59 SELECTOR = '.'+NAMESPACE,
60 INACTIVE_EVENTS = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' '),
61
62 CLASS_FIXED = NAMESPACE+'-fixed',
63 CLASS_DEFAULT = NAMESPACE + '-default',
64 CLASS_FOCUS = NAMESPACE + '-focus',
65 CLASS_HOVER = NAMESPACE + '-hover',
66 CLASS_DISABLED = NAMESPACE+'-disabled',
67
68 replaceSuffix = '_replacedByqTip',
69 oldtitle = 'oldtitle',
70 trackingBound,
71
72 // Browser detection
73 BROWSER = {
74 /*
75 * IE version detection
76 *
77 * Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment
78 * Credit to James Padolsey for the original implemntation!
79 */
80 ie: (function(){
81 for (
82 var v = 4, i = document.createElement("div");
83 (i.innerHTML = "<!--[if gt IE " + v + "]><i></i><![endif]-->") && i.getElementsByTagName("i")[0];
84 v+=1
85 ) {}
86 return v > 4 ? v : NaN;
87 }()),
88
89 /*
90 * iOS version detection
91 */
92 iOS: parseFloat(
93 ('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
94 .replace('undefined', '3_2').replace('_', '.').replace('_', '')
95 ) || FALSE
96 };
97 ;function QTip(target, options, id, attr) {
98 // Elements and ID
99 this.id = id;
100 this.target = target;
101 this.tooltip = NULL;
102 this.elements = { target: target };
103
104 // Internal constructs
105 this._id = NAMESPACE + '-' + id;
106 this.timers = { img: {} };
107 this.options = options;
108 this.plugins = {};
109
110 // Cache object
111 this.cache = {
112 event: {},
113 target: $(),
114 disabled: FALSE,
115 attr: attr,
116 onTooltip: FALSE,
117 lastClass: ''
118 };
119
120 // Set the initial flags
121 this.rendered = this.destroyed = this.disabled = this.waiting =
122 this.hiddenDuringWait = this.positioning = this.triggering = FALSE;
123 }
124 PROTOTYPE = QTip.prototype;
125
126 PROTOTYPE._when = function(deferreds) {
127 return $.when.apply($, deferreds);
128 };
129
130 PROTOTYPE.render = function(show) {
131 if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit
132
133 var self = this,
134 options = this.options,
135 cache = this.cache,
136 elements = this.elements,
137 text = options.content.text,
138 title = options.content.title,
139 button = options.content.button,
140 posOptions = options.position,
141 namespace = '.'+this._id+' ',
142 deferreds = [],
143 tooltip;
144
145 // Add ARIA attributes to target
146 $.attr(this.target[0], 'aria-describedby', this._id);
147
148 // Create public position object that tracks current position corners
149 cache.posClass = this._createPosClass(
150 (this.position = { my: posOptions.my, at: posOptions.at }).my
151 );
152
153 // Create tooltip element
154 this.tooltip = elements.tooltip = tooltip = $('<div/>', {
155 'id': this._id,
156 'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, cache.posClass ].join(' '),
157 'width': options.style.width || '',
158 'height': options.style.height || '',
159 'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,
160
161 /* ARIA specific attributes */
162 'role': 'alert',
163 'aria-live': 'polite',
164 'aria-atomic': FALSE,
165 'aria-describedby': this._id + '-content',
166 'aria-hidden': TRUE
167 })
168 .toggleClass(CLASS_DISABLED, this.disabled)
169 .attr(ATTR_ID, this.id)
170 .data(NAMESPACE, this)
171 .appendTo(posOptions.container)
172 .append(
173 // Create content element
174 elements.content = $('<div />', {
175 'class': NAMESPACE + '-content',
176 'id': this._id + '-content',
177 'aria-atomic': TRUE
178 })
179 );
180
181 // Set rendered flag and prevent redundant reposition calls for now
182 this.rendered = -1;
183 this.positioning = TRUE;
184
185 // Create title...
186 if(title) {
187 this._createTitle();
188
189 // Update title only if its not a callback (called in toggle if so)
190 if(!$.isFunction(title)) {
191 deferreds.push( this._updateTitle(title, FALSE) );
192 }
193 }
194
195 // Create button
196 if(button) { this._createButton(); }
197
198 // Set proper rendered flag and update content if not a callback function (called in toggle)
199 if(!$.isFunction(text)) {
200 deferreds.push( this._updateContent(text, FALSE) );
201 }
202 this.rendered = TRUE;
203
204 // Setup widget classes
205 this._setWidget();
206
207 // Initialize 'render' plugins
208 $.each(PLUGINS, function(name) {
209 var instance;
210 if(this.initialize === 'render' && (instance = this(self))) {
211 self.plugins[name] = instance;
212 }
213 });
214
215 // Unassign initial events and assign proper events
216 this._unassignEvents();
217 this._assignEvents();
218
219 // When deferreds have completed
220 this._when(deferreds).then(function() {
221 // tooltiprender event
222 self._trigger('render');
223
224 // Reset flags
225 self.positioning = FALSE;
226
227 // Show tooltip if not hidden during wait period
228 if(!self.hiddenDuringWait && (options.show.ready || show)) {
229 self.toggle(TRUE, cache.event, FALSE);
230 }
231 self.hiddenDuringWait = FALSE;
232 });
233
234 // Expose API
235 QTIP.api[this.id] = this;
236
237 return this;
238 };
239
240 PROTOTYPE.destroy = function(immediate) {
241 // Set flag the signify destroy is taking place to plugins
242 // and ensure it only gets destroyed once!
243 if(this.destroyed) { return this.target; }
244
245 function process() {
246 if(this.destroyed) { return; }
247 this.destroyed = TRUE;
248
249 var target = this.target,
250 title = target.attr(oldtitle),
251 timer;
252
253 // Destroy tooltip if rendered
254 if(this.rendered) {
255 this.tooltip.stop(1,0).find('*').remove().end().remove();
256 }
257
258 // Destroy all plugins
259 $.each(this.plugins, function(name) {
260 this.destroy && this.destroy();
261 });
262
263 // Clear timers
264 for(timer in this.timers) {
265 clearTimeout(this.timers[timer]);
266 }
267
268 // Remove api object and ARIA attributes
269 target.removeData(NAMESPACE)
270 .removeAttr(ATTR_ID)
271 .removeAttr(ATTR_HAS)
272 .removeAttr('aria-describedby');
273
274 // Reset old title attribute if removed
275 if(this.options.suppress && title) {
276 target.attr('title', title).removeAttr(oldtitle);
277 }
278
279 // Remove qTip events associated with this API
280 this._unassignEvents();
281
282 // Remove ID from used id objects, and delete object references
283 // for better garbage collection and leak protection
284 this.options = this.elements = this.cache = this.timers =
285 this.plugins = this.mouse = NULL;
286
287 // Delete epoxsed API object
288 delete QTIP.api[this.id];
289 }
290
291 // If an immediate destory is needed
292 if((immediate !== TRUE || this.triggering === 'hide') && this.rendered) {
293 this.tooltip.one('tooltiphidden', $.proxy(process, this));
294 !this.triggering && this.hide();
295 }
296
297 // If we're not in the process of hiding... process
298 else { process.call(this); }
299
300 return this.target;
301 };
302 ;function invalidOpt(a) {
303 return a === NULL || $.type(a) !== 'object';
304 }
305
306 function invalidContent(c) {
307 return !( $.isFunction(c) || (c && c.attr) || c.length || ($.type(c) === 'object' && (c.jquery || c.then) ));
308 }
309
310 // Option object sanitizer
311 function sanitizeOptions(opts) {
312 var content, text, ajax, once;
313
314 if(invalidOpt(opts)) { return FALSE; }
315
316 if(invalidOpt(opts.metadata)) {
317 opts.metadata = { type: opts.metadata };
318 }
319
320 if('content' in opts) {
321 content = opts.content;
322
323 if(invalidOpt(content) || content.jquery || content.done) {
324 content = opts.content = {
325 text: (text = invalidContent(content) ? FALSE : content)
326 };
327 }
328 else { text = content.text; }
329
330 // DEPRECATED - Old content.ajax plugin functionality
331 // Converts it into the proper Deferred syntax
332 if('ajax' in content) {
333 ajax = content.ajax;
334 once = ajax && ajax.once !== FALSE;
335 delete content.ajax;
336
337 content.text = function(event, api) {
338 var loading = text || $(this).attr(api.options.content.attr) || 'Loading...',
339
340 deferred = $.ajax(
341 $.extend({}, ajax, { context: api })
342 )
343 .then(ajax.success, NULL, ajax.error)
344 .then(function(content) {
345 if(content && once) { api.set('content.text', content); }
346 return content;
347 },
348 function(xhr, status, error) {
349 if(api.destroyed || xhr.status === 0) { return; }
350 api.set('content.text', status + ': ' + error);
351 });
352
353 return !once ? (api.set('content.text', loading), deferred) : loading;
354 };
355 }
356
357 if('title' in content) {
358 if($.isPlainObject(content.title)) {
359 content.button = content.title.button;
360 content.title = content.title.text;
361 }
362
363 if(invalidContent(content.title || FALSE)) {
364 content.title = FALSE;
365 }
366 }
367 }
368
369 if('position' in opts && invalidOpt(opts.position)) {
370 opts.position = { my: opts.position, at: opts.position };
371 }
372
373 if('show' in opts && invalidOpt(opts.show)) {
374 opts.show = opts.show.jquery ? { target: opts.show } :
375 opts.show === TRUE ? { ready: TRUE } : { event: opts.show };
376 }
377
378 if('hide' in opts && invalidOpt(opts.hide)) {
379 opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };
380 }
381
382 if('style' in opts && invalidOpt(opts.style)) {
383 opts.style = { classes: opts.style };
384 }
385
386 // Sanitize plugin options
387 $.each(PLUGINS, function() {
388 this.sanitize && this.sanitize(opts);
389 });
390
391 return opts;
392 }
393
394 // Setup builtin .set() option checks
395 CHECKS = PROTOTYPE.checks = {
396 builtin: {
397 // Core checks
398 '^id$': function(obj, o, v, prev) {
399 var id = v === TRUE ? QTIP.nextid : v,
400 new_id = NAMESPACE + '-' + id;
401
402 if(id !== FALSE && id.length > 0 && !$('#'+new_id).length) {
403 this._id = new_id;
404
405 if(this.rendered) {
406 this.tooltip[0].id = this._id;
407 this.elements.content[0].id = this._id + '-content';
408 this.elements.title[0].id = this._id + '-title';
409 }
410 }
411 else { obj[o] = prev; }
412 },
413 '^prerender': function(obj, o, v) {
414 v && !this.rendered && this.render(this.options.show.ready);
415 },
416
417 // Content checks
418 '^content.text$': function(obj, o, v) {
419 this._updateContent(v);
420 },
421 '^content.attr$': function(obj, o, v, prev) {
422 if(this.options.content.text === this.target.attr(prev)) {
423 this._updateContent( this.target.attr(v) );
424 }
425 },
426 '^content.title$': function(obj, o, v) {
427 // Remove title if content is null
428 if(!v) { return this._removeTitle(); }
429
430 // If title isn't already created, create it now and update
431 v && !this.elements.title && this._createTitle();
432 this._updateTitle(v);
433 },
434 '^content.button$': function(obj, o, v) {
435 this._updateButton(v);
436 },
437 '^content.title.(text|button)$': function(obj, o, v) {
438 this.set('content.'+o, v); // Backwards title.text/button compat
439 },
440
441 // Position checks
442 '^position.(my|at)$': function(obj, o, v){
443 'string' === typeof v && (this.position[o] = obj[o] = new CORNER(v, o === 'at'));
444 },
445 '^position.container$': function(obj, o, v){
446 this.rendered && this.tooltip.appendTo(v);
447 },
448
449 // Show checks
450 '^show.ready$': function(obj, o, v) {
451 v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE));
452 },
453
454 // Style checks
455 '^style.classes$': function(obj, o, v, p) {
456 this.rendered && this.tooltip.removeClass(p).addClass(v);
457 },
458 '^style.(width|height)': function(obj, o, v) {
459 this.rendered && this.tooltip.css(o, v);
460 },
461 '^style.widget|content.title': function() {
462 this.rendered && this._setWidget();
463 },
464 '^style.def': function(obj, o, v) {
465 this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v);
466 },
467
468 // Events check
469 '^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
470 this.rendered && this.tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
471 },
472
473 // Properties which require event reassignment
474 '^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
475 if(!this.rendered) { return; }
476
477 // Set tracking flag
478 var posOptions = this.options.position;
479 this.tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);
480
481 // Reassign events
482 this._unassignEvents();
483 this._assignEvents();
484 }
485 }
486 };
487
488 // Dot notation converter
489 function convertNotation(options, notation) {
490 var i = 0, obj, option = options,
491
492 // Split notation into array
493 levels = notation.split('.');
494
495 // Loop through
496 while( option = option[ levels[i++] ] ) {
497 if(i < levels.length) { obj = option; }
498 }
499
500 return [obj || options, levels.pop()];
501 }
502
503 PROTOTYPE.get = function(notation) {
504 if(this.destroyed) { return this; }
505
506 var o = convertNotation(this.options, notation.toLowerCase()),
507 result = o[0][ o[1] ];
508
509 return result.precedance ? result.string() : result;
510 };
511
512 function setCallback(notation, args) {
513 var category, rule, match;
514
515 for(category in this.checks) {
516 for(rule in this.checks[category]) {
517 if(match = (new RegExp(rule, 'i')).exec(notation)) {
518 args.push(match);
519
520 if(category === 'builtin' || this.plugins[category]) {
521 this.checks[category][rule].apply(
522 this.plugins[category] || this, args
523 );
524 }
525 }
526 }
527 }
528 }
529
530 var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,
531 rrender = /^prerender|show\.ready/i;
532
533 PROTOTYPE.set = function(option, value) {
534 if(this.destroyed) { return this; }
535
536 var rendered = this.rendered,
537 reposition = FALSE,
538 options = this.options,
539 checks = this.checks,
540 name;
541
542 // Convert singular option/value pair into object form
543 if('string' === typeof option) {
544 name = option; option = {}; option[name] = value;
545 }
546 else { option = $.extend({}, option); }
547
548 // Set all of the defined options to their new values
549 $.each(option, function(notation, value) {
550 if(rendered && rrender.test(notation)) {
551 delete option[notation]; return;
552 }
553
554 // Set new obj value
555 var obj = convertNotation(options, notation.toLowerCase()), previous;
556 previous = obj[0][ obj[1] ];
557 obj[0][ obj[1] ] = value && value.nodeType ? $(value) : value;
558
559 // Also check if we need to reposition
560 reposition = rmove.test(notation) || reposition;
561
562 // Set the new params for the callback
563 option[notation] = [obj[0], obj[1], value, previous];
564 });
565
566 // Re-sanitize options
567 sanitizeOptions(options);
568
569 /*
570 * Execute any valid callbacks for the set options
571 * Also set positioning flag so we don't get loads of redundant repositioning calls.
572 */
573 this.positioning = TRUE;
574 $.each(option, $.proxy(setCallback, this));
575 this.positioning = FALSE;
576
577 // Update position if needed
578 if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) {
579 this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event );
580 }
581
582 return this;
583 };
584 ;PROTOTYPE._update = function(content, element, reposition) {
585 var self = this,
586 cache = this.cache;
587
588 // Make sure tooltip is rendered and content is defined. If not return
589 if(!this.rendered || !content) { return FALSE; }
590
591 // Use function to parse content
592 if($.isFunction(content)) {
593 content = content.call(this.elements.target, cache.event, this) || '';
594 }
595
596 // Handle deferred content
597 if($.isFunction(content.then)) {
598 cache.waiting = TRUE;
599 return content.then(function(c) {
600 cache.waiting = FALSE;
601 return self._update(c, element);
602 }, NULL, function(e) {
603 return self._update(e, element);
604 });
605 }
606
607 // If content is null... return false
608 if(content === FALSE || (!content && content !== '')) { return FALSE; }
609
610 // Append new content if its a DOM array and show it if hidden
611 if(content.jquery && content.length > 0) {
612 element.empty().append(
613 content.css({ display: 'block', visibility: 'visible' })
614 );
615 }
616
617 // Content is a regular string, insert the new content
618 else { element.html(content); }
619
620 // Wait for content to be loaded, and reposition
621 return this._waitForContent(element).then(function(images) {
622 if(self.rendered && self.tooltip[0].offsetWidth > 0) {
623 self.reposition(cache.event, !images.length);
624 }
625 });
626 };
627
628 PROTOTYPE._waitForContent = function(element) {
629 var cache = this.cache;
630
631 // Set flag
632 cache.waiting = TRUE;
633
634 // If imagesLoaded is included, ensure images have loaded and return promise
635 return ( $.fn.imagesLoaded ? element.imagesLoaded() : $.Deferred().resolve([]) )
636 .done(function() { cache.waiting = FALSE; })
637 .promise();
638 };
639
640 PROTOTYPE._updateContent = function(content, reposition) {
641 this._update(content, this.elements.content, reposition);
642 };
643
644 PROTOTYPE._updateTitle = function(content, reposition) {
645 if(this._update(content, this.elements.title, reposition) === FALSE) {
646 this._removeTitle(FALSE);
647 }
648 };
649
650 PROTOTYPE._createTitle = function()
651 {
652 var elements = this.elements,
653 id = this._id+'-title';
654
655 // Destroy previous title element, if present
656 if(elements.titlebar) { this._removeTitle(); }
657
658 // Create title bar and title elements
659 elements.titlebar = $('<div />', {
660 'class': NAMESPACE + '-titlebar ' + (this.options.style.widget ? createWidgetClass('header') : '')
661 })
662 .append(
663 elements.title = $('<div />', {
664 'id': id,
665 'class': NAMESPACE + '-title',
666 'aria-atomic': TRUE
667 })
668 )
669 .insertBefore(elements.content)
670
671 // Button-specific events
672 .delegate('.qtip-close', 'mousedown keydown mouseup keyup mouseout', function(event) {
673 $(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');
674 })
675 .delegate('.qtip-close', 'mouseover mouseout', function(event){
676 $(this).toggleClass('ui-state-hover', event.type === 'mouseover');
677 });
678
679 // Create button if enabled
680 if(this.options.content.button) { this._createButton(); }
681 };
682
683 PROTOTYPE._removeTitle = function(reposition)
684 {
685 var elements = this.elements;
686
687 if(elements.title) {
688 elements.titlebar.remove();
689 elements.titlebar = elements.title = elements.button = NULL;
690
691 // Reposition if enabled
692 if(reposition !== FALSE) { this.reposition(); }
693 }
694 };
695 ;PROTOTYPE._createPosClass = function(my) {
696 return NAMESPACE + '-pos-' + (my || this.options.position.my).abbrev();
697 };
698
699 PROTOTYPE.reposition = function(event, effect) {
700 if(!this.rendered || this.positioning || this.destroyed) { return this; }
701
702 // Set positioning flag
703 this.positioning = TRUE;
704
705 var cache = this.cache,
706 tooltip = this.tooltip,
707 posOptions = this.options.position,
708 target = posOptions.target,
709 my = posOptions.my,
710 at = posOptions.at,
711 viewport = posOptions.viewport,
712 container = posOptions.container,
713 adjust = posOptions.adjust,
714 method = adjust.method.split(' '),
715 tooltipWidth = tooltip.outerWidth(FALSE),
716 tooltipHeight = tooltip.outerHeight(FALSE),
717 targetWidth = 0,
718 targetHeight = 0,
719 type = tooltip.css('position'),
720 position = { left: 0, top: 0 },
721 visible = tooltip[0].offsetWidth > 0,
722 isScroll = event && event.type === 'scroll',
723 win = $(window),
724 doc = container[0].ownerDocument,
725 mouse = this.mouse,
726 pluginCalculations, offset, adjusted, newClass;
727
728 // Check if absolute position was passed
729 if($.isArray(target) && target.length === 2) {
730 // Force left top and set position
731 at = { x: LEFT, y: TOP };
732 position = { left: target[0], top: target[1] };
733 }
734
735 // Check if mouse was the target
736 else if(target === 'mouse') {
737 // Force left top to allow flipping
738 at = { x: LEFT, y: TOP };
739
740 // Use the mouse origin that caused the show event, if distance hiding is enabled
741 if((!adjust.mouse || this.options.hide.distance) && cache.origin && cache.origin.pageX) {
742 event = cache.origin;
743 }
744
745 // Use cached event for resize/scroll events
746 else if(!event || (event && (event.type === 'resize' || event.type === 'scroll'))) {
747 event = cache.event;
748 }
749
750 // Otherwise, use the cached mouse coordinates if available
751 else if(mouse && mouse.pageX) {
752 event = mouse;
753 }
754
755 // Calculate body and container offset and take them into account below
756 if(type !== 'static') { position = container.offset(); }
757 if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) {
758 offset = $(document.body).offset();
759 }
760
761 // Use event coordinates for position
762 position = {
763 left: event.pageX - position.left + (offset && offset.left || 0),
764 top: event.pageY - position.top + (offset && offset.top || 0)
765 };
766
767 // Scroll events are a pain, some browsers
768 if(adjust.mouse && isScroll && mouse) {
769 position.left -= (mouse.scrollX || 0) - win.scrollLeft();
770 position.top -= (mouse.scrollY || 0) - win.scrollTop();
771 }
772 }
773
774 // Target wasn't mouse or absolute...
775 else {
776 // Check if event targetting is being used
777 if(target === 'event') {
778 if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
779 cache.target = $(event.target);
780 }
781 else if(!event.target) {
782 cache.target = this.elements.target;
783 }
784 }
785 else if(target !== 'event'){
786 cache.target = $(target.jquery ? target : this.elements.target);
787 }
788 target = cache.target;
789
790 // Parse the target into a jQuery object and make sure there's an element present
791 target = $(target).eq(0);
792 if(target.length === 0) { return this; }
793
794 // Check if window or document is the target
795 else if(target[0] === document || target[0] === window) {
796 targetWidth = BROWSER.iOS ? window.innerWidth : target.width();
797 targetHeight = BROWSER.iOS ? window.innerHeight : target.height();
798
799 if(target[0] === window) {
800 position = {
801 top: (viewport || target).scrollTop(),
802 left: (viewport || target).scrollLeft()
803 };
804 }
805 }
806
807 // Check if the target is an <AREA> element
808 else if(PLUGINS.imagemap && target.is('area')) {
809 pluginCalculations = PLUGINS.imagemap(this, target, at, PLUGINS.viewport ? method : FALSE);
810 }
811
812 // Check if the target is an SVG element
813 else if(PLUGINS.svg && target && target[0].ownerSVGElement) {
814 pluginCalculations = PLUGINS.svg(this, target, at, PLUGINS.viewport ? method : FALSE);
815 }
816
817 // Otherwise use regular jQuery methods
818 else {
819 targetWidth = target.outerWidth(FALSE);
820 targetHeight = target.outerHeight(FALSE);
821 position = target.offset();
822 }
823
824 // Parse returned plugin values into proper variables
825 if(pluginCalculations) {
826 targetWidth = pluginCalculations.width;
827 targetHeight = pluginCalculations.height;
828 offset = pluginCalculations.offset;
829 position = pluginCalculations.position;
830 }
831
832 // Adjust position to take into account offset parents
833 position = this.reposition.offset(target, position, container);
834
835 // Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2)
836 if((BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1) ||
837 (BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33) ||
838 (!BROWSER.iOS && type === 'fixed')
839 ){
840 position.left -= win.scrollLeft();
841 position.top -= win.scrollTop();
842 }
843
844 // Adjust position relative to target
845 if(!pluginCalculations || (pluginCalculations && pluginCalculations.adjustable !== FALSE)) {
846 position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0;
847 position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0;
848 }
849 }
850
851 // Adjust position relative to tooltip
852 position.left += adjust.x + (my.x === RIGHT ? -tooltipWidth : my.x === CENTER ? -tooltipWidth / 2 : 0);
853 position.top += adjust.y + (my.y === BOTTOM ? -tooltipHeight : my.y === CENTER ? -tooltipHeight / 2 : 0);
854
855 // Use viewport adjustment plugin if enabled
856 if(PLUGINS.viewport) {
857 adjusted = position.adjusted = PLUGINS.viewport(
858 this, position, posOptions, targetWidth, targetHeight, tooltipWidth, tooltipHeight
859 );
860
861 // Apply offsets supplied by positioning plugin (if used)
862 if(offset && adjusted.left) { position.left += offset.left; }
863 if(offset && adjusted.top) { position.top += offset.top; }
864
865 // Apply any new 'my' position
866 if(adjusted.my) { this.position.my = adjusted.my; }
867 }
868
869 // Viewport adjustment is disabled, set values to zero
870 else { position.adjusted = { left: 0, top: 0 }; }
871
872 // Set tooltip position class if it's changed
873 if(cache.posClass !== (newClass = this._createPosClass(this.position.my))) {
874 tooltip.removeClass(cache.posClass).addClass( (cache.posClass = newClass) );
875 }
876
877 // tooltipmove event
878 if(!this._trigger('move', [position, viewport.elem || viewport], event)) { return this; }
879 delete position.adjusted;
880
881 // If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly
882 if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) {
883 tooltip.css(position);
884 }
885
886 // Use custom function if provided
887 else if($.isFunction(posOptions.effect)) {
888 posOptions.effect.call(tooltip, this, $.extend({}, position));
889 tooltip.queue(function(next) {
890 // Reset attributes to avoid cross-browser rendering bugs
891 $(this).css({ opacity: '', height: '' });
892 if(BROWSER.ie) { this.style.removeAttribute('filter'); }
893
894 next();
895 });
896 }
897
898 // Set positioning flag
899 this.positioning = FALSE;
900
901 return this;
902 };
903
904 // Custom (more correct for qTip!) offset calculator
905 PROTOTYPE.reposition.offset = function(elem, pos, container) {
906 if(!container[0]) { return pos; }
907
908 var ownerDocument = $(elem[0].ownerDocument),
909 quirks = !!BROWSER.ie && document.compatMode !== 'CSS1Compat',
910 parent = container[0],
911 scrolled, position, parentOffset, overflow;
912
913 function scroll(e, i) {
914 pos.left += i * e.scrollLeft();
915 pos.top += i * e.scrollTop();
916 }
917
918 // Compensate for non-static containers offset
919 do {
920 if((position = $.css(parent, 'position')) !== 'static') {
921 if(position === 'fixed') {
922 parentOffset = parent.getBoundingClientRect();
923 scroll(ownerDocument, -1);
924 }
925 else {
926 parentOffset = $(parent).position();
927 parentOffset.left += (parseFloat($.css(parent, 'borderLeftWidth')) || 0);
928 parentOffset.top += (parseFloat($.css(parent, 'borderTopWidth')) || 0);
929 }
930
931 pos.left -= parentOffset.left + (parseFloat($.css(parent, 'marginLeft')) || 0);
932 pos.top -= parentOffset.top + (parseFloat($.css(parent, 'marginTop')) || 0);
933
934 // If this is the first parent element with an overflow of "scroll" or "auto", store it
935 if(!scrolled && (overflow = $.css(parent, 'overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = $(parent); }
936 }
937 }
938 while((parent = parent.offsetParent));
939
940 // Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode)
941 if(scrolled && (scrolled[0] !== ownerDocument[0] || quirks)) {
942 scroll(scrolled, 1);
943 }
944
945 return pos;
946 };
947
948 // Corner class
949 var C = (CORNER = PROTOTYPE.reposition.Corner = function(corner, forceY) {
950 corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase();
951 this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();
952 this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();
953 this.forceY = !!forceY;
954
955 var f = corner.charAt(0);
956 this.precedance = (f === 't' || f === 'b' ? Y : X);
957 }).prototype;
958
959 C.invert = function(z, center) {
960 this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z];
961 };
962
963 C.string = function(join) {
964 var x = this.x, y = this.y;
965
966 var result = x !== y ?
967 (x === 'center' || y !== 'center' && (this.precedance === Y || this.forceY) ?
968 [y,x] : [x,y]
969 ) :
970 [x];
971
972 return join !== false ? result.join(' ') : result;
973 };
974
975 C.abbrev = function() {
976 var result = this.string(false);
977 return result[0].charAt(0) + (result[1] && result[1].charAt(0) || '');
978 };
979
980 C.clone = function() {
981 return new CORNER( this.string(), this.forceY );
982 };
983
984 ;
985 PROTOTYPE.toggle = function(state, event) {
986 var cache = this.cache,
987 options = this.options,
988 tooltip = this.tooltip;
989
990 // Try to prevent flickering when tooltip overlaps show element
991 if(event) {
992 if((/over|enter/).test(event.type) && cache.event && (/out|leave/).test(cache.event.type) &&
993 options.show.target.add(event.target).length === options.show.target.length &&
994 tooltip.has(event.relatedTarget).length) {
995 return this;
996 }
997
998 // Cache event
999 cache.event = $.event.fix(event);
1000 }
1001
1002 // If we're currently waiting and we've just hidden... stop it
1003 this.waiting && !state && (this.hiddenDuringWait = TRUE);
1004
1005 // Render the tooltip if showing and it isn't already
1006 if(!this.rendered) { return state ? this.render(1) : this; }
1007 else if(this.destroyed || this.disabled) { return this; }
1008
1009 var type = state ? 'show' : 'hide',
1010 opts = this.options[type],
1011 otherOpts = this.options[ !state ? 'show' : 'hide' ],
1012 posOptions = this.options.position,
1013 contentOptions = this.options.content,
1014 width = this.tooltip.css('width'),
1015 visible = this.tooltip.is(':visible'),
1016 animate = state || opts.target.length === 1,
1017 sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,
1018 identicalState, allow, showEvent, delay, after;
1019
1020 // Detect state if valid one isn't provided
1021 if((typeof state).search('boolean|number')) { state = !visible; }
1022
1023 // Check if the tooltip is in an identical state to the new would-be state
1024 identicalState = !tooltip.is(':animated') && visible === state && sameTarget;
1025
1026 // Fire tooltip(show/hide) event and check if destroyed
1027 allow = !identicalState ? !!this._trigger(type, [90]) : NULL;
1028
1029 // Check to make sure the tooltip wasn't destroyed in the callback
1030 if(this.destroyed) { return this; }
1031
1032 // If the user didn't stop the method prematurely and we're showing the tooltip, focus it
1033 if(allow !== FALSE && state) { this.focus(event); }
1034
1035 // If the state hasn't changed or the user stopped it, return early
1036 if(!allow || identicalState) { return this; }
1037
1038 // Set ARIA hidden attribute
1039 $.attr(tooltip[0], 'aria-hidden', !!!state);
1040
1041 // Execute state specific properties
1042 if(state) {
1043 // Store show origin coordinates
1044 this.mouse && (cache.origin = $.event.fix(this.mouse));
1045
1046 // Update tooltip content & title if it's a dynamic function
1047 if($.isFunction(contentOptions.text)) { this._updateContent(contentOptions.text, FALSE); }
1048 if($.isFunction(contentOptions.title)) { this._updateTitle(contentOptions.title, FALSE); }
1049
1050 // Cache mousemove events for positioning purposes (if not already tracking)
1051 if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) {
1052 $(document).bind('mousemove.'+NAMESPACE, this._storeMouse);
1053 trackingBound = TRUE;
1054 }
1055
1056 // Update the tooltip position (set width first to prevent viewport/max-width issues)
1057 if(!width) { tooltip.css('width', tooltip.outerWidth(FALSE)); }
1058 this.reposition(event, arguments[2]);
1059 if(!width) { tooltip.css('width', ''); }
1060
1061 // Hide other tooltips if tooltip is solo
1062 if(!!opts.solo) {
1063 (typeof opts.solo === 'string' ? $(opts.solo) : $(SELECTOR, opts.solo))
1064 .not(tooltip).not(opts.target).qtip('hide', $.Event('tooltipsolo'));
1065 }
1066 }
1067 else {
1068 // Clear show timer if we're hiding
1069 clearTimeout(this.timers.show);
1070
1071 // Remove cached origin on hide
1072 delete cache.origin;
1073
1074 // Remove mouse tracking event if not needed (all tracking qTips are hidden)
1075 if(trackingBound && !$(SELECTOR+'[tracking="true"]:visible', opts.solo).not(tooltip).length) {
1076 $(document).unbind('mousemove.'+NAMESPACE);
1077 trackingBound = FALSE;
1078 }
1079
1080 // Blur the tooltip
1081 this.blur(event);
1082 }
1083
1084 // Define post-animation, state specific properties
1085 after = $.proxy(function() {
1086 if(state) {
1087 // Prevent antialias from disappearing in IE by removing filter
1088 if(BROWSER.ie) { tooltip[0].style.removeAttribute('filter'); }
1089
1090 // Remove overflow setting to prevent tip bugs
1091 tooltip.css('overflow', '');
1092
1093 // Autofocus elements if enabled
1094 if('string' === typeof opts.autofocus) {
1095 $(this.options.show.autofocus, tooltip).focus();
1096 }
1097
1098 // If set, hide tooltip when inactive for delay period
1099 this.options.show.target.trigger('qtip-'+this.id+'-inactive');
1100 }
1101 else {
1102 // Reset CSS states
1103 tooltip.css({
1104 display: '',
1105 visibility: '',
1106 opacity: '',
1107 left: '',
1108 top: ''
1109 });
1110 }
1111
1112 // tooltipvisible/tooltiphidden events
1113 this._trigger(state ? 'visible' : 'hidden');
1114 }, this);
1115
1116 // If no effect type is supplied, use a simple toggle
1117 if(opts.effect === FALSE || animate === FALSE) {
1118 tooltip[ type ]();
1119 after();
1120 }
1121
1122 // Use custom function if provided
1123 else if($.isFunction(opts.effect)) {
1124 tooltip.stop(1, 1);
1125 opts.effect.call(tooltip, this);
1126 tooltip.queue('fx', function(n) {
1127 after(); n();
1128 });
1129 }
1130
1131 // Use basic fade function by default
1132 else { tooltip.fadeTo(90, state ? 1 : 0, after); }
1133
1134 // If inactive hide method is set, active it
1135 if(state) { opts.target.trigger('qtip-'+this.id+'-inactive'); }
1136
1137 return this;
1138 };
1139
1140 PROTOTYPE.show = function(event) { return this.toggle(TRUE, event); };
1141
1142 PROTOTYPE.hide = function(event) { return this.toggle(FALSE, event); };
1143 ;PROTOTYPE.focus = function(event) {
1144 if(!this.rendered || this.destroyed) { return this; }
1145
1146 var qtips = $(SELECTOR),
1147 tooltip = this.tooltip,
1148 curIndex = parseInt(tooltip[0].style.zIndex, 10),
1149 newIndex = QTIP.zindex + qtips.length,
1150 focusedElem;
1151
1152 // Only update the z-index if it has changed and tooltip is not already focused
1153 if(!tooltip.hasClass(CLASS_FOCUS)) {
1154 // tooltipfocus event
1155 if(this._trigger('focus', [newIndex], event)) {
1156 // Only update z-index's if they've changed
1157 if(curIndex !== newIndex) {
1158 // Reduce our z-index's and keep them properly ordered
1159 qtips.each(function() {
1160 if(this.style.zIndex > curIndex) {
1161 this.style.zIndex = this.style.zIndex - 1;
1162 }
1163 });
1164
1165 // Fire blur event for focused tooltip
1166 qtips.filter('.' + CLASS_FOCUS).qtip('blur', event);
1167 }
1168
1169 // Set the new z-index
1170 tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;
1171 }
1172 }
1173
1174 return this;
1175 };
1176
1177 PROTOTYPE.blur = function(event) {
1178 if(!this.rendered || this.destroyed) { return this; }
1179
1180 // Set focused status to FALSE
1181 this.tooltip.removeClass(CLASS_FOCUS);
1182
1183 // tooltipblur event
1184 this._trigger('blur', [ this.tooltip.css('zIndex') ], event);
1185
1186 return this;
1187 };
1188 ;PROTOTYPE.disable = function(state) {
1189 if(this.destroyed) { return this; }
1190
1191 // If 'toggle' is passed, toggle the current state
1192 if(state === 'toggle') {
1193 state = !(this.rendered ? this.tooltip.hasClass(CLASS_DISABLED) : this.disabled);
1194 }
1195
1196 // Disable if no state passed
1197 else if('boolean' !== typeof state) {
1198 state = TRUE;
1199 }
1200
1201 if(this.rendered) {
1202 this.tooltip.toggleClass(CLASS_DISABLED, state)
1203 .attr('aria-disabled', state);
1204 }
1205
1206 this.disabled = !!state;
1207
1208 return this;
1209 };
1210
1211 PROTOTYPE.enable = function() { return this.disable(FALSE); };
1212 ;PROTOTYPE._createButton = function()
1213 {
1214 var self = this,
1215 elements = this.elements,
1216 tooltip = elements.tooltip,
1217 button = this.options.content.button,
1218 isString = typeof button === 'string',
1219 close = isString ? button : 'Close tooltip';
1220
1221 if(elements.button) { elements.button.remove(); }
1222
1223 // Use custom button if one was supplied by user, else use default
1224 if(button.jquery) {
1225 elements.button = button;
1226 }
1227 else {
1228 elements.button = $('<a />', {
1229 'class': 'qtip-close ' + (this.options.style.widget ? '' : NAMESPACE+'-icon'),
1230 'title': close,
1231 'aria-label': close
1232 })
1233 .prepend(
1234 $('<span />', {
1235 'class': 'ui-icon ui-icon-close',
1236 'html': '&times;'
1237 })
1238 );
1239 }
1240
1241 // Create button and setup attributes
1242 elements.button.appendTo(elements.titlebar || tooltip)
1243 .attr('role', 'button')
1244 .click(function(event) {
1245 if(!tooltip.hasClass(CLASS_DISABLED)) { self.hide(event); }
1246 return FALSE;
1247 });
1248 };
1249
1250 PROTOTYPE._updateButton = function(button)
1251 {
1252 // Make sure tooltip is rendered and if not, return
1253 if(!this.rendered) { return FALSE; }
1254
1255 var elem = this.elements.button;
1256 if(button) { this._createButton(); }
1257 else { elem.remove(); }
1258 };
1259 ;// Widget class creator
1260 function createWidgetClass(cls) {
1261 return WIDGET.concat('').join(cls ? '-'+cls+' ' : ' ');
1262 }
1263
1264 // Widget class setter method
1265 PROTOTYPE._setWidget = function()
1266 {
1267 var on = this.options.style.widget,
1268 elements = this.elements,
1269 tooltip = elements.tooltip,
1270 disabled = tooltip.hasClass(CLASS_DISABLED);
1271
1272 tooltip.removeClass(CLASS_DISABLED);
1273 CLASS_DISABLED = on ? 'ui-state-disabled' : 'qtip-disabled';
1274 tooltip.toggleClass(CLASS_DISABLED, disabled);
1275
1276 tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(CLASS_DEFAULT, this.options.style.def && !on);
1277
1278 if(elements.content) {
1279 elements.content.toggleClass( createWidgetClass('content'), on);
1280 }
1281 if(elements.titlebar) {
1282 elements.titlebar.toggleClass( createWidgetClass('header'), on);
1283 }
1284 if(elements.button) {
1285 elements.button.toggleClass(NAMESPACE+'-icon', !on);
1286 }
1287 };
1288 ;function delay(callback, duration) {
1289 // If tooltip has displayed, start hide timer
1290 if(duration > 0) {
1291 return setTimeout(
1292 $.proxy(callback, this), duration
1293 );
1294 }
1295 else{ callback.call(this); }
1296 }
1297
1298 function showMethod(event) {
1299 if(this.tooltip.hasClass(CLASS_DISABLED)) { return; }
1300
1301 // Clear hide timers
1302 clearTimeout(this.timers.show);
1303 clearTimeout(this.timers.hide);
1304
1305 // Start show timer
1306 this.timers.show = delay.call(this,
1307 function() { this.toggle(TRUE, event); },
1308 this.options.show.delay
1309 );
1310 }
1311
1312 function hideMethod(event) {
1313 if(this.tooltip.hasClass(CLASS_DISABLED) || this.destroyed) { return; }
1314
1315 // Check if new target was actually the tooltip element
1316 var relatedTarget = $(event.relatedTarget),
1317 ontoTooltip = relatedTarget.closest(SELECTOR)[0] === this.tooltip[0],
1318 ontoTarget = relatedTarget[0] === this.options.show.target[0];
1319
1320 // Clear timers and stop animation queue
1321 clearTimeout(this.timers.show);
1322 clearTimeout(this.timers.hide);
1323
1324 // Prevent hiding if tooltip is fixed and event target is the tooltip.
1325 // Or if mouse positioning is enabled and cursor momentarily overlaps
1326 if(this !== relatedTarget[0] &&
1327 (this.options.position.target === 'mouse' && ontoTooltip) ||
1328 (this.options.hide.fixed && (
1329 (/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget))
1330 ))
1331 {
1332 try {
1333 event.preventDefault();
1334 event.stopImmediatePropagation();
1335 } catch(e) {}
1336
1337 return;
1338 }
1339
1340 // If tooltip has displayed, start hide timer
1341 this.timers.hide = delay.call(this,
1342 function() { this.toggle(FALSE, event); },
1343 this.options.hide.delay,
1344 this
1345 );
1346 }
1347
1348 function inactiveMethod(event) {
1349 if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return; }
1350
1351 // Clear timer
1352 clearTimeout(this.timers.inactive);
1353
1354 this.timers.inactive = delay.call(this,
1355 function(){ this.hide(event); },
1356 this.options.hide.inactive
1357 );
1358 }
1359
1360 function repositionMethod(event) {
1361 if(this.rendered && this.tooltip[0].offsetWidth > 0) { this.reposition(event); }
1362 }
1363
1364 // Store mouse coordinates
1365 PROTOTYPE._storeMouse = function(event) {
1366 (this.mouse = $.event.fix(event)).type = 'mousemove';
1367 return this;
1368 };
1369
1370 // Bind events
1371 PROTOTYPE._bind = function(targets, events, method, suffix, context) {
1372 if(!targets || !method || !events.length) { return; }
1373 var ns = '.' + this._id + (suffix ? '-'+suffix : '');
1374 $(targets).bind(
1375 (events.split ? events : events.join(ns + ' ')) + ns,
1376 $.proxy(method, context || this)
1377 );
1378 return this;
1379 };
1380 PROTOTYPE._unbind = function(targets, suffix) {
1381 targets && $(targets).unbind('.' + this._id + (suffix ? '-'+suffix : ''));
1382 return this;
1383 };
1384
1385 // Global delegation helper
1386 function delegate(selector, events, method) {
1387 $(document.body).delegate(selector,
1388 (events.split ? events : events.join('.'+NAMESPACE + ' ')) + '.'+NAMESPACE,
1389 function() {
1390 var api = QTIP.api[ $.attr(this, ATTR_ID) ];
1391 api && !api.disabled && method.apply(api, arguments);
1392 }
1393 );
1394 }
1395 // Event trigger
1396 PROTOTYPE._trigger = function(type, args, event) {
1397 var callback = $.Event('tooltip'+type);
1398 callback.originalEvent = (event && $.extend({}, event)) || this.cache.event || NULL;
1399
1400 this.triggering = type;
1401 this.tooltip.trigger(callback, [this].concat(args || []));
1402 this.triggering = FALSE;
1403
1404 return !callback.isDefaultPrevented();
1405 };
1406
1407 PROTOTYPE._bindEvents = function(showEvents, hideEvents, showTargets, hideTargets, showMethod, hideMethod) {
1408 // Get tasrgets that lye within both
1409 var similarTargets = showTargets.filter( hideTargets ).add( hideTargets.filter(showTargets) ),
1410 toggleEvents = [];
1411
1412 // If hide and show targets are the same...
1413 if(similarTargets.length) {
1414
1415 // Filter identical show/hide events
1416 $.each(hideEvents, function(i, type) {
1417 var showIndex = $.inArray(type, showEvents);
1418
1419 // Both events are identical, remove from both hide and show events
1420 // and append to toggleEvents
1421 showIndex > -1 && toggleEvents.push( showEvents.splice( showIndex, 1 )[0] );
1422 });
1423
1424 // Toggle events are special case of identical show/hide events, which happen in sequence
1425 if(toggleEvents.length) {
1426 // Bind toggle events to the similar targets
1427 this._bind(similarTargets, toggleEvents, function(event) {
1428 var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false;
1429 (state ? hideMethod : showMethod).call(this, event);
1430 });
1431
1432 // Remove the similar targets from the regular show/hide bindings
1433 showTargets = showTargets.not(similarTargets);
1434 hideTargets = hideTargets.not(similarTargets);
1435 }
1436 }
1437
1438 // Apply show/hide/toggle events
1439 this._bind(showTargets, showEvents, showMethod);
1440 this._bind(hideTargets, hideEvents, hideMethod);
1441 };
1442
1443 PROTOTYPE._assignInitialEvents = function(event) {
1444 var options = this.options,
1445 showTarget = options.show.target,
1446 hideTarget = options.hide.target,
1447 showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
1448 hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];
1449
1450 // Catch remove/removeqtip events on target element to destroy redundant tooltips
1451 this._bind(this.elements.target, ['remove', 'removeqtip'], function(event) {
1452 this.destroy(true);
1453 }, 'destroy');
1454
1455 /*
1456 * Make sure hoverIntent functions properly by using mouseleave as a hide event if
1457 * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
1458 */
1459 if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) {
1460 hideEvents.push('mouseleave');
1461 }
1462
1463 /*
1464 * Also make sure initial mouse targetting works correctly by caching mousemove coords
1465 * on show targets before the tooltip has rendered. Also set onTarget when triggered to
1466 * keep mouse tracking working.
1467 */
1468 this._bind(showTarget, 'mousemove', function(event) {
1469 this._storeMouse(event);
1470 this.cache.onTarget = TRUE;
1471 });
1472
1473 // Define hoverIntent function
1474 function hoverIntent(event) {
1475 // Only continue if tooltip isn't disabled
1476 if(this.disabled || this.destroyed) { return FALSE; }
1477
1478 // Cache the event data
1479 this.cache.event = event && $.event.fix(event);
1480 this.cache.target = event && $(event.target);
1481
1482 // Start the event sequence
1483 clearTimeout(this.timers.show);
1484 this.timers.show = delay.call(this,
1485 function() { this.render(typeof event === 'object' || options.show.ready); },
1486 options.prerender ? 0 : options.show.delay
1487 );
1488 }
1489
1490 // Filter and bind events
1491 this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, hoverIntent, function() {
1492 if(!this.timers) { return FALSE; }
1493 clearTimeout(this.timers.show);
1494 });
1495
1496 // Prerendering is enabled, create tooltip now
1497 if(options.show.ready || options.prerender) { hoverIntent.call(this, event); }
1498 };
1499
1500 // Event assignment method
1501 PROTOTYPE._assignEvents = function() {
1502 var self = this,
1503 options = this.options,
1504 posOptions = options.position,
1505
1506 tooltip = this.tooltip,
1507 showTarget = options.show.target,
1508 hideTarget = options.hide.target,
1509 containerTarget = posOptions.container,
1510 viewportTarget = posOptions.viewport,
1511 documentTarget = $(document),
1512 bodyTarget = $(document.body),
1513 windowTarget = $(window),
1514
1515 showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
1516 hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];
1517
1518
1519 // Assign passed event callbacks
1520 $.each(options.events, function(name, callback) {
1521 self._bind(tooltip, name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name], callback, null, tooltip);
1522 });
1523
1524 // Hide tooltips when leaving current window/frame (but not select/option elements)
1525 if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') {
1526 this._bind(documentTarget, ['mouseout', 'blur'], function(event) {
1527 if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) {
1528 this.hide(event);
1529 }
1530 });
1531 }
1532
1533 // Enable hide.fixed by adding appropriate class
1534 if(options.hide.fixed) {
1535 hideTarget = hideTarget.add( tooltip.addClass(CLASS_FIXED) );
1536 }
1537
1538 /*
1539 * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
1540 * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
1541 */
1542 else if(/mouse(over|enter)/i.test(options.show.event)) {
1543 this._bind(hideTarget, 'mouseleave', function() {
1544 clearTimeout(this.timers.show);
1545 });
1546 }
1547
1548 // Hide tooltip on document mousedown if unfocus events are enabled
1549 if(('' + options.hide.event).indexOf('unfocus') > -1) {
1550 this._bind(containerTarget.closest('html'), ['mousedown', 'touchstart'], function(event) {
1551 var elem = $(event.target),
1552 enabled = this.rendered && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0,
1553 isAncestor = elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0;
1554
1555 if(elem[0] !== this.target[0] && elem[0] !== this.tooltip[0] && !isAncestor &&
1556 !this.target.has(elem[0]).length && enabled
1557 ) {
1558 this.hide(event);
1559 }
1560 });
1561 }
1562
1563 // Check if the tooltip hides when inactive
1564 if('number' === typeof options.hide.inactive) {
1565 // Bind inactive method to show target(s) as a custom event
1566 this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod, 'inactive');
1567
1568 // Define events which reset the 'inactive' event handler
1569 this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod);
1570 }
1571
1572 // Filter and bind events
1573 this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod);
1574
1575 // Mouse movement bindings
1576 this._bind(showTarget.add(tooltip), 'mousemove', function(event) {
1577 // Check if the tooltip hides when mouse is moved a certain distance
1578 if('number' === typeof options.hide.distance) {
1579 var origin = this.cache.origin || {},
1580 limit = this.options.hide.distance,
1581 abs = Math.abs;
1582
1583 // Check if the movement has gone beyond the limit, and hide it if so
1584 if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) {
1585 this.hide(event);
1586 }
1587 }
1588
1589 // Cache mousemove coords on show targets
1590 this._storeMouse(event);
1591 });
1592
1593 // Mouse positioning events
1594 if(posOptions.target === 'mouse') {
1595 // If mouse adjustment is on...
1596 if(posOptions.adjust.mouse) {
1597 // Apply a mouseleave event so we don't get problems with overlapping
1598 if(options.hide.event) {
1599 // Track if we're on the target or not
1600 this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) {
1601 if(!this.cache) {return FALSE; }
1602 this.cache.onTarget = event.type === 'mouseenter';
1603 });
1604 }
1605
1606 // Update tooltip position on mousemove
1607 this._bind(documentTarget, 'mousemove', function(event) {
1608 // Update the tooltip position only if the tooltip is visible and adjustment is enabled
1609 if(this.rendered && this.cache.onTarget && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0) {
1610 this.reposition(event);
1611 }
1612 });
1613 }
1614 }
1615
1616 // Adjust positions of the tooltip on window resize if enabled
1617 if(posOptions.adjust.resize || viewportTarget.length) {
1618 this._bind( $.event.special.resize ? viewportTarget : windowTarget, 'resize', repositionMethod );
1619 }
1620
1621 // Adjust tooltip position on scroll of the window or viewport element if present
1622 if(posOptions.adjust.scroll) {
1623 this._bind( windowTarget.add(posOptions.container), 'scroll', repositionMethod );
1624 }
1625 };
1626
1627 // Un-assignment method
1628 PROTOTYPE._unassignEvents = function() {
1629 var options = this.options,
1630 showTargets = options.show.target,
1631 hideTargets = options.hide.target,
1632 targets = $.grep([
1633 this.elements.target[0],
1634 this.rendered && this.tooltip[0],
1635 options.position.container[0],
1636 options.position.viewport[0],
1637 options.position.container.closest('html')[0], // unfocus
1638 window,
1639 document
1640 ], function(i) {
1641 return typeof i === 'object';
1642 });
1643
1644 // Add show and hide targets if they're valid
1645 if(showTargets && showTargets.toArray) {
1646 targets = targets.concat(showTargets.toArray());
1647 }
1648 if(hideTargets && hideTargets.toArray) {
1649 targets = targets.concat(hideTargets.toArray());
1650 }
1651
1652 // Unbind the events
1653 this._unbind(targets)
1654 ._unbind(targets, 'destroy')
1655 ._unbind(targets, 'inactive');
1656 };
1657
1658 // Apply common event handlers using delegate (avoids excessive .bind calls!)
1659 $(function() {
1660 delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) {
1661 var state = event.type === 'mouseenter',
1662 tooltip = $(event.currentTarget),
1663 target = $(event.relatedTarget || event.target),
1664 options = this.options;
1665
1666 // On mouseenter...
1667 if(state) {
1668 // Focus the tooltip on mouseenter (z-index stacking)
1669 this.focus(event);
1670
1671 // Clear hide timer on tooltip hover to prevent it from closing
1672 tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide);
1673 }
1674
1675 // On mouseleave...
1676 else {
1677 // When mouse tracking is enabled, hide when we leave the tooltip and not onto the show target (if a hide event is set)
1678 if(options.position.target === 'mouse' && options.position.adjust.mouse &&
1679 options.hide.event && options.show.target && !target.closest(options.show.target[0]).length) {
1680 this.hide(event);
1681 }
1682 }
1683
1684 // Add hover class
1685 tooltip.toggleClass(CLASS_HOVER, state);
1686 });
1687
1688 // Define events which reset the 'inactive' event handler
1689 delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod);
1690 });
1691 ;// Initialization method
1692 function init(elem, id, opts) {
1693 var obj, posOptions, attr, config, title,
1694
1695 // Setup element references
1696 docBody = $(document.body),
1697
1698 // Use document body instead of document element if needed
1699 newTarget = elem[0] === document ? docBody : elem,
1700
1701 // Grab metadata from element if plugin is present
1702 metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL,
1703
1704 // If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
1705 metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,
1706
1707 // Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
1708 html5 = elem.data(opts.metadata.name || 'qtipopts');
1709
1710 // If we don't get an object returned attempt to parse it manualyl without parseJSON
1711 try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; } catch(e) {}
1712
1713 // Merge in and sanitize metadata
1714 config = $.extend(TRUE, {}, QTIP.defaults, opts,
1715 typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,
1716 sanitizeOptions(metadata5 || metadata));
1717
1718 // Re-grab our positioning options now we've merged our metadata and set id to passed value
1719 posOptions = config.position;
1720 config.id = id;
1721
1722 // Setup missing content if none is detected
1723 if('boolean' === typeof config.content.text) {
1724 attr = elem.attr(config.content.attr);
1725
1726 // Grab from supplied attribute if available
1727 if(config.content.attr !== FALSE && attr) { config.content.text = attr; }
1728
1729 // No valid content was found, abort render
1730 else { return FALSE; }
1731 }
1732
1733 // Setup target options
1734 if(!posOptions.container.length) { posOptions.container = docBody; }
1735 if(posOptions.target === FALSE) { posOptions.target = newTarget; }
1736 if(config.show.target === FALSE) { config.show.target = newTarget; }
1737 if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); }
1738 if(config.hide.target === FALSE) { config.hide.target = newTarget; }
1739 if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }
1740
1741 // Ensure we only use a single container
1742 posOptions.container = posOptions.container.eq(0);
1743
1744 // Convert position corner values into x and y strings
1745 posOptions.at = new CORNER(posOptions.at, TRUE);
1746 posOptions.my = new CORNER(posOptions.my);
1747
1748 // Destroy previous tooltip if overwrite is enabled, or skip element if not
1749 if(elem.data(NAMESPACE)) {
1750 if(config.overwrite) {
1751 elem.qtip('destroy', true);
1752 }
1753 else if(config.overwrite === FALSE) {
1754 return FALSE;
1755 }
1756 }
1757
1758 // Add has-qtip attribute
1759 elem.attr(ATTR_HAS, id);
1760
1761 // Remove title attribute and store it if present
1762 if(config.suppress && (title = elem.attr('title'))) {
1763 // Final attr call fixes event delegatiom and IE default tooltip showing problem
1764 elem.removeAttr('title').attr(oldtitle, title).attr('title', '');
1765 }
1766
1767 // Initialize the tooltip and add API reference
1768 obj = new QTip(elem, config, id, !!attr);
1769 elem.data(NAMESPACE, obj);
1770
1771 return obj;
1772 }
1773
1774 // jQuery $.fn extension method
1775 QTIP = $.fn.qtip = function(options, notation, newValue)
1776 {
1777 var command = ('' + options).toLowerCase(), // Parse command
1778 returned = NULL,
1779 args = $.makeArray(arguments).slice(1),
1780 event = args[args.length - 1],
1781 opts = this[0] ? $.data(this[0], NAMESPACE) : NULL;
1782
1783 // Check for API request
1784 if((!arguments.length && opts) || command === 'api') {
1785 return opts;
1786 }
1787
1788 // Execute API command if present
1789 else if('string' === typeof options) {
1790 this.each(function() {
1791 var api = $.data(this, NAMESPACE);
1792 if(!api) { return TRUE; }
1793
1794 // Cache the event if possible
1795 if(event && event.timeStamp) { api.cache.event = event; }
1796
1797 // Check for specific API commands
1798 if(notation && (command === 'option' || command === 'options')) {
1799 if(newValue !== undefined || $.isPlainObject(notation)) {
1800 api.set(notation, newValue);
1801 }
1802 else {
1803 returned = api.get(notation);
1804 return FALSE;
1805 }
1806 }
1807
1808 // Execute API command
1809 else if(api[command]) {
1810 api[command].apply(api, args);
1811 }
1812 });
1813
1814 return returned !== NULL ? returned : this;
1815 }
1816
1817 // No API commands. validate provided options and setup qTips
1818 else if('object' === typeof options || !arguments.length) {
1819 // Sanitize options first
1820 opts = sanitizeOptions($.extend(TRUE, {}, options));
1821
1822 return this.each(function(i) {
1823 var api, id;
1824
1825 // Find next available ID, or use custom ID if provided
1826 id = $.isArray(opts.id) ? opts.id[i] : opts.id;
1827 id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id;
1828
1829 // Initialize the qTip and re-grab newly sanitized options
1830 api = init($(this), id, opts);
1831 if(api === FALSE) { return TRUE; }
1832 else { QTIP.api[id] = api; }
1833
1834 // Initialize plugins
1835 $.each(PLUGINS, function() {
1836 if(this.initialize === 'initialize') { this(api); }
1837 });
1838
1839 // Assign initial pre-render events
1840 api._assignInitialEvents(event);
1841 });
1842 }
1843 };
1844
1845 // Expose class
1846 $.qtip = QTip;
1847
1848 // Populated in render method
1849 QTIP.api = {};
1850 ;$.each({
1851 /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
1852 attr: function(attr, val) {
1853 if(this.length) {
1854 var self = this[0],
1855 title = 'title',
1856 api = $.data(self, 'qtip');
1857
1858 if(attr === title && api && 'object' === typeof api && api.options.suppress) {
1859 if(arguments.length < 2) {
1860 return $.attr(self, oldtitle);
1861 }
1862
1863 // If qTip is rendered and title was originally used as content, update it
1864 if(api && api.options.content.attr === title && api.cache.attr) {
1865 api.set('content.text', val);
1866 }
1867
1868 // Use the regular attr method to set, then cache the result
1869 return this.attr(oldtitle, val);
1870 }
1871 }
1872
1873 return $.fn['attr'+replaceSuffix].apply(this, arguments);
1874 },
1875
1876 /* Allow clone to correctly retrieve cached title attributes */
1877 clone: function(keepData) {
1878 var titles = $([]), title = 'title',
1879
1880 // Clone our element using the real clone method
1881 elems = $.fn['clone'+replaceSuffix].apply(this, arguments);
1882
1883 // Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false
1884 if(!keepData) {
1885 elems.filter('['+oldtitle+']').attr('title', function() {
1886 return $.attr(this, oldtitle);
1887 })
1888 .removeAttr(oldtitle);
1889 }
1890
1891 return elems;
1892 }
1893 }, function(name, func) {
1894 if(!func || $.fn[name+replaceSuffix]) { return TRUE; }
1895
1896 var old = $.fn[name+replaceSuffix] = $.fn[name];
1897 $.fn[name] = function() {
1898 return func.apply(this, arguments) || old.apply(this, arguments);
1899 };
1900 });
1901
1902 /* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar).
1903 * This snippet is taken directly from jQuery UI source code found here:
1904 * http://code.jquery.com/ui/jquery-ui-git.js
1905 */
1906 if(!$.ui) {
1907 $['cleanData'+replaceSuffix] = $.cleanData;
1908 $.cleanData = function( elems ) {
1909 for(var i = 0, elem; (elem = $( elems[i] )).length; i++) {
1910 if(elem.attr(ATTR_HAS)) {
1911 try { elem.triggerHandler('removeqtip'); }
1912 catch( e ) {}
1913 }
1914 }
1915 $['cleanData'+replaceSuffix].apply(this, arguments);
1916 };
1917 }
1918 ;// qTip version
1919 QTIP.version = '2.2.1';
1920
1921 // Base ID for all qTips
1922 QTIP.nextid = 0;
1923
1924 // Inactive events array
1925 QTIP.inactiveEvents = INACTIVE_EVENTS;
1926
1927 // Base z-index for all qTips
1928 QTIP.zindex = 15000;
1929
1930 // Define configuration defaults
1931 QTIP.defaults = {
1932 prerender: FALSE,
1933 id: FALSE,
1934 overwrite: TRUE,
1935 suppress: TRUE,
1936 content: {
1937 text: TRUE,
1938 attr: 'title',
1939 title: FALSE,
1940 button: FALSE
1941 },
1942 position: {
1943 my: 'top left',
1944 at: 'bottom right',
1945 target: FALSE,
1946 container: FALSE,
1947 viewport: FALSE,
1948 adjust: {
1949 x: 0, y: 0,
1950 mouse: TRUE,
1951 scroll: TRUE,
1952 resize: TRUE,
1953 method: 'flipinvert flipinvert'
1954 },
1955 effect: function(api, pos, viewport) {
1956 $(this).animate(pos, {
1957 duration: 200,
1958 queue: FALSE
1959 });
1960 }
1961 },
1962 show: {
1963 target: FALSE,
1964 event: 'mouseenter',
1965 effect: TRUE,
1966 delay: 90,
1967 solo: FALSE,
1968 ready: FALSE,
1969 autofocus: FALSE
1970 },
1971 hide: {
1972 target: FALSE,
1973 event: 'mouseleave',
1974 effect: TRUE,
1975 delay: 0,
1976 fixed: FALSE,
1977 inactive: FALSE,
1978 leave: 'window',
1979 distance: FALSE
1980 },
1981 style: {
1982 classes: '',
1983 widget: FALSE,
1984 width: FALSE,
1985 height: FALSE,
1986 def: TRUE
1987 },
1988 events: {
1989 render: NULL,
1990 move: NULL,
1991 show: NULL,
1992 hide: NULL,
1993 toggle: NULL,
1994 visible: NULL,
1995 hidden: NULL,
1996 focus: NULL,
1997 blur: NULL
1998 }
1999 };
2000 ;var TIP,
2001
2002 // .bind()/.on() namespace
2003 TIPNS = '.qtip-tip',
2004
2005 // Common CSS strings
2006 MARGIN = 'margin',
2007 BORDER = 'border',
2008 COLOR = 'color',
2009 BG_COLOR = 'background-color',
2010 TRANSPARENT = 'transparent',
2011 IMPORTANT = ' !important',
2012
2013 // Check if the browser supports <canvas/> elements
2014 HASCANVAS = !!document.createElement('canvas').getContext,
2015
2016 // Invalid colour values used in parseColours()
2017 INVALID = /rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i;
2018
2019 // Camel-case method, taken from jQuery source
2020 // http://code.jquery.com/jquery-1.8.0.js
2021 function camel(s) { return s.charAt(0).toUpperCase() + s.slice(1); }
2022
2023 /*
2024 * Modified from Modernizr's testPropsAll()
2025 * http://modernizr.com/downloads/modernizr-latest.js
2026 */
2027 var cssProps = {}, cssPrefixes = ["Webkit", "O", "Moz", "ms"];
2028 function vendorCss(elem, prop) {
2029 var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
2030 props = (prop + ' ' + cssPrefixes.join(ucProp + ' ') + ucProp).split(' '),
2031 cur, val, i = 0;
2032
2033 // If the property has already been mapped...
2034 if(cssProps[prop]) { return elem.css(cssProps[prop]); }
2035
2036 while((cur = props[i++])) {
2037 if((val = elem.css(cur)) !== undefined) {
2038 return cssProps[prop] = cur, val;
2039 }
2040 }
2041 }
2042
2043 // Parse a given elements CSS property into an int
2044 function intCss(elem, prop) {
2045 return Math.ceil(parseFloat(vendorCss(elem, prop)));
2046 }
2047
2048
2049 // VML creation (for IE only)
2050 if(!HASCANVAS) {
2051 var createVML = function(tag, props, style) {
2052 return '<qtipvml:'+tag+' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" '+(props||'')+
2053 ' style="behavior: url(#default#VML); '+(style||'')+ '" />';
2054 };
2055 }
2056
2057 // Canvas only definitions
2058 else {
2059 var PIXEL_RATIO = window.devicePixelRatio || 1,
2060 BACKING_STORE_RATIO = (function() {
2061 var context = document.createElement('canvas').getContext('2d');
2062 return context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio ||
2063 context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || 1;
2064 }()),
2065 SCALE = PIXEL_RATIO / BACKING_STORE_RATIO;
2066 }
2067
2068
2069 function Tip(qtip, options) {
2070 this._ns = 'tip';
2071 this.options = options;
2072 this.offset = options.offset;
2073 this.size = [ options.width, options.height ];
2074
2075 // Initialize
2076 this.init( (this.qtip = qtip) );
2077 }
2078
2079 $.extend(Tip.prototype, {
2080 init: function(qtip) {
2081 var context, tip;
2082
2083 // Create tip element and prepend to the tooltip
2084 tip = this.element = qtip.elements.tip = $('<div />', { 'class': NAMESPACE+'-tip' }).prependTo(qtip.tooltip);
2085
2086 // Create tip drawing element(s)
2087 if(HASCANVAS) {
2088 // save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()!
2089 context = $('<canvas />').appendTo(this.element)[0].getContext('2d');
2090
2091 // Setup constant parameters
2092 context.lineJoin = 'miter';
2093 context.miterLimit = 100000;
2094 context.save();
2095 }
2096 else {
2097 context = createVML('shape', 'coordorigin="0,0"', 'position:absolute;');
2098 this.element.html(context + context);
2099
2100 // Prevent mousing down on the tip since it causes problems with .live() handling in IE due to VML
2101 qtip._bind( $('*', tip).add(tip), ['click', 'mousedown'], function(event) { event.stopPropagation(); }, this._ns);
2102 }
2103
2104 // Bind update events
2105 qtip._bind(qtip.tooltip, 'tooltipmove', this.reposition, this._ns, this);
2106
2107 // Create it
2108 this.create();
2109 },
2110
2111 _swapDimensions: function() {
2112 this.size[0] = this.options.height;
2113 this.size[1] = this.options.width;
2114 },
2115 _resetDimensions: function() {
2116 this.size[0] = this.options.width;
2117 this.size[1] = this.options.height;
2118 },
2119
2120 _useTitle: function(corner) {
2121 var titlebar = this.qtip.elements.titlebar;
2122 return titlebar && (
2123 corner.y === TOP || (corner.y === CENTER && this.element.position().top + (this.size[1] / 2) + this.options.offset < titlebar.outerHeight(TRUE))
2124 );
2125 },
2126
2127 _parseCorner: function(corner) {
2128 var my = this.qtip.options.position.my;
2129
2130 // Detect corner and mimic properties
2131 if(corner === FALSE || my === FALSE) {
2132 corner = FALSE;
2133 }
2134 else if(corner === TRUE) {
2135 corner = new CORNER( my.string() );
2136 }
2137 else if(!corner.string) {
2138 corner = new CORNER(corner);
2139 corner.fixed = TRUE;
2140 }
2141
2142 return corner;
2143 },
2144
2145 _parseWidth: function(corner, side, use) {
2146 var elements = this.qtip.elements,
2147 prop = BORDER + camel(side) + 'Width';
2148
2149 return (use ? intCss(use, prop) : (
2150 intCss(elements.content, prop) ||
2151 intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||
2152 intCss(elements.tooltip, prop)
2153 )) || 0;
2154 },
2155
2156 _parseRadius: function(corner) {
2157 var elements = this.qtip.elements,
2158 prop = BORDER + camel(corner.y) + camel(corner.x) + 'Radius';
2159
2160 return BROWSER.ie < 9 ? 0 :
2161 intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||
2162 intCss(elements.tooltip, prop) || 0;
2163 },
2164
2165 _invalidColour: function(elem, prop, compare) {
2166 var val = elem.css(prop);
2167 return !val || (compare && val === elem.css(compare)) || INVALID.test(val) ? FALSE : val;
2168 },
2169
2170 _parseColours: function(corner) {
2171 var elements = this.qtip.elements,
2172 tip = this.element.css('cssText', ''),
2173 borderSide = BORDER + camel(corner[ corner.precedance ]) + camel(COLOR),
2174 colorElem = this._useTitle(corner) && elements.titlebar || elements.content,
2175 css = this._invalidColour, color = [];
2176
2177 // Attempt to detect the background colour from various elements, left-to-right precedance
2178 color[0] = css(tip, BG_COLOR) || css(colorElem, BG_COLOR) || css(elements.content, BG_COLOR) ||
2179 css(elements.tooltip, BG_COLOR) || tip.css(BG_COLOR);
2180
2181 // Attempt to detect the correct border side colour from various elements, left-to-right precedance
2182 color[1] = css(tip, borderSide, COLOR) || css(colorElem, borderSide, COLOR) ||
2183 css(elements.content, borderSide, COLOR) || css(elements.tooltip, borderSide, COLOR) || elements.tooltip.css(borderSide);
2184
2185 // Reset background and border colours
2186 $('*', tip).add(tip).css('cssText', BG_COLOR+':'+TRANSPARENT+IMPORTANT+';'+BORDER+':0'+IMPORTANT+';');
2187
2188 return color;
2189 },
2190
2191 _calculateSize: function(corner) {
2192 var y = corner.precedance === Y,
2193 width = this.options['width'],
2194 height = this.options['height'],
2195 isCenter = corner.abbrev() === 'c',
2196 base = (y ? width: height) * (isCenter ? 0.5 : 1),
2197 pow = Math.pow,
2198 round = Math.round,
2199 bigHyp, ratio, result,
2200
2201 smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ),
2202 hyp = [ (this.border / base) * smallHyp, (this.border / height) * smallHyp ];
2203
2204 hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(this.border, 2) );
2205 hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(this.border, 2) );
2206
2207 bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]);
2208 ratio = bigHyp / smallHyp;
2209
2210 result = [ round(ratio * width), round(ratio * height) ];
2211 return y ? result : result.reverse();
2212 },
2213
2214 // Tip coordinates calculator
2215 _calculateTip: function(corner, size, scale) {
2216 scale = scale || 1;
2217 size = size || this.size;
2218
2219 var width = size[0] * scale,
2220 height = size[1] * scale,
2221 width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2),
2222
2223 // Define tip coordinates in terms of height and width values
2224 tips = {
2225 br: [0,0, width,height, width,0],
2226 bl: [0,0, width,0, 0,height],
2227 tr: [0,height, width,0, width,height],
2228 tl: [0,0, 0,height, width,height],
2229 tc: [0,height, width2,0, width,height],
2230 bc: [0,0, width,0, width2,height],
2231 rc: [0,0, width,height2, 0,height],
2232 lc: [width,0, width,height, 0,height2]
2233 };
2234
2235 // Set common side shapes
2236 tips.lt = tips.br; tips.rt = tips.bl;
2237 tips.lb = tips.tr; tips.rb = tips.tl;
2238
2239 return tips[ corner.abbrev() ];
2240 },
2241
2242 // Tip coordinates drawer (canvas)
2243 _drawCoords: function(context, coords) {
2244 context.beginPath();
2245 context.moveTo(coords[0], coords[1]);
2246 context.lineTo(coords[2], coords[3]);
2247 context.lineTo(coords[4], coords[5]);
2248 context.closePath();
2249 },
2250
2251 create: function() {
2252 // Determine tip corner
2253 var c = this.corner = (HASCANVAS || BROWSER.ie) && this._parseCorner(this.options.corner);
2254
2255 // If we have a tip corner...
2256 if( (this.enabled = !!this.corner && this.corner.abbrev() !== 'c') ) {
2257 // Cache it
2258 this.qtip.cache.corner = c.clone();
2259
2260 // Create it
2261 this.update();
2262 }
2263
2264 // Toggle tip element
2265 this.element.toggle(this.enabled);
2266
2267 return this.corner;
2268 },
2269
2270 update: function(corner, position) {
2271 if(!this.enabled) { return this; }
2272
2273 var elements = this.qtip.elements,
2274 tip = this.element,
2275 inner = tip.children(),
2276 options = this.options,
2277 curSize = this.size,
2278 mimic = options.mimic,
2279 round = Math.round,
2280 color, precedance, context,
2281 coords, bigCoords, translate, newSize, border, BACKING_STORE_RATIO;
2282
2283 // Re-determine tip if not already set
2284 if(!corner) { corner = this.qtip.cache.corner || this.corner; }
2285
2286 // Use corner property if we detect an invalid mimic value
2287 if(mimic === FALSE) { mimic = corner; }
2288
2289 // Otherwise inherit mimic properties from the corner object as necessary
2290 else {
2291 mimic = new CORNER(mimic);
2292 mimic.precedance = corner.precedance;
2293
2294 if(mimic.x === 'inherit') { mimic.x = corner.x; }
2295 else if(mimic.y === 'inherit') { mimic.y = corner.y; }
2296 else if(mimic.x === mimic.y) {
2297 mimic[ corner.precedance ] = corner[ corner.precedance ];
2298 }
2299 }
2300 precedance = mimic.precedance;
2301
2302 // Ensure the tip width.height are relative to the tip position
2303 if(corner.precedance === X) { this._swapDimensions(); }
2304 else { this._resetDimensions(); }
2305
2306 // Update our colours
2307 color = this.color = this._parseColours(corner);
2308
2309 // Detect border width, taking into account colours
2310 if(color[1] !== TRANSPARENT) {
2311 // Grab border width
2312 border = this.border = this._parseWidth(corner, corner[corner.precedance]);
2313
2314 // If border width isn't zero, use border color as fill if it's not invalid (1.0 style tips)
2315 if(options.border && border < 1 && !INVALID.test(color[1])) { color[0] = color[1]; }
2316
2317 // Set border width (use detected border width if options.border is true)
2318 this.border = border = options.border !== TRUE ? options.border : border;
2319 }
2320
2321 // Border colour was invalid, set border to zero
2322 else { this.border = border = 0; }
2323
2324 // Determine tip size
2325 newSize = this.size = this._calculateSize(corner);
2326 tip.css({
2327 width: newSize[0],
2328 height: newSize[1],
2329 lineHeight: newSize[1]+'px'
2330 });
2331
2332 // Calculate tip translation
2333 if(corner.precedance === Y) {
2334 translate = [
2335 round(mimic.x === LEFT ? border : mimic.x === RIGHT ? newSize[0] - curSize[0] - border : (newSize[0] - curSize[0]) / 2),
2336 round(mimic.y === TOP ? newSize[1] - curSize[1] : 0)
2337 ];
2338 }
2339 else {
2340 translate = [
2341 round(mimic.x === LEFT ? newSize[0] - curSize[0] : 0),
2342 round(mimic.y === TOP ? border : mimic.y === BOTTOM ? newSize[1] - curSize[1] - border : (newSize[1] - curSize[1]) / 2)
2343 ];
2344 }
2345
2346 // Canvas drawing implementation
2347 if(HASCANVAS) {
2348 // Grab canvas context and clear/save it
2349 context = inner[0].getContext('2d');
2350 context.restore(); context.save();
2351 context.clearRect(0,0,6000,6000);
2352
2353 // Calculate coordinates
2354 coords = this._calculateTip(mimic, curSize, SCALE);
2355 bigCoords = this._calculateTip(mimic, this.size, SCALE);
2356
2357 // Set the canvas size using calculated size
2358 inner.attr(WIDTH, newSize[0] * SCALE).attr(HEIGHT, newSize[1] * SCALE);
2359 inner.css(WIDTH, newSize[0]).css(HEIGHT, newSize[1]);
2360
2361 // Draw the outer-stroke tip
2362 this._drawCoords(context, bigCoords);
2363 context.fillStyle = color[1];
2364 context.fill();
2365
2366 // Draw the actual tip
2367 context.translate(translate[0] * SCALE, translate[1] * SCALE);
2368 this._drawCoords(context, coords);
2369 context.fillStyle = color[0];
2370 context.fill();
2371 }
2372
2373 // VML (IE Proprietary implementation)
2374 else {
2375 // Calculate coordinates
2376 coords = this._calculateTip(mimic);
2377
2378 // Setup coordinates string
2379 coords = 'm' + coords[0] + ',' + coords[1] + ' l' + coords[2] +
2380 ',' + coords[3] + ' ' + coords[4] + ',' + coords[5] + ' xe';
2381
2382 // Setup VML-specific offset for pixel-perfection
2383 translate[2] = border && /^(r|b)/i.test(corner.string()) ?
2384 BROWSER.ie === 8 ? 2 : 1 : 0;
2385
2386 // Set initial CSS
2387 inner.css({
2388 coordsize: (newSize[0]+border) + ' ' + (newSize[1]+border),
2389 antialias: ''+(mimic.string().indexOf(CENTER) > -1),
2390 left: translate[0] - (translate[2] * Number(precedance === X)),
2391 top: translate[1] - (translate[2] * Number(precedance === Y)),
2392 width: newSize[0] + border,
2393 height: newSize[1] + border
2394 })
2395 .each(function(i) {
2396 var $this = $(this);
2397
2398 // Set shape specific attributes
2399 $this[ $this.prop ? 'prop' : 'attr' ]({
2400 coordsize: (newSize[0]+border) + ' ' + (newSize[1]+border),
2401 path: coords,
2402 fillcolor: color[0],
2403 filled: !!i,
2404 stroked: !i
2405 })
2406 .toggle(!!(border || i));
2407
2408 // Check if border is enabled and add stroke element
2409 !i && $this.html( createVML(
2410 'stroke', 'weight="'+(border*2)+'px" color="'+color[1]+'" miterlimit="1000" joinstyle="miter"'
2411 ) );
2412 });
2413 }
2414
2415 // Opera bug #357 - Incorrect tip position
2416 // https://github.com/Craga89/qTip2/issues/367
2417 window.opera && setTimeout(function() {
2418 elements.tip.css({
2419 display: 'inline-block',
2420 visibility: 'visible'
2421 });
2422 }, 1);
2423
2424 // Position if needed
2425 if(position !== FALSE) { this.calculate(corner, newSize); }
2426 },
2427
2428 calculate: function(corner, size) {
2429 if(!this.enabled) { return FALSE; }
2430
2431 var self = this,
2432 elements = this.qtip.elements,
2433 tip = this.element,
2434 userOffset = this.options.offset,
2435 isWidget = elements.tooltip.hasClass('ui-widget'),
2436 position = { },
2437 precedance, corners;
2438
2439 // Inherit corner if not provided
2440 corner = corner || this.corner;
2441 precedance = corner.precedance;
2442
2443 // Determine which tip dimension to use for adjustment
2444 size = size || this._calculateSize(corner);
2445
2446 // Setup corners and offset array
2447 corners = [ corner.x, corner.y ];
2448 if(precedance === X) { corners.reverse(); }
2449
2450 // Calculate tip position
2451 $.each(corners, function(i, side) {
2452 var b, bc, br;
2453
2454 if(side === CENTER) {
2455 b = precedance === Y ? LEFT : TOP;
2456 position[ b ] = '50%';
2457 position[MARGIN+'-' + b] = -Math.round(size[ precedance === Y ? 0 : 1 ] / 2) + userOffset;
2458 }
2459 else {
2460 b = self._parseWidth(corner, side, elements.tooltip);
2461 bc = self._parseWidth(corner, side, elements.content);
2462 br = self._parseRadius(corner);
2463
2464 position[ side ] = Math.max(-self.border, i ? bc : (userOffset + (br > b ? br : -b)));
2465 }
2466 });
2467
2468 // Adjust for tip size
2469 position[ corner[precedance] ] -= size[ precedance === X ? 0 : 1 ];
2470
2471 // Set and return new position
2472 tip.css({ margin: '', top: '', bottom: '', left: '', right: '' }).css(position);
2473 return position;
2474 },
2475
2476 reposition: function(event, api, pos, viewport) {
2477 if(!this.enabled) { return; }
2478
2479 var cache = api.cache,
2480 newCorner = this.corner.clone(),
2481 adjust = pos.adjusted,
2482 method = api.options.position.adjust.method.split(' '),
2483 horizontal = method[0],
2484 vertical = method[1] || method[0],
2485 shift = { left: FALSE, top: FALSE, x: 0, y: 0 },
2486 offset, css = {}, props;
2487
2488 function shiftflip(direction, precedance, popposite, side, opposite) {
2489 // Horizontal - Shift or flip method
2490 if(direction === SHIFT && newCorner.precedance === precedance && adjust[side] && newCorner[popposite] !== CENTER) {
2491 newCorner.precedance = newCorner.precedance === X ? Y : X;
2492 }
2493 else if(direction !== SHIFT && adjust[side]){
2494 newCorner[precedance] = newCorner[precedance] === CENTER ?
2495 (adjust[side] > 0 ? side : opposite) : (newCorner[precedance] === side ? opposite : side);
2496 }
2497 }
2498
2499 function shiftonly(xy, side, opposite) {
2500 if(newCorner[xy] === CENTER) {
2501 css[MARGIN+'-'+side] = shift[xy] = offset[MARGIN+'-'+side] - adjust[side];
2502 }
2503 else {
2504 props = offset[opposite] !== undefined ?
2505 [ adjust[side], -offset[side] ] : [ -adjust[side], offset[side] ];
2506
2507 if( (shift[xy] = Math.max(props[0], props[1])) > props[0] ) {
2508 pos[side] -= adjust[side];
2509 shift[side] = FALSE;
2510 }
2511
2512 css[ offset[opposite] !== undefined ? opposite : side ] = shift[xy];
2513 }
2514 }
2515
2516 // If our tip position isn't fixed e.g. doesn't adjust with viewport...
2517 if(this.corner.fixed !== TRUE) {
2518 // Perform shift/flip adjustments
2519 shiftflip(horizontal, X, Y, LEFT, RIGHT);
2520 shiftflip(vertical, Y, X, TOP, BOTTOM);
2521
2522 // Update and redraw the tip if needed (check cached details of last drawn tip)
2523 if(newCorner.string() !== cache.corner.string() || cache.cornerTop !== adjust.top || cache.cornerLeft !== adjust.left) {
2524 this.update(newCorner, FALSE);
2525 }
2526 }
2527
2528 // Setup tip offset properties
2529 offset = this.calculate(newCorner);
2530
2531 // Readjust offset object to make it left/top
2532 if(offset.right !== undefined) { offset.left = -offset.right; }
2533 if(offset.bottom !== undefined) { offset.top = -offset.bottom; }
2534 offset.user = this.offset;
2535
2536 // Perform shift adjustments
2537 if(shift.left = (horizontal === SHIFT && !!adjust.left)) { shiftonly(X, LEFT, RIGHT); }
2538 if(shift.top = (vertical === SHIFT && !!adjust.top)) { shiftonly(Y, TOP, BOTTOM); }
2539
2540 /*
2541 * If the tip is adjusted in both dimensions, or in a
2542 * direction that would cause it to be anywhere but the
2543 * outer border, hide it!
2544 */
2545 this.element.css(css).toggle(
2546 !((shift.x && shift.y) || (newCorner.x === CENTER && shift.y) || (newCorner.y === CENTER && shift.x))
2547 );
2548
2549 // Adjust position to accomodate tip dimensions
2550 pos.left -= offset.left.charAt ? offset.user :
2551 horizontal !== SHIFT || shift.top || !shift.left && !shift.top ? offset.left + this.border : 0;
2552 pos.top -= offset.top.charAt ? offset.user :
2553 vertical !== SHIFT || shift.left || !shift.left && !shift.top ? offset.top + this.border : 0;
2554
2555 // Cache details
2556 cache.cornerLeft = adjust.left; cache.cornerTop = adjust.top;
2557 cache.corner = newCorner.clone();
2558 },
2559
2560 destroy: function() {
2561 // Unbind events
2562 this.qtip._unbind(this.qtip.tooltip, this._ns);
2563
2564 // Remove the tip element(s)
2565 if(this.qtip.elements.tip) {
2566 this.qtip.elements.tip.find('*')
2567 .remove().end().remove();
2568 }
2569 }
2570 });
2571
2572 TIP = PLUGINS.tip = function(api) {
2573 return new Tip(api, api.options.style.tip);
2574 };
2575
2576 // Initialize tip on render
2577 TIP.initialize = 'render';
2578
2579 // Setup plugin sanitization options
2580 TIP.sanitize = function(options) {
2581 if(options.style && 'tip' in options.style) {
2582 var opts = options.style.tip;
2583 if(typeof opts !== 'object') { opts = options.style.tip = { corner: opts }; }
2584 if(!(/string|boolean/i).test(typeof opts.corner)) { opts.corner = TRUE; }
2585 }
2586 };
2587
2588 // Add new option checks for the plugin
2589 CHECKS.tip = {
2590 '^position.my|style.tip.(corner|mimic|border)$': function() {
2591 // Make sure a tip can be drawn
2592 this.create();
2593
2594 // Reposition the tooltip
2595 this.qtip.reposition();
2596 },
2597 '^style.tip.(height|width)$': function(obj) {
2598 // Re-set dimensions and redraw the tip
2599 this.size = [ obj.width, obj.height ];
2600 this.update();
2601
2602 // Reposition the tooltip
2603 this.qtip.reposition();
2604 },
2605 '^content.title|style.(classes|widget)$': function() {
2606 this.update();
2607 }
2608 };
2609
2610 // Extend original qTip defaults
2611 $.extend(TRUE, QTIP.defaults, {
2612 style: {
2613 tip: {
2614 corner: TRUE,
2615 mimic: FALSE,
2616 width: 6,
2617 height: 6,
2618 border: TRUE,
2619 offset: 0
2620 }
2621 }
2622 });
2623 ;PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight)
2624 {
2625 var target = posOptions.target,
2626 tooltip = api.elements.tooltip,
2627 my = posOptions.my,
2628 at = posOptions.at,
2629 adjust = posOptions.adjust,
2630 method = adjust.method.split(' '),
2631 methodX = method[0],
2632 methodY = method[1] || method[0],
2633 viewport = posOptions.viewport,
2634 container = posOptions.container,
2635 cache = api.cache,
2636 adjusted = { left: 0, top: 0 },
2637 fixed, newMy, containerOffset, containerStatic,
2638 viewportWidth, viewportHeight, viewportScroll, viewportOffset;
2639
2640 // If viewport is not a jQuery element, or it's the window/document, or no adjustment method is used... return
2641 if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') {
2642 return adjusted;
2643 }
2644
2645 // Cach container details
2646 containerOffset = container.offset() || adjusted;
2647 containerStatic = container.css('position') === 'static';
2648
2649 // Cache our viewport details
2650 fixed = tooltip.css('position') === 'fixed';
2651 viewportWidth = viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE);
2652 viewportHeight = viewport[0] === window ? viewport.height() : viewport.outerHeight(FALSE);
2653 viewportScroll = { left: fixed ? 0 : viewport.scrollLeft(), top: fixed ? 0 : viewport.scrollTop() };
2654 viewportOffset = viewport.offset() || adjusted;
2655
2656 // Generic calculation method
2657 function calculate(side, otherSide, type, adjust, side1, side2, lengthName, targetLength, elemLength) {
2658 var initialPos = position[side1],
2659 mySide = my[side],
2660 atSide = at[side],
2661 isShift = type === SHIFT,
2662 myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2,
2663 atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2,
2664 sideOffset = viewportScroll[side1] + viewportOffset[side1] - (containerStatic ? 0 : containerOffset[side1]),
2665 overflow1 = sideOffset - initialPos,
2666 overflow2 = initialPos + elemLength - (lengthName === WIDTH ? viewportWidth : viewportHeight) - sideOffset,
2667 offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0);
2668
2669 // shift
2670 if(isShift) {
2671 offset = (mySide === side1 ? 1 : -1) * myLength;
2672
2673 // Adjust position but keep it within viewport dimensions
2674 position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0;
2675 position[side1] = Math.max(
2676 -containerOffset[side1] + viewportOffset[side1],
2677 initialPos - offset,
2678 Math.min(
2679 Math.max(
2680 -containerOffset[side1] + viewportOffset[side1] + (lengthName === WIDTH ? viewportWidth : viewportHeight),
2681 initialPos + offset
2682 ),
2683 position[side1],
2684
2685 // Make sure we don't adjust complete off the element when using 'center'
2686 mySide === 'center' ? initialPos - myLength : 1E9
2687 )
2688 );
2689
2690 }
2691
2692 // flip/flipinvert
2693 else {
2694 // Update adjustment amount depending on if using flipinvert or flip
2695 adjust *= (type === FLIPINVERT ? 2 : 0);
2696
2697 // Check for overflow on the left/top
2698 if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) {
2699 position[side1] -= offset + adjust;
2700 newMy.invert(side, side1);
2701 }
2702
2703 // Check for overflow on the bottom/right
2704 else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0) ) {
2705 position[side1] -= (mySide === CENTER ? -offset : offset) + adjust;
2706 newMy.invert(side, side2);
2707 }
2708
2709 // Make sure we haven't made things worse with the adjustment and reset if so
2710 if(position[side1] < viewportScroll && -position[side1] > overflow2) {
2711 position[side1] = initialPos; newMy = my.clone();
2712 }
2713 }
2714
2715 return position[side1] - initialPos;
2716 }
2717
2718 // Set newMy if using flip or flipinvert methods
2719 if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); }
2720
2721 // Adjust position based onviewport and adjustment options
2722 adjusted = {
2723 left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0,
2724 top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0,
2725 my: newMy
2726 };
2727
2728 return adjusted;
2729 };
2730 ;PLUGINS.polys = {
2731 // POLY area coordinate calculator
2732 // Special thanks to Ed Cradock for helping out with this.
2733 // Uses a binary search algorithm to find suitable coordinates.
2734 polygon: function(baseCoords, corner) {
2735 var result = {
2736 width: 0, height: 0,
2737 position: {
2738 top: 1e10, right: 0,
2739 bottom: 0, left: 1e10
2740 },
2741 adjustable: FALSE
2742 },
2743 i = 0, next,
2744 coords = [],
2745 compareX = 1, compareY = 1,
2746 realX = 0, realY = 0,
2747 newWidth, newHeight;
2748
2749 // First pass, sanitize coords and determine outer edges
2750 i = baseCoords.length; while(i--) {
2751 next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ];
2752
2753 if(next[0] > result.position.right){ result.position.right = next[0]; }
2754 if(next[0] < result.position.left){ result.position.left = next[0]; }
2755 if(next[1] > result.position.bottom){ result.position.bottom = next[1]; }
2756 if(next[1] < result.position.top){ result.position.top = next[1]; }
2757
2758 coords.push(next);
2759 }
2760
2761 // Calculate height and width from outer edges
2762 newWidth = result.width = Math.abs(result.position.right - result.position.left);
2763 newHeight = result.height = Math.abs(result.position.bottom - result.position.top);
2764
2765 // If it's the center corner...
2766 if(corner.abbrev() === 'c') {
2767 result.position = {
2768 left: result.position.left + (result.width / 2),
2769 top: result.position.top + (result.height / 2)
2770 };
2771 }
2772 else {
2773 // Second pass, use a binary search algorithm to locate most suitable coordinate
2774 while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0)
2775 {
2776 newWidth = Math.floor(newWidth / 2);
2777 newHeight = Math.floor(newHeight / 2);
2778
2779 if(corner.x === LEFT){ compareX = newWidth; }
2780 else if(corner.x === RIGHT){ compareX = result.width - newWidth; }
2781 else{ compareX += Math.floor(newWidth / 2); }
2782
2783 if(corner.y === TOP){ compareY = newHeight; }
2784 else if(corner.y === BOTTOM){ compareY = result.height - newHeight; }
2785 else{ compareY += Math.floor(newHeight / 2); }
2786
2787 i = coords.length; while(i--)
2788 {
2789 if(coords.length < 2){ break; }
2790
2791 realX = coords[i][0] - result.position.left;
2792 realY = coords[i][1] - result.position.top;
2793
2794 if((corner.x === LEFT && realX >= compareX) ||
2795 (corner.x === RIGHT && realX <= compareX) ||
2796 (corner.x === CENTER && (realX < compareX || realX > (result.width - compareX))) ||
2797 (corner.y === TOP && realY >= compareY) ||
2798 (corner.y === BOTTOM && realY <= compareY) ||
2799 (corner.y === CENTER && (realY < compareY || realY > (result.height - compareY)))) {
2800 coords.splice(i, 1);
2801 }
2802 }
2803 }
2804 result.position = { left: coords[0][0], top: coords[0][1] };
2805 }
2806
2807 return result;
2808 },
2809
2810 rect: function(ax, ay, bx, by) {
2811 return {
2812 width: Math.abs(bx - ax),
2813 height: Math.abs(by - ay),
2814 position: {
2815 left: Math.min(ax, bx),
2816 top: Math.min(ay, by)
2817 }
2818 };
2819 },
2820
2821 _angles: {
2822 tc: 3 / 2, tr: 7 / 4, tl: 5 / 4,
2823 bc: 1 / 2, br: 1 / 4, bl: 3 / 4,
2824 rc: 2, lc: 1, c: 0
2825 },
2826 ellipse: function(cx, cy, rx, ry, corner) {
2827 var c = PLUGINS.polys._angles[ corner.abbrev() ],
2828 rxc = c === 0 ? 0 : rx * Math.cos( c * Math.PI ),
2829 rys = ry * Math.sin( c * Math.PI );
2830
2831 return {
2832 width: (rx * 2) - Math.abs(rxc),
2833 height: (ry * 2) - Math.abs(rys),
2834 position: {
2835 left: cx + rxc,
2836 top: cy + rys
2837 },
2838 adjustable: FALSE
2839 };
2840 },
2841 circle: function(cx, cy, r, corner) {
2842 return PLUGINS.polys.ellipse(cx, cy, r, r, corner);
2843 }
2844 };
2845 ;PLUGINS.imagemap = function(api, area, corner, adjustMethod)
2846 {
2847 if(!area.jquery) { area = $(area); }
2848
2849 var shape = (area.attr('shape') || 'rect').toLowerCase().replace('poly', 'polygon'),
2850 image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'),
2851 coordsString = $.trim(area.attr('coords')),
2852 coordsArray = coordsString.replace(/,$/, '').split(','),
2853 imageOffset, coords, i, next, result, len;
2854
2855 // If we can't find the image using the map...
2856 if(!image.length) { return FALSE; }
2857
2858 // Pass coordinates string if polygon
2859 if(shape === 'polygon') {
2860 result = PLUGINS.polys.polygon(coordsArray, corner);
2861 }
2862
2863 // Otherwise parse the coordinates and pass them as arguments
2864 else if(PLUGINS.polys[shape]) {
2865 for(i = -1, len = coordsArray.length, coords = []; ++i < len;) {
2866 coords.push( parseInt(coordsArray[i], 10) );
2867 }
2868
2869 result = PLUGINS.polys[shape].apply(
2870 this, coords.concat(corner)
2871 );
2872 }
2873
2874 // If no shapre calculation method was found, return false
2875 else { return FALSE; }
2876
2877 // Make sure we account for padding and borders on the image
2878 imageOffset = image.offset();
2879 imageOffset.left += Math.ceil((image.outerWidth(FALSE) - image.width()) / 2);
2880 imageOffset.top += Math.ceil((image.outerHeight(FALSE) - image.height()) / 2);
2881
2882 // Add image position to offset coordinates
2883 result.position.left += imageOffset.left;
2884 result.position.top += imageOffset.top;
2885
2886 return result;
2887 };
2888 ;PLUGINS.svg = function(api, svg, corner)
2889 {
2890 var doc = $(document),
2891 elem = svg[0],
2892 root = $(elem.ownerSVGElement),
2893 ownerDocument = elem.ownerDocument,
2894 strokeWidth2 = (parseInt(svg.css('stroke-width'), 10) || 0) / 2,
2895 frameOffset, mtx, transformed, viewBox,
2896 len, next, i, points,
2897 result, position, dimensions;
2898
2899 // Ascend the parentNode chain until we find an element with getBBox()
2900 while(!elem.getBBox) { elem = elem.parentNode; }
2901 if(!elem.getBBox || !elem.parentNode) { return FALSE; }
2902
2903 // Determine which shape calculation to use
2904 switch(elem.nodeName) {
2905 case 'ellipse':
2906 case 'circle':
2907 result = PLUGINS.polys.ellipse(
2908 elem.cx.baseVal.value,
2909 elem.cy.baseVal.value,
2910 (elem.rx || elem.r).baseVal.value + strokeWidth2,
2911 (elem.ry || elem.r).baseVal.value + strokeWidth2,
2912 corner
2913 );
2914 break;
2915
2916 case 'line':
2917 case 'polygon':
2918 case 'polyline':
2919 // Determine points object (line has none, so mimic using array)
2920 points = elem.points || [
2921 { x: elem.x1.baseVal.value, y: elem.y1.baseVal.value },
2922 { x: elem.x2.baseVal.value, y: elem.y2.baseVal.value }
2923 ];
2924
2925 for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) {
2926 next = points.getItem ? points.getItem(i) : points[i];
2927 result.push.apply(result, [next.x, next.y]);
2928 }
2929
2930 result = PLUGINS.polys.polygon(result, corner);
2931 break;
2932
2933 // Unknown shape or rectangle? Use bounding box
2934 default:
2935 result = elem.getBBox();
2936 result = {
2937 width: result.width,
2938 height: result.height,
2939 position: {
2940 left: result.x,
2941 top: result.y
2942 }
2943 };
2944 break;
2945 }
2946
2947 // Shortcut assignments
2948 position = result.position;
2949 root = root[0];
2950
2951 // Convert position into a pixel value
2952 if(root.createSVGPoint) {
2953 mtx = elem.getScreenCTM();
2954 points = root.createSVGPoint();
2955
2956 points.x = position.left;
2957 points.y = position.top;
2958 transformed = points.matrixTransform( mtx );
2959 position.left = transformed.x;
2960 position.top = transformed.y;
2961 }
2962
2963 // Check the element is not in a child document, and if so, adjust for frame elements offset
2964 if(ownerDocument !== document && api.position.target !== 'mouse') {
2965 frameOffset = $((ownerDocument.defaultView || ownerDocument.parentWindow).frameElement).offset();
2966 if(frameOffset) {
2967 position.left += frameOffset.left;
2968 position.top += frameOffset.top;
2969 }
2970 }
2971
2972 // Adjust by scroll offset of owner document
2973 ownerDocument = $(ownerDocument);
2974 position.left += ownerDocument.scrollLeft();
2975 position.top += ownerDocument.scrollTop();
2976
2977 return result;
2978 };
2979 ;var MODAL, OVERLAY,
2980 MODALCLASS = 'qtip-modal',
2981 MODALSELECTOR = '.'+MODALCLASS;
2982
2983 OVERLAY = function()
2984 {
2985 var self = this,
2986 focusableElems = {},
2987 current, onLast,
2988 prevState, elem;
2989
2990 // Modified code from jQuery UI 1.10.0 source
2991 // http://code.jquery.com/ui/1.10.0/jquery-ui.js
2992 function focusable(element) {
2993 // Use the defined focusable checker when possible
2994 if($.expr[':'].focusable) { return $.expr[':'].focusable; }
2995
2996 var isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex')),
2997 nodeName = element.nodeName && element.nodeName.toLowerCase(),
2998 map, mapName, img;
2999
3000 if('area' === nodeName) {
3001 map = element.parentNode;
3002 mapName = map.name;
3003 if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {
3004 return false;
3005 }
3006 img = $('img[usemap=#' + mapName + ']')[0];
3007 return !!img && img.is(':visible');
3008 }
3009 return (/input|select|textarea|button|object/.test( nodeName ) ?
3010 !element.disabled :
3011 'a' === nodeName ?
3012 element.href || isTabIndexNotNaN :
3013 isTabIndexNotNaN
3014 );
3015 }
3016
3017 // Focus inputs using cached focusable elements (see update())
3018 function focusInputs(blurElems) {
3019 // Blurring body element in IE causes window.open windows to unfocus!
3020 if(focusableElems.length < 1 && blurElems.length) { blurElems.not('body').blur(); }
3021
3022 // Focus the inputs
3023 else { focusableElems.first().focus(); }
3024 }
3025
3026 // Steal focus from elements outside tooltip
3027 function stealFocus(event) {
3028 if(!elem.is(':visible')) { return; }
3029
3030 var target = $(event.target),
3031 tooltip = current.tooltip,
3032 container = target.closest(SELECTOR),
3033 targetOnTop;
3034
3035 // Determine if input container target is above this
3036 targetOnTop = container.length < 1 ? FALSE :
3037 (parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10));
3038
3039 // If we're showing a modal, but focus has landed on an input below
3040 // this modal, divert focus to the first visible input in this modal
3041 // or if we can't find one... the tooltip itself
3042 if(!targetOnTop && target.closest(SELECTOR)[0] !== tooltip[0]) {
3043 focusInputs(target);
3044 }
3045
3046 // Detect when we leave the last focusable element...
3047 onLast = event.target === focusableElems[focusableElems.length - 1];
3048 }
3049
3050 $.extend(self, {
3051 init: function() {
3052 // Create document overlay
3053 elem = self.elem = $('<div />', {
3054 id: 'qtip-overlay',
3055 html: '<div></div>',
3056 mousedown: function() { return FALSE; }
3057 })
3058 .hide();
3059
3060 // Make sure we can't focus anything outside the tooltip
3061 $(document.body).bind('focusin'+MODALSELECTOR, stealFocus);
3062
3063 // Apply keyboard "Escape key" close handler
3064 $(document).bind('keydown'+MODALSELECTOR, function(event) {
3065 if(current && current.options.show.modal.escape && event.keyCode === 27) {
3066 current.hide(event);
3067 }
3068 });
3069
3070 // Apply click handler for blur option
3071 elem.bind('click'+MODALSELECTOR, function(event) {
3072 if(current && current.options.show.modal.blur) {
3073 current.hide(event);
3074 }
3075 });
3076
3077 return self;
3078 },
3079
3080 update: function(api) {
3081 // Update current API reference
3082 current = api;
3083
3084 // Update focusable elements if enabled
3085 if(api.options.show.modal.stealfocus !== FALSE) {
3086 focusableElems = api.tooltip.find('*').filter(function() {
3087 return focusable(this);
3088 });
3089 }
3090 else { focusableElems = []; }
3091 },
3092
3093 toggle: function(api, state, duration) {
3094 var docBody = $(document.body),
3095 tooltip = api.tooltip,
3096 options = api.options.show.modal,
3097 effect = options.effect,
3098 type = state ? 'show': 'hide',
3099 visible = elem.is(':visible'),
3100 visibleModals = $(MODALSELECTOR).filter(':visible:not(:animated)').not(tooltip),
3101 zindex;
3102
3103 // Set active tooltip API reference
3104 self.update(api);
3105
3106 // If the modal can steal the focus...
3107 // Blur the current item and focus anything in the modal we an
3108 if(state && options.stealfocus !== FALSE) {
3109 focusInputs( $(':focus') );
3110 }
3111
3112 // Toggle backdrop cursor style on show
3113 elem.toggleClass('blurs', options.blur);
3114
3115 // Append to body on show
3116 if(state) {
3117 elem.appendTo(document.body);
3118 }
3119
3120 // Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible
3121 if((elem.is(':animated') && visible === state && prevState !== FALSE) || (!state && visibleModals.length)) {
3122 return self;
3123 }
3124
3125 // Stop all animations
3126 elem.stop(TRUE, FALSE);
3127
3128 // Use custom function if provided
3129 if($.isFunction(effect)) {
3130 effect.call(elem, state);
3131 }
3132
3133 // If no effect type is supplied, use a simple toggle
3134 else if(effect === FALSE) {
3135 elem[ type ]();
3136 }
3137
3138 // Use basic fade function
3139 else {
3140 elem.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() {
3141 if(!state) { elem.hide(); }
3142 });
3143 }
3144
3145 // Reset position and detach from body on hide
3146 if(!state) {
3147 elem.queue(function(next) {
3148 elem.css({ left: '', top: '' });
3149 if(!$(MODALSELECTOR).length) { elem.detach(); }
3150 next();
3151 });
3152 }
3153
3154 // Cache the state
3155 prevState = state;
3156
3157 // If the tooltip is destroyed, set reference to null
3158 if(current.destroyed) { current = NULL; }
3159
3160 return self;
3161 }
3162 });
3163
3164 self.init();
3165 };
3166 OVERLAY = new OVERLAY();
3167
3168 function Modal(api, options) {
3169 this.options = options;
3170 this._ns = '-modal';
3171
3172 this.init( (this.qtip = api) );
3173 }
3174
3175 $.extend(Modal.prototype, {
3176 init: function(qtip) {
3177 var tooltip = qtip.tooltip;
3178
3179 // If modal is disabled... return
3180 if(!this.options.on) { return this; }
3181
3182 // Set overlay reference
3183 qtip.elements.overlay = OVERLAY.elem;
3184
3185 // Add unique attribute so we can grab modal tooltips easily via a SELECTOR, and set z-index
3186 tooltip.addClass(MODALCLASS).css('z-index', QTIP.modal_zindex + $(MODALSELECTOR).length);
3187
3188 // Apply our show/hide/focus modal events
3189 qtip._bind(tooltip, ['tooltipshow', 'tooltiphide'], function(event, api, duration) {
3190 var oEvent = event.originalEvent;
3191
3192 // Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop
3193 if(event.target === tooltip[0]) {
3194 if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(OVERLAY.elem[0]).length) {
3195 try { event.preventDefault(); } catch(e) {}
3196 }
3197 else if(!oEvent || (oEvent && oEvent.type !== 'tooltipsolo')) {
3198 this.toggle(event, event.type === 'tooltipshow', duration);
3199 }
3200 }
3201 }, this._ns, this);
3202
3203 // Adjust modal z-index on tooltip focus
3204 qtip._bind(tooltip, 'tooltipfocus', function(event, api) {
3205 // If focus was cancelled before it reached us, don't do anything
3206 if(event.isDefaultPrevented() || event.target !== tooltip[0]) { return; }
3207
3208 var qtips = $(MODALSELECTOR),
3209
3210 // Keep the modal's lower than other, regular qtips
3211 newIndex = QTIP.modal_zindex + qtips.length,
3212 curIndex = parseInt(tooltip[0].style.zIndex, 10);
3213
3214 // Set overlay z-index
3215 OVERLAY.elem[0].style.zIndex = newIndex - 1;
3216
3217 // Reduce modal z-index's and keep them properly ordered
3218 qtips.each(function() {
3219 if(this.style.zIndex > curIndex) {
3220 this.style.zIndex -= 1;
3221 }
3222 });
3223
3224 // Fire blur event for focused tooltip
3225 qtips.filter('.' + CLASS_FOCUS).qtip('blur', event.originalEvent);
3226
3227 // Set the new z-index
3228 tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;
3229
3230 // Set current
3231 OVERLAY.update(api);
3232
3233 // Prevent default handling
3234 try { event.preventDefault(); } catch(e) {}
3235 }, this._ns, this);
3236
3237 // Focus any other visible modals when this one hides
3238 qtip._bind(tooltip, 'tooltiphide', function(event) {
3239 if(event.target === tooltip[0]) {
3240 $(MODALSELECTOR).filter(':visible').not(tooltip).last().qtip('focus', event);
3241 }
3242 }, this._ns, this);
3243 },
3244
3245 toggle: function(event, state, duration) {
3246 // Make sure default event hasn't been prevented
3247 if(event && event.isDefaultPrevented()) { return this; }
3248
3249 // Toggle it
3250 OVERLAY.toggle(this.qtip, !!state, duration);
3251 },
3252
3253 destroy: function() {
3254 // Remove modal class
3255 this.qtip.tooltip.removeClass(MODALCLASS);
3256
3257 // Remove bound events
3258 this.qtip._unbind(this.qtip.tooltip, this._ns);
3259
3260 // Delete element reference
3261 OVERLAY.toggle(this.qtip, FALSE);
3262 delete this.qtip.elements.overlay;
3263 }
3264 });
3265
3266
3267 MODAL = PLUGINS.modal = function(api) {
3268 return new Modal(api, api.options.show.modal);
3269 };
3270
3271 // Setup sanitiztion rules
3272 MODAL.sanitize = function(opts) {
3273 if(opts.show) {
3274 if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; }
3275 else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; }
3276 }
3277 };
3278
3279 // Base z-index for all modal tooltips (use qTip core z-index as a base)
3280 QTIP.modal_zindex = QTIP.zindex - 200;
3281
3282 // Plugin needs to be initialized on render
3283 MODAL.initialize = 'render';
3284
3285 // Setup option set checks
3286 CHECKS.modal = {
3287 '^show.modal.(on|blur)$': function() {
3288 // Initialise
3289 this.destroy();
3290 this.init();
3291
3292 // Show the modal if not visible already and tooltip is visible
3293 this.qtip.elems.overlay.toggle(
3294 this.qtip.tooltip[0].offsetWidth > 0
3295 );
3296 }
3297 };
3298
3299 // Extend original api defaults
3300 $.extend(TRUE, QTIP.defaults, {
3301 show: {
3302 modal: {
3303 on: FALSE,
3304 effect: TRUE,
3305 blur: TRUE,
3306 stealfocus: TRUE,
3307 escape: TRUE
3308 }
3309 }
3310 });
3311 ;var IE6,
3312
3313 /*
3314 * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe)
3315 * Special thanks to Brandon Aaron
3316 */
3317 BGIFRAME = '<iframe class="qtip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' +
3318 ' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' +
3319 '-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>';
3320
3321 function Ie6(api, qtip) {
3322 this._ns = 'ie6';
3323 this.init( (this.qtip = api) );
3324 }
3325
3326 $.extend(Ie6.prototype, {
3327 _scroll : function() {
3328 var overlay = this.qtip.elements.overlay;
3329 overlay && (overlay[0].style.top = $(window).scrollTop() + 'px');
3330 },
3331
3332 init: function(qtip) {
3333 var tooltip = qtip.tooltip,
3334 scroll;
3335
3336 // Create the BGIFrame element if needed
3337 if($('select, object').length < 1) {
3338 this.bgiframe = qtip.elements.bgiframe = $(BGIFRAME).appendTo(tooltip);
3339
3340 // Update BGIFrame on tooltip move
3341 qtip._bind(tooltip, 'tooltipmove', this.adjustBGIFrame, this._ns, this);
3342 }
3343
3344 // redraw() container for width/height calculations
3345 this.redrawContainer = $('<div/>', { id: NAMESPACE+'-rcontainer' })
3346 .appendTo(document.body);
3347
3348 // Fixup modal plugin if present too
3349 if( qtip.elements.overlay && qtip.elements.overlay.addClass('qtipmodal-ie6fix') ) {
3350 qtip._bind(window, ['scroll', 'resize'], this._scroll, this._ns, this);
3351 qtip._bind(tooltip, ['tooltipshow'], this._scroll, this._ns, this);
3352 }
3353
3354 // Set dimensions
3355 this.redraw();
3356 },
3357
3358 adjustBGIFrame: function() {
3359 var tooltip = this.qtip.tooltip,
3360 dimensions = {
3361 height: tooltip.outerHeight(FALSE),
3362 width: tooltip.outerWidth(FALSE)
3363 },
3364 plugin = this.qtip.plugins.tip,
3365 tip = this.qtip.elements.tip,
3366 tipAdjust, offset;
3367
3368 // Adjust border offset
3369 offset = parseInt(tooltip.css('borderLeftWidth'), 10) || 0;
3370 offset = { left: -offset, top: -offset };
3371
3372 // Adjust for tips plugin
3373 if(plugin && tip) {
3374 tipAdjust = (plugin.corner.precedance === 'x') ? [WIDTH, LEFT] : [HEIGHT, TOP];
3375 offset[ tipAdjust[1] ] -= tip[ tipAdjust[0] ]();
3376 }
3377
3378 // Update bgiframe
3379 this.bgiframe.css(offset).css(dimensions);
3380 },
3381
3382 // Max/min width simulator function
3383 redraw: function() {
3384 if(this.qtip.rendered < 1 || this.drawing) { return this; }
3385
3386 var tooltip = this.qtip.tooltip,
3387 style = this.qtip.options.style,
3388 container = this.qtip.options.position.container,
3389 perc, width, max, min;
3390
3391 // Set drawing flag
3392 this.qtip.drawing = 1;
3393
3394 // If tooltip has a set height/width, just set it... like a boss!
3395 if(style.height) { tooltip.css(HEIGHT, style.height); }
3396 if(style.width) { tooltip.css(WIDTH, style.width); }
3397
3398 // Simulate max/min width if not set width present...
3399 else {
3400 // Reset width and add fluid class
3401 tooltip.css(WIDTH, '').appendTo(this.redrawContainer);
3402
3403 // Grab our tooltip width (add 1 if odd so we don't get wrapping problems.. huzzah!)
3404 width = tooltip.width();
3405 if(width % 2 < 1) { width += 1; }
3406
3407 // Grab our max/min properties
3408 max = tooltip.css('maxWidth') || '';
3409 min = tooltip.css('minWidth') || '';
3410
3411 // Parse into proper pixel values
3412 perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0;
3413 max = ((max.indexOf('%') > -1 ? perc : 1) * parseInt(max, 10)) || width;
3414 min = ((min.indexOf('%') > -1 ? perc : 1) * parseInt(min, 10)) || 0;
3415
3416 // Determine new dimension size based on max/min/current values
3417 width = max + min ? Math.min(Math.max(width, min), max) : width;
3418
3419 // Set the newly calculated width and remvoe fluid class
3420 tooltip.css(WIDTH, Math.round(width)).appendTo(container);
3421 }
3422
3423 // Set drawing flag
3424 this.drawing = 0;
3425
3426 return this;
3427 },
3428
3429 destroy: function() {
3430 // Remove iframe
3431 this.bgiframe && this.bgiframe.remove();
3432
3433 // Remove bound events
3434 this.qtip._unbind([window, this.qtip.tooltip], this._ns);
3435 }
3436 });
3437
3438 IE6 = PLUGINS.ie6 = function(api) {
3439 // Proceed only if the browser is IE6
3440 return BROWSER.ie === 6 ? new Ie6(api) : FALSE;
3441 };
3442
3443 IE6.initialize = 'render';
3444
3445 CHECKS.ie6 = {
3446 '^content|style$': function() {
3447 this.redraw();
3448 }
3449 };
3450 ;}));
3451 }( window, document ));
3452