jquery-qtip2-rails 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -98,6 +98,19 @@ $('.selector').qtip({
98
98
  })
99
99
  ```
100
100
 
101
+ ## Rails test/dummy
102
+
103
+ jquery-qtip2-rails comes with a dummy Rails application that you can run:
104
+
105
+ Development mode:
106
+ - `bundle`
107
+ - `cd test/dummy && rails s`
108
+
109
+ Production mode:
110
+ - `bundle`
111
+ - `cd test/dummy && rake assets:precompile`: generates .js and .css files inside test/dummy/public/assets/ + test/dummy/tmp/
112
+ - `cd test/dummy && rails s -e production`
113
+
101
114
  ## License
102
115
 
103
116
  qTip2 is being developed by [Craig Thompson](http://craigsworks.com/) and is dual-licensed
@@ -19,4 +19,5 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_development_dependency 'rails'
21
21
  gem.add_development_dependency 'jquery-rails'
22
+ gem.add_development_dependency 'uglifier'
22
23
  end
@@ -1,7 +1,7 @@
1
1
  module Jquery
2
2
  module Qtip2
3
3
  module Rails
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
6
6
  end
7
7
  end
@@ -9,7 +9,7 @@ Dummy::Application.configure do
9
9
  config.action_controller.perform_caching = true
10
10
 
11
11
  # Disable Rails's static asset server (Apache or nginx will already do this)
12
- config.serve_static_assets = false
12
+ config.serve_static_assets = true
13
13
 
14
14
  # Compress JavaScripts and CSS
15
15
  config.assets.compress = true
@@ -1,11 +1,13 @@
1
+ var AJAX,
2
+ AJAXNS = '.qtip-ajax',
3
+ RSCRIPT = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
4
+
1
5
  function Ajax(api)
2
6
  {
3
7
  var self = this,
4
8
  tooltip = api.elements.tooltip,
5
9
  opts = api.options.content.ajax,
6
10
  defaults = QTIP.defaults.content.ajax,
7
- namespace = '.qtip-ajax',
8
- rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
9
11
  first = TRUE,
10
12
  stop = FALSE,
11
13
  xhr;
@@ -22,7 +24,7 @@ function Ajax(api)
22
24
  self.load();
23
25
  }
24
26
  else {
25
- tooltip.unbind(namespace);
27
+ tooltip.unbind(AJAXNS);
26
28
  }
27
29
  }
28
30
  };
@@ -31,7 +33,7 @@ function Ajax(api)
31
33
  init: function() {
32
34
  // Make sure ajax options are enabled and bind event
33
35
  if(opts && opts.url) {
34
- tooltip.unbind(namespace)[ opts.once ? 'one' : 'bind' ]('tooltipshow'+namespace, self.load);
36
+ tooltip.unbind(AJAXNS)[ opts.once ? 'one' : 'bind' ]('tooltipshow'+AJAXNS, self.load);
35
37
  }
36
38
 
37
39
  return self;
@@ -92,7 +94,7 @@ function Ajax(api)
92
94
  content = $('<div/>')
93
95
  // inject the contents of the document in, removing the scripts
94
96
  // to avoid any 'Permission Denied' errors in IE
95
- .append(content.replace(rscript, ""))
97
+ .append(content.replace(RSCRIPT, ""))
96
98
 
97
99
  // Locate the specified elements
98
100
  .find(selector);
@@ -135,18 +137,17 @@ function Ajax(api)
135
137
  self.init();
136
138
  }
137
139
 
138
-
139
- PLUGINS.ajax = function(api)
140
+ AJAX = PLUGINS.ajax = function(api)
140
141
  {
141
142
  var self = api.plugins.ajax;
142
143
 
143
144
  return 'object' === typeof self ? self : (api.plugins.ajax = new Ajax(api));
144
145
  };
145
146
 
146
- PLUGINS.ajax.initialize = 'render';
147
+ AJAX.initialize = 'render';
147
148
 
148
149
  // Setup plugin sanitization
149
- PLUGINS.ajax.sanitize = function(options)
150
+ AJAX.sanitize = function(options)
150
151
  {
151
152
  var content = options.content, opts;
152
153
  if(content && 'ajax' in content) {
@@ -35,7 +35,8 @@ function sanitizeOptions(opts)
35
35
  }
36
36
 
37
37
  if('show' in opts && invalid(opts.show)) {
38
- opts.show = opts.show.jquery ? { target: opts.show } : { event: opts.show };
38
+ opts.show = opts.show.jquery ? { target: opts.show } :
39
+ opts.show === TRUE ? { ready: TRUE } : { event: opts.show };
39
40
  }
40
41
 
41
42
  if('hide' in opts && invalid(opts.hide)) {
@@ -286,67 +287,72 @@ function QTip(target, options, id, attr)
286
287
  // Content is a regular string, insert the new content
287
288
  else { elem.html(content); }
288
289
 
289
- // Image detection
290
- function detectImages(next) {
291
- var images, srcs = {};
292
-
293
- function imageLoad(image) {
294
- // Clear src from object and any timers and events associated with the image
295
- if(image) {
296
- delete srcs[image.src];
297
- clearTimeout(self.timers.img[image.src]);
298
- $(image).unbind(namespace);
299
- }
300
-
301
- // If queue is empty after image removal, update tooltip and continue the queue
302
- if($.isEmptyObject(srcs)) {
303
- if(reposition !== FALSE) {
304
- self.reposition(cache.event);
305
- }
306
-
307
- next();
290
+ /*
291
+ * New images loaded detection method slimmed down from David DeSandro's plugin
292
+ * GitHub: https://github.com/desandro/imagesloaded/
293
+ */
294
+ function imagesLoaded(next) {
295
+ var elem = $(this),
296
+ images = elem.find('img').add( elem.filter('img') ),
297
+ loaded = [];
298
+
299
+ function imgLoaded( img ) {
300
+ // don't proceed if BLANKIMG image, or image is already loaded
301
+ if(img.src === BLANKIMG || $.inArray(img, loaded) !== -1) { return; }
302
+
303
+ // store element in loaded images array
304
+ loaded.push(img);
305
+
306
+ // cache image and its state for future calls
307
+ $.data(img, 'imagesLoaded', { src: img.src });
308
+
309
+ // call doneLoading and clean listeners if all images are loaded
310
+ if(images.length === loaded.length) {
311
+ setTimeout(next);
312
+ images.unbind('.imagesLoaded');
308
313
  }
309
314
  }
310
315
 
311
- // Find all content images without dimensions, and if no images were found, continue
312
- if((images = elem.find('img[src]:not([height]):not([width])')).length === 0) { return imageLoad(); }
313
-
314
- // Apply timer to each image to poll for dimensions
315
- images.each(function(i, elem) {
316
- // Skip if the src is already present
317
- if(srcs[elem.src] !== undefined) { return; }
318
-
319
- // Keep track of how many times we poll for image dimensions.
320
- // If it doesn't return in a reasonable amount of time, it's better
321
- // to display the tooltip, rather than hold up the queue.
322
- var iterations = 0, maxIterations = 3;
323
-
324
- (function timer(){
325
- // When the dimensions are found, remove the image from the queue
326
- if(elem.height || elem.width || (iterations > maxIterations)) { return imageLoad(elem); }
316
+ // No images? Proceed with next
317
+ if(!images.length) { return next(); }
327
318
 
328
- // Increase iterations and restart timer
329
- iterations += 1;
330
- self.timers.img[elem.src] = setTimeout(timer, 700);
331
- }());
332
-
333
- // Also apply regular load/error event handlers
334
- $(elem).bind('error'+namespace+' load'+namespace, function(){ imageLoad(this); });
319
+ images.bind('load.imagesLoaded error.imagesLoaded', function() {
320
+ imgLoaded(event.target);
321
+ })
322
+ .each(function(i, el) {
323
+ var src = el.src, cached = $.data(el, 'imagesLoaded');
324
+
325
+ /*
326
+ * Find out if this image has been already checked for status, and
327
+ * if it was and src has not changed, call imgLoaded on it. Also,
328
+ * if complete is true and browser supports natural sizes, try to
329
+ * check for image status manually
330
+ */
331
+ if((cached && cached.src === src) || (el.complete && el.naturalWidth)) {
332
+ imgLoaded(el);
333
+ }
335
334
 
336
- // Store the src and element in our object
337
- srcs[elem.src] = elem;
335
+ /*
336
+ * Cached images don't fire load sometimes, so we reset src, but only when
337
+ * dealing with IE, or image is complete (loaded) and failed manual check
338
+ *
339
+ * Webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
340
+ */
341
+ else if(el.readyState || el.complete) {
342
+ el.src = BLANKIMG; el.src = src;
343
+ }
338
344
  });
339
345
  }
340
346
 
341
347
  /*
342
- * If we're still rendering... insert into 'fx' queue our image dimension
343
- * checker which will halt the showing of the tooltip until image dimensions
344
- * can be detected properly.
345
- */
346
- if(self.rendered < 0) { tooltip.queue('fx', detectImages); }
348
+ * If we're still rendering... insert into 'fx' queue our image dimension
349
+ * checker which will halt the showing of the tooltip until image dimensions
350
+ * can be detected properly.
351
+ */
352
+ if(self.rendered < 0) { tooltip.queue('fx', imagesLoaded); }
347
353
 
348
354
  // We're fully rendered, so reset isDrawing flag and proceed without queue delay
349
- else { isDrawing = 0; detectImages($.noop); }
355
+ else { isDrawing = 0; imagesLoaded.call(tooltip[0], $.noop); }
350
356
 
351
357
  return self;
352
358
  }
@@ -366,7 +372,7 @@ function QTip(target, options, id, attr)
366
372
  show: $.trim('' + options.show.event).split(' '),
367
373
  hide: $.trim('' + options.hide.event).split(' ')
368
374
  },
369
- IE6 = $.browser.msie && parseInt($.browser.version, 10) === 6;
375
+ IE6 = PLUGINS.ie === 6;
370
376
 
371
377
  // Define show event method
372
378
  function showMethod(event)
@@ -391,7 +397,7 @@ function QTip(target, options, id, attr)
391
397
  if(tooltip.hasClass(disabledClass) || isPositioning || isDrawing) { return FALSE; }
392
398
 
393
399
  // Check if new target was actually the tooltip element
394
- var relatedTarget = $(event.relatedTarget || event.target),
400
+ var relatedTarget = $(event.relatedTarget),
395
401
  ontoTooltip = relatedTarget.closest(selector)[0] === tooltip[0],
396
402
  ontoTarget = relatedTarget[0] === targets.show[0];
397
403
 
@@ -400,7 +406,11 @@ function QTip(target, options, id, attr)
400
406
  clearTimeout(self.timers.hide);
401
407
 
402
408
  // Prevent hiding if tooltip is fixed and event target is the tooltip. Or if mouse positioning is enabled and cursor momentarily overlaps
403
- if((posOptions.target === 'mouse' && ontoTooltip) || (options.hide.fixed && ((/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget)))) {
409
+ if(this !== relatedTarget[0] &&
410
+ (posOptions.target === 'mouse' && ontoTooltip) ||
411
+ (options.hide.fixed && (
412
+ (/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget))
413
+ )) {
404
414
  try { event.preventDefault(); event.stopImmediatePropagation(); } catch(e) {} return;
405
415
  }
406
416
 
@@ -440,8 +450,10 @@ function QTip(target, options, id, attr)
440
450
  if(/mouse(out|leave)/i.test(options.hide.event)) {
441
451
  // Hide tooltips when leaving current window/frame (but not select/option elements)
442
452
  if(options.hide.leave === 'window') {
443
- targets.window.bind('mouseout'+namespace+' blur'+namespace, function(event) {
444
- if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) { self.hide(event); }
453
+ targets.document.bind('mouseout'+namespace+' blur'+namespace, function(event) {
454
+ if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) {
455
+ self.hide(event);
456
+ }
445
457
  });
446
458
  }
447
459
  }
@@ -458,9 +470,9 @@ function QTip(target, options, id, attr)
458
470
  }
459
471
 
460
472
  /*
461
- * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
462
- * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
463
- */
473
+ * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
474
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
475
+ */
464
476
  else if(/mouse(over|enter)/i.test(options.show.event)) {
465
477
  targets.hide.bind('mouseleave'+namespace, function(event) {
466
478
  clearTimeout(self.timers.show);
@@ -475,7 +487,7 @@ function QTip(target, options, id, attr)
475
487
  isAncestor = elem.parents(selector).filter(tooltip[0]).length > 0;
476
488
 
477
489
  if(elem[0] !== target[0] && elem[0] !== tooltip[0] && !isAncestor &&
478
- !target.has(elem[0]).length && !elem.attr('disabled')
490
+ !target.has(elem[0]).length && enabled
479
491
  ) {
480
492
  self.hide(event);
481
493
  }
@@ -570,21 +582,23 @@ function QTip(target, options, id, attr)
570
582
  }
571
583
 
572
584
  // Adjust tooltip position on scroll of the window or viewport element if present
573
- targets.window.add(posOptions.container).bind('scroll'+namespace, repositionMethod);
585
+ if(posOptions.adjust.scroll) {
586
+ targets.window.add(posOptions.container).bind('scroll'+namespace, repositionMethod);
587
+ }
574
588
  }
575
589
 
576
590
  function unassignEvents()
577
591
  {
578
592
  var targets = [
579
- options.show.target[0],
580
- options.hide.target[0],
581
- self.rendered && elements.tooltip[0],
582
- options.position.container[0],
583
- options.position.viewport[0],
584
- options.position.container.closest('html')[0], // unfocus
585
- window,
586
- document
587
- ];
593
+ options.show.target[0],
594
+ options.hide.target[0],
595
+ self.rendered && elements.tooltip[0],
596
+ options.position.container[0],
597
+ options.position.viewport[0],
598
+ options.position.container.closest('html')[0], // unfocus
599
+ window,
600
+ document
601
+ ];
588
602
 
589
603
  // Check if tooltip is rendered
590
604
  if(self.rendered) {
@@ -667,8 +681,8 @@ function QTip(target, options, id, attr)
667
681
 
668
682
  $.extend(self, {
669
683
  /*
670
- * Psuedo-private API methods
671
- */
684
+ * Psuedo-private API methods
685
+ */
672
686
  _triggerEvent: function(type, args, event)
673
687
  {
674
688
  var callback = $.Event('tooltip'+type);
@@ -679,8 +693,8 @@ function QTip(target, options, id, attr)
679
693
  },
680
694
 
681
695
  /*
682
- * Public API methods
683
- */
696
+ * Public API methods
697
+ */
684
698
  render: function(show)
685
699
  {
686
700
  if(self.rendered) { return self; } // If tooltip has already been rendered, exit
@@ -757,10 +771,10 @@ function QTip(target, options, id, attr)
757
771
  assignEvents();
758
772
 
759
773
  /* Queue this part of the render process in our fx queue so we can
760
- * load images before the tooltip renders fully.
761
- *
762
- * See: updateContent method
763
- */
774
+ * load images before the tooltip renders fully.
775
+ *
776
+ * See: updateContent method
777
+ */
764
778
  tooltip.queue('fx', function(next) {
765
779
  // tooltiprender event
766
780
  self._triggerEvent('render');
@@ -852,9 +866,9 @@ function QTip(target, options, id, attr)
852
866
  sanitizeOptions(options);
853
867
 
854
868
  /*
855
- * Execute any valid callbacks for the set options
856
- * Also set isPositioning/isDrawing so we don't get loads of redundant repositioning calls.
857
- */
869
+ * Execute any valid callbacks for the set options
870
+ * Also set isPositioning/isDrawing so we don't get loads of redundant repositioning calls.
871
+ */
858
872
  isPositioning = 1; $.each(option, callback); isPositioning = 0;
859
873
 
860
874
  // Update position if needed
@@ -887,6 +901,7 @@ function QTip(target, options, id, attr)
887
901
  otherOpts = options[ !state ? 'show' : 'hide' ],
888
902
  posOptions = options.position,
889
903
  contentOptions = options.content,
904
+ width = tooltip.css('width'),
890
905
  visible = tooltip[0].offsetWidth > 0,
891
906
  animate = state || opts.target.length === 1,
892
907
  sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,
@@ -922,8 +937,10 @@ function QTip(target, options, id, attr)
922
937
  trackingBound = TRUE;
923
938
  }
924
939
 
925
- // Update the tooltip position
940
+ // Update the tooltip position (set width first to prevent viewport/max-width issues)
941
+ if(!width) { tooltip.css('width', tooltip.outerWidth()); }
926
942
  self.reposition(event, arguments[2]);
943
+ if(!width) { tooltip.css('width', ''); }
927
944
 
928
945
  // Hide other tooltips if tooltip is solo
929
946
  if(!!opts.solo) {
@@ -952,7 +969,7 @@ function QTip(target, options, id, attr)
952
969
  function after() {
953
970
  if(state) {
954
971
  // Prevent antialias from disappearing in IE by removing filter
955
- if($.browser.msie) { tooltip[0].style.removeAttribute('filter'); }
972
+ if(PLUGINS.ie) { tooltip[0].style.removeAttribute('filter'); }
956
973
 
957
974
  // Remove overflow setting to prevent tip bugs
958
975
  tooltip.css('overflow', '');
@@ -1206,7 +1223,7 @@ function QTip(target, options, id, attr)
1206
1223
  tooltip.queue(function(next) {
1207
1224
  // Reset attributes to avoid cross-browser rendering bugs
1208
1225
  $(this).css({ opacity: '', height: '' });
1209
- if($.browser.msie) { this.style.removeAttribute('filter'); }
1226
+ if(PLUGINS.ie) { this.style.removeAttribute('filter'); }
1210
1227
 
1211
1228
  next();
1212
1229
  });
@@ -1237,49 +1254,67 @@ function QTip(target, options, id, attr)
1237
1254
 
1238
1255
  enable: function() { return self.disable(FALSE); },
1239
1256
 
1240
- destroy: function()
1257
+ destroy: function(immediate)
1241
1258
  {
1242
- var t = target[0],
1243
- title = $.attr(t, oldtitle),
1244
- elemAPI = target.data('qtip');
1245
-
1246
1259
  // Set flag the signify destroy is taking place to plugins
1247
- self.destroyed = TRUE;
1248
-
1249
- // Destroy tooltip and any associated plugins if rendered
1250
- if(self.rendered) {
1251
- tooltip.stop(1,0).remove();
1252
-
1253
- $.each(self.plugins, function() {
1254
- if(this.destroy) { this.destroy(); }
1255
- });
1256
- }
1260
+ // and ensure it only gets destroyed once!
1261
+ if(self.destroyed) { return; }
1262
+ self.destroyed = !(self.rendered = FALSE);
1263
+
1264
+ function process() {
1265
+ var t = target[0],
1266
+ title = $.attr(t, oldtitle),
1267
+ elemAPI = target.data('qtip');
1268
+
1269
+ // Destroy tooltip and any associated plugins if rendered
1270
+ if(self.rendered) {
1271
+ // Destroy all plugins
1272
+ $.each(self.plugins, function(name) {
1273
+ if(this.destroy) { this.destroy(); }
1274
+ delete self.plugins[name];
1275
+ });
1257
1276
 
1258
- // Clear timers and remove bound events
1259
- clearTimeout(self.timers.show);
1260
- clearTimeout(self.timers.hide);
1261
- unassignEvents();
1277
+ // Remove all descendants and tooltip element
1278
+ tooltip.stop(1,0).find('*').remove().end().remove();
1279
+ }
1262
1280
 
1263
- // If the API if actually this qTip API...
1264
- if(!elemAPI || self === elemAPI) {
1265
- // Remove api object
1266
- $.removeData(t, 'qtip');
1281
+ // Clear timers and remove bound events
1282
+ clearTimeout(self.timers.show);
1283
+ clearTimeout(self.timers.hide);
1284
+ unassignEvents();
1285
+
1286
+ // If the API if actually this qTip API...
1287
+ if(!elemAPI || self === elemAPI) {
1288
+ // Remove api object
1289
+ target.removeData('qtip').removeAttr(HASATTR);
1290
+
1291
+ // Reset old title attribute if removed
1292
+ if(options.suppress && title) {
1293
+ target.attr('title', title);
1294
+ target.removeAttr(oldtitle);
1295
+ }
1267
1296
 
1268
- // Reset old title attribute if removed
1269
- if(options.suppress && title) {
1270
- $.attr(t, 'title', title);
1271
- target.removeAttr(oldtitle);
1297
+ // Remove ARIA attributes
1298
+ target.removeAttr('aria-describedby');
1272
1299
  }
1273
1300
 
1274
- // Remove ARIA attributes
1275
- target.removeAttr('aria-describedby');
1276
- }
1301
+ // Remove qTip events associated with this API
1302
+ target.unbind('.qtip-'+id);
1277
1303
 
1278
- // Remove qTip events associated with this API
1279
- target.unbind('.qtip-'+id);
1304
+ // Remove ID from used id objects, and delete object references
1305
+ // for better garbage collection and leak protection
1306
+ delete usedIDs[self.id];
1307
+ delete self.options; delete self.elements;
1308
+ delete self.cache; delete self.timers;
1309
+ delete self.checks;
1310
+ }
1280
1311
 
1281
- // Remove ID from sued id object
1282
- delete usedIDs[self.id];
1312
+ // Destroy after hide if no immediate
1313
+ if(immediate === TRUE) { process(); }
1314
+ else {
1315
+ tooltip.bind('tooltiphidden', process);
1316
+ self.hide();
1317
+ }
1283
1318
 
1284
1319
  return target;
1285
1320
  }
@@ -1287,16 +1322,15 @@ function QTip(target, options, id, attr)
1287
1322
  }
1288
1323
 
1289
1324
  // Initialization method
1290
- function init(id, opts)
1325
+ function init(elem, id, opts)
1291
1326
  {
1292
1327
  var obj, posOptions, attr, config, title,
1293
1328
 
1294
1329
  // Setup element references
1295
- elem = $(this),
1296
1330
  docBody = $(document.body),
1297
1331
 
1298
1332
  // Use document body instead of document element if needed
1299
- newTarget = this === document ? docBody : elem,
1333
+ newTarget = elem[0] === document ? docBody : elem,
1300
1334
 
1301
1335
  // Grab metadata from element if plugin is present
1302
1336
  metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL,
@@ -1346,7 +1380,7 @@ function init(id, opts)
1346
1380
  posOptions.my = new PLUGINS.Corner(posOptions.my);
1347
1381
 
1348
1382
  // Destroy previous tooltip if overwrite is enabled, or skip element if not
1349
- if($.data(this, 'qtip')) {
1383
+ if(elem.data('qtip')) {
1350
1384
  if(config.overwrite) {
1351
1385
  elem.qtip('destroy');
1352
1386
  }
@@ -1355,18 +1389,23 @@ function init(id, opts)
1355
1389
  }
1356
1390
  }
1357
1391
 
1392
+ // Add has-qtip attribute
1393
+ elem.attr(HASATTR, true);
1394
+
1358
1395
  // Remove title attribute and store it if present
1359
- if(config.suppress && (title = $.attr(this, 'title'))) {
1396
+ if(config.suppress && (title = elem.attr('title'))) {
1360
1397
  // Final attr call fixes event delegatiom and IE default tooltip showing problem
1361
- $(this).removeAttr('title').attr(oldtitle, title).attr('title', '');
1398
+ elem.removeAttr('title').attr(oldtitle, title).attr('title', '');
1362
1399
  }
1363
1400
 
1364
1401
  // Initialize the tooltip and add API reference
1365
1402
  obj = new QTip(elem, config, id, !!attr);
1366
- $.data(this, 'qtip', obj);
1403
+ elem.data('qtip', obj);
1367
1404
 
1368
1405
  // Catch remove/removeqtip events on target element to destroy redundant tooltip
1369
- elem.bind('remove.qtip-'+id+' removeqtip.qtip-'+id, function(){ obj.destroy(); });
1406
+ elem.one('remove.qtip-'+id+' removeqtip.qtip-'+id, function() {
1407
+ var api; if((api = $(this).data('qtip'))) { api.destroy(); }
1408
+ });
1370
1409
 
1371
1410
  return obj;
1372
1411
  }
@@ -1440,7 +1479,7 @@ QTIP.bind = function(opts, event)
1440
1479
  namespace = '.qtip-'+id+'-create';
1441
1480
 
1442
1481
  // Initialize the qTip and re-grab newly sanitized options
1443
- api = init.call(this, id, opts);
1482
+ api = init($(this), id, opts);
1444
1483
  if(api === FALSE) { return TRUE; }
1445
1484
  options = api.options;
1446
1485
 
@@ -1457,19 +1496,19 @@ QTIP.bind = function(opts, event)
1457
1496
  };
1458
1497
 
1459
1498
  /*
1460
- * Make sure hoverIntent functions properly by using mouseleave as a hide event if
1461
- * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
1462
- */
1499
+ * Make sure hoverIntent functions properly by using mouseleave as a hide event if
1500
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
1501
+ */
1463
1502
  if(/mouse(over|enter)/i.test(events.show) && !/mouse(out|leave)/i.test(events.hide)) {
1464
1503
  events.hide += ' mouseleave' + namespace;
1465
1504
  }
1466
1505
 
1467
1506
  /*
1468
- * Also make sure initial mouse targetting works correctly by caching mousemove coords
1469
- * on show targets before the tooltip has rendered.
1470
- *
1471
- * Also set onTarget when triggered to keep mouse tracking working
1472
- */
1507
+ * Also make sure initial mouse targetting works correctly by caching mousemove coords
1508
+ * on show targets before the tooltip has rendered.
1509
+ *
1510
+ * Also set onTarget when triggered to keep mouse tracking working
1511
+ */
1473
1512
  targets.show.bind('mousemove'+namespace, function(event) {
1474
1513
  storeMouse(event);
1475
1514
  api.cache.onTarget = TRUE;
@@ -1508,8 +1547,7 @@ QTIP.bind = function(opts, event)
1508
1547
 
1509
1548
  // Prerendering is enabled, create tooltip now
1510
1549
  if(options.show.ready || options.prerender) { hoverIntent(event); }
1511
- })
1512
- .attr('data-hasqtip', TRUE);
1550
+ });
1513
1551
  };
1514
1552
 
1515
1553
  // Setup base plugins
@@ -1544,7 +1582,7 @@ PLUGINS = QTIP.plugins = {
1544
1582
  offset: function(elem, container) {
1545
1583
  var pos = elem.offset(),
1546
1584
  docBody = elem.closest('body'),
1547
- quirks = $.browser.msie && document.compatMode !== 'CSS1Compat',
1585
+ quirks = PLUGINS.ie && document.compatMode !== 'CSS1Compat',
1548
1586
  parent = container, scrolled,
1549
1587
  coffset, overflow;
1550
1588
 
@@ -1579,16 +1617,30 @@ PLUGINS = QTIP.plugins = {
1579
1617
  },
1580
1618
 
1581
1619
  /*
1582
- * iOS version detection
1583
- */
1620
+ * IE version detection
1621
+ *
1622
+ * Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment
1623
+ * Credit to James Padolsey for the original implemntation!
1624
+ */
1625
+ ie: (function(){
1626
+ var v = 3, div = document.createElement('div');
1627
+ while ((div.innerHTML = '<!--[if gt IE '+(++v)+']><i></i><![endif]-->')) {
1628
+ if(!div.getElementsByTagName('i')[0]) { break; }
1629
+ }
1630
+ return v > 4 ? v : FALSE;
1631
+ }()),
1632
+
1633
+ /*
1634
+ * iOS version detection
1635
+ */
1584
1636
  iOS: parseFloat(
1585
1637
  ('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
1586
1638
  .replace('undefined', '3_2').replace('_', '.').replace('_', '')
1587
1639
  ) || FALSE,
1588
1640
 
1589
1641
  /*
1590
- * jQuery-specific $.fn overrides
1591
- */
1642
+ * jQuery-specific $.fn overrides
1643
+ */
1592
1644
  fn: {
1593
1645
  /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
1594
1646
  attr: function(attr, val) {
@@ -1652,8 +1704,8 @@ $.each(PLUGINS.fn, function(name, func) {
1652
1704
  if(!$.ui) {
1653
1705
  $['cleanData'+replaceSuffix] = $.cleanData;
1654
1706
  $.cleanData = function( elems ) {
1655
- for(var i = 0, elem; (elem = elems[i]) !== undefined; i++) {
1656
- try { $( elem ).triggerHandler('removeqtip'); }
1707
+ for(var i = 0, elem; (elem = elems[i]) !== undefined && elem.getAttribute(HASATTR); i++) {
1708
+ try { $( elem ).triggerHandler('removeqtip');}
1657
1709
  catch( e ) {}
1658
1710
  }
1659
1711
  $['cleanData'+replaceSuffix]( elems );
@@ -1690,6 +1742,7 @@ QTIP.defaults = {
1690
1742
  adjust: {
1691
1743
  x: 0, y: 0,
1692
1744
  mouse: TRUE,
1745
+ scroll: TRUE,
1693
1746
  resize: TRUE,
1694
1747
  method: 'flipinvert flipinvert'
1695
1748
  },