diff options
Diffstat (limited to 'ui/jquery.ui.tooltip.js')
-rw-r--r-- | ui/jquery.ui.tooltip.js | 206 |
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 ) ); |