You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2108 lines
61 KiB
2108 lines
61 KiB
/* |
|
* qTip2 - Pretty powerful tooltips - v2.2.1 |
|
* http://qtip2.com |
|
* |
|
* Copyright (c) 2015 |
|
* Released under the MIT licenses |
|
* http://jquery.org/license |
|
* |
|
* Date: Mon Aug 10 2015 04:42 EDT-0400 |
|
* Plugins: viewport |
|
* Styles: core basic css3 |
|
*/ |
|
/*global window: false, jQuery: false, console: false, define: false */ |
|
|
|
/* Cache window, document, undefined */ |
|
(function( window, document, undefined ) { |
|
|
|
// Uses AMD or browser globals to create a jQuery plugin. |
|
(function( factory ) { |
|
"use strict"; |
|
if(typeof define === 'function' && define.amd) { |
|
define(['jquery'], factory); |
|
} |
|
else if(jQuery && !jQuery.fn.qtip) { |
|
factory(jQuery); |
|
} |
|
} |
|
(function($) { |
|
"use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/ |
|
;// Munge the primitives - Paul Irish tip |
|
var TRUE = true, |
|
FALSE = false, |
|
NULL = null, |
|
|
|
// Common variables |
|
X = 'x', Y = 'y', |
|
WIDTH = 'width', |
|
HEIGHT = 'height', |
|
|
|
// Positioning sides |
|
TOP = 'top', |
|
LEFT = 'left', |
|
BOTTOM = 'bottom', |
|
RIGHT = 'right', |
|
CENTER = 'center', |
|
|
|
// Position adjustment types |
|
FLIP = 'flip', |
|
FLIPINVERT = 'flipinvert', |
|
SHIFT = 'shift', |
|
|
|
// Shortcut vars |
|
QTIP, PROTOTYPE, CORNER, CHECKS, |
|
PLUGINS = {}, |
|
NAMESPACE = 'qtip', |
|
ATTR_HAS = 'data-hasqtip', |
|
ATTR_ID = 'data-qtip-id', |
|
WIDGET = ['ui-widget', 'ui-tooltip'], |
|
SELECTOR = '.'+NAMESPACE, |
|
INACTIVE_EVENTS = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' '), |
|
|
|
CLASS_FIXED = NAMESPACE+'-fixed', |
|
CLASS_DEFAULT = NAMESPACE + '-default', |
|
CLASS_FOCUS = NAMESPACE + '-focus', |
|
CLASS_HOVER = NAMESPACE + '-hover', |
|
CLASS_DISABLED = NAMESPACE+'-disabled', |
|
|
|
replaceSuffix = '_replacedByqTip', |
|
oldtitle = 'oldtitle', |
|
trackingBound, |
|
|
|
// Browser detection |
|
BROWSER = { |
|
/* |
|
* IE version detection |
|
* |
|
* Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment |
|
* Credit to James Padolsey for the original implemntation! |
|
*/ |
|
ie: (function(){ |
|
for ( |
|
var v = 4, i = document.createElement("div"); |
|
(i.innerHTML = "<!--[if gt IE " + v + "]><i></i><![endif]-->") && i.getElementsByTagName("i")[0]; |
|
v+=1 |
|
) {} |
|
return v > 4 ? v : NaN; |
|
}()), |
|
|
|
/* |
|
* iOS version detection |
|
*/ |
|
iOS: parseFloat( |
|
('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1]) |
|
.replace('undefined', '3_2').replace('_', '.').replace('_', '') |
|
) || FALSE |
|
}; |
|
;function QTip(target, options, id, attr) { |
|
// Elements and ID |
|
this.id = id; |
|
this.target = target; |
|
this.tooltip = NULL; |
|
this.elements = { target: target }; |
|
|
|
// Internal constructs |
|
this._id = NAMESPACE + '-' + id; |
|
this.timers = { img: {} }; |
|
this.options = options; |
|
this.plugins = {}; |
|
|
|
// Cache object |
|
this.cache = { |
|
event: {}, |
|
target: $(), |
|
disabled: FALSE, |
|
attr: attr, |
|
onTooltip: FALSE, |
|
lastClass: '' |
|
}; |
|
|
|
// Set the initial flags |
|
this.rendered = this.destroyed = this.disabled = this.waiting = |
|
this.hiddenDuringWait = this.positioning = this.triggering = FALSE; |
|
} |
|
PROTOTYPE = QTip.prototype; |
|
|
|
PROTOTYPE._when = function(deferreds) { |
|
return $.when.apply($, deferreds); |
|
}; |
|
|
|
PROTOTYPE.render = function(show) { |
|
if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit |
|
|
|
var self = this, |
|
options = this.options, |
|
cache = this.cache, |
|
elements = this.elements, |
|
text = options.content.text, |
|
title = options.content.title, |
|
button = options.content.button, |
|
posOptions = options.position, |
|
namespace = '.'+this._id+' ', |
|
deferreds = [], |
|
tooltip; |
|
|
|
// Add ARIA attributes to target |
|
$.attr(this.target[0], 'aria-describedby', this._id); |
|
|
|
// Create public position object that tracks current position corners |
|
cache.posClass = this._createPosClass( |
|
(this.position = { my: posOptions.my, at: posOptions.at }).my |
|
); |
|
|
|
// Create tooltip element |
|
this.tooltip = elements.tooltip = tooltip = $('<div/>', { |
|
'id': this._id, |
|
'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, cache.posClass ].join(' '), |
|
'width': options.style.width || '', |
|
'height': options.style.height || '', |
|
'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse, |
|
|
|
/* ARIA specific attributes */ |
|
'role': 'alert', |
|
'aria-live': 'polite', |
|
'aria-atomic': FALSE, |
|
'aria-describedby': this._id + '-content', |
|
'aria-hidden': TRUE |
|
}) |
|
.toggleClass(CLASS_DISABLED, this.disabled) |
|
.attr(ATTR_ID, this.id) |
|
.data(NAMESPACE, this) |
|
.appendTo(posOptions.container) |
|
.append( |
|
// Create content element |
|
elements.content = $('<div />', { |
|
'class': NAMESPACE + '-content', |
|
'id': this._id + '-content', |
|
'aria-atomic': TRUE |
|
}) |
|
); |
|
|
|
// Set rendered flag and prevent redundant reposition calls for now |
|
this.rendered = -1; |
|
this.positioning = TRUE; |
|
|
|
// Create title... |
|
if(title) { |
|
this._createTitle(); |
|
|
|
// Update title only if its not a callback (called in toggle if so) |
|
if(!$.isFunction(title)) { |
|
deferreds.push( this._updateTitle(title, FALSE) ); |
|
} |
|
} |
|
|
|
// Create button |
|
if(button) { this._createButton(); } |
|
|
|
// Set proper rendered flag and update content if not a callback function (called in toggle) |
|
if(!$.isFunction(text)) { |
|
deferreds.push( this._updateContent(text, FALSE) ); |
|
} |
|
this.rendered = TRUE; |
|
|
|
// Setup widget classes |
|
this._setWidget(); |
|
|
|
// Initialize 'render' plugins |
|
$.each(PLUGINS, function(name) { |
|
var instance; |
|
if(this.initialize === 'render' && (instance = this(self))) { |
|
self.plugins[name] = instance; |
|
} |
|
}); |
|
|
|
// Unassign initial events and assign proper events |
|
this._unassignEvents(); |
|
this._assignEvents(); |
|
|
|
// When deferreds have completed |
|
this._when(deferreds).then(function() { |
|
// tooltiprender event |
|
self._trigger('render'); |
|
|
|
// Reset flags |
|
self.positioning = FALSE; |
|
|
|
// Show tooltip if not hidden during wait period |
|
if(!self.hiddenDuringWait && (options.show.ready || show)) { |
|
self.toggle(TRUE, cache.event, FALSE); |
|
} |
|
self.hiddenDuringWait = FALSE; |
|
}); |
|
|
|
// Expose API |
|
QTIP.api[this.id] = this; |
|
|
|
return this; |
|
}; |
|
|
|
PROTOTYPE.destroy = function(immediate) { |
|
// Set flag the signify destroy is taking place to plugins |
|
// and ensure it only gets destroyed once! |
|
if(this.destroyed) { return this.target; } |
|
|
|
function process() { |
|
if(this.destroyed) { return; } |
|
this.destroyed = TRUE; |
|
|
|
var target = this.target, |
|
title = target.attr(oldtitle), |
|
timer; |
|
|
|
// Destroy tooltip if rendered |
|
if(this.rendered) { |
|
this.tooltip.stop(1,0).find('*').remove().end().remove(); |
|
} |
|
|
|
// Destroy all plugins |
|
$.each(this.plugins, function(name) { |
|
this.destroy && this.destroy(); |
|
}); |
|
|
|
// Clear timers |
|
for(timer in this.timers) { |
|
clearTimeout(this.timers[timer]); |
|
} |
|
|
|
// Remove api object and ARIA attributes |
|
target.removeData(NAMESPACE) |
|
.removeAttr(ATTR_ID) |
|
.removeAttr(ATTR_HAS) |
|
.removeAttr('aria-describedby'); |
|
|
|
// Reset old title attribute if removed |
|
if(this.options.suppress && title) { |
|
target.attr('title', title).removeAttr(oldtitle); |
|
} |
|
|
|
// Remove qTip events associated with this API |
|
this._unassignEvents(); |
|
|
|
// Remove ID from used id objects, and delete object references |
|
// for better garbage collection and leak protection |
|
this.options = this.elements = this.cache = this.timers = |
|
this.plugins = this.mouse = NULL; |
|
|
|
// Delete epoxsed API object |
|
delete QTIP.api[this.id]; |
|
} |
|
|
|
// If an immediate destory is needed |
|
if((immediate !== TRUE || this.triggering === 'hide') && this.rendered) { |
|
this.tooltip.one('tooltiphidden', $.proxy(process, this)); |
|
!this.triggering && this.hide(); |
|
} |
|
|
|
// If we're not in the process of hiding... process |
|
else { process.call(this); } |
|
|
|
return this.target; |
|
}; |
|
;function invalidOpt(a) { |
|
return a === NULL || $.type(a) !== 'object'; |
|
} |
|
|
|
function invalidContent(c) { |
|
return !( $.isFunction(c) || (c && c.attr) || c.length || ($.type(c) === 'object' && (c.jquery || c.then) )); |
|
} |
|
|
|
// Option object sanitizer |
|
function sanitizeOptions(opts) { |
|
var content, text, ajax, once; |
|
|
|
if(invalidOpt(opts)) { return FALSE; } |
|
|
|
if(invalidOpt(opts.metadata)) { |
|
opts.metadata = { type: opts.metadata }; |
|
} |
|
|
|
if('content' in opts) { |
|
content = opts.content; |
|
|
|
if(invalidOpt(content) || content.jquery || content.done) { |
|
content = opts.content = { |
|
text: (text = invalidContent(content) ? FALSE : content) |
|
}; |
|
} |
|
else { text = content.text; } |
|
|
|
// DEPRECATED - Old content.ajax plugin functionality |
|
// Converts it into the proper Deferred syntax |
|
if('ajax' in content) { |
|
ajax = content.ajax; |
|
once = ajax && ajax.once !== FALSE; |
|
delete content.ajax; |
|
|
|
content.text = function(event, api) { |
|
var loading = text || $(this).attr(api.options.content.attr) || 'Loading...', |
|
|
|
deferred = $.ajax( |
|
$.extend({}, ajax, { context: api }) |
|
) |
|
.then(ajax.success, NULL, ajax.error) |
|
.then(function(content) { |
|
if(content && once) { api.set('content.text', content); } |
|
return content; |
|
}, |
|
function(xhr, status, error) { |
|
if(api.destroyed || xhr.status === 0) { return; } |
|
api.set('content.text', status + ': ' + error); |
|
}); |
|
|
|
return !once ? (api.set('content.text', loading), deferred) : loading; |
|
}; |
|
} |
|
|
|
if('title' in content) { |
|
if($.isPlainObject(content.title)) { |
|
content.button = content.title.button; |
|
content.title = content.title.text; |
|
} |
|
|
|
if(invalidContent(content.title || FALSE)) { |
|
content.title = FALSE; |
|
} |
|
} |
|
} |
|
|
|
if('position' in opts && invalidOpt(opts.position)) { |
|
opts.position = { my: opts.position, at: opts.position }; |
|
} |
|
|
|
if('show' in opts && invalidOpt(opts.show)) { |
|
opts.show = opts.show.jquery ? { target: opts.show } : |
|
opts.show === TRUE ? { ready: TRUE } : { event: opts.show }; |
|
} |
|
|
|
if('hide' in opts && invalidOpt(opts.hide)) { |
|
opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide }; |
|
} |
|
|
|
if('style' in opts && invalidOpt(opts.style)) { |
|
opts.style = { classes: opts.style }; |
|
} |
|
|
|
// Sanitize plugin options |
|
$.each(PLUGINS, function() { |
|
this.sanitize && this.sanitize(opts); |
|
}); |
|
|
|
return opts; |
|
} |
|
|
|
// Setup builtin .set() option checks |
|
CHECKS = PROTOTYPE.checks = { |
|
builtin: { |
|
// Core checks |
|
'^id$': function(obj, o, v, prev) { |
|
var id = v === TRUE ? QTIP.nextid : v, |
|
new_id = NAMESPACE + '-' + id; |
|
|
|
if(id !== FALSE && id.length > 0 && !$('#'+new_id).length) { |
|
this._id = new_id; |
|
|
|
if(this.rendered) { |
|
this.tooltip[0].id = this._id; |
|
this.elements.content[0].id = this._id + '-content'; |
|
this.elements.title[0].id = this._id + '-title'; |
|
} |
|
} |
|
else { obj[o] = prev; } |
|
}, |
|
'^prerender': function(obj, o, v) { |
|
v && !this.rendered && this.render(this.options.show.ready); |
|
}, |
|
|
|
// Content checks |
|
'^content.text$': function(obj, o, v) { |
|
this._updateContent(v); |
|
}, |
|
'^content.attr$': function(obj, o, v, prev) { |
|
if(this.options.content.text === this.target.attr(prev)) { |
|
this._updateContent( this.target.attr(v) ); |
|
} |
|
}, |
|
'^content.title$': function(obj, o, v) { |
|
// Remove title if content is null |
|
if(!v) { return this._removeTitle(); } |
|
|
|
// If title isn't already created, create it now and update |
|
v && !this.elements.title && this._createTitle(); |
|
this._updateTitle(v); |
|
}, |
|
'^content.button$': function(obj, o, v) { |
|
this._updateButton(v); |
|
}, |
|
'^content.title.(text|button)$': function(obj, o, v) { |
|
this.set('content.'+o, v); // Backwards title.text/button compat |
|
}, |
|
|
|
// Position checks |
|
'^position.(my|at)$': function(obj, o, v){ |
|
'string' === typeof v && (this.position[o] = obj[o] = new CORNER(v, o === 'at')); |
|
}, |
|
'^position.container$': function(obj, o, v){ |
|
this.rendered && this.tooltip.appendTo(v); |
|
}, |
|
|
|
// Show checks |
|
'^show.ready$': function(obj, o, v) { |
|
v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE)); |
|
}, |
|
|
|
// Style checks |
|
'^style.classes$': function(obj, o, v, p) { |
|
this.rendered && this.tooltip.removeClass(p).addClass(v); |
|
}, |
|
'^style.(width|height)': function(obj, o, v) { |
|
this.rendered && this.tooltip.css(o, v); |
|
}, |
|
'^style.widget|content.title': function() { |
|
this.rendered && this._setWidget(); |
|
}, |
|
'^style.def': function(obj, o, v) { |
|
this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v); |
|
}, |
|
|
|
// Events check |
|
'^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) { |
|
this.rendered && this.tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v); |
|
}, |
|
|
|
// Properties which require event reassignment |
|
'^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() { |
|
if(!this.rendered) { return; } |
|
|
|
// Set tracking flag |
|
var posOptions = this.options.position; |
|
this.tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse); |
|
|
|
// Reassign events |
|
this._unassignEvents(); |
|
this._assignEvents(); |
|
} |
|
} |
|
}; |
|
|
|
// Dot notation converter |
|
function convertNotation(options, notation) { |
|
var i = 0, obj, option = options, |
|
|
|
// Split notation into array |
|
levels = notation.split('.'); |
|
|
|
// Loop through |
|
while( option = option[ levels[i++] ] ) { |
|
if(i < levels.length) { obj = option; } |
|
} |
|
|
|
return [obj || options, levels.pop()]; |
|
} |
|
|
|
PROTOTYPE.get = function(notation) { |
|
if(this.destroyed) { return this; } |
|
|
|
var o = convertNotation(this.options, notation.toLowerCase()), |
|
result = o[0][ o[1] ]; |
|
|
|
return result.precedance ? result.string() : result; |
|
}; |
|
|
|
function setCallback(notation, args) { |
|
var category, rule, match; |
|
|
|
for(category in this.checks) { |
|
for(rule in this.checks[category]) { |
|
if(match = (new RegExp(rule, 'i')).exec(notation)) { |
|
args.push(match); |
|
|
|
if(category === 'builtin' || this.plugins[category]) { |
|
this.checks[category][rule].apply( |
|
this.plugins[category] || this, args |
|
); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i, |
|
rrender = /^prerender|show\.ready/i; |
|
|
|
PROTOTYPE.set = function(option, value) { |
|
if(this.destroyed) { return this; } |
|
|
|
var rendered = this.rendered, |
|
reposition = FALSE, |
|
options = this.options, |
|
checks = this.checks, |
|
name; |
|
|
|
// Convert singular option/value pair into object form |
|
if('string' === typeof option) { |
|
name = option; option = {}; option[name] = value; |
|
} |
|
else { option = $.extend({}, option); } |
|
|
|
// Set all of the defined options to their new values |
|
$.each(option, function(notation, value) { |
|
if(rendered && rrender.test(notation)) { |
|
delete option[notation]; return; |
|
} |
|
|
|
// Set new obj value |
|
var obj = convertNotation(options, notation.toLowerCase()), previous; |
|
previous = obj[0][ obj[1] ]; |
|
obj[0][ obj[1] ] = value && value.nodeType ? $(value) : value; |
|
|
|
// Also check if we need to reposition |
|
reposition = rmove.test(notation) || reposition; |
|
|
|
// Set the new params for the callback |
|
option[notation] = [obj[0], obj[1], value, previous]; |
|
}); |
|
|
|
// Re-sanitize options |
|
sanitizeOptions(options); |
|
|
|
/* |
|
* Execute any valid callbacks for the set options |
|
* Also set positioning flag so we don't get loads of redundant repositioning calls. |
|
*/ |
|
this.positioning = TRUE; |
|
$.each(option, $.proxy(setCallback, this)); |
|
this.positioning = FALSE; |
|
|
|
// Update position if needed |
|
if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) { |
|
this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event ); |
|
} |
|
|
|
return this; |
|
}; |
|
;PROTOTYPE._update = function(content, element, reposition) { |
|
var self = this, |
|
cache = this.cache; |
|
|
|
// Make sure tooltip is rendered and content is defined. If not return |
|
if(!this.rendered || !content) { return FALSE; } |
|
|
|
// Use function to parse content |
|
if($.isFunction(content)) { |
|
content = content.call(this.elements.target, cache.event, this) || ''; |
|
} |
|
|
|
// Handle deferred content |
|
if($.isFunction(content.then)) { |
|
cache.waiting = TRUE; |
|
return content.then(function(c) { |
|
cache.waiting = FALSE; |
|
return self._update(c, element); |
|
}, NULL, function(e) { |
|
return self._update(e, element); |
|
}); |
|
} |
|
|
|
// If content is null... return false |
|
if(content === FALSE || (!content && content !== '')) { return FALSE; } |
|
|
|
// Append new content if its a DOM array and show it if hidden |
|
if(content.jquery && content.length > 0) { |
|
element.empty().append( |
|
content.css({ display: 'block', visibility: 'visible' }) |
|
); |
|
} |
|
|
|
// Content is a regular string, insert the new content |
|
else { element.html(content); } |
|
|
|
// Wait for content to be loaded, and reposition |
|
return this._waitForContent(element).then(function(images) { |
|
if(self.rendered && self.tooltip[0].offsetWidth > 0) { |
|
self.reposition(cache.event, !images.length); |
|
} |
|
}); |
|
}; |
|
|
|
PROTOTYPE._waitForContent = function(element) { |
|
var cache = this.cache; |
|
|
|
// Set flag |
|
cache.waiting = TRUE; |
|
|
|
// If imagesLoaded is included, ensure images have loaded and return promise |
|
return ( $.fn.imagesLoaded ? element.imagesLoaded() : $.Deferred().resolve([]) ) |
|
.done(function() { cache.waiting = FALSE; }) |
|
.promise(); |
|
}; |
|
|
|
PROTOTYPE._updateContent = function(content, reposition) { |
|
this._update(content, this.elements.content, reposition); |
|
}; |
|
|
|
PROTOTYPE._updateTitle = function(content, reposition) { |
|
if(this._update(content, this.elements.title, reposition) === FALSE) { |
|
this._removeTitle(FALSE); |
|
} |
|
}; |
|
|
|
PROTOTYPE._createTitle = function() |
|
{ |
|
var elements = this.elements, |
|
id = this._id+'-title'; |
|
|
|
// Destroy previous title element, if present |
|
if(elements.titlebar) { this._removeTitle(); } |
|
|
|
// Create title bar and title elements |
|
elements.titlebar = $('<div />', { |
|
'class': NAMESPACE + '-titlebar ' + (this.options.style.widget ? createWidgetClass('header') : '') |
|
}) |
|
.append( |
|
elements.title = $('<div />', { |
|
'id': id, |
|
'class': NAMESPACE + '-title', |
|
'aria-atomic': TRUE |
|
}) |
|
) |
|
.insertBefore(elements.content) |
|
|
|
// Button-specific events |
|
.delegate('.qtip-close', 'mousedown keydown mouseup keyup mouseout', function(event) { |
|
$(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down'); |
|
}) |
|
.delegate('.qtip-close', 'mouseover mouseout', function(event){ |
|
$(this).toggleClass('ui-state-hover', event.type === 'mouseover'); |
|
}); |
|
|
|
// Create button if enabled |
|
if(this.options.content.button) { this._createButton(); } |
|
}; |
|
|
|
PROTOTYPE._removeTitle = function(reposition) |
|
{ |
|
var elements = this.elements; |
|
|
|
if(elements.title) { |
|
elements.titlebar.remove(); |
|
elements.titlebar = elements.title = elements.button = NULL; |
|
|
|
// Reposition if enabled |
|
if(reposition !== FALSE) { this.reposition(); } |
|
} |
|
}; |
|
;PROTOTYPE._createPosClass = function(my) { |
|
return NAMESPACE + '-pos-' + (my || this.options.position.my).abbrev(); |
|
}; |
|
|
|
PROTOTYPE.reposition = function(event, effect) { |
|
if(!this.rendered || this.positioning || this.destroyed) { return this; } |
|
|
|
// Set positioning flag |
|
this.positioning = TRUE; |
|
|
|
var cache = this.cache, |
|
tooltip = this.tooltip, |
|
posOptions = this.options.position, |
|
target = posOptions.target, |
|
my = posOptions.my, |
|
at = posOptions.at, |
|
viewport = posOptions.viewport, |
|
container = posOptions.container, |
|
adjust = posOptions.adjust, |
|
method = adjust.method.split(' '), |
|
tooltipWidth = tooltip.outerWidth(FALSE), |
|
tooltipHeight = tooltip.outerHeight(FALSE), |
|
targetWidth = 0, |
|
targetHeight = 0, |
|
type = tooltip.css('position'), |
|
position = { left: 0, top: 0 }, |
|
visible = tooltip[0].offsetWidth > 0, |
|
isScroll = event && event.type === 'scroll', |
|
win = $(window), |
|
doc = container[0].ownerDocument, |
|
mouse = this.mouse, |
|
pluginCalculations, offset, adjusted, newClass; |
|
|
|
// Check if absolute position was passed |
|
if($.isArray(target) && target.length === 2) { |
|
// Force left top and set position |
|
at = { x: LEFT, y: TOP }; |
|
position = { left: target[0], top: target[1] }; |
|
} |
|
|
|
// Check if mouse was the target |
|
else if(target === 'mouse') { |
|
// Force left top to allow flipping |
|
at = { x: LEFT, y: TOP }; |
|
|
|
// Use the mouse origin that caused the show event, if distance hiding is enabled |
|
if((!adjust.mouse || this.options.hide.distance) && cache.origin && cache.origin.pageX) { |
|
event = cache.origin; |
|
} |
|
|
|
// Use cached event for resize/scroll events |
|
else if(!event || (event && (event.type === 'resize' || event.type === 'scroll'))) { |
|
event = cache.event; |
|
} |
|
|
|
// Otherwise, use the cached mouse coordinates if available |
|
else if(mouse && mouse.pageX) { |
|
event = mouse; |
|
} |
|
|
|
// Calculate body and container offset and take them into account below |
|
if(type !== 'static') { position = container.offset(); } |
|
if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) { |
|
offset = $(document.body).offset(); |
|
} |
|
|
|
// Use event coordinates for position |
|
position = { |
|
left: event.pageX - position.left + (offset && offset.left || 0), |
|
top: event.pageY - position.top + (offset && offset.top || 0) |
|
}; |
|
|
|
// Scroll events are a pain, some browsers |
|
if(adjust.mouse && isScroll && mouse) { |
|
position.left -= (mouse.scrollX || 0) - win.scrollLeft(); |
|
position.top -= (mouse.scrollY || 0) - win.scrollTop(); |
|
} |
|
} |
|
|
|
// Target wasn't mouse or absolute... |
|
else { |
|
// Check if event targetting is being used |
|
if(target === 'event') { |
|
if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') { |
|
cache.target = $(event.target); |
|
} |
|
else if(!event.target) { |
|
cache.target = this.elements.target; |
|
} |
|
} |
|
else if(target !== 'event'){ |
|
cache.target = $(target.jquery ? target : this.elements.target); |
|
} |
|
target = cache.target; |
|
|
|
// Parse the target into a jQuery object and make sure there's an element present |
|
target = $(target).eq(0); |
|
if(target.length === 0) { return this; } |
|
|
|
// Check if window or document is the target |
|
else if(target[0] === document || target[0] === window) { |
|
targetWidth = BROWSER.iOS ? window.innerWidth : target.width(); |
|
targetHeight = BROWSER.iOS ? window.innerHeight : target.height(); |
|
|
|
if(target[0] === window) { |
|
position = { |
|
top: (viewport || target).scrollTop(), |
|
left: (viewport || target).scrollLeft() |
|
}; |
|
} |
|
} |
|
|
|
// Check if the target is an <AREA> element |
|
else if(PLUGINS.imagemap && target.is('area')) { |
|
pluginCalculations = PLUGINS.imagemap(this, target, at, PLUGINS.viewport ? method : FALSE); |
|
} |
|
|
|
// Check if the target is an SVG element |
|
else if(PLUGINS.svg && target && target[0].ownerSVGElement) { |
|
pluginCalculations = PLUGINS.svg(this, target, at, PLUGINS.viewport ? method : FALSE); |
|
} |
|
|
|
// Otherwise use regular jQuery methods |
|
else { |
|
targetWidth = target.outerWidth(FALSE); |
|
targetHeight = target.outerHeight(FALSE); |
|
position = target.offset(); |
|
} |
|
|
|
// Parse returned plugin values into proper variables |
|
if(pluginCalculations) { |
|
targetWidth = pluginCalculations.width; |
|
targetHeight = pluginCalculations.height; |
|
offset = pluginCalculations.offset; |
|
position = pluginCalculations.position; |
|
} |
|
|
|
// Adjust position to take into account offset parents |
|
position = this.reposition.offset(target, position, container); |
|
|
|
// Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2) |
|
if((BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1) || |
|
(BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33) || |
|
(!BROWSER.iOS && type === 'fixed') |
|
){ |
|
position.left -= win.scrollLeft(); |
|
position.top -= win.scrollTop(); |
|
} |
|
|
|
// Adjust position relative to target |
|
if(!pluginCalculations || (pluginCalculations && pluginCalculations.adjustable !== FALSE)) { |
|
position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0; |
|
position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0; |
|
} |
|
} |
|
|
|
// Adjust position relative to tooltip |
|
position.left += adjust.x + (my.x === RIGHT ? -tooltipWidth : my.x === CENTER ? -tooltipWidth / 2 : 0); |
|
position.top += adjust.y + (my.y === BOTTOM ? -tooltipHeight : my.y === CENTER ? -tooltipHeight / 2 : 0); |
|
|
|
// Use viewport adjustment plugin if enabled |
|
if(PLUGINS.viewport) { |
|
adjusted = position.adjusted = PLUGINS.viewport( |
|
this, position, posOptions, targetWidth, targetHeight, tooltipWidth, tooltipHeight |
|
); |
|
|
|
// Apply offsets supplied by positioning plugin (if used) |
|
if(offset && adjusted.left) { position.left += offset.left; } |
|
if(offset && adjusted.top) { position.top += offset.top; } |
|
|
|
// Apply any new 'my' position |
|
if(adjusted.my) { this.position.my = adjusted.my; } |
|
} |
|
|
|
// Viewport adjustment is disabled, set values to zero |
|
else { position.adjusted = { left: 0, top: 0 }; } |
|
|
|
// Set tooltip position class if it's changed |
|
if(cache.posClass !== (newClass = this._createPosClass(this.position.my))) { |
|
tooltip.removeClass(cache.posClass).addClass( (cache.posClass = newClass) ); |
|
} |
|
|
|
// tooltipmove event |
|
if(!this._trigger('move', [position, viewport.elem || viewport], event)) { return this; } |
|
delete position.adjusted; |
|
|
|
// If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly |
|
if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) { |
|
tooltip.css(position); |
|
} |
|
|
|
// Use custom function if provided |
|
else if($.isFunction(posOptions.effect)) { |
|
posOptions.effect.call(tooltip, this, $.extend({}, position)); |
|
tooltip.queue(function(next) { |
|
// Reset attributes to avoid cross-browser rendering bugs |
|
$(this).css({ opacity: '', height: '' }); |
|
if(BROWSER.ie) { this.style.removeAttribute('filter'); } |
|
|
|
next(); |
|
}); |
|
} |
|
|
|
// Set positioning flag |
|
this.positioning = FALSE; |
|
|
|
return this; |
|
}; |
|
|
|
// Custom (more correct for qTip!) offset calculator |
|
PROTOTYPE.reposition.offset = function(elem, pos, container) { |
|
if(!container[0]) { return pos; } |
|
|
|
var ownerDocument = $(elem[0].ownerDocument), |
|
quirks = !!BROWSER.ie && document.compatMode !== 'CSS1Compat', |
|
parent = container[0], |
|
scrolled, position, parentOffset, overflow; |
|
|
|
function scroll(e, i) { |
|
pos.left += i * e.scrollLeft(); |
|
pos.top += i * e.scrollTop(); |
|
} |
|
|
|
// Compensate for non-static containers offset |
|
do { |
|
if((position = $.css(parent, 'position')) !== 'static') { |
|
if(position === 'fixed') { |
|
parentOffset = parent.getBoundingClientRect(); |
|
scroll(ownerDocument, -1); |
|
} |
|
else { |
|
parentOffset = $(parent).position(); |
|
parentOffset.left += (parseFloat($.css(parent, 'borderLeftWidth')) || 0); |
|
parentOffset.top += (parseFloat($.css(parent, 'borderTopWidth')) || 0); |
|
} |
|
|
|
pos.left -= parentOffset.left + (parseFloat($.css(parent, 'marginLeft')) || 0); |
|
pos.top -= parentOffset.top + (parseFloat($.css(parent, 'marginTop')) || 0); |
|
|
|
// If this is the first parent element with an overflow of "scroll" or "auto", store it |
|
if(!scrolled && (overflow = $.css(parent, 'overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = $(parent); } |
|
} |
|
} |
|
while((parent = parent.offsetParent)); |
|
|
|
// Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode) |
|
if(scrolled && (scrolled[0] !== ownerDocument[0] || quirks)) { |
|
scroll(scrolled, 1); |
|
} |
|
|
|
return pos; |
|
}; |
|
|
|
// Corner class |
|
var C = (CORNER = PROTOTYPE.reposition.Corner = function(corner, forceY) { |
|
corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase(); |
|
this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase(); |
|
this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase(); |
|
this.forceY = !!forceY; |
|
|
|
var f = corner.charAt(0); |
|
this.precedance = (f === 't' || f === 'b' ? Y : X); |
|
}).prototype; |
|
|
|
C.invert = function(z, center) { |
|
this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z]; |
|
}; |
|
|
|
C.string = function(join) { |
|
var x = this.x, y = this.y; |
|
|
|
var result = x !== y ? |
|
(x === 'center' || y !== 'center' && (this.precedance === Y || this.forceY) ? |
|
[y,x] : [x,y] |
|
) : |
|
[x]; |
|
|
|
return join !== false ? result.join(' ') : result; |
|
}; |
|
|
|
C.abbrev = function() { |
|
var result = this.string(false); |
|
return result[0].charAt(0) + (result[1] && result[1].charAt(0) || ''); |
|
}; |
|
|
|
C.clone = function() { |
|
return new CORNER( this.string(), this.forceY ); |
|
}; |
|
|
|
; |
|
PROTOTYPE.toggle = function(state, event) { |
|
var cache = this.cache, |
|
options = this.options, |
|
tooltip = this.tooltip; |
|
|
|
// Try to prevent flickering when tooltip overlaps show element |
|
if(event) { |
|
if((/over|enter/).test(event.type) && cache.event && (/out|leave/).test(cache.event.type) && |
|
options.show.target.add(event.target).length === options.show.target.length && |
|
tooltip.has(event.relatedTarget).length) { |
|
return this; |
|
} |
|
|
|
// Cache event |
|
cache.event = $.event.fix(event); |
|
} |
|
|
|
// If we're currently waiting and we've just hidden... stop it |
|
this.waiting && !state && (this.hiddenDuringWait = TRUE); |
|
|
|
// Render the tooltip if showing and it isn't already |
|
if(!this.rendered) { return state ? this.render(1) : this; } |
|
else if(this.destroyed || this.disabled) { return this; } |
|
|
|
var type = state ? 'show' : 'hide', |
|
opts = this.options[type], |
|
otherOpts = this.options[ !state ? 'show' : 'hide' ], |
|
posOptions = this.options.position, |
|
contentOptions = this.options.content, |
|
width = this.tooltip.css('width'), |
|
visible = this.tooltip.is(':visible'), |
|
animate = state || opts.target.length === 1, |
|
sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target, |
|
identicalState, allow, showEvent, delay, after; |
|
|
|
// Detect state if valid one isn't provided |
|
if((typeof state).search('boolean|number')) { state = !visible; } |
|
|
|
// Check if the tooltip is in an identical state to the new would-be state |
|
identicalState = !tooltip.is(':animated') && visible === state && sameTarget; |
|
|
|
// Fire tooltip(show/hide) event and check if destroyed |
|
allow = !identicalState ? !!this._trigger(type, [90]) : NULL; |
|
|
|
// Check to make sure the tooltip wasn't destroyed in the callback |
|
if(this.destroyed) { return this; } |
|
|
|
// If the user didn't stop the method prematurely and we're showing the tooltip, focus it |
|
if(allow !== FALSE && state) { this.focus(event); } |
|
|
|
// If the state hasn't changed or the user stopped it, return early |
|
if(!allow || identicalState) { return this; } |
|
|
|
// Set ARIA hidden attribute |
|
$.attr(tooltip[0], 'aria-hidden', !!!state); |
|
|
|
// Execute state specific properties |
|
if(state) { |
|
// Store show origin coordinates |
|
this.mouse && (cache.origin = $.event.fix(this.mouse)); |
|
|
|
// Update tooltip content & title if it's a dynamic function |
|
if($.isFunction(contentOptions.text)) { this._updateContent(contentOptions.text, FALSE); } |
|
if($.isFunction(contentOptions.title)) { this._updateTitle(contentOptions.title, FALSE); } |
|
|
|
// Cache mousemove events for positioning purposes (if not already tracking) |
|
if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) { |
|
$(document).bind('mousemove.'+NAMESPACE, this._storeMouse); |
|
trackingBound = TRUE; |
|
} |
|
|
|
// Update the tooltip position (set width first to prevent viewport/max-width issues) |
|
if(!width) { tooltip.css('width', tooltip.outerWidth(FALSE)); } |
|
this.reposition(event, arguments[2]); |
|
if(!width) { tooltip.css('width', ''); } |
|
|
|
// Hide other tooltips if tooltip is solo |
|
if(!!opts.solo) { |
|
(typeof opts.solo === 'string' ? $(opts.solo) : $(SELECTOR, opts.solo)) |
|
.not(tooltip).not(opts.target).qtip('hide', $.Event('tooltipsolo')); |
|
} |
|
} |
|
else { |
|
// Clear show timer if we're hiding |
|
clearTimeout(this.timers.show); |
|
|
|
// Remove cached origin on hide |
|
delete cache.origin; |
|
|
|
// Remove mouse tracking event if not needed (all tracking qTips are hidden) |
|
if(trackingBound && !$(SELECTOR+'[tracking="true"]:visible', opts.solo).not(tooltip).length) { |
|
$(document).unbind('mousemove.'+NAMESPACE); |
|
trackingBound = FALSE; |
|
} |
|
|
|
// Blur the tooltip |
|
this.blur(event); |
|
} |
|
|
|
// Define post-animation, state specific properties |
|
after = $.proxy(function() { |
|
if(state) { |
|
// Prevent antialias from disappearing in IE by removing filter |
|
if(BROWSER.ie) { tooltip[0].style.removeAttribute('filter'); } |
|
|
|
// Remove overflow setting to prevent tip bugs |
|
tooltip.css('overflow', ''); |
|
|
|
// Autofocus elements if enabled |
|
if('string' === typeof opts.autofocus) { |
|
$(this.options.show.autofocus, tooltip).focus(); |
|
} |
|
|
|
// If set, hide tooltip when inactive for delay period |
|
this.options.show.target.trigger('qtip-'+this.id+'-inactive'); |
|
} |
|
else { |
|
// Reset CSS states |
|
tooltip.css({ |
|
display: '', |
|
visibility: '', |
|
opacity: '', |
|
left: '', |
|
top: '' |
|
}); |
|
} |
|
|
|
// tooltipvisible/tooltiphidden events |
|
this._trigger(state ? 'visible' : 'hidden'); |
|
}, this); |
|
|
|
// If no effect type is supplied, use a simple toggle |
|
if(opts.effect === FALSE || animate === FALSE) { |
|
tooltip[ type ](); |
|
after(); |
|
} |
|
|
|
// Use custom function if provided |
|
else if($.isFunction(opts.effect)) { |
|
tooltip.stop(1, 1); |
|
opts.effect.call(tooltip, this); |
|
tooltip.queue('fx', function(n) { |
|
after(); n(); |
|
}); |
|
} |
|
|
|
// Use basic fade function by default |
|
else { tooltip.fadeTo(90, state ? 1 : 0, after); } |
|
|
|
// If inactive hide method is set, active it |
|
if(state) { opts.target.trigger('qtip-'+this.id+'-inactive'); } |
|
|
|
return this; |
|
}; |
|
|
|
PROTOTYPE.show = function(event) { return this.toggle(TRUE, event); }; |
|
|
|
PROTOTYPE.hide = function(event) { return this.toggle(FALSE, event); }; |
|
;PROTOTYPE.focus = function(event) { |
|
if(!this.rendered || this.destroyed) { return this; } |
|
|
|
var qtips = $(SELECTOR), |
|
tooltip = this.tooltip, |
|
curIndex = parseInt(tooltip[0].style.zIndex, 10), |
|
newIndex = QTIP.zindex + qtips.length, |
|
focusedElem; |
|
|
|
// Only update the z-index if it has changed and tooltip is not already focused |
|
if(!tooltip.hasClass(CLASS_FOCUS)) { |
|
// tooltipfocus event |
|
if(this._trigger('focus', [newIndex], event)) { |
|
// Only update z-index's if they've changed |
|
if(curIndex !== newIndex) { |
|
// Reduce our z-index's and keep them properly ordered |
|
qtips.each(function() { |
|
if(this.style.zIndex > curIndex) { |
|
this.style.zIndex = this.style.zIndex - 1; |
|
} |
|
}); |
|
|
|
// Fire blur event for focused tooltip |
|
qtips.filter('.' + CLASS_FOCUS).qtip('blur', event); |
|
} |
|
|
|
// Set the new z-index |
|
tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex; |
|
} |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
PROTOTYPE.blur = function(event) { |
|
if(!this.rendered || this.destroyed) { return this; } |
|
|
|
// Set focused status to FALSE |
|
this.tooltip.removeClass(CLASS_FOCUS); |
|
|
|
// tooltipblur event |
|
this._trigger('blur', [ this.tooltip.css('zIndex') ], event); |
|
|
|
return this; |
|
}; |
|
;PROTOTYPE.disable = function(state) { |
|
if(this.destroyed) { return this; } |
|
|
|
// If 'toggle' is passed, toggle the current state |
|
if(state === 'toggle') { |
|
state = !(this.rendered ? this.tooltip.hasClass(CLASS_DISABLED) : this.disabled); |
|
} |
|
|
|
// Disable if no state passed |
|
else if('boolean' !== typeof state) { |
|
state = TRUE; |
|
} |
|
|
|
if(this.rendered) { |
|
this.tooltip.toggleClass(CLASS_DISABLED, state) |
|
.attr('aria-disabled', state); |
|
} |
|
|
|
this.disabled = !!state; |
|
|
|
return this; |
|
}; |
|
|
|
PROTOTYPE.enable = function() { return this.disable(FALSE); }; |
|
;PROTOTYPE._createButton = function() |
|
{ |
|
var self = this, |
|
elements = this.elements, |
|
tooltip = elements.tooltip, |
|
button = this.options.content.button, |
|
isString = typeof button === 'string', |
|
close = isString ? button : 'Close tooltip'; |
|
|
|
if(elements.button) { elements.button.remove(); } |
|
|
|
// Use custom button if one was supplied by user, else use default |
|
if(button.jquery) { |
|
elements.button = button; |
|
} |
|
else { |
|
elements.button = $('<a />', { |
|
'class': 'qtip-close ' + (this.options.style.widget ? '' : NAMESPACE+'-icon'), |
|
'title': close, |
|
'aria-label': close |
|
}) |
|
.prepend( |
|
$('<span />', { |
|
'class': 'ui-icon ui-icon-close', |
|
'html': '×' |
|
}) |
|
); |
|
} |
|
|
|
// Create button and setup attributes |
|
elements.button.appendTo(elements.titlebar || tooltip) |
|
.attr('role', 'button') |
|
.click(function(event) { |
|
if(!tooltip.hasClass(CLASS_DISABLED)) { self.hide(event); } |
|
return FALSE; |
|
}); |
|
}; |
|
|
|
PROTOTYPE._updateButton = function(button) |
|
{ |
|
// Make sure tooltip is rendered and if not, return |
|
if(!this.rendered) { return FALSE; } |
|
|
|
var elem = this.elements.button; |
|
if(button) { this._createButton(); } |
|
else { elem.remove(); } |
|
}; |
|
;// Widget class creator |
|
function createWidgetClass(cls) { |
|
return WIDGET.concat('').join(cls ? '-'+cls+' ' : ' '); |
|
} |
|
|
|
// Widget class setter method |
|
PROTOTYPE._setWidget = function() |
|
{ |
|
var on = this.options.style.widget, |
|
elements = this.elements, |
|
tooltip = elements.tooltip, |
|
disabled = tooltip.hasClass(CLASS_DISABLED); |
|
|
|
tooltip.removeClass(CLASS_DISABLED); |
|
CLASS_DISABLED = on ? 'ui-state-disabled' : 'qtip-disabled'; |
|
tooltip.toggleClass(CLASS_DISABLED, disabled); |
|
|
|
tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(CLASS_DEFAULT, this.options.style.def && !on); |
|
|
|
if(elements.content) { |
|
elements.content.toggleClass( createWidgetClass('content'), on); |
|
} |
|
if(elements.titlebar) { |
|
elements.titlebar.toggleClass( createWidgetClass('header'), on); |
|
} |
|
if(elements.button) { |
|
elements.button.toggleClass(NAMESPACE+'-icon', !on); |
|
} |
|
}; |
|
;function delay(callback, duration) { |
|
// If tooltip has displayed, start hide timer |
|
if(duration > 0) { |
|
return setTimeout( |
|
$.proxy(callback, this), duration |
|
); |
|
} |
|
else{ callback.call(this); } |
|
} |
|
|
|
function showMethod(event) { |
|
if(this.tooltip.hasClass(CLASS_DISABLED)) { return; } |
|
|
|
// Clear hide timers |
|
clearTimeout(this.timers.show); |
|
clearTimeout(this.timers.hide); |
|
|
|
// Start show timer |
|
this.timers.show = delay.call(this, |
|
function() { this.toggle(TRUE, event); }, |
|
this.options.show.delay |
|
); |
|
} |
|
|
|
function hideMethod(event) { |
|
if(this.tooltip.hasClass(CLASS_DISABLED) || this.destroyed) { return; } |
|
|
|
// Check if new target was actually the tooltip element |
|
var relatedTarget = $(event.relatedTarget), |
|
ontoTooltip = relatedTarget.closest(SELECTOR)[0] === this.tooltip[0], |
|
ontoTarget = relatedTarget[0] === this.options.show.target[0]; |
|
|
|
// Clear timers and stop animation queue |
|
clearTimeout(this.timers.show); |
|
clearTimeout(this.timers.hide); |
|
|
|
// Prevent hiding if tooltip is fixed and event target is the tooltip. |
|
// Or if mouse positioning is enabled and cursor momentarily overlaps |
|
if(this !== relatedTarget[0] && |
|
(this.options.position.target === 'mouse' && ontoTooltip) || |
|
(this.options.hide.fixed && ( |
|
(/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget)) |
|
)) |
|
{ |
|
try { |
|
event.preventDefault(); |
|
event.stopImmediatePropagation(); |
|
} catch(e) {} |
|
|
|
return; |
|
} |
|
|
|
// If tooltip has displayed, start hide timer |
|
this.timers.hide = delay.call(this, |
|
function() { this.toggle(FALSE, event); }, |
|
this.options.hide.delay, |
|
this |
|
); |
|
} |
|
|
|
function inactiveMethod(event) { |
|
if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return; } |
|
|
|
// Clear timer |
|
clearTimeout(this.timers.inactive); |
|
|
|
this.timers.inactive = delay.call(this, |
|
function(){ this.hide(event); }, |
|
this.options.hide.inactive |
|
); |
|
} |
|
|
|
function repositionMethod(event) { |
|
if(this.rendered && this.tooltip[0].offsetWidth > 0) { this.reposition(event); } |
|
} |
|
|
|
// Store mouse coordinates |
|
PROTOTYPE._storeMouse = function(event) { |
|
(this.mouse = $.event.fix(event)).type = 'mousemove'; |
|
return this; |
|
}; |
|
|
|
// Bind events |
|
PROTOTYPE._bind = function(targets, events, method, suffix, context) { |
|
if(!targets || !method || !events.length) { return; } |
|
var ns = '.' + this._id + (suffix ? '-'+suffix : ''); |
|
$(targets).bind( |
|
(events.split ? events : events.join(ns + ' ')) + ns, |
|
$.proxy(method, context || this) |
|
); |
|
return this; |
|
}; |
|
PROTOTYPE._unbind = function(targets, suffix) { |
|
targets && $(targets).unbind('.' + this._id + (suffix ? '-'+suffix : '')); |
|
return this; |
|
}; |
|
|
|
// Global delegation helper |
|
function delegate(selector, events, method) { |
|
$(document.body).delegate(selector, |
|
(events.split ? events : events.join('.'+NAMESPACE + ' ')) + '.'+NAMESPACE, |
|
function() { |
|
var api = QTIP.api[ $.attr(this, ATTR_ID) ]; |
|
api && !api.disabled && method.apply(api, arguments); |
|
} |
|
); |
|
} |
|
// Event trigger |
|
PROTOTYPE._trigger = function(type, args, event) { |
|
var callback = $.Event('tooltip'+type); |
|
callback.originalEvent = (event && $.extend({}, event)) || this.cache.event || NULL; |
|
|
|
this.triggering = type; |
|
this.tooltip.trigger(callback, [this].concat(args || [])); |
|
this.triggering = FALSE; |
|
|
|
return !callback.isDefaultPrevented(); |
|
}; |
|
|
|
PROTOTYPE._bindEvents = function(showEvents, hideEvents, showTargets, hideTargets, showMethod, hideMethod) { |
|
// Get tasrgets that lye within both |
|
var similarTargets = showTargets.filter( hideTargets ).add( hideTargets.filter(showTargets) ), |
|
toggleEvents = []; |
|
|
|
// If hide and show targets are the same... |
|
if(similarTargets.length) { |
|
|
|
// Filter identical show/hide events |
|
$.each(hideEvents, function(i, type) { |
|
var showIndex = $.inArray(type, showEvents); |
|
|
|
// Both events are identical, remove from both hide and show events |
|
// and append to toggleEvents |
|
showIndex > -1 && toggleEvents.push( showEvents.splice( showIndex, 1 )[0] ); |
|
}); |
|
|
|
// Toggle events are special case of identical show/hide events, which happen in sequence |
|
if(toggleEvents.length) { |
|
// Bind toggle events to the similar targets |
|
this._bind(similarTargets, toggleEvents, function(event) { |
|
var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false; |
|
(state ? hideMethod : showMethod).call(this, event); |
|
}); |
|
|
|
// Remove the similar targets from the regular show/hide bindings |
|
showTargets = showTargets.not(similarTargets); |
|
hideTargets = hideTargets.not(similarTargets); |
|
} |
|
} |
|
|
|
// Apply show/hide/toggle events |
|
this._bind(showTargets, showEvents, showMethod); |
|
this._bind(hideTargets, hideEvents, hideMethod); |
|
}; |
|
|
|
PROTOTYPE._assignInitialEvents = function(event) { |
|
var options = this.options, |
|
showTarget = options.show.target, |
|
hideTarget = options.hide.target, |
|
showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [], |
|
hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : []; |
|
|
|
// Catch remove/removeqtip events on target element to destroy redundant tooltips |
|
this._bind(this.elements.target, ['remove', 'removeqtip'], function(event) { |
|
this.destroy(true); |
|
}, 'destroy'); |
|
|
|
/* |
|
* Make sure hoverIntent functions properly by using mouseleave as a hide event if |
|
* mouseenter/mouseout is used for show.event, even if it isn't in the users options. |
|
*/ |
|
if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) { |
|
hideEvents.push('mouseleave'); |
|
} |
|
|
|
/* |
|
* Also make sure initial mouse targetting works correctly by caching mousemove coords |
|
* on show targets before the tooltip has rendered. Also set onTarget when triggered to |
|
* keep mouse tracking working. |
|
*/ |
|
this._bind(showTarget, 'mousemove', function(event) { |
|
this._storeMouse(event); |
|
this.cache.onTarget = TRUE; |
|
}); |
|
|
|
// Define hoverIntent function |
|
function hoverIntent(event) { |
|
// Only continue if tooltip isn't disabled |
|
if(this.disabled || this.destroyed) { return FALSE; } |
|
|
|
// Cache the event data |
|
this.cache.event = event && $.event.fix(event); |
|
this.cache.target = event && $(event.target); |
|
|
|
// Start the event sequence |
|
clearTimeout(this.timers.show); |
|
this.timers.show = delay.call(this, |
|
function() { this.render(typeof event === 'object' || options.show.ready); }, |
|
options.prerender ? 0 : options.show.delay |
|
); |
|
} |
|
|
|
// Filter and bind events |
|
this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, hoverIntent, function() { |
|
if(!this.timers) { return FALSE; } |
|
clearTimeout(this.timers.show); |
|
}); |
|
|
|
// Prerendering is enabled, create tooltip now |
|
if(options.show.ready || options.prerender) { hoverIntent.call(this, event); } |
|
}; |
|
|
|
// Event assignment method |
|
PROTOTYPE._assignEvents = function() { |
|
var self = this, |
|
options = this.options, |
|
posOptions = options.position, |
|
|
|
tooltip = this.tooltip, |
|
showTarget = options.show.target, |
|
hideTarget = options.hide.target, |
|
containerTarget = posOptions.container, |
|
viewportTarget = posOptions.viewport, |
|
documentTarget = $(document), |
|
bodyTarget = $(document.body), |
|
windowTarget = $(window), |
|
|
|
showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [], |
|
hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : []; |
|
|
|
|
|
// Assign passed event callbacks |
|
$.each(options.events, function(name, callback) { |
|
self._bind(tooltip, name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name], callback, null, tooltip); |
|
}); |
|
|
|
// Hide tooltips when leaving current window/frame (but not select/option elements) |
|
if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') { |
|
this._bind(documentTarget, ['mouseout', 'blur'], function(event) { |
|
if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) { |
|
this.hide(event); |
|
} |
|
}); |
|
} |
|
|
|
// Enable hide.fixed by adding appropriate class |
|
if(options.hide.fixed) { |
|
hideTarget = hideTarget.add( tooltip.addClass(CLASS_FIXED) ); |
|
} |
|
|
|
/* |
|
* Make sure hoverIntent functions properly by using mouseleave to clear show timer if |
|
* mouseenter/mouseout is used for show.event, even if it isn't in the users options. |
|
*/ |
|
else if(/mouse(over|enter)/i.test(options.show.event)) { |
|
this._bind(hideTarget, 'mouseleave', function() { |
|
clearTimeout(this.timers.show); |
|
}); |
|
} |
|
|
|
// Hide tooltip on document mousedown if unfocus events are enabled |
|
if(('' + options.hide.event).indexOf('unfocus') > -1) { |
|
this._bind(containerTarget.closest('html'), ['mousedown', 'touchstart'], function(event) { |
|
var elem = $(event.target), |
|
enabled = this.rendered && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0, |
|
isAncestor = elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0; |
|
|
|
if(elem[0] !== this.target[0] && elem[0] !== this.tooltip[0] && !isAncestor && |
|
!this.target.has(elem[0]).length && enabled |
|
) { |
|
this.hide(event); |
|
} |
|
}); |
|
} |
|
|
|
// Check if the tooltip hides when inactive |
|
if('number' === typeof options.hide.inactive) { |
|
// Bind inactive method to show target(s) as a custom event |
|
this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod, 'inactive'); |
|
|
|
// Define events which reset the 'inactive' event handler |
|
this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod); |
|
} |
|
|
|
// Filter and bind events |
|
this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod); |
|
|
|
// Mouse movement bindings |
|
this._bind(showTarget.add(tooltip), 'mousemove', function(event) { |
|
// Check if the tooltip hides when mouse is moved a certain distance |
|
if('number' === typeof options.hide.distance) { |
|
var origin = this.cache.origin || {}, |
|
limit = this.options.hide.distance, |
|
abs = Math.abs; |
|
|
|
// Check if the movement has gone beyond the limit, and hide it if so |
|
if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) { |
|
this.hide(event); |
|
} |
|
} |
|
|
|
// Cache mousemove coords on show targets |
|
this._storeMouse(event); |
|
}); |
|
|
|
// Mouse positioning events |
|
if(posOptions.target === 'mouse') { |
|
// If mouse adjustment is on... |
|
if(posOptions.adjust.mouse) { |
|
// Apply a mouseleave event so we don't get problems with overlapping |
|
if(options.hide.event) { |
|
// Track if we're on the target or not |
|
this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) { |
|
if(!this.cache) {return FALSE; } |
|
this.cache.onTarget = event.type === 'mouseenter'; |
|
}); |
|
} |
|
|
|
// Update tooltip position on mousemove |
|
this._bind(documentTarget, 'mousemove', function(event) { |
|
// Update the tooltip position only if the tooltip is visible and adjustment is enabled |
|
if(this.rendered && this.cache.onTarget && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0) { |
|
this.reposition(event); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
// Adjust positions of the tooltip on window resize if enabled |
|
if(posOptions.adjust.resize || viewportTarget.length) { |
|
this._bind( $.event.special.resize ? viewportTarget : windowTarget, 'resize', repositionMethod ); |
|
} |
|
|
|
// Adjust tooltip position on scroll of the window or viewport element if present |
|
if(posOptions.adjust.scroll) { |
|
this._bind( windowTarget.add(posOptions.container), 'scroll', repositionMethod ); |
|
} |
|
}; |
|
|
|
// Un-assignment method |
|
PROTOTYPE._unassignEvents = function() { |
|
var options = this.options, |
|
showTargets = options.show.target, |
|
hideTargets = options.hide.target, |
|
targets = $.grep([ |
|
this.elements.target[0], |
|
this.rendered && this.tooltip[0], |
|
options.position.container[0], |
|
options.position.viewport[0], |
|
options.position.container.closest('html')[0], // unfocus |
|
window, |
|
document |
|
], function(i) { |
|
return typeof i === 'object'; |
|
}); |
|
|
|
// Add show and hide targets if they're valid |
|
if(showTargets && showTargets.toArray) { |
|
targets = targets.concat(showTargets.toArray()); |
|
} |
|
if(hideTargets && hideTargets.toArray) { |
|
targets = targets.concat(hideTargets.toArray()); |
|
} |
|
|
|
// Unbind the events |
|
this._unbind(targets) |
|
._unbind(targets, 'destroy') |
|
._unbind(targets, 'inactive'); |
|
}; |
|
|
|
// Apply common event handlers using delegate (avoids excessive .bind calls!) |
|
$(function() { |
|
delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) { |
|
var state = event.type === 'mouseenter', |
|
tooltip = $(event.currentTarget), |
|
target = $(event.relatedTarget || event.target), |
|
options = this.options; |
|
|
|
// On mouseenter... |
|
if(state) { |
|
// Focus the tooltip on mouseenter (z-index stacking) |
|
this.focus(event); |
|
|
|
// Clear hide timer on tooltip hover to prevent it from closing |
|
tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide); |
|
} |
|
|
|
// On mouseleave... |
|
else { |
|
// When mouse tracking is enabled, hide when we leave the tooltip and not onto the show target (if a hide event is set) |
|
if(options.position.target === 'mouse' && options.position.adjust.mouse && |
|
options.hide.event && options.show.target && !target.closest(options.show.target[0]).length) { |
|
this.hide(event); |
|
} |
|
} |
|
|
|
// Add hover class |
|
tooltip.toggleClass(CLASS_HOVER, state); |
|
}); |
|
|
|
// Define events which reset the 'inactive' event handler |
|
delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod); |
|
}); |
|
;// Initialization method |
|
function init(elem, id, opts) { |
|
var obj, posOptions, attr, config, title, |
|
|
|
// Setup element references |
|
docBody = $(document.body), |
|
|
|
// Use document body instead of document element if needed |
|
newTarget = elem[0] === document ? docBody : elem, |
|
|
|
// Grab metadata from element if plugin is present |
|
metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL, |
|
|
|
// If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise |
|
metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL, |
|
|
|
// Grab data from metadata.name (or data-qtipopts as fallback) using .data() method, |
|
html5 = elem.data(opts.metadata.name || 'qtipopts'); |
|
|
|
// If we don't get an object returned attempt to parse it manualyl without parseJSON |
|
try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; } catch(e) {} |
|
|
|
// Merge in and sanitize metadata |
|
config = $.extend(TRUE, {}, QTIP.defaults, opts, |
|
typeof html5 === 'object' ? sanitizeOptions(html5) : NULL, |
|
sanitizeOptions(metadata5 || metadata)); |
|
|
|
// Re-grab our positioning options now we've merged our metadata and set id to passed value |
|
posOptions = config.position; |
|
config.id = id; |
|
|
|
// Setup missing content if none is detected |
|
if('boolean' === typeof config.content.text) { |
|
attr = elem.attr(config.content.attr); |
|
|
|
// Grab from supplied attribute if available |
|
if(config.content.attr !== FALSE && attr) { config.content.text = attr; } |
|
|
|
// No valid content was found, abort render |
|
else { return FALSE; } |
|
} |
|
|
|
// Setup target options |
|
if(!posOptions.container.length) { posOptions.container = docBody; } |
|
if(posOptions.target === FALSE) { posOptions.target = newTarget; } |
|
if(config.show.target === FALSE) { config.show.target = newTarget; } |
|
if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); } |
|
if(config.hide.target === FALSE) { config.hide.target = newTarget; } |
|
if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; } |
|
|
|
// Ensure we only use a single container |
|
posOptions.container = posOptions.container.eq(0); |
|
|
|
// Convert position corner values into x and y strings |
|
posOptions.at = new CORNER(posOptions.at, TRUE); |
|
posOptions.my = new CORNER(posOptions.my); |
|
|
|
// Destroy previous tooltip if overwrite is enabled, or skip element if not |
|
if(elem.data(NAMESPACE)) { |
|
if(config.overwrite) { |
|
elem.qtip('destroy', true); |
|
} |
|
else if(config.overwrite === FALSE) { |
|
return FALSE; |
|
} |
|
} |
|
|
|
// Add has-qtip attribute |
|
elem.attr(ATTR_HAS, id); |
|
|
|
// Remove title attribute and store it if present |
|
if(config.suppress && (title = elem.attr('title'))) { |
|
// Final attr call fixes event delegatiom and IE default tooltip showing problem |
|
elem.removeAttr('title').attr(oldtitle, title).attr('title', ''); |
|
} |
|
|
|
// Initialize the tooltip and add API reference |
|
obj = new QTip(elem, config, id, !!attr); |
|
elem.data(NAMESPACE, obj); |
|
|
|
return obj; |
|
} |
|
|
|
// jQuery $.fn extension method |
|
QTIP = $.fn.qtip = function(options, notation, newValue) |
|
{ |
|
var command = ('' + options).toLowerCase(), // Parse command |
|
returned = NULL, |
|
args = $.makeArray(arguments).slice(1), |
|
event = args[args.length - 1], |
|
opts = this[0] ? $.data(this[0], NAMESPACE) : NULL; |
|
|
|
// Check for API request |
|
if((!arguments.length && opts) || command === 'api') { |
|
return opts; |
|
} |
|
|
|
// Execute API command if present |
|
else if('string' === typeof options) { |
|
this.each(function() { |
|
var api = $.data(this, NAMESPACE); |
|
if(!api) { return TRUE; } |
|
|
|
// Cache the event if possible |
|
if(event && event.timeStamp) { api.cache.event = event; } |
|
|
|
// Check for specific API commands |
|
if(notation && (command === 'option' || command === 'options')) { |
|
if(newValue !== undefined || $.isPlainObject(notation)) { |
|
api.set(notation, newValue); |
|
} |
|
else { |
|
returned = api.get(notation); |
|
return FALSE; |
|
} |
|
} |
|
|
|
// Execute API command |
|
else if(api[command]) { |
|
api[command].apply(api, args); |
|
} |
|
}); |
|
|
|
return returned !== NULL ? returned : this; |
|
} |
|
|
|
// No API commands. validate provided options and setup qTips |
|
else if('object' === typeof options || !arguments.length) { |
|
// Sanitize options first |
|
opts = sanitizeOptions($.extend(TRUE, {}, options)); |
|
|
|
return this.each(function(i) { |
|
var api, id; |
|
|
|
// Find next available ID, or use custom ID if provided |
|
id = $.isArray(opts.id) ? opts.id[i] : opts.id; |
|
id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id; |
|
|
|
// Initialize the qTip and re-grab newly sanitized options |
|
api = init($(this), id, opts); |
|
if(api === FALSE) { return TRUE; } |
|
else { QTIP.api[id] = api; } |
|
|
|
// Initialize plugins |
|
$.each(PLUGINS, function() { |
|
if(this.initialize === 'initialize') { this(api); } |
|
}); |
|
|
|
// Assign initial pre-render events |
|
api._assignInitialEvents(event); |
|
}); |
|
} |
|
}; |
|
|
|
// Expose class |
|
$.qtip = QTip; |
|
|
|
// Populated in render method |
|
QTIP.api = {}; |
|
;$.each({ |
|
/* Allow other plugins to successfully retrieve the title of an element with a qTip applied */ |
|
attr: function(attr, val) { |
|
if(this.length) { |
|
var self = this[0], |
|
title = 'title', |
|
api = $.data(self, 'qtip'); |
|
|
|
if(attr === title && api && 'object' === typeof api && api.options.suppress) { |
|
if(arguments.length < 2) { |
|
return $.attr(self, oldtitle); |
|
} |
|
|
|
// If qTip is rendered and title was originally used as content, update it |
|
if(api && api.options.content.attr === title && api.cache.attr) { |
|
api.set('content.text', val); |
|
} |
|
|
|
// Use the regular attr method to set, then cache the result |
|
return this.attr(oldtitle, val); |
|
} |
|
} |
|
|
|
return $.fn['attr'+replaceSuffix].apply(this, arguments); |
|
}, |
|
|
|
/* Allow clone to correctly retrieve cached title attributes */ |
|
clone: function(keepData) { |
|
var titles = $([]), title = 'title', |
|
|
|
// Clone our element using the real clone method |
|
elems = $.fn['clone'+replaceSuffix].apply(this, arguments); |
|
|
|
// Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false |
|
if(!keepData) { |
|
elems.filter('['+oldtitle+']').attr('title', function() { |
|
return $.attr(this, oldtitle); |
|
}) |
|
.removeAttr(oldtitle); |
|
} |
|
|
|
return elems; |
|
} |
|
}, function(name, func) { |
|
if(!func || $.fn[name+replaceSuffix]) { return TRUE; } |
|
|
|
var old = $.fn[name+replaceSuffix] = $.fn[name]; |
|
$.fn[name] = function() { |
|
return func.apply(this, arguments) || old.apply(this, arguments); |
|
}; |
|
}); |
|
|
|
/* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar). |
|
* This snippet is taken directly from jQuery UI source code found here: |
|
* http://code.jquery.com/ui/jquery-ui-git.js |
|
*/ |
|
if(!$.ui) { |
|
$['cleanData'+replaceSuffix] = $.cleanData; |
|
$.cleanData = function( elems ) { |
|
for(var i = 0, elem; (elem = $( elems[i] )).length; i++) { |
|
if(elem.attr(ATTR_HAS)) { |
|
try { elem.triggerHandler('removeqtip'); } |
|
catch( e ) {} |
|
} |
|
} |
|
$['cleanData'+replaceSuffix].apply(this, arguments); |
|
}; |
|
} |
|
;// qTip version |
|
QTIP.version = '2.2.1'; |
|
|
|
// Base ID for all qTips |
|
QTIP.nextid = 0; |
|
|
|
// Inactive events array |
|
QTIP.inactiveEvents = INACTIVE_EVENTS; |
|
|
|
// Base z-index for all qTips |
|
QTIP.zindex = 15000; |
|
|
|
// Define configuration defaults |
|
QTIP.defaults = { |
|
prerender: FALSE, |
|
id: FALSE, |
|
overwrite: TRUE, |
|
suppress: TRUE, |
|
content: { |
|
text: TRUE, |
|
attr: 'title', |
|
title: FALSE, |
|
button: FALSE |
|
}, |
|
position: { |
|
my: 'top left', |
|
at: 'bottom right', |
|
target: FALSE, |
|
container: FALSE, |
|
viewport: FALSE, |
|
adjust: { |
|
x: 0, y: 0, |
|
mouse: TRUE, |
|
scroll: TRUE, |
|
resize: TRUE, |
|
method: 'flipinvert flipinvert' |
|
}, |
|
effect: function(api, pos, viewport) { |
|
$(this).animate(pos, { |
|
duration: 200, |
|
queue: FALSE |
|
}); |
|
} |
|
}, |
|
show: { |
|
target: FALSE, |
|
event: 'mouseenter', |
|
effect: TRUE, |
|
delay: 90, |
|
solo: FALSE, |
|
ready: FALSE, |
|
autofocus: FALSE |
|
}, |
|
hide: { |
|
target: FALSE, |
|
event: 'mouseleave', |
|
effect: TRUE, |
|
delay: 0, |
|
fixed: FALSE, |
|
inactive: FALSE, |
|
leave: 'window', |
|
distance: FALSE |
|
}, |
|
style: { |
|
classes: '', |
|
widget: FALSE, |
|
width: FALSE, |
|
height: FALSE, |
|
def: TRUE |
|
}, |
|
events: { |
|
render: NULL, |
|
move: NULL, |
|
show: NULL, |
|
hide: NULL, |
|
toggle: NULL, |
|
visible: NULL, |
|
hidden: NULL, |
|
focus: NULL, |
|
blur: NULL |
|
} |
|
}; |
|
;PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight) |
|
{ |
|
var target = posOptions.target, |
|
tooltip = api.elements.tooltip, |
|
my = posOptions.my, |
|
at = posOptions.at, |
|
adjust = posOptions.adjust, |
|
method = adjust.method.split(' '), |
|
methodX = method[0], |
|
methodY = method[1] || method[0], |
|
viewport = posOptions.viewport, |
|
container = posOptions.container, |
|
cache = api.cache, |
|
adjusted = { left: 0, top: 0 }, |
|
fixed, newMy, containerOffset, containerStatic, |
|
viewportWidth, viewportHeight, viewportScroll, viewportOffset; |
|
|
|
// If viewport is not a jQuery element, or it's the window/document, or no adjustment method is used... return |
|
if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') { |
|
return adjusted; |
|
} |
|
|
|
// Cach container details |
|
containerOffset = container.offset() || adjusted; |
|
containerStatic = container.css('position') === 'static'; |
|
|
|
// Cache our viewport details |
|
fixed = tooltip.css('position') === 'fixed'; |
|
viewportWidth = viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE); |
|
viewportHeight = viewport[0] === window ? viewport.height() : viewport.outerHeight(FALSE); |
|
viewportScroll = { left: fixed ? 0 : viewport.scrollLeft(), top: fixed ? 0 : viewport.scrollTop() }; |
|
viewportOffset = viewport.offset() || adjusted; |
|
|
|
// Generic calculation method |
|
function calculate(side, otherSide, type, adjust, side1, side2, lengthName, targetLength, elemLength) { |
|
var initialPos = position[side1], |
|
mySide = my[side], |
|
atSide = at[side], |
|
isShift = type === SHIFT, |
|
myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2, |
|
atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2, |
|
sideOffset = viewportScroll[side1] + viewportOffset[side1] - (containerStatic ? 0 : containerOffset[side1]), |
|
overflow1 = sideOffset - initialPos, |
|
overflow2 = initialPos + elemLength - (lengthName === WIDTH ? viewportWidth : viewportHeight) - sideOffset, |
|
offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0); |
|
|
|
// shift |
|
if(isShift) { |
|
offset = (mySide === side1 ? 1 : -1) * myLength; |
|
|
|
// Adjust position but keep it within viewport dimensions |
|
position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0; |
|
position[side1] = Math.max( |
|
-containerOffset[side1] + viewportOffset[side1], |
|
initialPos - offset, |
|
Math.min( |
|
Math.max( |
|
-containerOffset[side1] + viewportOffset[side1] + (lengthName === WIDTH ? viewportWidth : viewportHeight), |
|
initialPos + offset |
|
), |
|
position[side1], |
|
|
|
// Make sure we don't adjust complete off the element when using 'center' |
|
mySide === 'center' ? initialPos - myLength : 1E9 |
|
) |
|
); |
|
|
|
} |
|
|
|
// flip/flipinvert |
|
else { |
|
// Update adjustment amount depending on if using flipinvert or flip |
|
adjust *= (type === FLIPINVERT ? 2 : 0); |
|
|
|
// Check for overflow on the left/top |
|
if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) { |
|
position[side1] -= offset + adjust; |
|
newMy.invert(side, side1); |
|
} |
|
|
|
// Check for overflow on the bottom/right |
|
else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0) ) { |
|
position[side1] -= (mySide === CENTER ? -offset : offset) + adjust; |
|
newMy.invert(side, side2); |
|
} |
|
|
|
// Make sure we haven't made things worse with the adjustment and reset if so |
|
if(position[side1] < viewportScroll && -position[side1] > overflow2) { |
|
position[side1] = initialPos; newMy = my.clone(); |
|
} |
|
} |
|
|
|
return position[side1] - initialPos; |
|
} |
|
|
|
// Set newMy if using flip or flipinvert methods |
|
if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); } |
|
|
|
// Adjust position based onviewport and adjustment options |
|
adjusted = { |
|
left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0, |
|
top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0, |
|
my: newMy |
|
}; |
|
|
|
return adjusted; |
|
}; |
|
;})); |
|
}( window, document ));
|
|
|