qtip2-rails 2.2.1.1

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