Merge "Added release notes for 'ContentHandler::runLegacyHooks' removal"
[mediawiki.git] / resources / lib / jquery.ui / jquery.ui.tooltip.js
blob2b50b443e33eb51f126964d46856faa01a88d3b8
1 /*!
2  * jQuery UI Tooltip 1.9.2
3  * http://jqueryui.com
4  *
5  * Copyright 2012 jQuery Foundation and other contributors
6  * Released under the MIT license.
7  * http://jquery.org/license
8  *
9  * http://api.jqueryui.com/tooltip/
10  *
11  * Depends:
12  *      jquery.ui.core.js
13  *      jquery.ui.widget.js
14  *      jquery.ui.position.js
15  */
16 (function( $ ) {
18 var increments = 0;
20 function addDescribedBy( elem, id ) {
21         var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
22         describedby.push( id );
23         elem
24                 .data( "ui-tooltip-id", id )
25                 .attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
28 function removeDescribedBy( elem ) {
29         var id = elem.data( "ui-tooltip-id" ),
30                 describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
31                 index = $.inArray( id, describedby );
32         if ( index !== -1 ) {
33                 describedby.splice( index, 1 );
34         }
36         elem.removeData( "ui-tooltip-id" );
37         describedby = $.trim( describedby.join( " " ) );
38         if ( describedby ) {
39                 elem.attr( "aria-describedby", describedby );
40         } else {
41                 elem.removeAttr( "aria-describedby" );
42         }
45 $.widget( "ui.tooltip", {
46         version: "1.9.2",
47         options: {
48                 content: function() {
49                         return $( this ).attr( "title" );
50                 },
51                 hide: true,
52                 // Disabled elements have inconsistent behavior across browsers (#8661)
53                 items: "[title]:not([disabled])",
54                 position: {
55                         my: "left top+15",
56                         at: "left bottom",
57                         collision: "flipfit flip"
58                 },
59                 show: true,
60                 tooltipClass: null,
61                 track: false,
63                 // callbacks
64                 close: null,
65                 open: null
66         },
68         _create: function() {
69                 this._on({
70                         mouseover: "open",
71                         focusin: "open"
72                 });
74                 // IDs of generated tooltips, needed for destroy
75                 this.tooltips = {};
76                 // IDs of parent tooltips where we removed the title attribute
77                 this.parents = {};
79                 if ( this.options.disabled ) {
80                         this._disable();
81                 }
82         },
84         _setOption: function( key, value ) {
85                 var that = this;
87                 if ( key === "disabled" ) {
88                         this[ value ? "_disable" : "_enable" ]();
89                         this.options[ key ] = value;
90                         // disable element style changes
91                         return;
92                 }
94                 this._super( key, value );
96                 if ( key === "content" ) {
97                         $.each( this.tooltips, function( id, element ) {
98                                 that._updateContent( element );
99                         });
100                 }
101         },
103         _disable: function() {
104                 var that = this;
106                 // close open tooltips
107                 $.each( this.tooltips, function( id, element ) {
108                         var event = $.Event( "blur" );
109                         event.target = event.currentTarget = element[0];
110                         that.close( event, true );
111                 });
113                 // remove title attributes to prevent native tooltips
114                 this.element.find( this.options.items ).andSelf().each(function() {
115                         var element = $( this );
116                         if ( element.is( "[title]" ) ) {
117                                 element
118                                         .data( "ui-tooltip-title", element.attr( "title" ) )
119                                         .attr( "title", "" );
120                         }
121                 });
122         },
124         _enable: function() {
125                 // restore title attributes
126                 this.element.find( this.options.items ).andSelf().each(function() {
127                         var element = $( this );
128                         if ( element.data( "ui-tooltip-title" ) ) {
129                                 element.attr( "title", element.data( "ui-tooltip-title" ) );
130                         }
131                 });
132         },
134         open: function( event ) {
135                 var that = this,
136                         target = $( event ? event.target : this.element )
137                                 // we need closest here due to mouseover bubbling,
138                                 // but always pointing at the same event target
139                                 .closest( this.options.items );
141                 // No element to show a tooltip for or the tooltip is already open
142                 if ( !target.length || target.data( "ui-tooltip-id" ) ) {
143                         return;
144                 }
146                 if ( target.attr( "title" ) ) {
147                         target.data( "ui-tooltip-title", target.attr( "title" ) );
148                 }
150                 target.data( "ui-tooltip-open", true );
152                 // kill parent tooltips, custom or native, for hover
153                 if ( event && event.type === "mouseover" ) {
154                         target.parents().each(function() {
155                                 var parent = $( this ),
156                                         blurEvent;
157                                 if ( parent.data( "ui-tooltip-open" ) ) {
158                                         blurEvent = $.Event( "blur" );
159                                         blurEvent.target = blurEvent.currentTarget = this;
160                                         that.close( blurEvent, true );
161                                 }
162                                 if ( parent.attr( "title" ) ) {
163                                         parent.uniqueId();
164                                         that.parents[ this.id ] = {
165                                                 element: this,
166                                                 title: parent.attr( "title" )
167                                         };
168                                         parent.attr( "title", "" );
169                                 }
170                         });
171                 }
173                 this._updateContent( target, event );
174         },
176         _updateContent: function( target, event ) {
177                 var content,
178                         contentOption = this.options.content,
179                         that = this,
180                         eventType = event ? event.type : null;
182                 if ( typeof contentOption === "string" ) {
183                         return this._open( event, target, contentOption );
184                 }
186                 content = contentOption.call( target[0], function( response ) {
187                         // ignore async response if tooltip was closed already
188                         if ( !target.data( "ui-tooltip-open" ) ) {
189                                 return;
190                         }
191                         // IE may instantly serve a cached response for ajax requests
192                         // delay this call to _open so the other call to _open runs first
193                         that._delay(function() {
194                                 // jQuery creates a special event for focusin when it doesn't
195                                 // exist natively. To improve performance, the native event
196                                 // object is reused and the type is changed. Therefore, we can't
197                                 // rely on the type being correct after the event finished
198                                 // bubbling, so we set it back to the previous value. (#8740)
199                                 if ( event ) {
200                                         event.type = eventType;
201                                 }
202                                 this._open( event, target, response );
203                         });
204                 });
205                 if ( content ) {
206                         this._open( event, target, content );
207                 }
208         },
210         _open: function( event, target, content ) {
211                 var tooltip, events, delayedShow,
212                         positionOption = $.extend( {}, this.options.position );
214                 if ( !content ) {
215                         return;
216                 }
218                 // Content can be updated multiple times. If the tooltip already
219                 // exists, then just update the content and bail.
220                 tooltip = this._find( target );
221                 if ( tooltip.length ) {
222                         tooltip.find( ".ui-tooltip-content" ).html( content );
223                         return;
224                 }
226                 // if we have a title, clear it to prevent the native tooltip
227                 // we have to check first to avoid defining a title if none exists
228                 // (we don't want to cause an element to start matching [title])
229                 //
230                 // We use removeAttr only for key events, to allow IE to export the correct
231                 // accessible attributes. For mouse events, set to empty string to avoid
232                 // native tooltip showing up (happens only when removing inside mouseover).
233                 if ( target.is( "[title]" ) ) {
234                         if ( event && event.type === "mouseover" ) {
235                                 target.attr( "title", "" );
236                         } else {
237                                 target.removeAttr( "title" );
238                         }
239                 }
241                 tooltip = this._tooltip( target );
242                 addDescribedBy( target, tooltip.attr( "id" ) );
243                 tooltip.find( ".ui-tooltip-content" ).html( content );
245                 function position( event ) {
246                         positionOption.of = event;
247                         if ( tooltip.is( ":hidden" ) ) {
248                                 return;
249                         }
250                         tooltip.position( positionOption );
251                 }
252                 if ( this.options.track && event && /^mouse/.test( event.type ) ) {
253                         this._on( this.document, {
254                                 mousemove: position
255                         });
256                         // trigger once to override element-relative positioning
257                         position( event );
258                 } else {
259                         tooltip.position( $.extend({
260                                 of: target
261                         }, this.options.position ) );
262                 }
264                 tooltip.hide();
266                 this._show( tooltip, this.options.show );
267                 // Handle tracking tooltips that are shown with a delay (#8644). As soon
268                 // as the tooltip is visible, position the tooltip using the most recent
269                 // event.
270                 if ( this.options.show && this.options.show.delay ) {
271                         delayedShow = setInterval(function() {
272                                 if ( tooltip.is( ":visible" ) ) {
273                                         position( positionOption.of );
274                                         clearInterval( delayedShow );
275                                 }
276                         }, $.fx.interval );
277                 }
279                 this._trigger( "open", event, { tooltip: tooltip } );
281                 events = {
282                         keyup: function( event ) {
283                                 if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
284                                         var fakeEvent = $.Event(event);
285                                         fakeEvent.currentTarget = target[0];
286                                         this.close( fakeEvent, true );
287                                 }
288                         },
289                         remove: function() {
290                                 this._removeTooltip( tooltip );
291                         }
292                 };
293                 if ( !event || event.type === "mouseover" ) {
294                         events.mouseleave = "close";
295                 }
296                 if ( !event || event.type === "focusin" ) {
297                         events.focusout = "close";
298                 }
299                 this._on( true, target, events );
300         },
302         close: function( event ) {
303                 var that = this,
304                         target = $( event ? event.currentTarget : this.element ),
305                         tooltip = this._find( target );
307                 // disabling closes the tooltip, so we need to track when we're closing
308                 // to avoid an infinite loop in case the tooltip becomes disabled on close
309                 if ( this.closing ) {
310                         return;
311                 }
313                 // only set title if we had one before (see comment in _open())
314                 if ( target.data( "ui-tooltip-title" ) ) {
315                         target.attr( "title", target.data( "ui-tooltip-title" ) );
316                 }
318                 removeDescribedBy( target );
320                 tooltip.stop( true );
321                 this._hide( tooltip, this.options.hide, function() {
322                         that._removeTooltip( $( this ) );
323                 });
325                 target.removeData( "ui-tooltip-open" );
326                 this._off( target, "mouseleave focusout keyup" );
327                 // Remove 'remove' binding only on delegated targets
328                 if ( target[0] !== this.element[0] ) {
329                         this._off( target, "remove" );
330                 }
331                 this._off( this.document, "mousemove" );
333                 if ( event && event.type === "mouseleave" ) {
334                         $.each( this.parents, function( id, parent ) {
335                                 $( parent.element ).attr( "title", parent.title );
336                                 delete that.parents[ id ];
337                         });
338                 }
340                 this.closing = true;
341                 this._trigger( "close", event, { tooltip: tooltip } );
342                 this.closing = false;
343         },
345         _tooltip: function( element ) {
346                 var id = "ui-tooltip-" + increments++,
347                         tooltip = $( "<div>" )
348                                 .attr({
349                                         id: id,
350                                         role: "tooltip"
351                                 })
352                                 .addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
353                                         ( this.options.tooltipClass || "" ) );
354                 $( "<div>" )
355                         .addClass( "ui-tooltip-content" )
356                         .appendTo( tooltip );
357                 tooltip.appendTo( this.document[0].body );
358                 if ( $.fn.bgiframe ) {
359                         tooltip.bgiframe();
360                 }
361                 this.tooltips[ id ] = element;
362                 return tooltip;
363         },
365         _find: function( target ) {
366                 var id = target.data( "ui-tooltip-id" );
367                 return id ? $( "#" + id ) : $();
368         },
370         _removeTooltip: function( tooltip ) {
371                 tooltip.remove();
372                 delete this.tooltips[ tooltip.attr( "id" ) ];
373         },
375         _destroy: function() {
376                 var that = this;
378                 // close open tooltips
379                 $.each( this.tooltips, function( id, element ) {
380                         // Delegate to close method to handle common cleanup
381                         var event = $.Event( "blur" );
382                         event.target = event.currentTarget = element[0];
383                         that.close( event, true );
385                         // Remove immediately; destroying an open tooltip doesn't use the
386                         // hide animation
387                         $( "#" + id ).remove();
389                         // Restore the title
390                         if ( element.data( "ui-tooltip-title" ) ) {
391                                 element.attr( "title", element.data( "ui-tooltip-title" ) );
392                                 element.removeData( "ui-tooltip-title" );
393                         }
394                 });
395         }
398 }( jQuery ) );