contour 1.2.0.pre2 → 1.2.0.pre3

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