PageSourceSearch

http://www.blackdiamondcaviarnyc.com/catalog/view/theme/mrcarter/js/tooltip/jquery.qtip.js

js blackdiamondcaviarnyc.com collected 2026-10-08 07:22:14 UTC 99,901 bytes, 3,365 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.