jquery-qtip2-rails 0.1.1 → 0.2.0

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