Path: blob/main/contrib/libxo/xohtml/external/jquery.qtip.js
39507 views
/*1* qTip2 - Pretty powerful tooltips - v2.1.12* http://qtip2.com3*4* Copyright (c) 2013 Craig Michael Thompson5* Released under the MIT, GPL licenses6* http://jquery.org/license7*8* Date: Thu Jul 11 2013 02:15 UTC+00009* Plugins: tips viewport10* Styles: basic css311*/12/*global window: false, jQuery: false, console: false, define: false */1314/* Cache window, document, undefined */15(function( window, document, undefined ) {1617// Uses AMD or browser globals to create a jQuery plugin.18(function( factory ) {19"use strict";20if(typeof define === 'function' && define.amd) {21define(['jquery', 'imagesloaded'], factory);22}23else if(jQuery && !jQuery.fn.qtip) {24factory(jQuery);25}26}27(function($) {28/* This currently causes issues with Safari 6, so for it's disabled */29//"use strict"; // (Dis)able ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/3031;// Munge the primitives - Paul Irish tip32var TRUE = true,33FALSE = false,34NULL = null,3536// Common variables37X = 'x', Y = 'y',38WIDTH = 'width',39HEIGHT = 'height',4041// Positioning sides42TOP = 'top',43LEFT = 'left',44BOTTOM = 'bottom',45RIGHT = 'right',46CENTER = 'center',4748// Position adjustment types49FLIP = 'flip',50FLIPINVERT = 'flipinvert',51SHIFT = 'shift',5253// Shortcut vars54QTIP, PROTOTYPE, CORNER, CHECKS,55PLUGINS = {},56NAMESPACE = 'qtip',57ATTR_HAS = 'data-hasqtip',58ATTR_ID = 'data-qtip-id',59WIDGET = ['ui-widget', 'ui-tooltip'],60SELECTOR = '.'+NAMESPACE,61INACTIVE_EVENTS = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' '),6263CLASS_FIXED = NAMESPACE+'-fixed',64CLASS_DEFAULT = NAMESPACE + '-default',65CLASS_FOCUS = NAMESPACE + '-focus',66CLASS_HOVER = NAMESPACE + '-hover',67CLASS_DISABLED = NAMESPACE+'-disabled',6869replaceSuffix = '_replacedByqTip',70oldtitle = 'oldtitle',71trackingBound;7273// Browser detection74BROWSER = {75/*76* IE version detection77*78* Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment79* Credit to James Padolsey for the original implemntation!80*/81ie: (function(){82var v = 3, div = document.createElement('div');83while ((div.innerHTML = '<!--[if gt IE '+(++v)+']><i></i><![endif]-->')) {84if(!div.getElementsByTagName('i')[0]) { break; }85}86return v > 4 ? v : NaN;87}()),8889/*90* iOS version detection91*/92iOS: parseFloat(93('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])94.replace('undefined', '3_2').replace('_', '.').replace('_', '')95) || FALSE96};9798;function QTip(target, options, id, attr) {99// Elements and ID100this.id = id;101this.target = target;102this.tooltip = NULL;103this.elements = elements = { target: target };104105// Internal constructs106this._id = NAMESPACE + '-' + id;107this.timers = { img: {} };108this.options = options;109this.plugins = {};110111// Cache object112this.cache = cache = {113event: {},114target: $(),115disabled: FALSE,116attr: attr,117onTooltip: FALSE,118lastClass: ''119};120121// Set the initial flags122this.rendered = this.destroyed = this.disabled = this.waiting =123this.hiddenDuringWait = this.positioning = this.triggering = FALSE;124}125PROTOTYPE = QTip.prototype;126127PROTOTYPE.render = function(show) {128if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit129130var self = this,131options = this.options,132cache = this.cache,133elements = this.elements,134text = options.content.text,135title = options.content.title,136button = options.content.button,137posOptions = options.position,138namespace = '.'+this._id+' ',139deferreds = [];140141// Add ARIA attributes to target142$.attr(this.target[0], 'aria-describedby', this._id);143144// Create tooltip element145this.tooltip = elements.tooltip = tooltip = $('<div/>', {146'id': this._id,147'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, NAMESPACE + '-pos-' + options.position.my.abbrev() ].join(' '),148'width': options.style.width || '',149'height': options.style.height || '',150'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,151152/* ARIA specific attributes */153'role': 'alert',154'aria-live': 'polite',155'aria-atomic': FALSE,156'aria-describedby': this._id + '-content',157'aria-hidden': TRUE158})159.toggleClass(CLASS_DISABLED, this.disabled)160.attr(ATTR_ID, this.id)161.data(NAMESPACE, this)162.appendTo(posOptions.container)163.append(164// Create content element165elements.content = $('<div />', {166'class': NAMESPACE + '-content',167'id': this._id + '-content',168'aria-atomic': TRUE169})170);171172// Set rendered flag and prevent redundant reposition calls for now173this.rendered = -1;174this.positioning = TRUE;175176// Create title...177if(title) {178this._createTitle();179180// Update title only if its not a callback (called in toggle if so)181if(!$.isFunction(title)) {182deferreds.push( this._updateTitle(title, FALSE) );183}184}185186// Create button187if(button) { this._createButton(); }188189// Set proper rendered flag and update content if not a callback function (called in toggle)190if(!$.isFunction(text)) {191deferreds.push( this._updateContent(text, FALSE) );192}193this.rendered = TRUE;194195// Setup widget classes196this._setWidget();197198// Assign passed event callbacks (before plugins!)199$.each(options.events, function(name, callback) {200$.isFunction(callback) && tooltip.bind(201(name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name])202.join(namespace)+namespace, callback203);204});205206// Initialize 'render' plugins207$.each(PLUGINS, function(name) {208var instance;209if(this.initialize === 'render' && (instance = this(self))) {210self.plugins[name] = instance;211}212});213214// Assign events215this._assignEvents();216217// When deferreds have completed218$.when.apply($, deferreds).then(function() {219// tooltiprender event220self._trigger('render');221222// Reset flags223self.positioning = FALSE;224225// Show tooltip if not hidden during wait period226if(!self.hiddenDuringWait && (options.show.ready || show)) {227self.toggle(TRUE, cache.event, FALSE);228}229self.hiddenDuringWait = FALSE;230});231232// Expose API233QTIP.api[this.id] = this;234235return this;236};237238PROTOTYPE.destroy = function(immediate) {239// Set flag the signify destroy is taking place to plugins240// and ensure it only gets destroyed once!241if(this.destroyed) { return this.target; }242243function process() {244if(this.destroyed) { return; }245this.destroyed = TRUE;246247var target = this.target,248title = target.attr(oldtitle);249250// Destroy tooltip if rendered251if(this.rendered) {252this.tooltip.stop(1,0).find('*').remove().end().remove();253}254255// Destroy all plugins256$.each(this.plugins, function(name) {257this.destroy && this.destroy();258});259260// Clear timers and remove bound events261clearTimeout(this.timers.show);262clearTimeout(this.timers.hide);263this._unassignEvents();264265// Remove api object and ARIA attributes266target.removeData(NAMESPACE).removeAttr(ATTR_ID)267.removeAttr('aria-describedby');268269// Reset old title attribute if removed270if(this.options.suppress && title) {271target.attr('title', title).removeAttr(oldtitle);272}273274// Remove qTip events associated with this API275this._unbind(target);276277// Remove ID from used id objects, and delete object references278// for better garbage collection and leak protection279this.options = this.elements = this.cache = this.timers =280this.plugins = this.mouse = NULL;281282// Delete epoxsed API object283delete QTIP.api[this.id];284}285286// If an immediate destory is needed287if(immediate !== TRUE && this.rendered) {288tooltip.one('tooltiphidden', $.proxy(process, this));289!this.triggering && this.hide();290}291292// If we're not in the process of hiding... process293else { process.call(this); }294295return this.target;296};297298;function invalidOpt(a) {299return a === NULL || $.type(a) !== 'object';300}301302function invalidContent(c) {303return !( $.isFunction(c) || (c && c.attr) || c.length || ($.type(c) === 'object' && (c.jquery || c.then) ));304}305306// Option object sanitizer307function sanitizeOptions(opts) {308var content, text, ajax, once;309310if(invalidOpt(opts)) { return FALSE; }311312if(invalidOpt(opts.metadata)) {313opts.metadata = { type: opts.metadata };314}315316if('content' in opts) {317content = opts.content;318319if(invalidOpt(content) || content.jquery || content.done) {320content = opts.content = {321text: (text = invalidContent(content) ? FALSE : content)322};323}324else { text = content.text; }325326// DEPRECATED - Old content.ajax plugin functionality327// Converts it into the proper Deferred syntax328if('ajax' in content) {329ajax = content.ajax;330once = ajax && ajax.once !== FALSE;331delete content.ajax;332333content.text = function(event, api) {334var loading = text || $(this).attr(api.options.content.attr) || 'Loading...',335336deferred = $.ajax(337$.extend({}, ajax, { context: api })338)339.then(ajax.success, NULL, ajax.error)340.then(function(content) {341if(content && once) { api.set('content.text', content); }342return content;343},344function(xhr, status, error) {345if(api.destroyed || xhr.status === 0) { return; }346api.set('content.text', status + ': ' + error);347});348349return !once ? (api.set('content.text', loading), deferred) : loading;350};351}352353if('title' in content) {354if(!invalidOpt(content.title)) {355content.button = content.title.button;356content.title = content.title.text;357}358359if(invalidContent(content.title || FALSE)) {360content.title = FALSE;361}362}363}364365if('position' in opts && invalidOpt(opts.position)) {366opts.position = { my: opts.position, at: opts.position };367}368369if('show' in opts && invalidOpt(opts.show)) {370opts.show = opts.show.jquery ? { target: opts.show } :371opts.show === TRUE ? { ready: TRUE } : { event: opts.show };372}373374if('hide' in opts && invalidOpt(opts.hide)) {375opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };376}377378if('style' in opts && invalidOpt(opts.style)) {379opts.style = { classes: opts.style };380}381382// Sanitize plugin options383$.each(PLUGINS, function() {384this.sanitize && this.sanitize(opts);385});386387return opts;388}389390// Setup builtin .set() option checks391CHECKS = PROTOTYPE.checks = {392builtin: {393// Core checks394'^id$': function(obj, o, v, prev) {395var id = v === TRUE ? QTIP.nextid : v,396new_id = NAMESPACE + '-' + id;397398if(id !== FALSE && id.length > 0 && !$('#'+new_id).length) {399this._id = new_id;400401if(this.rendered) {402this.tooltip[0].id = this._id;403this.elements.content[0].id = this._id + '-content';404this.elements.title[0].id = this._id + '-title';405}406}407else { obj[o] = prev; }408},409'^prerender': function(obj, o, v) {410v && !this.rendered && this.render(this.options.show.ready);411},412413// Content checks414'^content.text$': function(obj, o, v) {415this._updateContent(v);416},417'^content.attr$': function(obj, o, v, prev) {418if(this.options.content.text === this.target.attr(prev)) {419this._updateContent( this.target.attr(v) );420}421},422'^content.title$': function(obj, o, v) {423// Remove title if content is null424if(!v) { return this._removeTitle(); }425426// If title isn't already created, create it now and update427v && !this.elements.title && this._createTitle();428this._updateTitle(v);429},430'^content.button$': function(obj, o, v) {431this._updateButton(v);432},433'^content.title.(text|button)$': function(obj, o, v) {434this.set('content.'+o, v); // Backwards title.text/button compat435},436437// Position checks438'^position.(my|at)$': function(obj, o, v){439'string' === typeof v && (obj[o] = new CORNER(v, o === 'at'));440},441'^position.container$': function(obj, o, v){442this.tooltip.appendTo(v);443},444445// Show checks446'^show.ready$': function(obj, o, v) {447v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE));448},449450// Style checks451'^style.classes$': function(obj, o, v, p) {452this.tooltip.removeClass(p).addClass(v);453},454'^style.width|height': function(obj, o, v) {455this.tooltip.css(o, v);456},457'^style.widget|content.title': function() {458this._setWidget();459},460'^style.def': function(obj, o, v) {461this.tooltip.toggleClass(CLASS_DEFAULT, !!v);462},463464// Events check465'^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {466tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);467},468469// Properties which require event reassignment470'^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {471var posOptions = this.options.position;472473// Set tracking flag474tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);475476// Reassign events477this._unassignEvents();478this._assignEvents();479}480}481};482483// Dot notation converter484function convertNotation(options, notation) {485var i = 0, obj, option = options,486487// Split notation into array488levels = notation.split('.');489490// Loop through491while( option = option[ levels[i++] ] ) {492if(i < levels.length) { obj = option; }493}494495return [obj || options, levels.pop()];496}497498PROTOTYPE.get = function(notation) {499if(this.destroyed) { return this; }500501var o = convertNotation(this.options, notation.toLowerCase()),502result = o[0][ o[1] ];503504return result.precedance ? result.string() : result;505};506507function setCallback(notation, args) {508var category, rule, match;509510for(category in this.checks) {511for(rule in this.checks[category]) {512if(match = (new RegExp(rule, 'i')).exec(notation)) {513args.push(match);514515if(category === 'builtin' || this.plugins[category]) {516this.checks[category][rule].apply(517this.plugins[category] || this, args518);519}520}521}522}523}524525var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,526rrender = /^prerender|show\.ready/i;527528PROTOTYPE.set = function(option, value) {529if(this.destroyed) { return this; }530531var rendered = this.rendered,532reposition = FALSE,533options = this.options,534checks = this.checks,535name;536537// Convert singular option/value pair into object form538if('string' === typeof option) {539name = option; option = {}; option[name] = value;540}541else { option = $.extend({}, option); }542543// Set all of the defined options to their new values544$.each(option, function(notation, value) {545if(!rendered && !rrender.test(notation)) {546delete option[notation]; return;547}548549// Set new obj value550var obj = convertNotation(options, notation.toLowerCase()), previous;551previous = obj[0][ obj[1] ];552obj[0][ obj[1] ] = value && value.nodeType ? $(value) : value;553554// Also check if we need to reposition555reposition = rmove.test(notation) || reposition;556557// Set the new params for the callback558option[notation] = [obj[0], obj[1], value, previous];559});560561// Re-sanitize options562sanitizeOptions(options);563564/*565* Execute any valid callbacks for the set options566* Also set positioning flag so we don't get loads of redundant repositioning calls.567*/568this.positioning = TRUE;569$.each(option, $.proxy(setCallback, this));570this.positioning = FALSE;571572// Update position if needed573if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) {574this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event );575}576577return this;578};579580;PROTOTYPE._update = function(content, element, reposition) {581var self = this,582cache = this.cache;583584// Make sure tooltip is rendered and content is defined. If not return585if(!this.rendered || !content) { return FALSE; }586587// Use function to parse content588if($.isFunction(content)) {589content = content.call(this.elements.target, cache.event, this) || '';590}591592// Handle deferred content593if($.isFunction(content.then)) {594cache.waiting = TRUE;595return content.then(function(c) {596cache.waiting = FALSE;597return self._update(c, element);598}, NULL, function(e) {599return self._update(e, element);600});601}602603// If content is null... return false604if(content === FALSE || (!content && content !== '')) { return FALSE; }605606// Append new content if its a DOM array and show it if hidden607if(content.jquery && content.length > 0) {608element.children().detach().end().append( content.css({ display: 'block' }) );609}610611// Content is a regular string, insert the new content612else { element.html(content); }613614// If imagesLoaded is included, ensure images have loaded and return promise615cache.waiting = TRUE;616617return ( $.fn.imagesLoaded ? element.imagesLoaded() : $.Deferred().resolve($([])) )618.done(function(images) {619cache.waiting = FALSE;620621// Reposition if rendered622if(images.length && self.rendered && self.tooltip[0].offsetWidth > 0) {623self.reposition(cache.event, !images.length);624}625})626.promise();627};628629PROTOTYPE._updateContent = function(content, reposition) {630this._update(content, this.elements.content, reposition);631};632633PROTOTYPE._updateTitle = function(content, reposition) {634if(this._update(content, this.elements.title, reposition) === FALSE) {635this._removeTitle(FALSE);636}637};638639PROTOTYPE._createTitle = function()640{641var elements = this.elements,642id = this._id+'-title';643644// Destroy previous title element, if present645if(elements.titlebar) { this._removeTitle(); }646647// Create title bar and title elements648elements.titlebar = $('<div />', {649'class': NAMESPACE + '-titlebar ' + (this.options.style.widget ? createWidgetClass('header') : '')650})651.append(652elements.title = $('<div />', {653'id': id,654'class': NAMESPACE + '-title',655'aria-atomic': TRUE656})657)658.insertBefore(elements.content)659660// Button-specific events661.delegate('.qtip-close', 'mousedown keydown mouseup keyup mouseout', function(event) {662$(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');663})664.delegate('.qtip-close', 'mouseover mouseout', function(event){665$(this).toggleClass('ui-state-hover', event.type === 'mouseover');666});667668// Create button if enabled669if(this.options.content.button) { this._createButton(); }670};671672PROTOTYPE._removeTitle = function(reposition)673{674var elements = this.elements;675676if(elements.title) {677elements.titlebar.remove();678elements.titlebar = elements.title = elements.button = NULL;679680// Reposition if enabled681if(reposition !== FALSE) { this.reposition(); }682}683};684685;PROTOTYPE.reposition = function(event, effect) {686if(!this.rendered || this.positioning || this.destroyed) { return this; }687688// Set positioning flag689this.positioning = TRUE;690691var cache = this.cache,692tooltip = this.tooltip,693posOptions = this.options.position,694target = posOptions.target,695my = posOptions.my,696at = posOptions.at,697viewport = posOptions.viewport,698container = posOptions.container,699adjust = posOptions.adjust,700method = adjust.method.split(' '),701elemWidth = tooltip.outerWidth(FALSE),702elemHeight = tooltip.outerHeight(FALSE),703targetWidth = 0,704targetHeight = 0,705type = tooltip.css('position'),706position = { left: 0, top: 0 },707visible = tooltip[0].offsetWidth > 0,708isScroll = event && event.type === 'scroll',709win = $(window),710doc = container[0].ownerDocument,711mouse = this.mouse,712pluginCalculations, offset;713714// Check if absolute position was passed715if($.isArray(target) && target.length === 2) {716// Force left top and set position717at = { x: LEFT, y: TOP };718position = { left: target[0], top: target[1] };719}720721// Check if mouse was the target722else if(target === 'mouse' && ((event && event.pageX) || cache.event.pageX)) {723// Force left top to allow flipping724at = { x: LEFT, y: TOP };725726// Use cached event if one isn't available for positioning727event = mouse && mouse.pageX && (adjust.mouse || !event || !event.pageX) ? mouse :728(event && (event.type === 'resize' || event.type === 'scroll') ? cache.event :729event && event.pageX && event.type === 'mousemove' ? event :730(!adjust.mouse || this.options.show.distance) && cache.origin && cache.origin.pageX ? cache.origin :731event) || event || cache.event || mouse || {};732733// Calculate body and container offset and take them into account below734if(type !== 'static') { position = container.offset(); }735if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) { offset = $(doc.body).offset(); }736737// Use event coordinates for position738position = {739left: event.pageX - position.left + (offset && offset.left || 0),740top: event.pageY - position.top + (offset && offset.top || 0)741};742743// Scroll events are a pain, some browsers744if(adjust.mouse && isScroll) {745position.left -= mouse.scrollX - win.scrollLeft();746position.top -= mouse.scrollY - win.scrollTop();747}748}749750// Target wasn't mouse or absolute...751else {752// Check if event targetting is being used753if(target === 'event' && event && event.target && event.type !== 'scroll' && event.type !== 'resize') {754cache.target = $(event.target);755}756else if(target !== 'event'){757cache.target = $(target.jquery ? target : elements.target);758}759target = cache.target;760761// Parse the target into a jQuery object and make sure there's an element present762target = $(target).eq(0);763if(target.length === 0) { return this; }764765// Check if window or document is the target766else if(target[0] === document || target[0] === window) {767targetWidth = BROWSER.iOS ? window.innerWidth : target.width();768targetHeight = BROWSER.iOS ? window.innerHeight : target.height();769770if(target[0] === window) {771position = {772top: (viewport || target).scrollTop(),773left: (viewport || target).scrollLeft()774};775}776}777778// Check if the target is an <AREA> element779else if(PLUGINS.imagemap && target.is('area')) {780pluginCalculations = PLUGINS.imagemap(this, target, at, PLUGINS.viewport ? method : FALSE);781}782783// Check if the target is an SVG element784else if(PLUGINS.svg && target[0].ownerSVGElement) {785pluginCalculations = PLUGINS.svg(this, target, at, PLUGINS.viewport ? method : FALSE);786}787788// Otherwise use regular jQuery methods789else {790targetWidth = target.outerWidth(FALSE);791targetHeight = target.outerHeight(FALSE);792position = target.offset();793}794795// Parse returned plugin values into proper variables796if(pluginCalculations) {797targetWidth = pluginCalculations.width;798targetHeight = pluginCalculations.height;799offset = pluginCalculations.offset;800position = pluginCalculations.position;801}802803// Adjust position to take into account offset parents804position = this.reposition.offset(target, position, container);805806// Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2)807if((BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1) ||808(BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33) ||809(!BROWSER.iOS && type === 'fixed')810){811position.left -= win.scrollLeft();812position.top -= win.scrollTop();813}814815// Adjust position relative to target816if(!pluginCalculations || (pluginCalculations && pluginCalculations.adjustable !== FALSE)) {817position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0;818position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0;819}820}821822// Adjust position relative to tooltip823position.left += adjust.x + (my.x === RIGHT ? -elemWidth : my.x === CENTER ? -elemWidth / 2 : 0);824position.top += adjust.y + (my.y === BOTTOM ? -elemHeight : my.y === CENTER ? -elemHeight / 2 : 0);825826// Use viewport adjustment plugin if enabled827if(PLUGINS.viewport) {828position.adjusted = PLUGINS.viewport(829this, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight830);831832// Apply offsets supplied by positioning plugin (if used)833if(offset && position.adjusted.left) { position.left += offset.left; }834if(offset && position.adjusted.top) { position.top += offset.top; }835}836837// Viewport adjustment is disabled, set values to zero838else { position.adjusted = { left: 0, top: 0 }; }839840// tooltipmove event841if(!this._trigger('move', [position, viewport.elem || viewport], event)) { return this; }842delete position.adjusted;843844// If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly845if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) {846tooltip.css(position);847}848849// Use custom function if provided850else if($.isFunction(posOptions.effect)) {851posOptions.effect.call(tooltip, this, $.extend({}, position));852tooltip.queue(function(next) {853// Reset attributes to avoid cross-browser rendering bugs854$(this).css({ opacity: '', height: '' });855if(BROWSER.ie) { this.style.removeAttribute('filter'); }856857next();858});859}860861// Set positioning flag862this.positioning = FALSE;863864return this;865};866867// Custom (more correct for qTip!) offset calculator868PROTOTYPE.reposition.offset = function(elem, pos, container) {869if(!container[0]) { return pos; }870871var ownerDocument = $(elem[0].ownerDocument),872quirks = !!BROWSER.ie && document.compatMode !== 'CSS1Compat',873parent = container[0],874scrolled, position, parentOffset, overflow;875876function scroll(e, i) {877pos.left += i * e.scrollLeft();878pos.top += i * e.scrollTop();879}880881// Compensate for non-static containers offset882do {883if((position = $.css(parent, 'position')) !== 'static') {884if(position === 'fixed') {885parentOffset = parent.getBoundingClientRect();886scroll(ownerDocument, -1);887}888else {889parentOffset = $(parent).position();890parentOffset.left += (parseFloat($.css(parent, 'borderLeftWidth')) || 0);891parentOffset.top += (parseFloat($.css(parent, 'borderTopWidth')) || 0);892}893894pos.left -= parentOffset.left + (parseFloat($.css(parent, 'marginLeft')) || 0);895pos.top -= parentOffset.top + (parseFloat($.css(parent, 'marginTop')) || 0);896897// If this is the first parent element with an overflow of "scroll" or "auto", store it898if(!scrolled && (overflow = $.css(parent, 'overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = $(parent); }899}900}901while((parent = parent.offsetParent));902903// Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode)904if(scrolled && (scrolled[0] !== ownerDocument[0] || quirks)) {905scroll(scrolled, 1);906}907908return pos;909};910911// Corner class912var C = (CORNER = PROTOTYPE.reposition.Corner = function(corner, forceY) {913corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase();914this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();915this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();916this.forceY = !!forceY;917918var f = corner.charAt(0);919this.precedance = (f === 't' || f === 'b' ? Y : X);920}).prototype;921922C.invert = function(z, center) {923this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z];924};925926C.string = function() {927var x = this.x, y = this.y;928return x === y ? x : this.precedance === Y || (this.forceY && y !== 'center') ? y+' '+x : x+' '+y;929};930931C.abbrev = function() {932var result = this.string().split(' ');933return result[0].charAt(0) + (result[1] && result[1].charAt(0) || '');934};935936C.clone = function() {937return new CORNER( this.string(), this.forceY );938};;939PROTOTYPE.toggle = function(state, event) {940var cache = this.cache,941options = this.options,942tooltip = this.tooltip;943944// Try to prevent flickering when tooltip overlaps show element945if(event) {946if((/over|enter/).test(event.type) && (/out|leave/).test(cache.event.type) &&947options.show.target.add(event.target).length === options.show.target.length &&948tooltip.has(event.relatedTarget).length) {949return this;950}951952// Cache event953cache.event = $.extend({}, event);954}955956// If we're currently waiting and we've just hidden... stop it957this.waiting && !state && (this.hiddenDuringWait = TRUE);958959// Render the tooltip if showing and it isn't already960if(!this.rendered) { return state ? this.render(1) : this; }961else if(this.destroyed || this.disabled) { return this; }962963var type = state ? 'show' : 'hide',964opts = this.options[type],965otherOpts = this.options[ !state ? 'show' : 'hide' ],966posOptions = this.options.position,967contentOptions = this.options.content,968width = this.tooltip.css('width'),969visible = this.tooltip[0].offsetWidth > 0,970animate = state || opts.target.length === 1,971sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,972identicalState, allow, showEvent, delay;973974// Detect state if valid one isn't provided975if((typeof state).search('boolean|number')) { state = !visible; }976977// Check if the tooltip is in an identical state to the new would-be state978identicalState = !tooltip.is(':animated') && visible === state && sameTarget;979980// Fire tooltip(show/hide) event and check if destroyed981allow = !identicalState ? !!this._trigger(type, [90]) : NULL;982983// If the user didn't stop the method prematurely and we're showing the tooltip, focus it984if(allow !== FALSE && state) { this.focus(event); }985986// If the state hasn't changed or the user stopped it, return early987if(!allow || identicalState) { return this; }988989// Set ARIA hidden attribute990$.attr(tooltip[0], 'aria-hidden', !!!state);991992// Execute state specific properties993if(state) {994// Store show origin coordinates995cache.origin = $.extend({}, this.mouse);996997// Update tooltip content & title if it's a dynamic function998if($.isFunction(contentOptions.text)) { this._updateContent(contentOptions.text, FALSE); }999if($.isFunction(contentOptions.title)) { this._updateTitle(contentOptions.title, FALSE); }10001001// Cache mousemove events for positioning purposes (if not already tracking)1002if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) {1003$(document).bind('mousemove.'+NAMESPACE, this._storeMouse);1004trackingBound = TRUE;1005}10061007// Update the tooltip position (set width first to prevent viewport/max-width issues)1008if(!width) { tooltip.css('width', tooltip.outerWidth(FALSE)); }1009this.reposition(event, arguments[2]);1010if(!width) { tooltip.css('width', ''); }10111012// Hide other tooltips if tooltip is solo1013if(!!opts.solo) {1014(typeof opts.solo === 'string' ? $(opts.solo) : $(SELECTOR, opts.solo))1015.not(tooltip).not(opts.target).qtip('hide', $.Event('tooltipsolo'));1016}1017}1018else {1019// Clear show timer if we're hiding1020clearTimeout(this.timers.show);10211022// Remove cached origin on hide1023delete cache.origin;10241025// Remove mouse tracking event if not needed (all tracking qTips are hidden)1026if(trackingBound && !$(SELECTOR+'[tracking="true"]:visible', opts.solo).not(tooltip).length) {1027$(document).unbind('mousemove.'+NAMESPACE);1028trackingBound = FALSE;1029}10301031// Blur the tooltip1032this.blur(event);1033}10341035// Define post-animation, state specific properties1036after = $.proxy(function() {1037if(state) {1038// Prevent antialias from disappearing in IE by removing filter1039if(BROWSER.ie) { tooltip[0].style.removeAttribute('filter'); }10401041// Remove overflow setting to prevent tip bugs1042tooltip.css('overflow', '');10431044// Autofocus elements if enabled1045if('string' === typeof opts.autofocus) {1046$(this.options.show.autofocus, tooltip).focus();1047}10481049// If set, hide tooltip when inactive for delay period1050this.options.show.target.trigger('qtip-'+this.id+'-inactive');1051}1052else {1053// Reset CSS states1054tooltip.css({1055display: '',1056visibility: '',1057opacity: '',1058left: '',1059top: ''1060});1061}10621063// tooltipvisible/tooltiphidden events1064this._trigger(state ? 'visible' : 'hidden');1065}, this);10661067// If no effect type is supplied, use a simple toggle1068if(opts.effect === FALSE || animate === FALSE) {1069tooltip[ type ]();1070after();1071}10721073// Use custom function if provided1074else if($.isFunction(opts.effect)) {1075tooltip.stop(1, 1);1076opts.effect.call(tooltip, this);1077tooltip.queue('fx', function(n) {1078after(); n();1079});1080}10811082// Use basic fade function by default1083else { tooltip.fadeTo(90, state ? 1 : 0, after); }10841085// If inactive hide method is set, active it1086if(state) { opts.target.trigger('qtip-'+this.id+'-inactive'); }10871088return this;1089};10901091PROTOTYPE.show = function(event) { return this.toggle(TRUE, event); };10921093PROTOTYPE.hide = function(event) { return this.toggle(FALSE, event); };10941095;PROTOTYPE.focus = function(event) {1096if(!this.rendered || this.destroyed) { return this; }10971098var qtips = $(SELECTOR),1099tooltip = this.tooltip,1100curIndex = parseInt(tooltip[0].style.zIndex, 10),1101newIndex = QTIP.zindex + qtips.length,1102focusedElem;11031104// Only update the z-index if it has changed and tooltip is not already focused1105if(!tooltip.hasClass(CLASS_FOCUS)) {1106// tooltipfocus event1107if(this._trigger('focus', [newIndex], event)) {1108// Only update z-index's if they've changed1109if(curIndex !== newIndex) {1110// Reduce our z-index's and keep them properly ordered1111qtips.each(function() {1112if(this.style.zIndex > curIndex) {1113this.style.zIndex = this.style.zIndex - 1;1114}1115});11161117// Fire blur event for focused tooltip1118qtips.filter('.' + CLASS_FOCUS).qtip('blur', event);1119}11201121// Set the new z-index1122tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;1123}1124}11251126return this;1127};11281129PROTOTYPE.blur = function(event) {1130if(!this.rendered || this.destroyed) { return this; }11311132// Set focused status to FALSE1133this.tooltip.removeClass(CLASS_FOCUS);11341135// tooltipblur event1136this._trigger('blur', [ this.tooltip.css('zIndex') ], event);11371138return this;1139};11401141;PROTOTYPE.disable = function(state) {1142if(this.destroyed) { return this; }11431144if('boolean' !== typeof state) {1145state = !(this.tooltip.hasClass(CLASS_DISABLED) || this.disabled);1146}11471148if(this.rendered) {1149this.tooltip.toggleClass(CLASS_DISABLED, state)1150.attr('aria-disabled', state);1151}11521153this.disabled = !!state;11541155return this;1156};11571158PROTOTYPE.enable = function() { return this.disable(FALSE); };11591160;PROTOTYPE._createButton = function()1161{1162var self = this,1163elements = this.elements,1164tooltip = elements.tooltip,1165button = this.options.content.button,1166isString = typeof button === 'string',1167close = isString ? button : 'Close tooltip';11681169if(elements.button) { elements.button.remove(); }11701171// Use custom button if one was supplied by user, else use default1172if(button.jquery) {1173elements.button = button;1174}1175else {1176elements.button = $('<a />', {1177'class': 'qtip-close ' + (this.options.style.widget ? '' : NAMESPACE+'-icon'),1178'title': close,1179'aria-label': close1180})1181.prepend(1182$('<span />', {1183'class': 'ui-icon ui-icon-close',1184'html': '×'1185})1186);1187}11881189// Create button and setup attributes1190elements.button.appendTo(elements.titlebar || tooltip)1191.attr('role', 'button')1192.click(function(event) {1193if(!tooltip.hasClass(CLASS_DISABLED)) { self.hide(event); }1194return FALSE;1195});1196};11971198PROTOTYPE._updateButton = function(button)1199{1200// Make sure tooltip is rendered and if not, return1201if(!this.rendered) { return FALSE; }12021203var elem = this.elements.button;1204if(button) { this._createButton(); }1205else { elem.remove(); }1206};12071208;// Widget class creator1209function createWidgetClass(cls) {1210return WIDGET.concat('').join(cls ? '-'+cls+' ' : ' ');1211}12121213// Widget class setter method1214PROTOTYPE._setWidget = function()1215{1216var on = this.options.style.widget,1217elements = this.elements,1218tooltip = elements.tooltip,1219disabled = tooltip.hasClass(CLASS_DISABLED);12201221tooltip.removeClass(CLASS_DISABLED);1222CLASS_DISABLED = on ? 'ui-state-disabled' : 'qtip-disabled';1223tooltip.toggleClass(CLASS_DISABLED, disabled);12241225tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(CLASS_DEFAULT, this.options.style.def && !on);12261227if(elements.content) {1228elements.content.toggleClass( createWidgetClass('content'), on);1229}1230if(elements.titlebar) {1231elements.titlebar.toggleClass( createWidgetClass('header'), on);1232}1233if(elements.button) {1234elements.button.toggleClass(NAMESPACE+'-icon', !on);1235}1236};;function showMethod(event) {1237if(this.tooltip.hasClass(CLASS_DISABLED)) { return FALSE; }12381239// Clear hide timers1240clearTimeout(this.timers.show);1241clearTimeout(this.timers.hide);12421243// Start show timer1244var callback = $.proxy(function(){ this.toggle(TRUE, event); }, this);1245if(this.options.show.delay > 0) {1246this.timers.show = setTimeout(callback, this.options.show.delay);1247}1248else{ callback(); }1249}12501251function hideMethod(event) {1252if(this.tooltip.hasClass(CLASS_DISABLED)) { return FALSE; }12531254// Check if new target was actually the tooltip element1255var relatedTarget = $(event.relatedTarget),1256ontoTooltip = relatedTarget.closest(SELECTOR)[0] === this.tooltip[0],1257ontoTarget = relatedTarget[0] === this.options.show.target[0];12581259// Clear timers and stop animation queue1260clearTimeout(this.timers.show);1261clearTimeout(this.timers.hide);12621263// Prevent hiding if tooltip is fixed and event target is the tooltip.1264// Or if mouse positioning is enabled and cursor momentarily overlaps1265if(this !== relatedTarget[0] &&1266(this.options.position.target === 'mouse' && ontoTooltip) ||1267(this.options.hide.fixed && (1268(/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget))1269))1270{1271try {1272event.preventDefault();1273event.stopImmediatePropagation();1274} catch(e) {}12751276return;1277}12781279// If tooltip has displayed, start hide timer1280var callback = $.proxy(function(){ this.toggle(FALSE, event); }, this);1281if(this.options.hide.delay > 0) {1282this.timers.hide = setTimeout(callback, this.options.hide.delay);1283}1284else{ callback(); }1285}12861287function inactiveMethod(event) {1288if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return FALSE; }12891290// Clear timer1291clearTimeout(this.timers.inactive);1292this.timers.inactive = setTimeout(1293$.proxy(function(){ this.hide(event); }, this), this.options.hide.inactive1294);1295}12961297function repositionMethod(event) {1298if(this.rendered && this.tooltip[0].offsetWidth > 0) { this.reposition(event); }1299}13001301// Store mouse coordinates1302PROTOTYPE._storeMouse = function(event) {1303this.mouse = {1304pageX: event.pageX,1305pageY: event.pageY,1306type: 'mousemove',1307scrollX: window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft,1308scrollY: window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop1309};1310};13111312// Bind events1313PROTOTYPE._bind = function(targets, events, method, suffix, context) {1314var ns = '.' + this._id + (suffix ? '-'+suffix : '');1315events.length && $(targets).bind(1316(events.split ? events : events.join(ns + ' ')) + ns,1317$.proxy(method, context || this)1318);1319};1320PROTOTYPE._unbind = function(targets, suffix) {1321$(targets).unbind('.' + this._id + (suffix ? '-'+suffix : ''));1322};13231324// Apply common event handlers using delegate (avoids excessive .bind calls!)1325var ns = '.'+NAMESPACE;1326function delegate(selector, events, method) {1327$(document.body).delegate(selector,1328(events.split ? events : events.join(ns + ' ')) + ns,1329function() {1330var api = QTIP.api[ $.attr(this, ATTR_ID) ];1331api && !api.disabled && method.apply(api, arguments);1332}1333);1334}13351336$(function() {1337delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) {1338var state = event.type === 'mouseenter',1339tooltip = $(event.currentTarget),1340target = $(event.relatedTarget || event.target),1341options = this.options;13421343// On mouseenter...1344if(state) {1345// Focus the tooltip on mouseenter (z-index stacking)1346this.focus(event);13471348// Clear hide timer on tooltip hover to prevent it from closing1349tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide);1350}13511352// On mouseleave...1353else {1354// Hide when we leave the tooltip and not onto the show target (if a hide event is set)1355if(options.position.target === 'mouse' && options.hide.event &&1356options.show.target && !target.closest(options.show.target[0]).length) {1357this.hide(event);1358}1359}13601361// Add hover class1362tooltip.toggleClass(CLASS_HOVER, state);1363});13641365// Define events which reset the 'inactive' event handler1366delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod);1367});13681369// Event trigger1370PROTOTYPE._trigger = function(type, args, event) {1371var callback = $.Event('tooltip'+type);1372callback.originalEvent = (event && $.extend({}, event)) || this.cache.event || NULL;13731374this.triggering = TRUE;1375this.tooltip.trigger(callback, [this].concat(args || []));1376this.triggering = FALSE;13771378return !callback.isDefaultPrevented();1379};13801381// Event assignment method1382PROTOTYPE._assignEvents = function() {1383var options = this.options,1384posOptions = options.position,13851386tooltip = this.tooltip,1387showTarget = options.show.target,1388hideTarget = options.hide.target,1389containerTarget = posOptions.container,1390viewportTarget = posOptions.viewport,1391documentTarget = $(document),1392bodyTarget = $(document.body),1393windowTarget = $(window),13941395showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],1396hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [],1397toggleEvents = [];13981399// Hide tooltips when leaving current window/frame (but not select/option elements)1400if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') {1401this._bind(documentTarget, ['mouseout', 'blur'], function(event) {1402if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) {1403this.hide(event);1404}1405});1406}14071408// Enable hide.fixed by adding appropriate class1409if(options.hide.fixed) {1410hideTarget = hideTarget.add( tooltip.addClass(CLASS_FIXED) );1411}14121413/*1414* Make sure hoverIntent functions properly by using mouseleave to clear show timer if1415* mouseenter/mouseout is used for show.event, even if it isn't in the users options.1416*/1417else if(/mouse(over|enter)/i.test(options.show.event)) {1418this._bind(hideTarget, 'mouseleave', function() {1419clearTimeout(this.timers.show);1420});1421}14221423// Hide tooltip on document mousedown if unfocus events are enabled1424if(('' + options.hide.event).indexOf('unfocus') > -1) {1425this._bind(containerTarget.closest('html'), ['mousedown', 'touchstart'], function(event) {1426var elem = $(event.target),1427enabled = this.rendered && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0,1428isAncestor = elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0;14291430if(elem[0] !== this.target[0] && elem[0] !== this.tooltip[0] && !isAncestor &&1431!this.target.has(elem[0]).length && enabled1432) {1433this.hide(event);1434}1435});1436}14371438// Check if the tooltip hides when inactive1439if('number' === typeof options.hide.inactive) {1440// Bind inactive method to show target(s) as a custom event1441this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod);14421443// Define events which reset the 'inactive' event handler1444this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod, '-inactive');1445}14461447// Apply hide events (and filter identical show events)1448hideEvents = $.map(hideEvents, function(type) {1449var showIndex = $.inArray(type, showEvents);14501451// Both events and targets are identical, apply events using a toggle1452if((showIndex > -1 && hideTarget.add(showTarget).length === hideTarget.length)) {1453toggleEvents.push( showEvents.splice( showIndex, 1 )[0] ); return;1454}14551456return type;1457});14581459// Apply show/hide/toggle events1460this._bind(showTarget, showEvents, showMethod);1461this._bind(hideTarget, hideEvents, hideMethod);1462this._bind(showTarget, toggleEvents, function(event) {1463(this.tooltip[0].offsetWidth > 0 ? hideMethod : showMethod).call(this, event);1464});146514661467// Mouse movement bindings1468this._bind(showTarget.add(tooltip), 'mousemove', function(event) {1469// Check if the tooltip hides when mouse is moved a certain distance1470if('number' === typeof options.hide.distance) {1471var origin = this.cache.origin || {},1472limit = this.options.hide.distance,1473abs = Math.abs;14741475// Check if the movement has gone beyond the limit, and hide it if so1476if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) {1477this.hide(event);1478}1479}14801481// Cache mousemove coords on show targets1482this._storeMouse(event);1483});14841485// Mouse positioning events1486if(posOptions.target === 'mouse') {1487// If mouse adjustment is on...1488if(posOptions.adjust.mouse) {1489// Apply a mouseleave event so we don't get problems with overlapping1490if(options.hide.event) {1491// Track if we're on the target or not1492this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) {1493this.cache.onTarget = event.type === 'mouseenter';1494});1495}14961497// Update tooltip position on mousemove1498this._bind(documentTarget, 'mousemove', function(event) {1499// Update the tooltip position only if the tooltip is visible and adjustment is enabled1500if(this.rendered && this.cache.onTarget && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0) {1501this.reposition(event);1502}1503});1504}1505}15061507// Adjust positions of the tooltip on window resize if enabled1508if(posOptions.adjust.resize || viewportTarget.length) {1509this._bind( $.event.special.resize ? viewportTarget : windowTarget, 'resize', repositionMethod );1510}15111512// Adjust tooltip position on scroll of the window or viewport element if present1513if(posOptions.adjust.scroll) {1514this._bind( windowTarget.add(posOptions.container), 'scroll', repositionMethod );1515}1516};15171518// Un-assignment method1519PROTOTYPE._unassignEvents = function() {1520var targets = [1521this.options.show.target[0],1522this.options.hide.target[0],1523this.rendered && this.tooltip[0],1524this.options.position.container[0],1525this.options.position.viewport[0],1526this.options.position.container.closest('html')[0], // unfocus1527window,1528document1529];15301531// Check if tooltip is rendered1532if(this.rendered) {1533this._unbind($([]).pushStack( $.grep(targets, function(i) {1534return typeof i === 'object';1535})));1536}15371538// Tooltip isn't yet rendered, remove render event1539else { $(targets[0]).unbind('.'+this._id+'-create'); }1540};15411542;// Initialization method1543function init(elem, id, opts)1544{1545var obj, posOptions, attr, config, title,15461547// Setup element references1548docBody = $(document.body),15491550// Use document body instead of document element if needed1551newTarget = elem[0] === document ? docBody : elem,15521553// Grab metadata from element if plugin is present1554metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL,15551556// If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise1557metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,15581559// Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,1560html5 = elem.data(opts.metadata.name || 'qtipopts');15611562// If we don't get an object returned attempt to parse it manualyl without parseJSON1563try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; } catch(e) {}15641565// Merge in and sanitize metadata1566config = $.extend(TRUE, {}, QTIP.defaults, opts,1567typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,1568sanitizeOptions(metadata5 || metadata));15691570// Re-grab our positioning options now we've merged our metadata and set id to passed value1571posOptions = config.position;1572config.id = id;15731574// Setup missing content if none is detected1575if('boolean' === typeof config.content.text) {1576attr = elem.attr(config.content.attr);15771578// Grab from supplied attribute if available1579if(config.content.attr !== FALSE && attr) { config.content.text = attr; }15801581// No valid content was found, abort render1582else { return FALSE; }1583}15841585// Setup target options1586if(!posOptions.container.length) { posOptions.container = docBody; }1587if(posOptions.target === FALSE) { posOptions.target = newTarget; }1588if(config.show.target === FALSE) { config.show.target = newTarget; }1589if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); }1590if(config.hide.target === FALSE) { config.hide.target = newTarget; }1591if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }15921593// Ensure we only use a single container1594posOptions.container = posOptions.container.eq(0);15951596// Convert position corner values into x and y strings1597posOptions.at = new CORNER(posOptions.at, TRUE);1598posOptions.my = new CORNER(posOptions.my);15991600// Destroy previous tooltip if overwrite is enabled, or skip element if not1601if(elem.data(NAMESPACE)) {1602if(config.overwrite) {1603elem.qtip('destroy');1604}1605else if(config.overwrite === FALSE) {1606return FALSE;1607}1608}16091610// Add has-qtip attribute1611elem.attr(ATTR_HAS, id);16121613// Remove title attribute and store it if present1614if(config.suppress && (title = elem.attr('title'))) {1615// Final attr call fixes event delegatiom and IE default tooltip showing problem1616elem.removeAttr('title').attr(oldtitle, title).attr('title', '');1617}16181619// Initialize the tooltip and add API reference1620obj = new QTip(elem, config, id, !!attr);1621elem.data(NAMESPACE, obj);16221623// Catch remove/removeqtip events on target element to destroy redundant tooltip1624elem.one('remove.qtip-'+id+' removeqtip.qtip-'+id, function() {1625var api; if((api = $(this).data(NAMESPACE))) { api.destroy(); }1626});16271628return obj;1629}16301631// jQuery $.fn extension method1632QTIP = $.fn.qtip = function(options, notation, newValue)1633{1634var command = ('' + options).toLowerCase(), // Parse command1635returned = NULL,1636args = $.makeArray(arguments).slice(1),1637event = args[args.length - 1],1638opts = this[0] ? $.data(this[0], NAMESPACE) : NULL;16391640// Check for API request1641if((!arguments.length && opts) || command === 'api') {1642return opts;1643}16441645// Execute API command if present1646else if('string' === typeof options)1647{1648this.each(function()1649{1650var api = $.data(this, NAMESPACE);1651if(!api) { return TRUE; }16521653// Cache the event if possible1654if(event && event.timeStamp) { api.cache.event = event; }16551656// Check for specific API commands1657if(notation && (command === 'option' || command === 'options')) {1658if(newValue !== undefined || $.isPlainObject(notation)) {1659api.set(notation, newValue);1660}1661else {1662returned = api.get(notation);1663return FALSE;1664}1665}16661667// Execute API command1668else if(api[command]) {1669api[command].apply(api, args);1670}1671});16721673return returned !== NULL ? returned : this;1674}16751676// No API commands. validate provided options and setup qTips1677else if('object' === typeof options || !arguments.length)1678{1679opts = sanitizeOptions($.extend(TRUE, {}, options));16801681// Bind the qTips1682return QTIP.bind.call(this, opts, event);1683}1684};16851686// $.fn.qtip Bind method1687QTIP.bind = function(opts, event)1688{1689return this.each(function(i) {1690var options, targets, events, namespace, api, id;16911692// Find next available ID, or use custom ID if provided1693id = $.isArray(opts.id) ? opts.id[i] : opts.id;1694id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id;16951696// Setup events namespace1697namespace = '.qtip-'+id+'-create';16981699// Initialize the qTip and re-grab newly sanitized options1700api = init($(this), id, opts);1701if(api === FALSE) { return TRUE; }1702else { QTIP.api[id] = api; }1703options = api.options;17041705// Initialize plugins1706$.each(PLUGINS, function() {1707if(this.initialize === 'initialize') { this(api); }1708});17091710// Determine hide and show targets1711targets = { show: options.show.target, hide: options.hide.target };1712events = {1713show: $.trim('' + options.show.event).replace(/ /g, namespace+' ') + namespace,1714hide: $.trim('' + options.hide.event).replace(/ /g, namespace+' ') + namespace1715};17161717/*1718* Make sure hoverIntent functions properly by using mouseleave as a hide event if1719* mouseenter/mouseout is used for show.event, even if it isn't in the users options.1720*/1721if(/mouse(over|enter)/i.test(events.show) && !/mouse(out|leave)/i.test(events.hide)) {1722events.hide += ' mouseleave' + namespace;1723}17241725/*1726* Also make sure initial mouse targetting works correctly by caching mousemove coords1727* on show targets before the tooltip has rendered.1728*1729* Also set onTarget when triggered to keep mouse tracking working1730*/1731targets.show.bind('mousemove'+namespace, function(event) {1732api._storeMouse(event);1733api.cache.onTarget = TRUE;1734});17351736// Define hoverIntent function1737function hoverIntent(event) {1738function render() {1739// Cache mouse coords,render and render the tooltip1740api.render(typeof event === 'object' || options.show.ready);17411742// Unbind show and hide events1743targets.show.add(targets.hide).unbind(namespace);1744}17451746// Only continue if tooltip isn't disabled1747if(api.disabled) { return FALSE; }17481749// Cache the event data1750api.cache.event = $.extend({}, event);1751api.cache.target = event ? $(event.target) : [undefined];17521753// Start the event sequence1754if(options.show.delay > 0) {1755clearTimeout(api.timers.show);1756api.timers.show = setTimeout(render, options.show.delay);1757if(events.show !== events.hide) {1758targets.hide.bind(events.hide, function() { clearTimeout(api.timers.show); });1759}1760}1761else { render(); }1762}17631764// Bind show events to target1765targets.show.bind(events.show, hoverIntent);17661767// Prerendering is enabled, create tooltip now1768if(options.show.ready || options.prerender) { hoverIntent(event); }1769});1770};17711772// Populated in render method1773QTIP.api = {};1774;$.each({1775/* Allow other plugins to successfully retrieve the title of an element with a qTip applied */1776attr: function(attr, val) {1777if(this.length) {1778var self = this[0],1779title = 'title',1780api = $.data(self, 'qtip');17811782if(attr === title && api && 'object' === typeof api && api.options.suppress) {1783if(arguments.length < 2) {1784return $.attr(self, oldtitle);1785}17861787// If qTip is rendered and title was originally used as content, update it1788if(api && api.options.content.attr === title && api.cache.attr) {1789api.set('content.text', val);1790}17911792// Use the regular attr method to set, then cache the result1793return this.attr(oldtitle, val);1794}1795}17961797return $.fn['attr'+replaceSuffix].apply(this, arguments);1798},17991800/* Allow clone to correctly retrieve cached title attributes */1801clone: function(keepData) {1802var titles = $([]), title = 'title',18031804// Clone our element using the real clone method1805elems = $.fn['clone'+replaceSuffix].apply(this, arguments);18061807// Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false1808if(!keepData) {1809elems.filter('['+oldtitle+']').attr('title', function() {1810return $.attr(this, oldtitle);1811})1812.removeAttr(oldtitle);1813}18141815return elems;1816}1817}, function(name, func) {1818if(!func || $.fn[name+replaceSuffix]) { return TRUE; }18191820var old = $.fn[name+replaceSuffix] = $.fn[name];1821$.fn[name] = function() {1822return func.apply(this, arguments) || old.apply(this, arguments);1823};1824});18251826/* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar).1827* This snippet is taken directly from jQuery UI source code found here:1828* http://code.jquery.com/ui/jquery-ui-git.js1829*/1830if(!$.ui) {1831$['cleanData'+replaceSuffix] = $.cleanData;1832$.cleanData = function( elems ) {1833for(var i = 0, elem; (elem = $( elems[i] )).length; i++) {1834if(elem.attr(ATTR_HAS)) {1835try { elem.triggerHandler('removeqtip'); }1836catch( e ) {}1837}1838}1839$['cleanData'+replaceSuffix].apply(this, arguments);1840};1841}18421843;// qTip version1844QTIP.version = '2.1.1';18451846// Base ID for all qTips1847QTIP.nextid = 0;18481849// Inactive events array1850QTIP.inactiveEvents = INACTIVE_EVENTS;18511852// Base z-index for all qTips1853QTIP.zindex = 15000;18541855// Define configuration defaults1856QTIP.defaults = {1857prerender: FALSE,1858id: FALSE,1859overwrite: TRUE,1860suppress: TRUE,1861content: {1862text: TRUE,1863attr: 'title',1864title: FALSE,1865button: FALSE1866},1867position: {1868my: 'top left',1869at: 'bottom right',1870target: FALSE,1871container: FALSE,1872viewport: FALSE,1873adjust: {1874x: 0, y: 0,1875mouse: TRUE,1876scroll: TRUE,1877resize: TRUE,1878method: 'flipinvert flipinvert'1879},1880effect: function(api, pos, viewport) {1881$(this).animate(pos, {1882duration: 200,1883queue: FALSE1884});1885}1886},1887show: {1888target: FALSE,1889event: 'mouseenter',1890effect: TRUE,1891delay: 90,1892solo: FALSE,1893ready: FALSE,1894autofocus: FALSE1895},1896hide: {1897target: FALSE,1898event: 'mouseleave',1899effect: TRUE,1900delay: 0,1901fixed: FALSE,1902inactive: FALSE,1903leave: 'window',1904distance: FALSE1905},1906style: {1907classes: '',1908widget: FALSE,1909width: FALSE,1910height: FALSE,1911def: TRUE1912},1913events: {1914render: NULL,1915move: NULL,1916show: NULL,1917hide: NULL,1918toggle: NULL,1919visible: NULL,1920hidden: NULL,1921focus: NULL,1922blur: NULL1923}1924};19251926;var TIP,19271928// .bind()/.on() namespace1929TIPNS = '.qtip-tip',19301931// Common CSS strings1932MARGIN = 'margin',1933BORDER = 'border',1934COLOR = 'color',1935BG_COLOR = 'background-color',1936TRANSPARENT = 'transparent',1937IMPORTANT = ' !important',19381939// Check if the browser supports <canvas/> elements1940HASCANVAS = !!document.createElement('canvas').getContext,19411942// Invalid colour values used in parseColours()1943INVALID = /rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i;19441945// Camel-case method, taken from jQuery source1946// http://code.jquery.com/jquery-1.8.0.js1947function camel(s) { return s.charAt(0).toUpperCase() + s.slice(1); }19481949/*1950* Modified from Modernizr's testPropsAll()1951* http://modernizr.com/downloads/modernizr-latest.js1952*/1953var cssProps = {}, cssPrefixes = ["Webkit", "O", "Moz", "ms"];1954function vendorCss(elem, prop) {1955var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),1956props = (prop + ' ' + cssPrefixes.join(ucProp + ' ') + ucProp).split(' '),1957cur, val, i = 0;19581959// If the property has already been mapped...1960if(cssProps[prop]) { return elem.css(cssProps[prop]); }19611962while((cur = props[i++])) {1963if((val = elem.css(cur)) !== undefined) {1964return cssProps[prop] = cur, val;1965}1966}1967}19681969// Parse a given elements CSS property into an int1970function intCss(elem, prop) {1971return parseInt(vendorCss(elem, prop), 10);1972}197319741975// VML creation (for IE only)1976if(!HASCANVAS) {1977createVML = function(tag, props, style) {1978return '<qtipvml:'+tag+' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" '+(props||'')+1979' style="behavior: url(#default#VML); '+(style||'')+ '" />';1980};1981}1982198319841985function Tip(qtip, options) {1986this._ns = 'tip';1987this.options = options;1988this.offset = options.offset;1989this.size = [ options.width, options.height ];19901991// Initialize1992this.init( (this.qtip = qtip) );1993}19941995$.extend(Tip.prototype, {1996init: function(qtip) {1997var context, tip;19981999// Create tip element and prepend to the tooltip2000tip = this.element = qtip.elements.tip = $('<div />', { 'class': NAMESPACE+'-tip' }).prependTo(qtip.tooltip);20012002// Create tip drawing element(s)2003if(HASCANVAS) {2004// save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()!2005context = $('<canvas />').appendTo(this.element)[0].getContext('2d');20062007// Setup constant parameters2008context.lineJoin = 'miter';2009context.miterLimit = 100;2010context.save();2011}2012else {2013context = createVML('shape', 'coordorigin="0,0"', 'position:absolute;');2014this.element.html(context + context);20152016// Prevent mousing down on the tip since it causes problems with .live() handling in IE due to VML2017qtip._bind( $('*', tip).add(tip), ['click', 'mousedown'], function(event) { event.stopPropagation(); }, this._ns);2018}20192020// Bind update events2021qtip._bind(qtip.tooltip, 'tooltipmove', this.reposition, this._ns, this);20222023// Create it2024this.create();2025},20262027_swapDimensions: function() {2028this.size[0] = this.options.height;2029this.size[1] = this.options.width;2030},2031_resetDimensions: function() {2032this.size[0] = this.options.width;2033this.size[1] = this.options.height;2034},20352036_useTitle: function(corner) {2037var titlebar = this.qtip.elements.titlebar;2038return titlebar && (2039corner.y === TOP || (corner.y === CENTER && this.element.position().top + (this.size[1] / 2) + this.options.offset < titlebar.outerHeight(TRUE))2040);2041},20422043_parseCorner: function(corner) {2044var my = this.qtip.options.position.my;20452046// Detect corner and mimic properties2047if(corner === FALSE || my === FALSE) {2048corner = FALSE;2049}2050else if(corner === TRUE) {2051corner = new CORNER( my.string() );2052}2053else if(!corner.string) {2054corner = new CORNER(corner);2055corner.fixed = TRUE;2056}20572058return corner;2059},20602061_parseWidth: function(corner, side, use) {2062var elements = this.qtip.elements,2063prop = BORDER + camel(side) + 'Width';20642065return (use ? intCss(use, prop) : (2066intCss(elements.content, prop) ||2067intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||2068intCss(tooltip, prop)2069)) || 0;2070},20712072_parseRadius: function(corner) {2073var elements = this.qtip.elements,2074prop = BORDER + camel(corner.y) + camel(corner.x) + 'Radius';20752076return BROWSER.ie < 9 ? 0 :2077intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||2078intCss(elements.tooltip, prop) || 0;2079},20802081_invalidColour: function(elem, prop, compare) {2082var val = elem.css(prop);2083return !val || (compare && val === elem.css(compare)) || INVALID.test(val) ? FALSE : val;2084},20852086_parseColours: function(corner) {2087var elements = this.qtip.elements,2088tip = this.element.css('cssText', ''),2089borderSide = BORDER + camel(corner[ corner.precedance ]) + camel(COLOR),2090colorElem = this._useTitle(corner) && elements.titlebar || elements.content,2091css = this._invalidColour, color = [];20922093// Attempt to detect the background colour from various elements, left-to-right precedance2094color[0] = css(tip, BG_COLOR) || css(colorElem, BG_COLOR) || css(elements.content, BG_COLOR) ||2095css(tooltip, BG_COLOR) || tip.css(BG_COLOR);20962097// Attempt to detect the correct border side colour from various elements, left-to-right precedance2098color[1] = css(tip, borderSide, COLOR) || css(colorElem, borderSide, COLOR) ||2099css(elements.content, borderSide, COLOR) || css(tooltip, borderSide, COLOR) || tooltip.css(borderSide);21002101// Reset background and border colours2102$('*', tip).add(tip).css('cssText', BG_COLOR+':'+TRANSPARENT+IMPORTANT+';'+BORDER+':0'+IMPORTANT+';');21032104return color;2105},21062107_calculateSize: function(corner) {2108var y = corner.precedance === Y,2109width = this.options[ y ? 'height' : 'width' ],2110height = this.options[ y ? 'width' : 'height' ],2111isCenter = corner.abbrev() === 'c',2112base = width * (isCenter ? 0.5 : 1),2113pow = Math.pow,2114round = Math.round,2115bigHyp, ratio, result,21162117smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ),2118hyp = [ (this.border / base) * smallHyp, (this.border / height) * smallHyp ];21192120hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(this.border, 2) );2121hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(this.border, 2) );21222123bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]);2124ratio = bigHyp / smallHyp;21252126result = [ round(ratio * width), round(ratio * height) ];21272128return y ? result : result.reverse();2129},21302131// Tip coordinates calculator2132_calculateTip: function(corner) {2133var width = this.size[0], height = this.size[1],2134width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2),21352136// Define tip coordinates in terms of height and width values2137tips = {2138br: [0,0, width,height, width,0],2139bl: [0,0, width,0, 0,height],2140tr: [0,height, width,0, width,height],2141tl: [0,0, 0,height, width,height],2142tc: [0,height, width2,0, width,height],2143bc: [0,0, width,0, width2,height],2144rc: [0,0, width,height2, 0,height],2145lc: [width,0, width,height, 0,height2]2146};21472148// Set common side shapes2149tips.lt = tips.br; tips.rt = tips.bl;2150tips.lb = tips.tr; tips.rb = tips.tl;21512152return tips[ corner.abbrev() ];2153},21542155create: function() {2156// Determine tip corner2157var c = this.corner = (HASCANVAS || BROWSER.ie) && this._parseCorner(this.options.corner);21582159// If we have a tip corner...2160if( (this.enabled = !!this.corner && this.corner.abbrev() !== 'c') ) {2161// Cache it2162this.qtip.cache.corner = c.clone();21632164// Create it2165this.update();2166}21672168// Toggle tip element2169this.element.toggle(this.enabled);21702171return this.corner;2172},21732174update: function(corner, position) {2175if(!this.enabled) { return this; }21762177var elements = this.qtip.elements,2178tip = this.element,2179inner = tip.children(),2180options = this.options,2181size = this.size,2182mimic = options.mimic,2183round = Math.round,2184color, precedance, context,2185coords, translate, newSize, border;21862187// Re-determine tip if not already set2188if(!corner) { corner = this.qtip.cache.corner || this.corner; }21892190// Use corner property if we detect an invalid mimic value2191if(mimic === FALSE) { mimic = corner; }21922193// Otherwise inherit mimic properties from the corner object as necessary2194else {2195mimic = new CORNER(mimic);2196mimic.precedance = corner.precedance;21972198if(mimic.x === 'inherit') { mimic.x = corner.x; }2199else if(mimic.y === 'inherit') { mimic.y = corner.y; }2200else if(mimic.x === mimic.y) {2201mimic[ corner.precedance ] = corner[ corner.precedance ];2202}2203}2204precedance = mimic.precedance;22052206// Ensure the tip width.height are relative to the tip position2207if(corner.precedance === X) { this._swapDimensions(); }2208else { this._resetDimensions(); }22092210// Update our colours2211color = this.color = this._parseColours(corner);22122213// Detect border width, taking into account colours2214if(color[1] !== TRANSPARENT) {2215// Grab border width2216border = this.border = this._parseWidth(corner, corner[corner.precedance]);22172218// If border width isn't zero, use border color as fill (1.0 style tips)2219if(options.border && border < 1) { color[0] = color[1]; }22202221// Set border width (use detected border width if options.border is true)2222this.border = border = options.border !== TRUE ? options.border : border;2223}22242225// Border colour was invalid, set border to zero2226else { this.border = border = 0; }22272228// Calculate coordinates2229coords = this._calculateTip(mimic);22302231// Determine tip size2232newSize = this.size = this._calculateSize(corner);2233tip.css({2234width: newSize[0],2235height: newSize[1],2236lineHeight: newSize[1]+'px'2237});22382239// Calculate tip translation2240if(corner.precedance === Y) {2241translate = [2242round(mimic.x === LEFT ? border : mimic.x === RIGHT ? newSize[0] - size[0] - border : (newSize[0] - size[0]) / 2),2243round(mimic.y === TOP ? newSize[1] - size[1] : 0)2244];2245}2246else {2247translate = [2248round(mimic.x === LEFT ? newSize[0] - size[0] : 0),2249round(mimic.y === TOP ? border : mimic.y === BOTTOM ? newSize[1] - size[1] - border : (newSize[1] - size[1]) / 2)2250];2251}22522253// Canvas drawing implementation2254if(HASCANVAS) {2255// Set the canvas size using calculated size2256inner.attr(WIDTH, newSize[0]).attr(HEIGHT, newSize[1]);22572258// Grab canvas context and clear/save it2259context = inner[0].getContext('2d');2260context.restore(); context.save();2261context.clearRect(0,0,3000,3000);22622263// Set properties2264context.fillStyle = color[0];2265context.strokeStyle = color[1];2266context.lineWidth = border * 2;22672268// Draw the tip2269context.translate(translate[0], translate[1]);2270context.beginPath();2271context.moveTo(coords[0], coords[1]);2272context.lineTo(coords[2], coords[3]);2273context.lineTo(coords[4], coords[5]);2274context.closePath();22752276// Apply fill and border2277if(border) {2278// Make sure transparent borders are supported by doing a stroke2279// of the background colour before the stroke colour2280if(tooltip.css('background-clip') === 'border-box') {2281context.strokeStyle = color[0];2282context.stroke();2283}2284context.strokeStyle = color[1];2285context.stroke();2286}2287context.fill();2288}22892290// VML (IE Proprietary implementation)2291else {2292// Setup coordinates string2293coords = 'm' + coords[0] + ',' + coords[1] + ' l' + coords[2] +2294',' + coords[3] + ' ' + coords[4] + ',' + coords[5] + ' xe';22952296// Setup VML-specific offset for pixel-perfection2297translate[2] = border && /^(r|b)/i.test(corner.string()) ?2298BROWSER.ie === 8 ? 2 : 1 : 0;22992300// Set initial CSS2301inner.css({2302coordsize: (size[0]+border) + ' ' + (size[1]+border),2303antialias: ''+(mimic.string().indexOf(CENTER) > -1),2304left: translate[0] - (translate[2] * Number(precedance === X)),2305top: translate[1] - (translate[2] * Number(precedance === Y)),2306width: size[0] + border,2307height: size[1] + border2308})2309.each(function(i) {2310var $this = $(this);23112312// Set shape specific attributes2313$this[ $this.prop ? 'prop' : 'attr' ]({2314coordsize: (size[0]+border) + ' ' + (size[1]+border),2315path: coords,2316fillcolor: color[0],2317filled: !!i,2318stroked: !i2319})2320.toggle(!!(border || i));23212322// Check if border is enabled and add stroke element2323!i && $this.html( createVML(2324'stroke', 'weight="'+(border*2)+'px" color="'+color[1]+'" miterlimit="1000" joinstyle="miter"'2325) );2326});2327}23282329// Position if needed2330if(position !== FALSE) { this.calculate(corner); }2331},23322333calculate: function(corner) {2334if(!this.enabled) { return FALSE; }23352336var self = this,2337elements = this.qtip.elements,2338tip = this.element,2339userOffset = this.options.offset,2340isWidget = this.qtip.tooltip.hasClass('ui-widget'),2341position = { },2342precedance, size, corners;23432344// Inherit corner if not provided2345corner = corner || this.corner;2346precedance = corner.precedance;23472348// Determine which tip dimension to use for adjustment2349size = this._calculateSize(corner);23502351// Setup corners and offset array2352corners = [ corner.x, corner.y ];2353if(precedance === X) { corners.reverse(); }23542355// Calculate tip position2356$.each(corners, function(i, side) {2357var b, bc, br;23582359if(side === CENTER) {2360b = precedance === Y ? LEFT : TOP;2361position[ b ] = '50%';2362position[MARGIN+'-' + b] = -Math.round(size[ precedance === Y ? 0 : 1 ] / 2) + userOffset;2363}2364else {2365b = self._parseWidth(corner, side, elements.tooltip);2366bc = self._parseWidth(corner, side, elements.content);2367br = self._parseRadius(corner);23682369position[ side ] = Math.max(-self.border, i ? bc : (userOffset + (br > b ? br : -b)));2370}2371});23722373// Adjust for tip size2374position[ corner[precedance] ] -= size[ precedance === X ? 0 : 1 ];23752376// Set and return new position2377tip.css({ margin: '', top: '', bottom: '', left: '', right: '' }).css(position);2378return position;2379},23802381reposition: function(event, api, pos, viewport) {2382if(!this.enabled) { return; }23832384var cache = api.cache,2385newCorner = this.corner.clone(),2386adjust = pos.adjusted,2387method = api.options.position.adjust.method.split(' '),2388horizontal = method[0],2389vertical = method[1] || method[0],2390shift = { left: FALSE, top: FALSE, x: 0, y: 0 },2391offset, css = {}, props;23922393// If our tip position isn't fixed e.g. doesn't adjust with viewport...2394if(this.corner.fixed !== TRUE) {2395// Horizontal - Shift or flip method2396if(horizontal === SHIFT && newCorner.precedance === X && adjust.left && newCorner.y !== CENTER) {2397newCorner.precedance = newCorner.precedance === X ? Y : X;2398}2399else if(horizontal !== SHIFT && adjust.left){2400newCorner.x = newCorner.x === CENTER ? (adjust.left > 0 ? LEFT : RIGHT) : (newCorner.x === LEFT ? RIGHT : LEFT);2401}24022403// Vertical - Shift or flip method2404if(vertical === SHIFT && newCorner.precedance === Y && adjust.top && newCorner.x !== CENTER) {2405newCorner.precedance = newCorner.precedance === Y ? X : Y;2406}2407else if(vertical !== SHIFT && adjust.top) {2408newCorner.y = newCorner.y === CENTER ? (adjust.top > 0 ? TOP : BOTTOM) : (newCorner.y === TOP ? BOTTOM : TOP);2409}24102411// Update and redraw the tip if needed (check cached details of last drawn tip)2412if(newCorner.string() !== cache.corner.string() && (cache.cornerTop !== adjust.top || cache.cornerLeft !== adjust.left)) {2413this.update(newCorner, FALSE);2414}2415}24162417// Setup tip offset properties2418offset = this.calculate(newCorner, adjust);24192420// Readjust offset object to make it left/top2421if(offset.right !== undefined) { offset.left = -offset.right; }2422if(offset.bottom !== undefined) { offset.top = -offset.bottom; }2423offset.user = this.offset;24242425// Viewport "shift" specific adjustments2426if(shift.left = (horizontal === SHIFT && !!adjust.left)) {2427if(newCorner.x === CENTER) {2428css[MARGIN+'-left'] = shift.x = offset[MARGIN+'-left'] - adjust.left;2429}2430else {2431props = offset.right !== undefined ?2432[ adjust.left, -offset.left ] : [ -adjust.left, offset.left ];24332434if( (shift.x = Math.max(props[0], props[1])) > props[0] ) {2435pos.left -= adjust.left;2436shift.left = FALSE;2437}24382439css[ offset.right !== undefined ? RIGHT : LEFT ] = shift.x;2440}2441}2442if(shift.top = (vertical === SHIFT && !!adjust.top)) {2443if(newCorner.y === CENTER) {2444css[MARGIN+'-top'] = shift.y = offset[MARGIN+'-top'] - adjust.top;2445}2446else {2447props = offset.bottom !== undefined ?2448[ adjust.top, -offset.top ] : [ -adjust.top, offset.top ];24492450if( (shift.y = Math.max(props[0], props[1])) > props[0] ) {2451pos.top -= adjust.top;2452shift.top = FALSE;2453}24542455css[ offset.bottom !== undefined ? BOTTOM : TOP ] = shift.y;2456}2457}24582459/*2460* If the tip is adjusted in both dimensions, or in a2461* direction that would cause it to be anywhere but the2462* outer border, hide it!2463*/2464this.element.css(css).toggle(2465!((shift.x && shift.y) || (newCorner.x === CENTER && shift.y) || (newCorner.y === CENTER && shift.x))2466);24672468// Adjust position to accomodate tip dimensions2469pos.left -= offset.left.charAt ? offset.user : horizontal !== SHIFT || shift.top || !shift.left && !shift.top ? offset.left : 0;2470pos.top -= offset.top.charAt ? offset.user : vertical !== SHIFT || shift.left || !shift.left && !shift.top ? offset.top : 0;24712472// Cache details2473cache.cornerLeft = adjust.left; cache.cornerTop = adjust.top;2474cache.corner = newCorner.clone();2475},24762477destroy: function() {2478// Unbind events2479this.qtip._unbind(this.qtip.tooltip, this._ns);24802481// Remove the tip element(s)2482if(this.qtip.elements.tip) {2483this.qtip.elements.tip.find('*')2484.remove().end().remove();2485}2486}2487});24882489TIP = PLUGINS.tip = function(api) {2490return new Tip(api, api.options.style.tip);2491};24922493// Initialize tip on render2494TIP.initialize = 'render';24952496// Setup plugin sanitization options2497TIP.sanitize = function(options) {2498if(options.style && 'tip' in options.style) {2499opts = options.style.tip;2500if(typeof opts !== 'object') { opts = options.style.tip = { corner: opts }; }2501if(!(/string|boolean/i).test(typeof opts.corner)) { opts.corner = TRUE; }2502}2503};25042505// Add new option checks for the plugin2506CHECKS.tip = {2507'^position.my|style.tip.(corner|mimic|border)$': function() {2508// Make sure a tip can be drawn2509this.create();25102511// Reposition the tooltip2512this.qtip.reposition();2513},2514'^style.tip.(height|width)$': function(obj) {2515// Re-set dimensions and redraw the tip2516this.size = size = [ obj.width, obj.height ];2517this.update();25182519// Reposition the tooltip2520this.qtip.reposition();2521},2522'^content.title|style.(classes|widget)$': function() {2523this.update();2524}2525};25262527// Extend original qTip defaults2528$.extend(TRUE, QTIP.defaults, {2529style: {2530tip: {2531corner: TRUE,2532mimic: FALSE,2533width: 6,2534height: 6,2535border: TRUE,2536offset: 02537}2538}2539});25402541;PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight)2542{2543var target = posOptions.target,2544tooltip = api.elements.tooltip,2545my = posOptions.my,2546at = posOptions.at,2547adjust = posOptions.adjust,2548method = adjust.method.split(' '),2549methodX = method[0],2550methodY = method[1] || method[0],2551viewport = posOptions.viewport,2552container = posOptions.container,2553cache = api.cache,2554tip = api.plugins.tip,2555adjusted = { left: 0, top: 0 },2556fixed, newMy, newClass;25572558// If viewport is not a jQuery element, or it's the window/document or no adjustment method is used... return2559if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') {2560return adjusted;2561}25622563// Cache our viewport details2564fixed = tooltip.css('position') === 'fixed';2565viewport = {2566elem: viewport,2567width: viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE),2568height: viewport[0] === window ? viewport.height() : viewport.outerHeight(FALSE),2569scrollleft: fixed ? 0 : viewport.scrollLeft(),2570scrolltop: fixed ? 0 : viewport.scrollTop(),2571offset: viewport.offset() || { left: 0, top: 0 }2572};2573container = {2574elem: container,2575scrollLeft: container.scrollLeft(),2576scrollTop: container.scrollTop(),2577offset: container.offset() || { left: 0, top: 0 }2578};25792580// Generic calculation method2581function calculate(side, otherSide, type, adjust, side1, side2, lengthName, targetLength, elemLength) {2582var initialPos = position[side1],2583mySide = my[side], atSide = at[side],2584isShift = type === SHIFT,2585viewportScroll = -container.offset[side1] + viewport.offset[side1] + viewport['scroll'+side1],2586myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2,2587atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2,2588tipLength = tip && tip.size ? tip.size[lengthName] || 0 : 0,2589tipAdjust = tip && tip.corner && tip.corner.precedance === side && !isShift ? tipLength : 0,2590overflow1 = viewportScroll - initialPos + tipAdjust,2591overflow2 = initialPos + elemLength - viewport[lengthName] - viewportScroll + tipAdjust,2592offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0);25932594// shift2595if(isShift) {2596tipAdjust = tip && tip.corner && tip.corner.precedance === otherSide ? tipLength : 0;2597offset = (mySide === side1 ? 1 : -1) * myLength - tipAdjust;25982599// Adjust position but keep it within viewport dimensions2600position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0;2601position[side1] = Math.max(2602-container.offset[side1] + viewport.offset[side1] + (tipAdjust && tip.corner[side] === CENTER ? tip.offset : 0),2603initialPos - offset,2604Math.min(2605Math.max(-container.offset[side1] + viewport.offset[side1] + viewport[lengthName], initialPos + offset),2606position[side1]2607)2608);2609}26102611// flip/flipinvert2612else {2613// Update adjustment amount depending on if using flipinvert or flip2614adjust *= (type === FLIPINVERT ? 2 : 0);26152616// Check for overflow on the left/top2617if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) {2618position[side1] -= offset + adjust;2619newMy.invert(side, side1);2620}26212622// Check for overflow on the bottom/right2623else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0) ) {2624position[side1] -= (mySide === CENTER ? -offset : offset) + adjust;2625newMy.invert(side, side2);2626}26272628// Make sure we haven't made things worse with the adjustment and reset if so2629if(position[side1] < viewportScroll && -position[side1] > overflow2) {2630position[side1] = initialPos; newMy = my.clone();2631}2632}26332634return position[side1] - initialPos;2635}26362637// Set newMy if using flip or flipinvert methods2638if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); }26392640// Adjust position based onviewport and adjustment options2641adjusted = {2642left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0,2643top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 02644};26452646// Set tooltip position class if it's changed2647if(newMy && cache.lastClass !== (newClass = NAMESPACE + '-pos-' + newMy.abbrev())) {2648tooltip.removeClass(api.cache.lastClass).addClass( (api.cache.lastClass = newClass) );2649}26502651return adjusted;2652};;}));2653}( window, document ));26542655265626572658