aboutsummaryrefslogtreecommitdiffstats
path: root/ui/jquery.ui.tooltip.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui/jquery.ui.tooltip.js')
-rw-r--r--ui/jquery.ui.tooltip.js206
1 files changed, 136 insertions, 70 deletions
diff --git a/ui/jquery.ui.tooltip.js b/ui/jquery.ui.tooltip.js
index a8a44f1c0..d30e49c66 100644
--- a/ui/jquery.ui.tooltip.js
+++ b/ui/jquery.ui.tooltip.js
@@ -12,136 +12,202 @@
* jquery.ui.widget.js
* jquery.ui.position.js
*/
-(function($) {
+(function( $ ) {
var increments = 0;
-$.widget("ui.tooltip", {
+$.widget( "ui.tooltip", {
+ version: "@VERSION",
options: {
- tooltipClass: null,
- items: "[title]",
content: function() {
return $( this ).attr( "title" );
},
+ items: "[title]",
position: {
my: "left+15 center",
- at: "right center"
- }
+ at: "right center",
+ collision: "flip fit"
+ },
+ tooltipClass: null
},
+
_create: function() {
- this._bind( {
+ this._bind({
mouseover: "open",
focusin: "open"
});
+
+ // IDs of generated tooltips, needed for destroy
+ this.tooltips = {};
},
-
- enable: function() {
- this.options.disabled = false;
+
+ _setOption: function( key, value ) {
+ if ( key === "disabled" ) {
+ this[ value ? "_disable" : "_enable" ]();
+ this.options[ key ] = value;
+ // disable element style changes
+ return;
+ }
+ this._super( "_setOption", key, value );
+ },
+
+ _disable: function() {
+ var that = this;
+
+ // close open tooltips
+ $.each( this.tooltips, function( id, element ) {
+ var event = $.Event( "blur" );
+ event.target = event.currentTarget = element[0];
+ that.close( event, true );
+ });
+
+ // remove title attributes to prevent native tooltips
+ this.element.find( this.options.items ).andSelf().each(function() {
+ var element = $( this );
+ if ( element.is( "[title]" ) ) {
+ element
+ .data( "tooltip-title", element.attr( "title" ) )
+ .attr( "title", "" );
+ }
+ });
},
-
- disable: function() {
- // only set option, disable element style changes
- this.options.disabled = true;
+
+ _enable: function() {
+ // restore title attributes
+ this.element.find( this.options.items ).andSelf().each(function() {
+ var element = $( this );
+ if ( element.data( "tooltip-title" ) ) {
+ element.attr( "title", element.data( "tooltip-title" ) );
+ }
+ });
},
-
- open: function(event) {
- var target = $(event && event.target || this.element).closest(this.options.items);
- if ( !target.length ) {
+
+ open: function( event ) {
+ var content,
+ that = this,
+ target = $( event ? event.target : this.element )
+ .closest( this.options.items );
+
+ // if aria-describedby exists, then the tooltip is already open
+ if ( !target.length || target.attr( "aria-describedby" ) ) {
return;
}
- var self = this;
- if ( !target.data("tooltip-title") ) {
- target.data("tooltip-title", target.attr("title"));
+
+ if ( !target.data( "tooltip-title" ) ) {
+ target.data( "tooltip-title", target.attr( "title" ) );
}
- 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
+
+ content = this.options.content.call( target[0], function( response ) {
+ // IE may instantly serve a cached response for ajax requests
+ // delay this call to _open so the other call to _open runs first
setTimeout(function() {
- // when undefined, it got removeAttr, then ignore (ajax response)
- // 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);
- }
- }, 13);
+ that._open( event, target, response );
+ }, 1 );
});
- if (content) {
- self._open(event, target, content);
+ if ( content ) {
+ that._open( event, target, content );
}
},
-
+
_open: function( event, target, content ) {
- if ( !content )
+ if ( !content ) {
return;
+ }
- target.attr("title", "");
-
- if ( this.options.disabled )
- return;
+ // if we have a title, clear it to prevent the native tooltip
+ // we have to check first to avoid defining a title if none exists
+ // (we don't want to cause an element to start matching [title])
+ // TODO: document why we don't use .removeAttr()
+ if ( target.is( "[title]" ) ) {
+ target.attr( "title", "" );
+ }
// ajaxy tooltip can update an existing one
var tooltip = this._find( target );
- if (!tooltip.length) {
- tooltip = this._tooltip();
+ if ( !tooltip.length ) {
+ tooltip = this._tooltip( target );
target.attr( "aria-describedby", tooltip.attr( "id" ) );
}
- tooltip.find(".ui-tooltip-content").html( content );
- tooltip.position( $.extend({
- of: target
- }, this.options.position ) ).hide();
+ tooltip.find( ".ui-tooltip-content" ).html( content );
+ tooltip
+ .stop( true )
+ .position( $.extend({
+ of: target,
+ using: function( pos ) {
+ // we only want to hide if there's no custom using defined
+ $( this ).css( pos ).hide();
+ }
+ }, this.options.position ) );
- tooltip.stop( true );
this._show( tooltip, this.options.show );
this._trigger( "open", event );
this._bind( target, {
mouseleave: "close",
- blur: "close",
- click: "close"
+ blur: "close"
});
},
-
- close: function( event ) {
- var target = $( event && event.currentTarget || this.element );
- target.attr( "title", target.data( "tooltip-title" ) );
-
- if ( this.options.disabled )
+
+ close: function( event, force ) {
+ var that = this,
+ target = $( event ? event.currentTarget : this.element ),
+ tooltip = this._find( target );
+
+ // don't close if the element has focus
+ // this prevents the tooltip from closing if you hover while focused
+ if ( !force && document.activeElement === target[0] ) {
return;
+ }
+
+ // only set title if we had one before (see comment in _open())
+ if ( target.data( "tooltip-title" ) ) {
+ target.attr( "title", target.data( "tooltip-title" ) );
+ }
- var tooltip = this._find( target );
target.removeAttr( "aria-describedby" );
-
+
tooltip.stop( true );
this._hide( tooltip, this.options.hide, function() {
$( this ).remove();
+ delete that.tooltips[ this.id ];
});
-
+
target.unbind( "mouseleave.tooltip blur.tooltip" );
-
+
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>" )
+ _tooltip: function( element ) {
+ var id = "ui-tooltip-" + increments++,
+ tooltip = $( "<div>" )
+ .attr({
+ id: id,
+ role: "tooltip"
+ })
+ .addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
+ ( this.options.tooltipClass || "" ) );
+ $( "<div>" )
.addClass( "ui-tooltip-content" )
.appendTo( tooltip );
tooltip.appendTo( document.body );
+ if ( $.fn.bgiframe ) {
+ tooltip.bgiframe();
+ }
+ this.tooltips[ id ] = element;
return tooltip;
},
_find: function( target ) {
var id = target.attr( "aria-describedby" );
- return id ? $( document.getElementById( id ) ) : $();
+ return id ? $( "#" + id ) : $();
+ },
+
+ _destroy: function() {
+ $.each( this.tooltips, function( id ) {
+ $( "#" + id ).remove();
+ });
}
});
-$.ui.tooltip.version = "@VERSION";
-
-})(jQuery); \ No newline at end of file
+}( jQuery ) );