jquery-qtip2-rails 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
  },