jquery-qtip2-wrapper-rails 2.2.0

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