Overhaul tooltip implementation. Avoid queuing and other problems by

creating the tooltip element on the fly, never reusing it.
Use aria-describedby attribute to find the associated tooltip again.
Allows customizing animations much easier (just replace fadeIn/fadeOut),
still open.
Updated demos and visual test to replace now-missing .widget() method.
Added tooltipClass for that.
This commit is contained in:
jzaefferer 2011-04-13 14:01:46 +02:00
parent bc65675330
commit 67d49f593b
5 changed files with 99 additions and 85 deletions

View File

@ -11,12 +11,15 @@
<link type="text/css" href="../demos.css" rel="stylesheet" /> <link type="text/css" href="../demos.css" rel="stylesheet" />
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
// TODO overhaul this with custom animation API
$(".demo").tooltip({ $(".demo").tooltip({
open: function() { open: function() {
$(this).tooltip("widget").stop(false, true).hide().slideDown(); $(".ui-tooltip").stop(false, true).hide().slideDown();
}, },
close: function() { close: function() {
$(this).tooltip("widget").stop(false, true).show().slideUp(); $(".ui-tooltip").stop(false, false).show().slideUp(function() {
$(this).remove();
});
} }
}); });
}); });

View File

@ -14,6 +14,7 @@
<li><a href="tracking.html">Track the mouse</a></li> <li><a href="tracking.html">Track the mouse</a></li>
<li><a href="custom-animation.html">Custom animation</a></li> <li><a href="custom-animation.html">Custom animation</a></li>
<li><a href="delegation-mixbag.html">Delegation Mixbag</a></li> <li><a href="delegation-mixbag.html">Delegation Mixbag</a></li>
<li><a href="lots.html">Lots</a></li>
</ul> </ul>
</div> </div>

View File

@ -13,12 +13,11 @@
$(function() { $(function() {
$(".demo").tooltip({ $(".demo").tooltip({
open: function() { open: function() {
var tooltip = $(this).tooltip("widget"); var tooltip = $( ".ui-tooltip" );
$(document).mousemove(function(event) { $(document).mousemove(function( event ) {
tooltip.position({ tooltip.position( {
my: "left center", my: "left+25 center",
at: "right center", at: "right+25 center",
offset: "25 25",
of: event of: event
}); });
}) })
@ -26,7 +25,7 @@
.mousemove(); .mousemove();
}, },
close: function() { close: function() {
$(document).unbind("mousemove"); $(document).unbind( "mousemove" );
} }
}); });
}); });

View File

@ -10,7 +10,9 @@
<script type="text/javascript" src="../../../ui/jquery.ui.position.js"></script> <script type="text/javascript" src="../../../ui/jquery.ui.position.js"></script>
<script type="text/javascript" src="../../../ui/jquery.ui.button.js"></script> <script type="text/javascript" src="../../../ui/jquery.ui.button.js"></script>
<script type="text/javascript" src="../../../ui/jquery.ui.tooltip.js"></script> <script type="text/javascript" src="../../../ui/jquery.ui.tooltip.js"></script>
<!--
<script type="text/javascript" src="http://jqueryui.com/themeroller/themeswitchertool/"></script> <script type="text/javascript" src="http://jqueryui.com/themeroller/themeswitchertool/"></script>
-->
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
$.fn.themeswitcher && $('<div/>').css({ $.fn.themeswitcher && $('<div/>').css({
@ -24,14 +26,16 @@
$("#context1, form, #childish").tooltip(); $("#context1, form, #childish").tooltip();
// custom class, replaces ui-widget-content // custom class, replaces ui-widget-content
$("#context2").tooltip().each(function() { $("#context2").tooltip({
$(this).tooltip("widget").addClass("ui-widget-header"); tooltipClass: "ui-widget-header"
}) });
$("#right1").tooltip().tooltip("widget").addClass("ui-state-error"); $("#right1").tooltip({
tooltipClass: "ui-state-error"
});
// synchronous content // synchronous content
$("#footnotes").tooltip({ $("#footnotes").tooltip({
items: "[href^=#]", items: "[href^='#']",
content: function() { content: function() {
return $($(this).attr("href")).html(); return $($(this).attr("href")).html();
} }
@ -64,12 +68,13 @@
// custom position // custom position
$("#right2").tooltip({ $("#right2").tooltip({
tooltipClass: "ui-state-highlight",
position: { position: {
my: "center top", my: "center top",
at: "center bottom", at: "center bottom",
offset: "0 10" offset: "0 10"
} }
}).tooltip("widget").addClass("ui-state-highlight"); });
$("#button1").button(); $("#button1").button();
$("#button2").button({ $("#button2").button({
@ -94,12 +99,12 @@
enable(); enable();
$("#disable").toggle(function() { $("#disable").toggle(function() {
$("*").tooltip("disable"); $(":ui-tooltip").tooltip("disable");
}, function() { }, function() {
$("*").tooltip("enable"); $(":ui-tooltip").tooltip("enable");
}); });
$("#toggle").toggle(function() { $("#toggle").toggle(function() {
$("*").tooltip("destroy"); $(":ui-tooltip").tooltip("destroy");
}, function() { }, function() {
enable(); enable();
}); });

View File

@ -18,9 +18,10 @@ var increments = 0;
$.widget("ui.tooltip", { $.widget("ui.tooltip", {
options: { options: {
tooltipClass: null,
items: "[title]", items: "[title]",
content: function() { content: function() {
return $(this).attr("title"); return $( this ).attr( "title" );
}, },
position: { position: {
my: "left center", my: "left center",
@ -29,25 +30,10 @@ $.widget("ui.tooltip", {
} }
}, },
_create: function() { _create: function() {
var self = this; this._bind( {
this.tooltip = $("<div></div>") mouseover: "open",
.attr("id", "ui-tooltip-" + increments++) focusin: "open"
.attr("role", "tooltip") });
.attr("aria-hidden", "true")
.addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content")
.appendTo(document.body)
.hide();
this.tooltipContent = $("<div></div>")
.addClass("ui-tooltip-content")
.appendTo(this.tooltip);
this.opacity = this.tooltip.css("opacity");
this.element
.bind("focus.tooltip mouseover.tooltip", function(event) {
self.open( event );
})
.bind("blur.tooltip mouseout.tooltip", function(event) {
self.close( event );
});
}, },
enable: function() { enable: function() {
@ -55,34 +41,28 @@ $.widget("ui.tooltip", {
}, },
disable: function() { disable: function() {
// only set option, disable element style changes
this.options.disabled = true; this.options.disabled = true;
}, },
_destroy: function() {
this.tooltip.remove();
},
widget: function() {
return this.element.pushStack(this.tooltip.get());
},
open: function(event) { open: function(event) {
var target = $(event && event.target || this.element).closest(this.options.items); var target = $(event && event.target || this.element).closest(this.options.items);
if ( !target.length ) { if ( !target.length ) {
return; return;
} }
// already visible? possible when both focus and mouseover events occur
if (this.current && this.current[0] == target[0])
return;
var self = this; var self = this;
this.current = target; if ( !target.data("tooltip-title") ) {
this.currentTitle = target.attr("title"); target.data("tooltip-title", target.attr("title"));
}
var content = this.options.content.call(target[0], function(response) { var content = this.options.content.call(target[0], function(response) {
// IE may instantly serve a cached response, need to give it a chance to finish with _open before that // IE may instantly serve a cached response, need to give it a chance to finish with _open before that
setTimeout(function() { setTimeout(function() {
// ignore async responses that come in after the tooltip is already hidden // when undefined, it got removeAttr, then ignore (ajax response)
if (self.current == target) // intially its an empty string, so not undefined
// TODO is there a better approach to enable ajax tooltips to have two updates?
if (target.attr( "aria-describedby" ) !== undefined) {
self._open(event, target, response); self._open(event, target, response);
}
}, 13); }, 13);
}); });
if (content) { if (content) {
@ -90,48 +70,74 @@ $.widget("ui.tooltip", {
} }
}, },
_open: function(event, target, content) { _open: function( event, target, content ) {
if (!content) if ( !content )
return; return;
target.attr("title", ""); target.attr("title", "");
if (this.options.disabled) if ( this.options.disabled )
return; return;
this.tooltipContent.html(content); // ajaxy tooltip can update an existing one
this.tooltip.css({ var tooltip = this._find( target );
top: 0, if (!tooltip.length) {
left: 0 tooltip = this._tooltip();
}).show().position( $.extend({ target.attr( "aria-describedby", tooltip.attr( "id" ) );
}
tooltip.find(".ui-tooltip-content").html( content );
tooltip.position( $.extend({
of: target of: target
}, this.options.position )).hide(); }, this.options.position ) ).hide();
this.tooltip.attr("aria-hidden", "false");
target.attr("aria-describedby", this.tooltip.attr("id"));
this.tooltip.stop(false, true).fadeIn(); tooltip.fadeIn();
this._trigger( "open", event ); this._trigger( "open", event );
this._bind( target, {
mouseleave: "close",
blur: "close"
});
}, },
close: function(event) { close: function( event ) {
if (!this.current) var target = $( event && event.currentTarget || this.element );
target.attr( "title", target.data( "tooltip-title" ) );
if ( this.options.disabled )
return; return;
var current = this.current; var tooltip = this._find( target );
this.current = null; target.removeAttr( "aria-describedby" );
current.attr("title", this.currentTitle);
if (this.options.disabled) tooltip.fadeOut( function() {
return; $( this ).remove();
});
current.removeAttr("aria-describedby"); target.unbind( "mouseleave.tooltip blur.tooltip" );
this.tooltip.attr("aria-hidden", "true");
this.tooltip.stop(false, true).fadeOut();
this._trigger( "close", event ); this._trigger( "close", event );
},
_tooltip: function() {
var tooltip = $( "<div></div>" )
.attr( "id", "ui-tooltip-" + increments++ )
.attr( "role", "tooltip" )
.addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content" );
if (this.options.tooltipClass) {
tooltip.addClass(this.options.tooltipClass);
}
$( "<div></div>" )
.addClass( "ui-tooltip-content" )
.appendTo( tooltip );
tooltip.appendTo( document.body );
return tooltip;
},
_find: function( target ) {
var id = target.attr( "aria-describedby" );
return id ? $( document.getElementById( id ) ) : $();
} }
}); });