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