Subversion-Projekte lars-tiefland.webanos.zeldi.de

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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