| 1 |
/* |
| 2 |
* qTip2 - Pretty powerful tooltips - v3.0.3 |
| 3 |
* http://qtip2.com |
| 4 |
* |
| 5 |
* Copyright (c) 2017 |
| 6 |
* Released under the MIT licenses |
| 7 |
* http://jquery.org/license |
| 8 |
* |
| 9 |
* Date: Tue Aug 29 2017 06:12 EDT-0400 |
| 10 |
* Plugins: None |
| 11 |
* Styles: core basic css3 |
| 12 |
*/ |
| 13 |
|
| 14 |
/*global window: false, jQuery: false, console: false, define: false */ |
| 15 |
|
| 16 |
/* Cache window, document, undefined */ |
| 17 |
(function( window, document, undefined ) { |
| 18 |
|
| 19 |
// Uses AMD or browser globals to create a jQuery plugin. |
| 20 |
(function( factory ) { |
| 21 |
"use strict"; |
| 22 |
if(typeof define === 'function' && define.amd) { |
| 23 |
define(['jquery'], factory); |
| 24 |
} |
| 25 |
else if(jQuery && !jQuery.fn.qtip) { |
| 26 |
factory(jQuery); |
| 27 |
} |
| 28 |
} |
| 29 |
(function($) { |
| 30 |
"use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/ |
| 31 |
;// Munge the primitives - Paul Irish tip |
| 32 |
var TRUE = true, |
| 33 |
FALSE = false, |
| 34 |
NULL = null, |
| 35 |
|
| 36 |
// Common variables |
| 37 |
X = 'x', Y = 'y', |
| 38 |
WIDTH = 'width', |
| 39 |
HEIGHT = 'height', |
| 40 |
|
| 41 |
// Positioning sides |
| 42 |
TOP = 'top', |
| 43 |
LEFT = 'left', |
| 44 |
BOTTOM = 'bottom', |
| 45 |
RIGHT = 'right', |
| 46 |
CENTER = 'center', |
| 47 |
|
| 48 |
// Position adjustment types |
| 49 |
FLIP = 'flip', |
| 50 |
FLIPINVERT = 'flipinvert', |
| 51 |
SHIFT = 'shift', |
| 52 |
|
| 53 |
// Shortcut vars |
| 54 |
QTIP, PROTOTYPE, CORNER, CHECKS, |
| 55 |
PLUGINS = {}, |
| 56 |
NAMESPACE = 'qtip', |
| 57 |
ATTR_HAS = 'data-hasqtip', |
| 58 |
ATTR_ID = 'data-qtip-id', |
| 59 |
WIDGET = ['ui-widget', 'ui-tooltip'], |
| 60 |
SELECTOR = '.'+NAMESPACE, |
| 61 |
INACTIVE_EVENTS = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' '), |
| 62 |
|
| 63 |
CLASS_FIXED = NAMESPACE+'-fixed', |
| 64 |
CLASS_DEFAULT = NAMESPACE + '-default', |
| 65 |
CLASS_FOCUS = NAMESPACE + '-focus', |
| 66 |
CLASS_HOVER = NAMESPACE + '-hover', |
| 67 |
CLASS_DISABLED = NAMESPACE+'-disabled', |
| 68 |
|
| 69 |
replaceSuffix = '_replacedByqTip', |
| 70 |
oldtitle = 'oldtitle', |
| 71 |
trackingBound, |
| 72 |
|
| 73 |
// Browser detection |
| 74 |
BROWSER = { |
| 75 |
/* |
| 76 |
* IE version detection |
| 77 |
* |
| 78 |
* Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment |
| 79 |
* Credit to James Padolsey for the original implemntation! |
| 80 |
*/ |
| 81 |
ie: (function() { |
| 82 |
/* eslint-disable no-empty */ |
| 83 |
var v, i; |
| 84 |
for ( |
| 85 |
v = 4, i = document.createElement('div'); |
| 86 |
(i.innerHTML = '<!--[if gt IE ' + v + ']><i></i><![endif]-->') && i.getElementsByTagName('i')[0]; |
| 87 |
v+=1 |
| 88 |
) {} |
| 89 |
return v > 4 ? v : NaN; |
| 90 |
/* eslint-enable no-empty */ |
| 91 |
})(), |
| 92 |
|
| 93 |
/* |
| 94 |
* iOS version detection |
| 95 |
*/ |
| 96 |
iOS: parseFloat( |
| 97 |
('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1]) |
| 98 |
.replace('undefined', '3_2').replace('_', '.').replace('_', '') |
| 99 |
) || FALSE |
| 100 |
}; |
| 101 |
;function QTip(target, options, id, attr) { |
| 102 |
// Elements and ID |
| 103 |
this.id = id; |
| 104 |
this.target = target; |
| 105 |
this.tooltip = NULL; |
| 106 |
this.elements = { target: target }; |
| 107 |
|
| 108 |
// Internal constructs |
| 109 |
this._id = NAMESPACE + '-' + id; |
| 110 |
this.timers = { img: {} }; |
| 111 |
this.options = options; |
| 112 |
this.plugins = {}; |
| 113 |
|
| 114 |
// Cache object |
| 115 |
this.cache = { |
| 116 |
event: {}, |
| 117 |
target: $(), |
| 118 |
disabled: FALSE, |
| 119 |
attr: attr, |
| 120 |
onTooltip: FALSE, |
| 121 |
lastClass: '' |
| 122 |
}; |
| 123 |
|
| 124 |
// Set the initial flags |
| 125 |
this.rendered = this.destroyed = this.disabled = this.waiting = |
| 126 |
this.hiddenDuringWait = this.positioning = this.triggering = FALSE; |
| 127 |
} |
| 128 |
PROTOTYPE = QTip.prototype; |
| 129 |
|
| 130 |
PROTOTYPE._when = function(deferreds) { |
| 131 |
return $.when.apply($, deferreds); |
| 132 |
}; |
| 133 |
|
| 134 |
PROTOTYPE.render = function(show) { |
| 135 |
if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit |
| 136 |
|
| 137 |
var self = this, |
| 138 |
options = this.options, |
| 139 |
cache = this.cache, |
| 140 |
elements = this.elements, |
| 141 |
text = options.content.text, |
| 142 |
title = options.content.title, |
| 143 |
button = options.content.button, |
| 144 |
posOptions = options.position, |
| 145 |
deferreds = []; |
| 146 |
|
| 147 |
// Add ARIA attributes to target |
| 148 |
$.attr(this.target[0], 'aria-describedby', this._id); |
| 149 |
|
| 150 |
// Create public position object that tracks current position corners |
| 151 |
cache.posClass = this._createPosClass( |
| 152 |
(this.position = { my: posOptions.my, at: posOptions.at }).my |
| 153 |
); |
| 154 |
|
| 155 |
// Create tooltip element |
| 156 |
this.tooltip = elements.tooltip = $('<div/>', { |
| 157 |
'id': this._id, |
| 158 |
'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, cache.posClass ].join(' '), |
| 159 |
'width': options.style.width || '', |
| 160 |
'height': options.style.height || '', |
| 161 |
'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse, |
| 162 |
|
| 163 |
/* ARIA specific attributes */ |
| 164 |
'role': 'alert', |
| 165 |
'aria-live': 'polite', |
| 166 |
'aria-atomic': FALSE, |
| 167 |
'aria-describedby': this._id + '-content', |
| 168 |
'aria-hidden': TRUE |
| 169 |
}) |
| 170 |
.toggleClass(CLASS_DISABLED, this.disabled) |
| 171 |
.attr(ATTR_ID, this.id) |
| 172 |
.data(NAMESPACE, this) |
| 173 |
.appendTo(posOptions.container) |
| 174 |
.append( |
| 175 |
// Create content element |
| 176 |
elements.content = $('<div />', { |
| 177 |
'class': NAMESPACE + '-content', |
| 178 |
'id': this._id + '-content', |
| 179 |
'aria-atomic': TRUE |
| 180 |
}) |
| 181 |
); |
| 182 |
|
| 183 |
// Set rendered flag and prevent redundant reposition calls for now |
| 184 |
this.rendered = -1; |
| 185 |
this.positioning = TRUE; |
| 186 |
|
| 187 |
// Create title... |
| 188 |
if(title) { |
| 189 |
this._createTitle(); |
| 190 |
|
| 191 |
// Update title only if its not a callback (called in toggle if so) |
| 192 |
if(!$.isFunction(title)) { |
| 193 |
deferreds.push( this._updateTitle(title, FALSE) ); |
| 194 |
} |
| 195 |
} |
| 196 |
|
| 197 |
// Create button |
| 198 |
if(button) { this._createButton(); } |
| 199 |
|
| 200 |
// Set proper rendered flag and update content if not a callback function (called in toggle) |
| 201 |
if(!$.isFunction(text)) { |
| 202 |
deferreds.push( this._updateContent(text, FALSE) ); |
| 203 |
} |
| 204 |
this.rendered = TRUE; |
| 205 |
|
| 206 |
// Setup widget classes |
| 207 |
this._setWidget(); |
| 208 |
|
| 209 |
// Initialize 'render' plugins |
| 210 |
$.each(PLUGINS, function(name) { |
| 211 |
var instance; |
| 212 |
if(this.initialize === 'render' && (instance = this(self))) { |
| 213 |
self.plugins[name] = instance; |
| 214 |
} |
| 215 |
}); |
| 216 |
|
| 217 |
// Unassign initial events and assign proper events |
| 218 |
this._unassignEvents(); |
| 219 |
this._assignEvents(); |
| 220 |
|
| 221 |
// When deferreds have completed |
| 222 |
this._when(deferreds).then(function() { |
| 223 |
// tooltiprender event |
| 224 |
self._trigger('render'); |
| 225 |
|
| 226 |
// Reset flags |
| 227 |
self.positioning = FALSE; |
| 228 |
|
| 229 |
// Show tooltip if not hidden during wait period |
| 230 |
if(!self.hiddenDuringWait && (options.show.ready || show)) { |
| 231 |
self.toggle(TRUE, cache.event, FALSE); |
| 232 |
} |
| 233 |
self.hiddenDuringWait = FALSE; |
| 234 |
}); |
| 235 |
|
| 236 |
// Expose API |
| 237 |
QTIP.api[this.id] = this; |
| 238 |
|
| 239 |
return this; |
| 240 |
}; |
| 241 |
|
| 242 |
PROTOTYPE.destroy = function(immediate) { |
| 243 |
// Set flag the signify destroy is taking place to plugins |
| 244 |
// and ensure it only gets destroyed once! |
| 245 |
if(this.destroyed) { return this.target; } |
| 246 |
|
| 247 |
function process() { |
| 248 |
if(this.destroyed) { return; } |
| 249 |
this.destroyed = TRUE; |
| 250 |
|
| 251 |
var target = this.target, |
| 252 |
title = target.attr(oldtitle), |
| 253 |
timer; |
| 254 |
|
| 255 |
// Destroy tooltip if rendered |
| 256 |
if(this.rendered) { |
| 257 |
this.tooltip.stop(1,0).find('*').remove().end().remove(); |
| 258 |
} |
| 259 |
|
| 260 |
// Destroy all plugins |
| 261 |
$.each(this.plugins, function() { |
| 262 |
this.destroy && this.destroy(); |
| 263 |
}); |
| 264 |
|
| 265 |
// Clear timers |
| 266 |
for (timer in this.timers) { |
| 267 |
if (this.timers.hasOwnProperty(timer)) { |
| 268 |
clearTimeout(this.timers[timer]); |
| 269 |
} |
| 270 |
} |
| 271 |
|
| 272 |
// Remove api object and ARIA attributes |
| 273 |
target.removeData(NAMESPACE) |
| 274 |
.removeAttr(ATTR_ID) |
| 275 |
.removeAttr(ATTR_HAS) |
| 276 |
.removeAttr('aria-describedby'); |
| 277 |
|
| 278 |
// Reset old title attribute if removed |
| 279 |
if(this.options.suppress && title) { |
| 280 |
target.attr('title', title).removeAttr(oldtitle); |
| 281 |
} |
| 282 |
|
| 283 |
// Remove qTip events associated with this API |
| 284 |
this._unassignEvents(); |
| 285 |
|
| 286 |
// Remove ID from used id objects, and delete object references |
| 287 |
// for better garbage collection and leak protection |
| 288 |
this.options = this.elements = this.cache = this.timers = |
| 289 |
this.plugins = this.mouse = NULL; |
| 290 |
|
| 291 |
// Delete epoxsed API object |
| 292 |
delete QTIP.api[this.id]; |
| 293 |
} |
| 294 |
|
| 295 |
// If an immediate destroy is needed |
| 296 |
if((immediate !== TRUE || this.triggering === 'hide') && this.rendered) { |
| 297 |
this.tooltip.one('tooltiphidden', $.proxy(process, this)); |
| 298 |
!this.triggering && this.hide(); |
| 299 |
} |
| 300 |
|
| 301 |
// If we're not in the process of hiding... process |
| 302 |
else { process.call(this); } |
| 303 |
|
| 304 |
return this.target; |
| 305 |
}; |
| 306 |
;function invalidOpt(a) { |
| 307 |
return a === NULL || $.type(a) !== 'object'; |
| 308 |
} |
| 309 |
|
| 310 |
function invalidContent(c) { |
| 311 |
return !($.isFunction(c) || |
| 312 |
c && c.attr || |
| 313 |
c.length || |
| 314 |
$.type(c) === 'object' && (c.jquery || c.then)); |
| 315 |
} |
| 316 |
|
| 317 |
// Option object sanitizer |
| 318 |
function sanitizeOptions(opts) { |
| 319 |
var content, text, ajax, once; |
| 320 |
|
| 321 |
if(invalidOpt(opts)) { return FALSE; } |
| 322 |
|
| 323 |
if(invalidOpt(opts.metadata)) { |
| 324 |
opts.metadata = { type: opts.metadata }; |
| 325 |
} |
| 326 |
|
| 327 |
if('content' in opts) { |
| 328 |
content = opts.content; |
| 329 |
|
| 330 |
if(invalidOpt(content) || content.jquery || content.done) { |
| 331 |
text = invalidContent(content) ? FALSE : content; |
| 332 |
content = opts.content = { |
| 333 |
text: text |
| 334 |
}; |
| 335 |
} |
| 336 |
else { text = content.text; } |
| 337 |
|
| 338 |
// DEPRECATED - Old content.ajax plugin functionality |
| 339 |
// Converts it into the proper Deferred syntax |
| 340 |
if('ajax' in content) { |
| 341 |
ajax = content.ajax; |
| 342 |
once = ajax && ajax.once !== FALSE; |
| 343 |
delete content.ajax; |
| 344 |
|
| 345 |
content.text = function(event, api) { |
| 346 |
var loading = text || $(this).attr(api.options.content.attr) || 'Loading...', |
| 347 |
|
| 348 |
deferred = $.ajax( |
| 349 |
$.extend({}, ajax, { context: api }) |
| 350 |
) |
| 351 |
.then(ajax.success, NULL, ajax.error) |
| 352 |
.then(function(newContent) { |
| 353 |
if(newContent && once) { api.set('content.text', newContent); } |
| 354 |
return newContent; |
| 355 |
}, |
| 356 |
function(xhr, status, error) { |
| 357 |
if(api.destroyed || xhr.status === 0) { return; } |
| 358 |
api.set('content.text', status + ': ' + error); |
| 359 |
}); |
| 360 |
|
| 361 |
return !once ? (api.set('content.text', loading), deferred) : loading; |
| 362 |
}; |
| 363 |
} |
| 364 |
|
| 365 |
if('title' in content) { |
| 366 |
if($.isPlainObject(content.title)) { |
| 367 |
content.button = content.title.button; |
| 368 |
content.title = content.title.text; |
| 369 |
} |
| 370 |
|
| 371 |
if(invalidContent(content.title || FALSE)) { |
| 372 |
content.title = FALSE; |
| 373 |
} |
| 374 |
} |
| 375 |
} |
| 376 |
|
| 377 |
if('position' in opts && invalidOpt(opts.position)) { |
| 378 |
opts.position = { my: opts.position, at: opts.position }; |
| 379 |
} |
| 380 |
|
| 381 |
if('show' in opts && invalidOpt(opts.show)) { |
| 382 |
opts.show = opts.show.jquery ? { target: opts.show } : |
| 383 |
opts.show === TRUE ? { ready: TRUE } : { event: opts.show }; |
| 384 |
} |
| 385 |
|
| 386 |
if('hide' in opts && invalidOpt(opts.hide)) { |
| 387 |
opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide }; |
| 388 |
} |
| 389 |
|
| 390 |
if('style' in opts && invalidOpt(opts.style)) { |
| 391 |
opts.style = { classes: opts.style }; |
| 392 |
} |
| 393 |
|
| 394 |
// Sanitize plugin options |
| 395 |
$.each(PLUGINS, function() { |
| 396 |
this.sanitize && this.sanitize(opts); |
| 397 |
}); |
| 398 |
|
| 399 |
return opts; |
| 400 |
} |
| 401 |
|
| 402 |
// Setup builtin .set() option checks |
| 403 |
CHECKS = PROTOTYPE.checks = { |
| 404 |
builtin: { |
| 405 |
// Core checks |
| 406 |
'^id$': function(obj, o, v, prev) { |
| 407 |
var id = v === TRUE ? QTIP.nextid : v, |
| 408 |
newId = NAMESPACE + '-' + id; |
| 409 |
|
| 410 |
if(id !== FALSE && id.length > 0 && !$('#'+newId).length) { |
| 411 |
this._id = newId; |
| 412 |
|
| 413 |
if(this.rendered) { |
| 414 |
this.tooltip[0].id = this._id; |
| 415 |
this.elements.content[0].id = this._id + '-content'; |
| 416 |
this.elements.title[0].id = this._id + '-title'; |
| 417 |
} |
| 418 |
} |
| 419 |
else { obj[o] = prev; } |
| 420 |
}, |
| 421 |
'^prerender': function(obj, o, v) { |
| 422 |
v && !this.rendered && this.render(this.options.show.ready); |
| 423 |
}, |
| 424 |
|
| 425 |
// Content checks |
| 426 |
'^content.text$': function(obj, o, v) { |
| 427 |
this._updateContent(v); |
| 428 |
}, |
| 429 |
'^content.attr$': function(obj, o, v, prev) { |
| 430 |
if(this.options.content.text === this.target.attr(prev)) { |
| 431 |
this._updateContent( this.target.attr(v) ); |
| 432 |
} |
| 433 |
}, |
| 434 |
'^content.title$': function(obj, o, v) { |
| 435 |
// Remove title if content is null |
| 436 |
if(!v) { return this._removeTitle(); } |
| 437 |
|
| 438 |
// If title isn't already created, create it now and update |
| 439 |
v && !this.elements.title && this._createTitle(); |
| 440 |
this._updateTitle(v); |
| 441 |
}, |
| 442 |
'^content.button$': function(obj, o, v) { |
| 443 |
this._updateButton(v); |
| 444 |
}, |
| 445 |
'^content.title.(text|button)$': function(obj, o, v) { |
| 446 |
this.set('content.'+o, v); // Backwards title.text/button compat |
| 447 |
}, |
| 448 |
|
| 449 |
// Position checks |
| 450 |
'^position.(my|at)$': function(obj, o, v){ |
| 451 |
if('string' === typeof v) { |
| 452 |
this.position[o] = obj[o] = new CORNER(v, o === 'at'); |
| 453 |
} |
| 454 |
}, |
| 455 |
'^position.container$': function(obj, o, v){ |
| 456 |
this.rendered && this.tooltip.appendTo(v); |
| 457 |
}, |
| 458 |
|
| 459 |
// Show checks |
| 460 |
'^show.ready$': function(obj, o, v) { |
| 461 |
v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE)); |
| 462 |
}, |
| 463 |
|
| 464 |
// Style checks |
| 465 |
'^style.classes$': function(obj, o, v, p) { |
| 466 |
this.rendered && this.tooltip.removeClass(p).addClass(v); |
| 467 |
}, |
| 468 |
'^style.(width|height)': function(obj, o, v) { |
| 469 |
this.rendered && this.tooltip.css(o, v); |
| 470 |
}, |
| 471 |
'^style.widget|content.title': function() { |
| 472 |
this.rendered && this._setWidget(); |
| 473 |
}, |
| 474 |
'^style.def': function(obj, o, v) { |
| 475 |
this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v); |
| 476 |
}, |
| 477 |
|
| 478 |
// Events check |
| 479 |
'^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) { |
| 480 |
this.rendered && this.tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v); |
| 481 |
}, |
| 482 |
|
| 483 |
// Properties which require event reassignment |
| 484 |
'^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() { |
| 485 |
if(!this.rendered) { return; } |
| 486 |
|
| 487 |
// Set tracking flag |
| 488 |
var posOptions = this.options.position; |
| 489 |
this.tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse); |
| 490 |
|
| 491 |
// Reassign events |
| 492 |
this._unassignEvents(); |
| 493 |
this._assignEvents(); |
| 494 |
} |
| 495 |
} |
| 496 |
}; |
| 497 |
|
| 498 |
// Dot notation converter |
| 499 |
function convertNotation(options, notation) { |
| 500 |
var i = 0, obj, option = options, |
| 501 |
|
| 502 |
// Split notation into array |
| 503 |
levels = notation.split('.'); |
| 504 |
|
| 505 |
// Loop through |
| 506 |
while(option = option[ levels[i++] ]) { |
| 507 |
if(i < levels.length) { obj = option; } |
| 508 |
} |
| 509 |
|
| 510 |
return [obj || options, levels.pop()]; |
| 511 |
} |
| 512 |
|
| 513 |
PROTOTYPE.get = function(notation) { |
| 514 |
if(this.destroyed) { return this; } |
| 515 |
|
| 516 |
var o = convertNotation(this.options, notation.toLowerCase()), |
| 517 |
result = o[0][ o[1] ]; |
| 518 |
|
| 519 |
return result.precedance ? result.string() : result; |
| 520 |
}; |
| 521 |
|
| 522 |
function setCallback(notation, args) { |
| 523 |
var category, rule, match; |
| 524 |
|
| 525 |
for(category in this.checks) { |
| 526 |
if (!this.checks.hasOwnProperty(category)) { continue; } |
| 527 |
|
| 528 |
for(rule in this.checks[category]) { |
| 529 |
if (!this.checks[category].hasOwnProperty(rule)) { continue; } |
| 530 |
|
| 531 |
if(match = (new RegExp(rule, 'i')).exec(notation)) { |
| 532 |
args.push(match); |
| 533 |
|
| 534 |
if(category === 'builtin' || this.plugins[category]) { |
| 535 |
this.checks[category][rule].apply( |
| 536 |
this.plugins[category] || this, args |
| 537 |
); |
| 538 |
} |
| 539 |
} |
| 540 |
} |
| 541 |
} |
| 542 |
} |
| 543 |
|
| 544 |
var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i, |
| 545 |
rrender = /^prerender|show\.ready/i; |
| 546 |
|
| 547 |
PROTOTYPE.set = function(option, value) { |
| 548 |
if(this.destroyed) { return this; } |
| 549 |
|
| 550 |
var rendered = this.rendered, |
| 551 |
reposition = FALSE, |
| 552 |
options = this.options, |
| 553 |
name; |
| 554 |
|
| 555 |
// Convert singular option/value pair into object form |
| 556 |
if('string' === typeof option) { |
| 557 |
name = option; option = {}; option[name] = value; |
| 558 |
} |
| 559 |
else { option = $.extend({}, option); } |
| 560 |
|
| 561 |
// Set all of the defined options to their new values |
| 562 |
$.each(option, function(notation, val) { |
| 563 |
if(rendered && rrender.test(notation)) { |
| 564 |
delete option[notation]; return; |
| 565 |
} |
| 566 |
|
| 567 |
// Set new obj value |
| 568 |
var obj = convertNotation(options, notation.toLowerCase()), previous; |
| 569 |
previous = obj[0][ obj[1] ]; |
| 570 |
obj[0][ obj[1] ] = val && val.nodeType ? $(val) : val; |
| 571 |
|
| 572 |
// Also check if we need to reposition |
| 573 |
reposition = rmove.test(notation) || reposition; |
| 574 |
|
| 575 |
// Set the new params for the callback |
| 576 |
option[notation] = [obj[0], obj[1], val, previous]; |
| 577 |
}); |
| 578 |
|
| 579 |
// Re-sanitize options |
| 580 |
sanitizeOptions(options); |
| 581 |
|
| 582 |
/* |
| 583 |
* Execute any valid callbacks for the set options |
| 584 |
* Also set positioning flag so we don't get loads of redundant repositioning calls. |
| 585 |
*/ |
| 586 |
this.positioning = TRUE; |
| 587 |
$.each(option, $.proxy(setCallback, this)); |
| 588 |
this.positioning = FALSE; |
| 589 |
|
| 590 |
// Update position if needed |
| 591 |
if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) { |
| 592 |
this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event ); |
| 593 |
} |
| 594 |
|
| 595 |
return this; |
| 596 |
}; |
| 597 |
;PROTOTYPE._update = function(content, element) { |
| 598 |
var self = this, |
| 599 |
cache = this.cache; |
| 600 |
|
| 601 |
// Make sure tooltip is rendered and content is defined. If not return |
| 602 |
if(!this.rendered || !content) { return FALSE; } |
| 603 |
|
| 604 |
// Use function to parse content |
| 605 |
if($.isFunction(content)) { |
| 606 |
content = content.call(this.elements.target, cache.event, this) || ''; |
| 607 |
} |
| 608 |
|
| 609 |
// Handle deferred content |
| 610 |
if($.isFunction(content.then)) { |
| 611 |
cache.waiting = TRUE; |
| 612 |
return content.then(function(c) { |
| 613 |
cache.waiting = FALSE; |
| 614 |
return self._update(c, element); |
| 615 |
}, NULL, function(e) { |
| 616 |
return self._update(e, element); |
| 617 |
}); |
| 618 |
} |
| 619 |
|
| 620 |
// If content is null... return false |
| 621 |
if(content === FALSE || !content && content !== '') { return FALSE; } |
| 622 |
|
| 623 |
// Append new content if its a DOM array and show it if hidden |
| 624 |
if(content.jquery && content.length > 0) { |
| 625 |
element.empty().append( |
| 626 |
content.css({ display: 'block', visibility: 'visible' }) |
| 627 |
); |
| 628 |
} |
| 629 |
|
| 630 |
// Content is a regular string, insert the new content |
| 631 |
else { element.html(content); } |
| 632 |
|
| 633 |
// Wait for content to be loaded, and reposition |
| 634 |
return this._waitForContent(element).then(function(images) { |
| 635 |
if(self.rendered && self.tooltip[0].offsetWidth > 0) { |
| 636 |
self.reposition(cache.event, !images.length); |
| 637 |
} |
| 638 |
}); |
| 639 |
}; |
| 640 |
|
| 641 |
PROTOTYPE._waitForContent = function(element) { |
| 642 |
var cache = this.cache; |
| 643 |
|
| 644 |
// Set flag |
| 645 |
cache.waiting = TRUE; |
| 646 |
|
| 647 |
// If imagesLoaded is included, ensure images have loaded and return promise |
| 648 |
return ( $.fn.imagesLoaded ? element.imagesLoaded() : new $.Deferred().resolve([]) ) |
| 649 |
.done(function() { cache.waiting = FALSE; }) |
| 650 |
.promise(); |
| 651 |
}; |
| 652 |
|
| 653 |
PROTOTYPE._updateContent = function(content, reposition) { |
| 654 |
this._update(content, this.elements.content, reposition); |
| 655 |
}; |
| 656 |
|
| 657 |
PROTOTYPE._updateTitle = function(content, reposition) { |
| 658 |
if(this._update(content, this.elements.title, reposition) === FALSE) { |
| 659 |
this._removeTitle(FALSE); |
| 660 |
} |
| 661 |
}; |
| 662 |
|
| 663 |
PROTOTYPE._createTitle = function() |
| 664 |
{ |
| 665 |
var elements = this.elements, |
| 666 |
id = this._id+'-title'; |
| 667 |
|
| 668 |
// Destroy previous title element, if present |
| 669 |
if(elements.titlebar) { this._removeTitle(); } |
| 670 |
|
| 671 |
// Create title bar and title elements |
| 672 |
elements.titlebar = $('<div />', { |
| 673 |
'class': NAMESPACE + '-titlebar ' + (this.options.style.widget ? createWidgetClass('header') : '') |
| 674 |
}) |
| 675 |
.append( |
| 676 |
elements.title = $('<div />', { |
| 677 |
'id': id, |
| 678 |
'class': NAMESPACE + '-title', |
| 679 |
'aria-atomic': TRUE |
| 680 |
}) |
| 681 |
) |
| 682 |
.insertBefore(elements.content) |
| 683 |
|
| 684 |
// Button-specific events |
| 685 |
.delegate('.qtip-close', 'mousedown keydown mouseup keyup mouseout', function(event) { |
| 686 |
$(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down'); |
| 687 |
}) |
| 688 |
.delegate('.qtip-close', 'mouseover mouseout', function(event){ |
| 689 |
$(this).toggleClass('ui-state-hover', event.type === 'mouseover'); |
| 690 |
}); |
| 691 |
|
| 692 |
// Create button if enabled |
| 693 |
if(this.options.content.button) { this._createButton(); } |
| 694 |
}; |
| 695 |
|
| 696 |
PROTOTYPE._removeTitle = function(reposition) |
| 697 |
{ |
| 698 |
var elements = this.elements; |
| 699 |
|
| 700 |
if(elements.title) { |
| 701 |
elements.titlebar.remove(); |
| 702 |
elements.titlebar = elements.title = elements.button = NULL; |
| 703 |
|
| 704 |
// Reposition if enabled |
| 705 |
if(reposition !== FALSE) { this.reposition(); } |
| 706 |
} |
| 707 |
}; |
| 708 |
;PROTOTYPE._createPosClass = function(my) { |
| 709 |
return NAMESPACE + '-pos-' + (my || this.options.position.my).abbrev(); |
| 710 |
}; |
| 711 |
|
| 712 |
PROTOTYPE.reposition = function(event, effect) { |
| 713 |
if(!this.rendered || this.positioning || this.destroyed) { return this; } |
| 714 |
|
| 715 |
// Set positioning flag |
| 716 |
this.positioning = TRUE; |
| 717 |
|
| 718 |
var cache = this.cache, |
| 719 |
tooltip = this.tooltip, |
| 720 |
posOptions = this.options.position, |
| 721 |
target = posOptions.target, |
| 722 |
my = posOptions.my, |
| 723 |
at = posOptions.at, |
| 724 |
viewport = posOptions.viewport, |
| 725 |
container = posOptions.container, |
| 726 |
adjust = posOptions.adjust, |
| 727 |
method = adjust.method.split(' '), |
| 728 |
tooltipWidth = tooltip.outerWidth(FALSE), |
| 729 |
tooltipHeight = tooltip.outerHeight(FALSE), |
| 730 |
targetWidth = 0, |
| 731 |
targetHeight = 0, |
| 732 |
type = tooltip.css('position'), |
| 733 |
position = { left: 0, top: 0 }, |
| 734 |
visible = tooltip[0].offsetWidth > 0, |
| 735 |
isScroll = event && event.type === 'scroll', |
| 736 |
win = $(window), |
| 737 |
doc = container[0].ownerDocument, |
| 738 |
mouse = this.mouse, |
| 739 |
pluginCalculations, offset, adjusted, newClass; |
| 740 |
|
| 741 |
// Check if absolute position was passed |
| 742 |
if($.isArray(target) && target.length === 2) { |
| 743 |
// Force left top and set position |
| 744 |
at = { x: LEFT, y: TOP }; |
| 745 |
position = { left: target[0], top: target[1] }; |
| 746 |
} |
| 747 |
|
| 748 |
// Check if mouse was the target |
| 749 |
else if(target === 'mouse') { |
| 750 |
// Force left top to allow flipping |
| 751 |
at = { x: LEFT, y: TOP }; |
| 752 |
|
| 753 |
// Use the mouse origin that caused the show event, if distance hiding is enabled |
| 754 |
if((!adjust.mouse || this.options.hide.distance) && cache.origin && cache.origin.pageX) { |
| 755 |
event = cache.origin; |
| 756 |
} |
| 757 |
|
| 758 |
// Use cached event for resize/scroll events |
| 759 |
else if(!event || event && (event.type === 'resize' || event.type === 'scroll')) { |
| 760 |
event = cache.event; |
| 761 |
} |
| 762 |
|
| 763 |
// Otherwise, use the cached mouse coordinates if available |
| 764 |
else if(mouse && mouse.pageX) { |
| 765 |
event = mouse; |
| 766 |
} |
| 767 |
|
| 768 |
// Calculate body and container offset and take them into account below |
| 769 |
if(type !== 'static') { position = container.offset(); } |
| 770 |
if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) { |
| 771 |
offset = $(document.body).offset(); |
| 772 |
} |
| 773 |
|
| 774 |
// Use event coordinates for position |
| 775 |
position = { |
| 776 |
left: event.pageX - position.left + (offset && offset.left || 0), |
| 777 |
top: event.pageY - position.top + (offset && offset.top || 0) |
| 778 |
}; |
| 779 |
|
| 780 |
// Scroll events are a pain, some browsers |
| 781 |
if(adjust.mouse && isScroll && mouse) { |
| 782 |
position.left -= (mouse.scrollX || 0) - win.scrollLeft(); |
| 783 |
position.top -= (mouse.scrollY || 0) - win.scrollTop(); |
| 784 |
} |
| 785 |
} |
| 786 |
|
| 787 |
// Target wasn't mouse or absolute... |
| 788 |
else { |
| 789 |
// Check if event targetting is being used |
| 790 |
if(target === 'event') { |
| 791 |
if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') { |
| 792 |
cache.target = $(event.target); |
| 793 |
} |
| 794 |
else if(!event.target) { |
| 795 |
cache.target = this.elements.target; |
| 796 |
} |
| 797 |
} |
| 798 |
else if(target !== 'event'){ |
| 799 |
cache.target = $(target.jquery ? target : this.elements.target); |
| 800 |
} |
| 801 |
target = cache.target; |
| 802 |
|
| 803 |
// Parse the target into a jQuery object and make sure there's an element present |
| 804 |
target = $(target).eq(0); |
| 805 |
if(target.length === 0) { return this; } |
| 806 |
|
| 807 |
// Check if window or document is the target |
| 808 |
else if(target[0] === document || target[0] === window) { |
| 809 |
targetWidth = BROWSER.iOS ? window.innerWidth : target.width(); |
| 810 |
targetHeight = BROWSER.iOS ? window.innerHeight : target.height(); |
| 811 |
|
| 812 |
if(target[0] === window) { |
| 813 |
position = { |
| 814 |
top: (viewport || target).scrollTop(), |
| 815 |
left: (viewport || target).scrollLeft() |
| 816 |
}; |
| 817 |
} |
| 818 |
} |
| 819 |
|
| 820 |
// Check if the target is an <AREA> element |
| 821 |
else if(PLUGINS.imagemap && target.is('area')) { |
| 822 |
pluginCalculations = PLUGINS.imagemap(this, target, at, PLUGINS.viewport ? method : FALSE); |
| 823 |
} |
| 824 |
|
| 825 |
// Check if the target is an SVG element |
| 826 |
else if(PLUGINS.svg && target && target[0].ownerSVGElement) { |
| 827 |
pluginCalculations = PLUGINS.svg(this, target, at, PLUGINS.viewport ? method : FALSE); |
| 828 |
} |
| 829 |
|
| 830 |
// Otherwise use regular jQuery methods |
| 831 |
else { |
| 832 |
targetWidth = target.outerWidth(FALSE); |
| 833 |
targetHeight = target.outerHeight(FALSE); |
| 834 |
position = target.offset(); |
| 835 |
} |
| 836 |
|
| 837 |
// Parse returned plugin values into proper variables |
| 838 |
if(pluginCalculations) { |
| 839 |
targetWidth = pluginCalculations.width; |
| 840 |
targetHeight = pluginCalculations.height; |
| 841 |
offset = pluginCalculations.offset; |
| 842 |
position = pluginCalculations.position; |
| 843 |
} |
| 844 |
|
| 845 |
// Adjust position to take into account offset parents |
| 846 |
position = this.reposition.offset(target, position, container); |
| 847 |
|
| 848 |
// Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2) |
| 849 |
if(BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1 || |
| 850 |
BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33 || |
| 851 |
!BROWSER.iOS && type === 'fixed' |
| 852 |
){ |
| 853 |
position.left -= win.scrollLeft(); |
| 854 |
position.top -= win.scrollTop(); |
| 855 |
} |
| 856 |
|
| 857 |
// Adjust position relative to target |
| 858 |
if(!pluginCalculations || pluginCalculations && pluginCalculations.adjustable !== FALSE) { |
| 859 |
position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0; |
| 860 |
position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0; |
| 861 |
} |
| 862 |
} |
| 863 |
|
| 864 |
// Adjust position relative to tooltip |
| 865 |
position.left += adjust.x + (my.x === RIGHT ? -tooltipWidth : my.x === CENTER ? -tooltipWidth / 2 : 0); |
| 866 |
position.top += adjust.y + (my.y === BOTTOM ? -tooltipHeight : my.y === CENTER ? -tooltipHeight / 2 : 0); |
| 867 |
|
| 868 |
// Use viewport adjustment plugin if enabled |
| 869 |
if(PLUGINS.viewport) { |
| 870 |
adjusted = position.adjusted = PLUGINS.viewport( |
| 871 |
this, position, posOptions, targetWidth, targetHeight, tooltipWidth, tooltipHeight |
| 872 |
); |
| 873 |
|
| 874 |
// Apply offsets supplied by positioning plugin (if used) |
| 875 |
if(offset && adjusted.left) { position.left += offset.left; } |
| 876 |
if(offset && adjusted.top) { position.top += offset.top; } |
| 877 |
|
| 878 |
// Apply any new 'my' position |
| 879 |
if(adjusted.my) { this.position.my = adjusted.my; } |
| 880 |
} |
| 881 |
|
| 882 |
// Viewport adjustment is disabled, set values to zero |
| 883 |
else { position.adjusted = { left: 0, top: 0 }; } |
| 884 |
|
| 885 |
// Set tooltip position class if it's changed |
| 886 |
if(cache.posClass !== (newClass = this._createPosClass(this.position.my))) { |
| 887 |
cache.posClass = newClass; |
| 888 |
tooltip.removeClass(cache.posClass).addClass(newClass); |
| 889 |
} |
| 890 |
|
| 891 |
// tooltipmove event |
| 892 |
if(!this._trigger('move', [position, viewport.elem || viewport], event)) { return this; } |
| 893 |
delete position.adjusted; |
| 894 |
|
| 895 |
// If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly |
| 896 |
if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) { |
| 897 |
tooltip.css(position); |
| 898 |
} |
| 899 |
|
| 900 |
// Use custom function if provided |
| 901 |
else if($.isFunction(posOptions.effect)) { |
| 902 |
posOptions.effect.call(tooltip, this, $.extend({}, position)); |
| 903 |
tooltip.queue(function(next) { |
| 904 |
// Reset attributes to avoid cross-browser rendering bugs |
| 905 |
$(this).css({ opacity: '', height: '' }); |
| 906 |
if(BROWSER.ie) { this.style.removeAttribute('filter'); } |
| 907 |
|
| 908 |
next(); |
| 909 |
}); |
| 910 |
} |
| 911 |
|
| 912 |
// Set positioning flag |
| 913 |
this.positioning = FALSE; |
| 914 |
|
| 915 |
return this; |
| 916 |
}; |
| 917 |
|
| 918 |
// Custom (more correct for qTip!) offset calculator |
| 919 |
PROTOTYPE.reposition.offset = function(elem, pos, container) { |
| 920 |
if(!container[0]) { return pos; } |
| 921 |
|
| 922 |
var ownerDocument = $(elem[0].ownerDocument), |
| 923 |
quirks = !!BROWSER.ie && document.compatMode !== 'CSS1Compat', |
| 924 |
parent = container[0], |
| 925 |
scrolled, position, parentOffset, overflow; |
| 926 |
|
| 927 |
function scroll(e, i) { |
| 928 |
pos.left += i * e.scrollLeft(); |
| 929 |
pos.top += i * e.scrollTop(); |
| 930 |
} |
| 931 |
|
| 932 |
// Compensate for non-static containers offset |
| 933 |
do { |
| 934 |
if((position = $.css(parent, 'position')) !== 'static') { |
| 935 |
if(position === 'fixed') { |
| 936 |
parentOffset = parent.getBoundingClientRect(); |
| 937 |
scroll(ownerDocument, -1); |
| 938 |
} |
| 939 |
else { |
| 940 |
parentOffset = $(parent).position(); |
| 941 |
parentOffset.left += parseFloat($.css(parent, 'borderLeftWidth')) || 0; |
| 942 |
parentOffset.top += parseFloat($.css(parent, 'borderTopWidth')) || 0; |
| 943 |
} |
| 944 |
|
| 945 |
pos.left -= parentOffset.left + (parseFloat($.css(parent, 'marginLeft')) || 0); |
| 946 |
pos.top -= parentOffset.top + (parseFloat($.css(parent, 'marginTop')) || 0); |
| 947 |
|
| 948 |
// If this is the first parent element with an overflow of "scroll" or "auto", store it |
| 949 |
if(!scrolled && (overflow = $.css(parent, 'overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = $(parent); } |
| 950 |
} |
| 951 |
} |
| 952 |
while(parent = parent.offsetParent); |
| 953 |
|
| 954 |
// Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode) |
| 955 |
if(scrolled && (scrolled[0] !== ownerDocument[0] || quirks)) { |
| 956 |
scroll(scrolled, 1); |
| 957 |
} |
| 958 |
|
| 959 |
return pos; |
| 960 |
}; |
| 961 |
|
| 962 |
// Corner class |
| 963 |
var C = (CORNER = PROTOTYPE.reposition.Corner = function(corner, forceY) { |
| 964 |
corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase(); |
| 965 |
this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase(); |
| 966 |
this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase(); |
| 967 |
this.forceY = !!forceY; |
| 968 |
|
| 969 |
var f = corner.charAt(0); |
| 970 |
this.precedance = f === 't' || f === 'b' ? Y : X; |
| 971 |
}).prototype; |
| 972 |
|
| 973 |
C.invert = function(z, center) { |
| 974 |
this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z]; |
| 975 |
}; |
| 976 |
|
| 977 |
C.string = function(join) { |
| 978 |
var x = this.x, y = this.y; |
| 979 |
|
| 980 |
var result = x !== y ? |
| 981 |
x === 'center' || y !== 'center' && (this.precedance === Y || this.forceY) ? |
| 982 |
[y,x] : |
| 983 |
[x,y] : |
| 984 |
[x]; |
| 985 |
|
| 986 |
return join !== false ? result.join(' ') : result; |
| 987 |
}; |
| 988 |
|
| 989 |
C.abbrev = function() { |
| 990 |
var result = this.string(false); |
| 991 |
return result[0].charAt(0) + (result[1] && result[1].charAt(0) || ''); |
| 992 |
}; |
| 993 |
|
| 994 |
C.clone = function() { |
| 995 |
return new CORNER( this.string(), this.forceY ); |
| 996 |
}; |
| 997 |
|
| 998 |
; |
| 999 |
PROTOTYPE.toggle = function(state, event) { |
| 1000 |
var cache = this.cache, |
| 1001 |
options = this.options, |
| 1002 |
tooltip = this.tooltip; |
| 1003 |
|
| 1004 |
// Try to prevent flickering when tooltip overlaps show element |
| 1005 |
if(event) { |
| 1006 |
if((/over|enter/).test(event.type) && cache.event && (/out|leave/).test(cache.event.type) && |
| 1007 |
options.show.target.add(event.target).length === options.show.target.length && |
| 1008 |
tooltip.has(event.relatedTarget).length) { |
| 1009 |
return this; |
| 1010 |
} |
| 1011 |
|
| 1012 |
// Cache event |
| 1013 |
cache.event = $.event.fix(event); |
| 1014 |
} |
| 1015 |
|
| 1016 |
// If we're currently waiting and we've just hidden... stop it |
| 1017 |
this.waiting && !state && (this.hiddenDuringWait = TRUE); |
| 1018 |
|
| 1019 |
// Render the tooltip if showing and it isn't already |
| 1020 |
if(!this.rendered) { return state ? this.render(1) : this; } |
| 1021 |
else if(this.destroyed || this.disabled) { return this; } |
| 1022 |
|
| 1023 |
var type = state ? 'show' : 'hide', |
| 1024 |
opts = this.options[type], |
| 1025 |
posOptions = this.options.position, |
| 1026 |
contentOptions = this.options.content, |
| 1027 |
width = this.tooltip.css('width'), |
| 1028 |
visible = this.tooltip.is(':visible'), |
| 1029 |
animate = state || opts.target.length === 1, |
| 1030 |
sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target, |
| 1031 |
identicalState, allow, after; |
| 1032 |
|
| 1033 |
// Detect state if valid one isn't provided |
| 1034 |
if((typeof state).search('boolean|number')) { state = !visible; } |
| 1035 |
|
| 1036 |
// Check if the tooltip is in an identical state to the new would-be state |
| 1037 |
identicalState = !tooltip.is(':animated') && visible === state && sameTarget; |
| 1038 |
|
| 1039 |
// Fire tooltip(show/hide) event and check if destroyed |
| 1040 |
allow = !identicalState ? !!this._trigger(type, [90]) : NULL; |
| 1041 |
|
| 1042 |
// Check to make sure the tooltip wasn't destroyed in the callback |
| 1043 |
if(this.destroyed) { return this; } |
| 1044 |
|
| 1045 |
// If the user didn't stop the method prematurely and we're showing the tooltip, focus it |
| 1046 |
if(allow !== FALSE && state) { this.focus(event); } |
| 1047 |
|
| 1048 |
// If the state hasn't changed or the user stopped it, return early |
| 1049 |
if(!allow || identicalState) { return this; } |
| 1050 |
|
| 1051 |
// Set ARIA hidden attribute |
| 1052 |
$.attr(tooltip[0], 'aria-hidden', !!!state); |
| 1053 |
|
| 1054 |
// Execute state specific properties |
| 1055 |
if(state) { |
| 1056 |
// Store show origin coordinates |
| 1057 |
this.mouse && (cache.origin = $.event.fix(this.mouse)); |
| 1058 |
|
| 1059 |
// Update tooltip content & title if it's a dynamic function |
| 1060 |
if($.isFunction(contentOptions.text)) { this._updateContent(contentOptions.text, FALSE); } |
| 1061 |
if($.isFunction(contentOptions.title)) { this._updateTitle(contentOptions.title, FALSE); } |
| 1062 |
|
| 1063 |
// Cache mousemove events for positioning purposes (if not already tracking) |
| 1064 |
if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) { |
| 1065 |
$(document).bind('mousemove.'+NAMESPACE, this._storeMouse); |
| 1066 |
trackingBound = TRUE; |
| 1067 |
} |
| 1068 |
|
| 1069 |
// Update the tooltip position (set width first to prevent viewport/max-width issues) |
| 1070 |
if(!width) { tooltip.css('width', tooltip.outerWidth(FALSE)); } |
| 1071 |
this.reposition(event, arguments[2]); |
| 1072 |
if(!width) { tooltip.css('width', ''); } |
| 1073 |
|
| 1074 |
// Hide other tooltips if tooltip is solo |
| 1075 |
if(!!opts.solo) { |
| 1076 |
(typeof opts.solo === 'string' ? $(opts.solo) : $(SELECTOR, opts.solo)) |
| 1077 |
.not(tooltip).not(opts.target).qtip('hide', new $.Event('tooltipsolo')); |
| 1078 |
} |
| 1079 |
} |
| 1080 |
else { |
| 1081 |
// Clear show timer if we're hiding |
| 1082 |
clearTimeout(this.timers.show); |
| 1083 |
|
| 1084 |
// Remove cached origin on hide |
| 1085 |
delete cache.origin; |
| 1086 |
|
| 1087 |
// Remove mouse tracking event if not needed (all tracking qTips are hidden) |
| 1088 |
if(trackingBound && !$(SELECTOR+'[tracking="true"]:visible', opts.solo).not(tooltip).length) { |
| 1089 |
$(document).unbind('mousemove.'+NAMESPACE); |
| 1090 |
trackingBound = FALSE; |
| 1091 |
} |
| 1092 |
|
| 1093 |
// Blur the tooltip |
| 1094 |
this.blur(event); |
| 1095 |
} |
| 1096 |
|
| 1097 |
// Define post-animation, state specific properties |
| 1098 |
after = $.proxy(function() { |
| 1099 |
if(state) { |
| 1100 |
// Prevent antialias from disappearing in IE by removing filter |
| 1101 |
if(BROWSER.ie) { tooltip[0].style.removeAttribute('filter'); } |
| 1102 |
|
| 1103 |
// Remove overflow setting to prevent tip bugs |
| 1104 |
tooltip.css('overflow', ''); |
| 1105 |
|
| 1106 |
// Autofocus elements if enabled |
| 1107 |
if('string' === typeof opts.autofocus) { |
| 1108 |
$(this.options.show.autofocus, tooltip).focus(); |
| 1109 |
} |
| 1110 |
|
| 1111 |
// If set, hide tooltip when inactive for delay period |
| 1112 |
this.options.show.target.trigger('qtip-'+this.id+'-inactive'); |
| 1113 |
} |
| 1114 |
else { |
| 1115 |
// Reset CSS states |
| 1116 |
tooltip.css({ |
| 1117 |
display: '', |
| 1118 |
visibility: '', |
| 1119 |
opacity: '', |
| 1120 |
left: '', |
| 1121 |
top: '' |
| 1122 |
}); |
| 1123 |
} |
| 1124 |
|
| 1125 |
// tooltipvisible/tooltiphidden events |
| 1126 |
this._trigger(state ? 'visible' : 'hidden'); |
| 1127 |
}, this); |
| 1128 |
|
| 1129 |
// If no effect type is supplied, use a simple toggle |
| 1130 |
if(opts.effect === FALSE || animate === FALSE) { |
| 1131 |
tooltip[ type ](); |
| 1132 |
after(); |
| 1133 |
} |
| 1134 |
|
| 1135 |
// Use custom function if provided |
| 1136 |
else if($.isFunction(opts.effect)) { |
| 1137 |
tooltip.stop(1, 1); |
| 1138 |
opts.effect.call(tooltip, this); |
| 1139 |
tooltip.queue('fx', function(n) { |
| 1140 |
after(); n(); |
| 1141 |
}); |
| 1142 |
} |
| 1143 |
|
| 1144 |
// Use basic fade function by default |
| 1145 |
else { tooltip.fadeTo(90, state ? 1 : 0, after); } |
| 1146 |
|
| 1147 |
// If inactive hide method is set, active it |
| 1148 |
if(state) { opts.target.trigger('qtip-'+this.id+'-inactive'); } |
| 1149 |
|
| 1150 |
return this; |
| 1151 |
}; |
| 1152 |
|
| 1153 |
PROTOTYPE.show = function(event) { return this.toggle(TRUE, event); }; |
| 1154 |
|
| 1155 |
PROTOTYPE.hide = function(event) { return this.toggle(FALSE, event); }; |
| 1156 |
;PROTOTYPE.focus = function(event) { |
| 1157 |
if(!this.rendered || this.destroyed) { return this; } |
| 1158 |
|
| 1159 |
var qtips = $(SELECTOR), |
| 1160 |
tooltip = this.tooltip, |
| 1161 |
curIndex = parseInt(tooltip[0].style.zIndex, 10), |
| 1162 |
newIndex = QTIP.zindex + qtips.length; |
| 1163 |
|
| 1164 |
// Only update the z-index if it has changed and tooltip is not already focused |
| 1165 |
if(!tooltip.hasClass(CLASS_FOCUS)) { |
| 1166 |
// tooltipfocus event |
| 1167 |
if(this._trigger('focus', [newIndex], event)) { |
| 1168 |
// Only update z-index's if they've changed |
| 1169 |
if(curIndex !== newIndex) { |
| 1170 |
// Reduce our z-index's and keep them properly ordered |
| 1171 |
qtips.each(function() { |
| 1172 |
if(this.style.zIndex > curIndex) { |
| 1173 |
this.style.zIndex = this.style.zIndex - 1; |
| 1174 |
} |
| 1175 |
}); |
| 1176 |
|
| 1177 |
// Fire blur event for focused tooltip |
| 1178 |
qtips.filter('.' + CLASS_FOCUS).qtip('blur', event); |
| 1179 |
} |
| 1180 |
|
| 1181 |
// Set the new z-index |
| 1182 |
tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex; |
| 1183 |
} |
| 1184 |
} |
| 1185 |
|
| 1186 |
return this; |
| 1187 |
}; |
| 1188 |
|
| 1189 |
PROTOTYPE.blur = function(event) { |
| 1190 |
if(!this.rendered || this.destroyed) { return this; } |
| 1191 |
|
| 1192 |
// Set focused status to FALSE |
| 1193 |
this.tooltip.removeClass(CLASS_FOCUS); |
| 1194 |
|
| 1195 |
// tooltipblur event |
| 1196 |
this._trigger('blur', [ this.tooltip.css('zIndex') ], event); |
| 1197 |
|
| 1198 |
return this; |
| 1199 |
}; |
| 1200 |
;PROTOTYPE.disable = function(state) { |
| 1201 |
if(this.destroyed) { return this; } |
| 1202 |
|
| 1203 |
// If 'toggle' is passed, toggle the current state |
| 1204 |
if(state === 'toggle') { |
| 1205 |
state = !(this.rendered ? this.tooltip.hasClass(CLASS_DISABLED) : this.disabled); |
| 1206 |
} |
| 1207 |
|
| 1208 |
// Disable if no state passed |
| 1209 |
else if('boolean' !== typeof state) { |
| 1210 |
state = TRUE; |
| 1211 |
} |
| 1212 |
|
| 1213 |
if(this.rendered) { |
| 1214 |
this.tooltip.toggleClass(CLASS_DISABLED, state) |
| 1215 |
.attr('aria-disabled', state); |
| 1216 |
} |
| 1217 |
|
| 1218 |
this.disabled = !!state; |
| 1219 |
|
| 1220 |
return this; |
| 1221 |
}; |
| 1222 |
|
| 1223 |
PROTOTYPE.enable = function() { return this.disable(FALSE); }; |
| 1224 |
;PROTOTYPE._createButton = function() |
| 1225 |
{ |
| 1226 |
var self = this, |
| 1227 |
elements = this.elements, |
| 1228 |
tooltip = elements.tooltip, |
| 1229 |
button = this.options.content.button, |
| 1230 |
isString = typeof button === 'string', |
| 1231 |
close = isString ? button : 'Close tooltip'; |
| 1232 |
|
| 1233 |
if(elements.button) { elements.button.remove(); } |
| 1234 |
|
| 1235 |
// Use custom button if one was supplied by user, else use default |
| 1236 |
if(button.jquery) { |
| 1237 |
elements.button = button; |
| 1238 |
} |
| 1239 |
else { |
| 1240 |
elements.button = $('<a />', { |
| 1241 |
'class': 'qtip-close ' + (this.options.style.widget ? '' : NAMESPACE+'-icon'), |
| 1242 |
'title': close, |
| 1243 |
'aria-label': close |
| 1244 |
}) |
| 1245 |
.prepend( |
| 1246 |
$('<span />', { |
| 1247 |
'class': 'ui-icon ui-icon-close', |
| 1248 |
'html': '×' |
| 1249 |
}) |
| 1250 |
); |
| 1251 |
} |
| 1252 |
|
| 1253 |
// Create button and setup attributes |
| 1254 |
elements.button.appendTo(elements.titlebar || tooltip) |
| 1255 |
.attr('role', 'button') |
| 1256 |
.click(function(event) { |
| 1257 |
if(!tooltip.hasClass(CLASS_DISABLED)) { self.hide(event); } |
| 1258 |
return FALSE; |
| 1259 |
}); |
| 1260 |
}; |
| 1261 |
|
| 1262 |
PROTOTYPE._updateButton = function(button) |
| 1263 |
{ |
| 1264 |
// Make sure tooltip is rendered and if not, return |
| 1265 |
if(!this.rendered) { return FALSE; } |
| 1266 |
|
| 1267 |
var elem = this.elements.button; |
| 1268 |
if(button) { this._createButton(); } |
| 1269 |
else { elem.remove(); } |
| 1270 |
}; |
| 1271 |
;// Widget class creator |
| 1272 |
function createWidgetClass(cls) { |
| 1273 |
return WIDGET.concat('').join(cls ? '-'+cls+' ' : ' '); |
| 1274 |
} |
| 1275 |
|
| 1276 |
// Widget class setter method |
| 1277 |
PROTOTYPE._setWidget = function() |
| 1278 |
{ |
| 1279 |
var on = this.options.style.widget, |
| 1280 |
elements = this.elements, |
| 1281 |
tooltip = elements.tooltip, |
| 1282 |
disabled = tooltip.hasClass(CLASS_DISABLED); |
| 1283 |
|
| 1284 |
tooltip.removeClass(CLASS_DISABLED); |
| 1285 |
CLASS_DISABLED = on ? 'ui-state-disabled' : 'qtip-disabled'; |
| 1286 |
tooltip.toggleClass(CLASS_DISABLED, disabled); |
| 1287 |
|
| 1288 |
tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(CLASS_DEFAULT, this.options.style.def && !on); |
| 1289 |
|
| 1290 |
if(elements.content) { |
| 1291 |
elements.content.toggleClass( createWidgetClass('content'), on); |
| 1292 |
} |
| 1293 |
if(elements.titlebar) { |
| 1294 |
elements.titlebar.toggleClass( createWidgetClass('header'), on); |
| 1295 |
} |
| 1296 |
if(elements.button) { |
| 1297 |
elements.button.toggleClass(NAMESPACE+'-icon', !on); |
| 1298 |
} |
| 1299 |
}; |
| 1300 |
;function delay(callback, duration) { |
| 1301 |
// If tooltip has displayed, start hide timer |
| 1302 |
if(duration > 0) { |
| 1303 |
return setTimeout( |
| 1304 |
$.proxy(callback, this), duration |
| 1305 |
); |
| 1306 |
} |
| 1307 |
else{ callback.call(this); } |
| 1308 |
} |
| 1309 |
|
| 1310 |
function showMethod(event) { |
| 1311 |
if(this.tooltip.hasClass(CLASS_DISABLED)) { return; } |
| 1312 |
|
| 1313 |
// Clear hide timers |
| 1314 |
clearTimeout(this.timers.show); |
| 1315 |
clearTimeout(this.timers.hide); |
| 1316 |
|
| 1317 |
// Start show timer |
| 1318 |
this.timers.show = delay.call(this, |
| 1319 |
function() { this.toggle(TRUE, event); }, |
| 1320 |
this.options.show.delay |
| 1321 |
); |
| 1322 |
} |
| 1323 |
|
| 1324 |
function hideMethod(event) { |
| 1325 |
if(this.tooltip.hasClass(CLASS_DISABLED) || this.destroyed) { return; } |
| 1326 |
|
| 1327 |
// Check if new target was actually the tooltip element |
| 1328 |
var relatedTarget = $(event.relatedTarget), |
| 1329 |
ontoTooltip = relatedTarget.closest(SELECTOR)[0] === this.tooltip[0], |
| 1330 |
ontoTarget = relatedTarget[0] === this.options.show.target[0]; |
| 1331 |
|
| 1332 |
// Clear timers and stop animation queue |
| 1333 |
clearTimeout(this.timers.show); |
| 1334 |
clearTimeout(this.timers.hide); |
| 1335 |
|
| 1336 |
// Prevent hiding if tooltip is fixed and event target is the tooltip. |
| 1337 |
// Or if mouse positioning is enabled and cursor momentarily overlaps |
| 1338 |
if(this !== relatedTarget[0] && |
| 1339 |
(this.options.position.target === 'mouse' && ontoTooltip) || |
| 1340 |
this.options.hide.fixed && ( |
| 1341 |
(/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget)) |
| 1342 |
) |
| 1343 |
{ |
| 1344 |
/* eslint-disable no-empty */ |
| 1345 |
try { |
| 1346 |
event.preventDefault(); |
| 1347 |
event.stopImmediatePropagation(); |
| 1348 |
} catch(e) {} |
| 1349 |
/* eslint-enable no-empty */ |
| 1350 |
|
| 1351 |
return; |
| 1352 |
} |
| 1353 |
|
| 1354 |
// If tooltip has displayed, start hide timer |
| 1355 |
this.timers.hide = delay.call(this, |
| 1356 |
function() { this.toggle(FALSE, event); }, |
| 1357 |
this.options.hide.delay, |
| 1358 |
this |
| 1359 |
); |
| 1360 |
} |
| 1361 |
|
| 1362 |
function inactiveMethod(event) { |
| 1363 |
if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return; } |
| 1364 |
|
| 1365 |
// Clear timer |
| 1366 |
clearTimeout(this.timers.inactive); |
| 1367 |
|
| 1368 |
this.timers.inactive = delay.call(this, |
| 1369 |
function(){ this.hide(event); }, |
| 1370 |
this.options.hide.inactive |
| 1371 |
); |
| 1372 |
} |
| 1373 |
|
| 1374 |
function repositionMethod(event) { |
| 1375 |
if(this.rendered && this.tooltip[0].offsetWidth > 0) { this.reposition(event); } |
| 1376 |
} |
| 1377 |
|
| 1378 |
// Store mouse coordinates |
| 1379 |
PROTOTYPE._storeMouse = function(event) { |
| 1380 |
(this.mouse = $.event.fix(event)).type = 'mousemove'; |
| 1381 |
return this; |
| 1382 |
}; |
| 1383 |
|
| 1384 |
// Bind events |
| 1385 |
PROTOTYPE._bind = function(targets, events, method, suffix, context) { |
| 1386 |
if(!targets || !method || !events.length) { return; } |
| 1387 |
var ns = '.' + this._id + (suffix ? '-'+suffix : ''); |
| 1388 |
$(targets).bind( |
| 1389 |
(events.split ? events : events.join(ns + ' ')) + ns, |
| 1390 |
$.proxy(method, context || this) |
| 1391 |
); |
| 1392 |
return this; |
| 1393 |
}; |
| 1394 |
PROTOTYPE._unbind = function(targets, suffix) { |
| 1395 |
targets && $(targets).unbind('.' + this._id + (suffix ? '-'+suffix : '')); |
| 1396 |
return this; |
| 1397 |
}; |
| 1398 |
|
| 1399 |
// Global delegation helper |
| 1400 |
function delegate(selector, events, method) { |
| 1401 |
$(document.body).delegate(selector, |
| 1402 |
(events.split ? events : events.join('.'+NAMESPACE + ' ')) + '.'+NAMESPACE, |
| 1403 |
function() { |
| 1404 |
var api = QTIP.api[ $.attr(this, ATTR_ID) ]; |
| 1405 |
api && !api.disabled && method.apply(api, arguments); |
| 1406 |
} |
| 1407 |
); |
| 1408 |
} |
| 1409 |
// Event trigger |
| 1410 |
PROTOTYPE._trigger = function(type, args, event) { |
| 1411 |
var callback = new $.Event('tooltip'+type); |
| 1412 |
callback.originalEvent = event && $.extend({}, event) || this.cache.event || NULL; |
| 1413 |
|
| 1414 |
this.triggering = type; |
| 1415 |
this.tooltip.trigger(callback, [this].concat(args || [])); |
| 1416 |
this.triggering = FALSE; |
| 1417 |
|
| 1418 |
return !callback.isDefaultPrevented(); |
| 1419 |
}; |
| 1420 |
|
| 1421 |
PROTOTYPE._bindEvents = function(showEvents, hideEvents, showTargets, hideTargets, showCallback, hideCallback) { |
| 1422 |
// Get tasrgets that lye within both |
| 1423 |
var similarTargets = showTargets.filter( hideTargets ).add( hideTargets.filter(showTargets) ), |
| 1424 |
toggleEvents = []; |
| 1425 |
|
| 1426 |
// If hide and show targets are the same... |
| 1427 |
if(similarTargets.length) { |
| 1428 |
|
| 1429 |
// Filter identical show/hide events |
| 1430 |
$.each(hideEvents, function(i, type) { |
| 1431 |
var showIndex = $.inArray(type, showEvents); |
| 1432 |
|
| 1433 |
// Both events are identical, remove from both hide and show events |
| 1434 |
// and append to toggleEvents |
| 1435 |
showIndex > -1 && toggleEvents.push( showEvents.splice( showIndex, 1 )[0] ); |
| 1436 |
}); |
| 1437 |
|
| 1438 |
// Toggle events are special case of identical show/hide events, which happen in sequence |
| 1439 |
if(toggleEvents.length) { |
| 1440 |
// Bind toggle events to the similar targets |
| 1441 |
this._bind(similarTargets, toggleEvents, function(event) { |
| 1442 |
var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false; |
| 1443 |
(state ? hideCallback : showCallback).call(this, event); |
| 1444 |
}); |
| 1445 |
|
| 1446 |
// Remove the similar targets from the regular show/hide bindings |
| 1447 |
showTargets = showTargets.not(similarTargets); |
| 1448 |
hideTargets = hideTargets.not(similarTargets); |
| 1449 |
} |
| 1450 |
} |
| 1451 |
|
| 1452 |
// Apply show/hide/toggle events |
| 1453 |
this._bind(showTargets, showEvents, showCallback); |
| 1454 |
this._bind(hideTargets, hideEvents, hideCallback); |
| 1455 |
}; |
| 1456 |
|
| 1457 |
PROTOTYPE._assignInitialEvents = function(event) { |
| 1458 |
var options = this.options, |
| 1459 |
showTarget = options.show.target, |
| 1460 |
hideTarget = options.hide.target, |
| 1461 |
showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [], |
| 1462 |
hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : []; |
| 1463 |
|
| 1464 |
// Catch remove/removeqtip events on target element to destroy redundant tooltips |
| 1465 |
this._bind(this.elements.target, ['remove', 'removeqtip'], function() { |
| 1466 |
this.destroy(true); |
| 1467 |
}, 'destroy'); |
| 1468 |
|
| 1469 |
/* |
| 1470 |
* Make sure hoverIntent functions properly by using mouseleave as a hide event if |
| 1471 |
* mouseenter/mouseout is used for show.event, even if it isn't in the users options. |
| 1472 |
*/ |
| 1473 |
if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) { |
| 1474 |
hideEvents.push('mouseleave'); |
| 1475 |
} |
| 1476 |
|
| 1477 |
/* |
| 1478 |
* Also make sure initial mouse targetting works correctly by caching mousemove coords |
| 1479 |
* on show targets before the tooltip has rendered. Also set onTarget when triggered to |
| 1480 |
* keep mouse tracking working. |
| 1481 |
*/ |
| 1482 |
this._bind(showTarget, 'mousemove', function(moveEvent) { |
| 1483 |
this._storeMouse(moveEvent); |
| 1484 |
this.cache.onTarget = TRUE; |
| 1485 |
}); |
| 1486 |
|
| 1487 |
// Define hoverIntent function |
| 1488 |
function hoverIntent(hoverEvent) { |
| 1489 |
// Only continue if tooltip isn't disabled |
| 1490 |
if(this.disabled || this.destroyed) { return FALSE; } |
| 1491 |
|
| 1492 |
// Cache the event data |
| 1493 |
this.cache.event = hoverEvent && $.event.fix(hoverEvent); |
| 1494 |
this.cache.target = hoverEvent && $(hoverEvent.target); |
| 1495 |
|
| 1496 |
// Start the event sequence |
| 1497 |
clearTimeout(this.timers.show); |
| 1498 |
this.timers.show = delay.call(this, |
| 1499 |
function() { this.render(typeof hoverEvent === 'object' || options.show.ready); }, |
| 1500 |
options.prerender ? 0 : options.show.delay |
| 1501 |
); |
| 1502 |
} |
| 1503 |
|
| 1504 |
// Filter and bind events |
| 1505 |
this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, hoverIntent, function() { |
| 1506 |
if(!this.timers) { return FALSE; } |
| 1507 |
clearTimeout(this.timers.show); |
| 1508 |
}); |
| 1509 |
|
| 1510 |
// Prerendering is enabled, create tooltip now |
| 1511 |
if(options.show.ready || options.prerender) { hoverIntent.call(this, event); } |
| 1512 |
}; |
| 1513 |
|
| 1514 |
// Event assignment method |
| 1515 |
PROTOTYPE._assignEvents = function() { |
| 1516 |
var self = this, |
| 1517 |
options = this.options, |
| 1518 |
posOptions = options.position, |
| 1519 |
|
| 1520 |
tooltip = this.tooltip, |
| 1521 |
showTarget = options.show.target, |
| 1522 |
hideTarget = options.hide.target, |
| 1523 |
containerTarget = posOptions.container, |
| 1524 |
viewportTarget = posOptions.viewport, |
| 1525 |
documentTarget = $(document), |
| 1526 |
windowTarget = $(window), |
| 1527 |
|
| 1528 |
showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [], |
| 1529 |
hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : []; |
| 1530 |
|
| 1531 |
|
| 1532 |
// Assign passed event callbacks |
| 1533 |
$.each(options.events, function(name, callback) { |
| 1534 |
self._bind(tooltip, name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name], callback, null, tooltip); |
| 1535 |
}); |
| 1536 |
|
| 1537 |
// Hide tooltips when leaving current window/frame (but not select/option elements) |
| 1538 |
if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') { |
| 1539 |
this._bind(documentTarget, ['mouseout', 'blur'], function(event) { |
| 1540 |
if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) { |
| 1541 |
this.hide(event); |
| 1542 |
} |
| 1543 |
}); |
| 1544 |
} |
| 1545 |
|
| 1546 |
// Enable hide.fixed by adding appropriate class |
| 1547 |
if(options.hide.fixed) { |
| 1548 |
hideTarget = hideTarget.add( tooltip.addClass(CLASS_FIXED) ); |
| 1549 |
} |
| 1550 |
|
| 1551 |
/* |
| 1552 |
* Make sure hoverIntent functions properly by using mouseleave to clear show timer if |
| 1553 |
* mouseenter/mouseout is used for show.event, even if it isn't in the users options. |
| 1554 |
*/ |
| 1555 |
else if(/mouse(over|enter)/i.test(options.show.event)) { |
| 1556 |
this._bind(hideTarget, 'mouseleave', function() { |
| 1557 |
clearTimeout(this.timers.show); |
| 1558 |
}); |
| 1559 |
} |
| 1560 |
|
| 1561 |
// Hide tooltip on document mousedown if unfocus events are enabled |
| 1562 |
if(('' + options.hide.event).indexOf('unfocus') > -1) { |
| 1563 |
this._bind(containerTarget.closest('html'), ['mousedown', 'touchstart'], function(event) { |
| 1564 |
var elem = $(event.target), |
| 1565 |
enabled = this.rendered && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0, |
| 1566 |
isAncestor = elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0; |
| 1567 |
|
| 1568 |
if(elem[0] !== this.target[0] && elem[0] !== this.tooltip[0] && !isAncestor && |
| 1569 |
!this.target.has(elem[0]).length && enabled |
| 1570 |
) { |
| 1571 |
this.hide(event); |
| 1572 |
} |
| 1573 |
}); |
| 1574 |
} |
| 1575 |
|
| 1576 |
// Check if the tooltip hides when inactive |
| 1577 |
if('number' === typeof options.hide.inactive) { |
| 1578 |
// Bind inactive method to show target(s) as a custom event |
| 1579 |
this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod, 'inactive'); |
| 1580 |
|
| 1581 |
// Define events which reset the 'inactive' event handler |
| 1582 |
this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod); |
| 1583 |
} |
| 1584 |
|
| 1585 |
// Filter and bind events |
| 1586 |
this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod); |
| 1587 |
|
| 1588 |
// Mouse movement bindings |
| 1589 |
this._bind(showTarget.add(tooltip), 'mousemove', function(event) { |
| 1590 |
// Check if the tooltip hides when mouse is moved a certain distance |
| 1591 |
if('number' === typeof options.hide.distance) { |
| 1592 |
var origin = this.cache.origin || {}, |
| 1593 |
limit = this.options.hide.distance, |
| 1594 |
abs = Math.abs; |
| 1595 |
|
| 1596 |
// Check if the movement has gone beyond the limit, and hide it if so |
| 1597 |
if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) { |
| 1598 |
this.hide(event); |
| 1599 |
} |
| 1600 |
} |
| 1601 |
|
| 1602 |
// Cache mousemove coords on show targets |
| 1603 |
this._storeMouse(event); |
| 1604 |
}); |
| 1605 |
|
| 1606 |
// Mouse positioning events |
| 1607 |
if(posOptions.target === 'mouse') { |
| 1608 |
// If mouse adjustment is on... |
| 1609 |
if(posOptions.adjust.mouse) { |
| 1610 |
// Apply a mouseleave event so we don't get problems with overlapping |
| 1611 |
if(options.hide.event) { |
| 1612 |
// Track if we're on the target or not |
| 1613 |
this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) { |
| 1614 |
if(!this.cache) {return FALSE; } |
| 1615 |
this.cache.onTarget = event.type === 'mouseenter'; |
| 1616 |
}); |
| 1617 |
} |
| 1618 |
|
| 1619 |
// Update tooltip position on mousemove |
| 1620 |
this._bind(documentTarget, 'mousemove', function(event) { |
| 1621 |
// Update the tooltip position only if the tooltip is visible and adjustment is enabled |
| 1622 |
if(this.rendered && this.cache.onTarget && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0) { |
| 1623 |
this.reposition(event); |
| 1624 |
} |
| 1625 |
}); |
| 1626 |
} |
| 1627 |
} |
| 1628 |
|
| 1629 |
// Adjust positions of the tooltip on window resize if enabled |
| 1630 |
if(posOptions.adjust.resize || viewportTarget.length) { |
| 1631 |
this._bind( $.event.special.resize ? viewportTarget : windowTarget, 'resize', repositionMethod ); |
| 1632 |
} |
| 1633 |
|
| 1634 |
// Adjust tooltip position on scroll of the window or viewport element if present |
| 1635 |
if(posOptions.adjust.scroll) { |
| 1636 |
this._bind( windowTarget.add(posOptions.container), 'scroll', repositionMethod ); |
| 1637 |
} |
| 1638 |
}; |
| 1639 |
|
| 1640 |
// Un-assignment method |
| 1641 |
PROTOTYPE._unassignEvents = function() { |
| 1642 |
var options = this.options, |
| 1643 |
showTargets = options.show.target, |
| 1644 |
hideTargets = options.hide.target, |
| 1645 |
targets = $.grep([ |
| 1646 |
this.elements.target[0], |
| 1647 |
this.rendered && this.tooltip[0], |
| 1648 |
options.position.container[0], |
| 1649 |
options.position.viewport[0], |
| 1650 |
options.position.container.closest('html')[0], // unfocus |
| 1651 |
window, |
| 1652 |
document |
| 1653 |
], function(i) { |
| 1654 |
return typeof i === 'object'; |
| 1655 |
}); |
| 1656 |
|
| 1657 |
// Add show and hide targets if they're valid |
| 1658 |
if(showTargets && showTargets.toArray) { |
| 1659 |
targets = targets.concat(showTargets.toArray()); |
| 1660 |
} |
| 1661 |
if(hideTargets && hideTargets.toArray) { |
| 1662 |
targets = targets.concat(hideTargets.toArray()); |
| 1663 |
} |
| 1664 |
|
| 1665 |
// Unbind the events |
| 1666 |
this._unbind(targets) |
| 1667 |
._unbind(targets, 'destroy') |
| 1668 |
._unbind(targets, 'inactive'); |
| 1669 |
}; |
| 1670 |
|
| 1671 |
// Apply common event handlers using delegate (avoids excessive .bind calls!) |
| 1672 |
$(function() { |
| 1673 |
delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) { |
| 1674 |
var state = event.type === 'mouseenter', |
| 1675 |
tooltip = $(event.currentTarget), |
| 1676 |
target = $(event.relatedTarget || event.target), |
| 1677 |
options = this.options; |
| 1678 |
|
| 1679 |
// On mouseenter... |
| 1680 |
if(state) { |
| 1681 |
// Focus the tooltip on mouseenter (z-index stacking) |
| 1682 |
this.focus(event); |
| 1683 |
|
| 1684 |
// Clear hide timer on tooltip hover to prevent it from closing |
| 1685 |
tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide); |
| 1686 |
} |
| 1687 |
|
| 1688 |
// On mouseleave... |
| 1689 |
else { |
| 1690 |
// When mouse tracking is enabled, hide when we leave the tooltip and not onto the show target (if a hide event is set) |
| 1691 |
if(options.position.target === 'mouse' && options.position.adjust.mouse && |
| 1692 |
options.hide.event && options.show.target && !target.closest(options.show.target[0]).length) { |
| 1693 |
this.hide(event); |
| 1694 |
} |
| 1695 |
} |
| 1696 |
|
| 1697 |
// Add hover class |
| 1698 |
tooltip.toggleClass(CLASS_HOVER, state); |
| 1699 |
}); |
| 1700 |
|
| 1701 |
// Define events which reset the 'inactive' event handler |
| 1702 |
delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod); |
| 1703 |
}); |
| 1704 |
;// Initialization method |
| 1705 |
function init(elem, id, opts) { |
| 1706 |
var obj, posOptions, attr, config, title, |
| 1707 |
|
| 1708 |
// Setup element references |
| 1709 |
docBody = $(document.body), |
| 1710 |
|
| 1711 |
// Use document body instead of document element if needed |
| 1712 |
newTarget = elem[0] === document ? docBody : elem, |
| 1713 |
|
| 1714 |
// Grab metadata from element if plugin is present |
| 1715 |
metadata = elem.metadata ? elem.metadata(opts.metadata) : NULL, |
| 1716 |
|
| 1717 |
// If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise |
| 1718 |
metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL, |
| 1719 |
|
| 1720 |
// Grab data from metadata.name (or data-qtipopts as fallback) using .data() method, |
| 1721 |
html5 = elem.data(opts.metadata.name || 'qtipopts'); |
| 1722 |
|
| 1723 |
// If we don't get an object returned attempt to parse it manualyl without parseJSON |
| 1724 |
/* eslint-disable no-empty */ |
| 1725 |
try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; } |
| 1726 |
catch(e) {} |
| 1727 |
/* eslint-enable no-empty */ |
| 1728 |
|
| 1729 |
// Merge in and sanitize metadata |
| 1730 |
config = $.extend(TRUE, {}, QTIP.defaults, opts, |
| 1731 |
typeof html5 === 'object' ? sanitizeOptions(html5) : NULL, |
| 1732 |
sanitizeOptions(metadata5 || metadata)); |
| 1733 |
|
| 1734 |
// Re-grab our positioning options now we've merged our metadata and set id to passed value |
| 1735 |
posOptions = config.position; |
| 1736 |
config.id = id; |
| 1737 |
|
| 1738 |
// Setup missing content if none is detected |
| 1739 |
if('boolean' === typeof config.content.text) { |
| 1740 |
attr = elem.attr(config.content.attr); |
| 1741 |
|
| 1742 |
// Grab from supplied attribute if available |
| 1743 |
if(config.content.attr !== FALSE && attr) { config.content.text = attr; } |
| 1744 |
|
| 1745 |
// No valid content was found, abort render |
| 1746 |
else { return FALSE; } |
| 1747 |
} |
| 1748 |
|
| 1749 |
// Setup target options |
| 1750 |
if(!posOptions.container.length) { posOptions.container = docBody; } |
| 1751 |
if(posOptions.target === FALSE) { posOptions.target = newTarget; } |
| 1752 |
if(config.show.target === FALSE) { config.show.target = newTarget; } |
| 1753 |
if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); } |
| 1754 |
if(config.hide.target === FALSE) { config.hide.target = newTarget; } |
| 1755 |
if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; } |
| 1756 |
|
| 1757 |
// Ensure we only use a single container |
| 1758 |
posOptions.container = posOptions.container.eq(0); |
| 1759 |
|
| 1760 |
// Convert position corner values into x and y strings |
| 1761 |
posOptions.at = new CORNER(posOptions.at, TRUE); |
| 1762 |
posOptions.my = new CORNER(posOptions.my); |
| 1763 |
|
| 1764 |
// Destroy previous tooltip if overwrite is enabled, or skip element if not |
| 1765 |
if(elem.data(NAMESPACE)) { |
| 1766 |
if(config.overwrite) { |
| 1767 |
elem.qtip('destroy', true); |
| 1768 |
} |
| 1769 |
else if(config.overwrite === FALSE) { |
| 1770 |
return FALSE; |
| 1771 |
} |
| 1772 |
} |
| 1773 |
|
| 1774 |
// Add has-qtip attribute |
| 1775 |
elem.attr(ATTR_HAS, id); |
| 1776 |
|
| 1777 |
// Remove title attribute and store it if present |
| 1778 |
if(config.suppress && (title = elem.attr('title'))) { |
| 1779 |
// Final attr call fixes event delegatiom and IE default tooltip showing problem |
| 1780 |
elem.removeAttr('title').attr(oldtitle, title).attr('title', ''); |
| 1781 |
} |
| 1782 |
|
| 1783 |
// Initialize the tooltip and add API reference |
| 1784 |
obj = new QTip(elem, config, id, !!attr); |
| 1785 |
elem.data(NAMESPACE, obj); |
| 1786 |
|
| 1787 |
return obj; |
| 1788 |
} |
| 1789 |
|
| 1790 |
// jQuery $.fn extension method |
| 1791 |
QTIP = $.fn.qtip = function(options, notation, newValue) |
| 1792 |
{ |
| 1793 |
var command = ('' + options).toLowerCase(), // Parse command |
| 1794 |
returned = NULL, |
| 1795 |
args = $.makeArray(arguments).slice(1), |
| 1796 |
event = args[args.length - 1], |
| 1797 |
opts = this[0] ? $.data(this[0], NAMESPACE) : NULL; |
| 1798 |
|
| 1799 |
// Check for API request |
| 1800 |
if(!arguments.length && opts || command === 'api') { |
| 1801 |
return opts; |
| 1802 |
} |
| 1803 |
|
| 1804 |
// Execute API command if present |
| 1805 |
else if('string' === typeof options) { |
| 1806 |
this.each(function() { |
| 1807 |
var api = $.data(this, NAMESPACE); |
| 1808 |
if(!api) { return TRUE; } |
| 1809 |
|
| 1810 |
// Cache the event if possible |
| 1811 |
if(event && event.timeStamp) { api.cache.event = event; } |
| 1812 |
|
| 1813 |
// Check for specific API commands |
| 1814 |
if(notation && (command === 'option' || command === 'options')) { |
| 1815 |
if(newValue !== undefined || $.isPlainObject(notation)) { |
| 1816 |
api.set(notation, newValue); |
| 1817 |
} |
| 1818 |
else { |
| 1819 |
returned = api.get(notation); |
| 1820 |
return FALSE; |
| 1821 |
} |
| 1822 |
} |
| 1823 |
|
| 1824 |
// Execute API command |
| 1825 |
else if(api[command]) { |
| 1826 |
api[command].apply(api, args); |
| 1827 |
} |
| 1828 |
}); |
| 1829 |
|
| 1830 |
return returned !== NULL ? returned : this; |
| 1831 |
} |
| 1832 |
|
| 1833 |
// No API commands. validate provided options and setup qTips |
| 1834 |
else if('object' === typeof options || !arguments.length) { |
| 1835 |
// Sanitize options first |
| 1836 |
opts = sanitizeOptions($.extend(TRUE, {}, options)); |
| 1837 |
|
| 1838 |
return this.each(function(i) { |
| 1839 |
var api, id; |
| 1840 |
|
| 1841 |
// Find next available ID, or use custom ID if provided |
| 1842 |
id = $.isArray(opts.id) ? opts.id[i] : opts.id; |
| 1843 |
id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id; |
| 1844 |
|
| 1845 |
// Initialize the qTip and re-grab newly sanitized options |
| 1846 |
api = init($(this), id, opts); |
| 1847 |
if(api === FALSE) { return TRUE; } |
| 1848 |
else { QTIP.api[id] = api; } |
| 1849 |
|
| 1850 |
// Initialize plugins |
| 1851 |
$.each(PLUGINS, function() { |
| 1852 |
if(this.initialize === 'initialize') { this(api); } |
| 1853 |
}); |
| 1854 |
|
| 1855 |
// Assign initial pre-render events |
| 1856 |
api._assignInitialEvents(event); |
| 1857 |
}); |
| 1858 |
} |
| 1859 |
}; |
| 1860 |
|
| 1861 |
// Expose class |
| 1862 |
$.qtip = QTip; |
| 1863 |
|
| 1864 |
// Populated in render method |
| 1865 |
QTIP.api = {}; |
| 1866 |
;$.each({ |
| 1867 |
/* Allow other plugins to successfully retrieve the title of an element with a qTip applied */ |
| 1868 |
attr: function(attr, val) { |
| 1869 |
if(this.length) { |
| 1870 |
var self = this[0], |
| 1871 |
title = 'title', |
| 1872 |
api = $.data(self, 'qtip'); |
| 1873 |
|
| 1874 |
if(attr === title && api && api.options && 'object' === typeof api && 'object' === typeof api.options && api.options.suppress) { |
| 1875 |
if(arguments.length < 2) { |
| 1876 |
return $.attr(self, oldtitle); |
| 1877 |
} |
| 1878 |
|
| 1879 |
// If qTip is rendered and title was originally used as content, update it |
| 1880 |
if(api && api.options.content.attr === title && api.cache.attr) { |
| 1881 |
api.set('content.text', val); |
| 1882 |
} |
| 1883 |
|
| 1884 |
// Use the regular attr method to set, then cache the result |
| 1885 |
return this.attr(oldtitle, val); |
| 1886 |
} |
| 1887 |
} |
| 1888 |
|
| 1889 |
return $.fn['attr'+replaceSuffix].apply(this, arguments); |
| 1890 |
}, |
| 1891 |
|
| 1892 |
/* Allow clone to correctly retrieve cached title attributes */ |
| 1893 |
clone: function(keepData) { |
| 1894 |
// Clone our element using the real clone method |
| 1895 |
var elems = $.fn['clone'+replaceSuffix].apply(this, arguments); |
| 1896 |
|
| 1897 |
// Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false |
| 1898 |
if(!keepData) { |
| 1899 |
elems.filter('['+oldtitle+']').attr('title', function() { |
| 1900 |
return $.attr(this, oldtitle); |
| 1901 |
}) |
| 1902 |
.removeAttr(oldtitle); |
| 1903 |
} |
| 1904 |
|
| 1905 |
return elems; |
| 1906 |
} |
| 1907 |
}, function(name, func) { |
| 1908 |
if(!func || $.fn[name+replaceSuffix]) { return TRUE; } |
| 1909 |
|
| 1910 |
var old = $.fn[name+replaceSuffix] = $.fn[name]; |
| 1911 |
$.fn[name] = function() { |
| 1912 |
return func.apply(this, arguments) || old.apply(this, arguments); |
| 1913 |
}; |
| 1914 |
}); |
| 1915 |
|
| 1916 |
/* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar). |
| 1917 |
* This snippet is taken directly from jQuery UI source code found here: |
| 1918 |
* http://code.jquery.com/ui/jquery-ui-git.js |
| 1919 |
*/ |
| 1920 |
if(!$.ui) { |
| 1921 |
$['cleanData'+replaceSuffix] = $.cleanData; |
| 1922 |
$.cleanData = function( elems ) { |
| 1923 |
for(var i = 0, elem; (elem = $( elems[i] )).length; i++) { |
| 1924 |
if(elem.attr(ATTR_HAS)) { |
| 1925 |
/* eslint-disable no-empty */ |
| 1926 |
try { elem.triggerHandler('removeqtip'); } |
| 1927 |
catch( e ) {} |
| 1928 |
/* eslint-enable no-empty */ |
| 1929 |
} |
| 1930 |
} |
| 1931 |
$['cleanData'+replaceSuffix].apply(this, arguments); |
| 1932 |
}; |
| 1933 |
} |
| 1934 |
;// qTip version |
| 1935 |
QTIP.version = '3.0.3'; |
| 1936 |
|
| 1937 |
// Base ID for all qTips |
| 1938 |
QTIP.nextid = 0; |
| 1939 |
|
| 1940 |
// Inactive events array |
| 1941 |
QTIP.inactiveEvents = INACTIVE_EVENTS; |
| 1942 |
|
| 1943 |
// Base z-index for all qTips |
| 1944 |
QTIP.zindex = 15000; |
| 1945 |
|
| 1946 |
// Define configuration defaults |
| 1947 |
QTIP.defaults = { |
| 1948 |
prerender: FALSE, |
| 1949 |
id: FALSE, |
| 1950 |
overwrite: TRUE, |
| 1951 |
suppress: TRUE, |
| 1952 |
content: { |
| 1953 |
text: TRUE, |
| 1954 |
attr: 'title', |
| 1955 |
title: FALSE, |
| 1956 |
button: FALSE |
| 1957 |
}, |
| 1958 |
position: { |
| 1959 |
my: 'top left', |
| 1960 |
at: 'bottom right', |
| 1961 |
target: FALSE, |
| 1962 |
container: FALSE, |
| 1963 |
viewport: FALSE, |
| 1964 |
adjust: { |
| 1965 |
x: 0, y: 0, |
| 1966 |
mouse: TRUE, |
| 1967 |
scroll: TRUE, |
| 1968 |
resize: TRUE, |
| 1969 |
method: 'flipinvert flipinvert' |
| 1970 |
}, |
| 1971 |
effect: function(api, pos) { |
| 1972 |
$(this).animate(pos, { |
| 1973 |
duration: 200, |
| 1974 |
queue: FALSE |
| 1975 |
}); |
| 1976 |
} |
| 1977 |
}, |
| 1978 |
show: { |
| 1979 |
target: FALSE, |
| 1980 |
event: 'mouseenter', |
| 1981 |
effect: TRUE, |
| 1982 |
delay: 90, |
| 1983 |
solo: FALSE, |
| 1984 |
ready: FALSE, |
| 1985 |
autofocus: FALSE |
| 1986 |
}, |
| 1987 |
hide: { |
| 1988 |
target: FALSE, |
| 1989 |
event: 'mouseleave', |
| 1990 |
effect: TRUE, |
| 1991 |
delay: 0, |
| 1992 |
fixed: FALSE, |
| 1993 |
inactive: FALSE, |
| 1994 |
leave: 'window', |
| 1995 |
distance: FALSE |
| 1996 |
}, |
| 1997 |
style: { |
| 1998 |
classes: '', |
| 1999 |
widget: FALSE, |
| 2000 |
width: FALSE, |
| 2001 |
height: FALSE, |
| 2002 |
def: TRUE |
| 2003 |
}, |
| 2004 |
events: { |
| 2005 |
render: NULL, |
| 2006 |
move: NULL, |
| 2007 |
show: NULL, |
| 2008 |
hide: NULL, |
| 2009 |
toggle: NULL, |
| 2010 |
visible: NULL, |
| 2011 |
hidden: NULL, |
| 2012 |
focus: NULL, |
| 2013 |
blur: NULL |
| 2014 |
} |
| 2015 |
}; |
| 2016 |
;})); |
| 2017 |
}( window, document )); |
| 2018 |
|