inline_forms 1.4.1 → 1.4.2

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