2 * Implements mediaWiki.notification library
7 var isPageReady
= false,
9 preReadyNotifQueue
= [],
12 * The #mw-notification-area div that all notifications are contained inside.
17 * Creates a Notification object for 1 message.
18 * Does not insert anything into the document (see .start()).
21 * @see mw.notification.notify
23 function Notification( message
, options
) {
24 var $notification
, $notificationTitle
, $notificationContent
;
26 $notification
= $( '<div class="mw-notification"></div>' )
27 .data( 'mw.notification', this )
28 .addClass( options
.autoHide
? 'mw-notification-autohide' : 'mw-notification-noautohide' );
31 // Sanitize options.tag before it is used by any code. (Including Notification class methods)
32 options
.tag
= options
.tag
.replace( /[ _\-]+/g, '-' ).replace( /[^\-a-z0-9]+/ig, '' );
34 $notification
.addClass( 'mw-notification-tag-' + options
.tag
);
40 if ( options
.title
) {
41 $notificationTitle
= $( '<div class="mw-notification-title"></div>' )
42 .text( options
.title
)
43 .appendTo( $notification
);
46 $notificationContent
= $( '<div class="mw-notification-content"></div>' );
48 if ( typeof message
=== 'object' ) {
49 // Handle mw.Message objects separately from DOM nodes and jQuery objects
50 if ( message
instanceof mw
.Message
) {
51 $notificationContent
.html( message
.parse() );
53 $notificationContent
.append( message
);
56 $notificationContent
.text( message
);
59 $notificationContent
.appendTo( $notification
);
61 // Private state parameters, meant for internal use only
62 // isOpen: Set to true after .start() is called to avoid double calls.
63 // Set back to false after .close() to avoid duplicating the close animation.
64 // isPaused: false after .resume(), true after .pause(). Avoids duplicating or breaking the hide timeouts.
65 // Set to true initially so .start() can call .resume().
66 // message: The message passed to the notification. Unused now but may be used in the future
67 // to stop replacement of a tagged notification with another notification using the same message.
68 // options: The options passed to the notification with a little sanitization. Used by various methods.
69 // $notification: jQuery object containing the notification DOM node.
72 this.message
= message
;
73 this.options
= options
;
74 this.$notification
= $notification
;
78 * Start the notification.
79 * This inserts it into the page, closes any matching tagged notifications,
80 * handles the fadeIn animations and repacement transitions, and starts autoHide timers.
82 Notification
.prototype.start = function () {
85 $notification
, options
,
86 // Original opacity so that we can animate back to it later
88 // Other notification elements matching the same tag
99 options
= this.options
;
100 $notification
= this.$notification
;
102 opacity
= this.$notification
.css( 'opacity' );
104 // Set the opacity to 0 so we can fade in later.
105 $notification
.css( 'opacity', 0 );
108 // Check to see if there are any tagged notifications with the same tag as the new one
109 $tagMatches
= $area
.find( '.mw-notification-tag-' + options
.tag
);
112 // If we found a tagged notification use the replacement pattern instead of the new
113 // notification fade-in pattern.
114 if ( options
.tag
&& $tagMatches
.length
) {
116 // Iterate over the tag matches to find the outerHeight we should use
117 // for the placeholder.
119 $tagMatches
.each( function () {
120 var notif
= $( this ).data( 'mw.notification' );
122 // Use the notification's height + padding + border + margins
123 // as the placeholder height.
124 outerHeight
= notif
.$notification
.outerHeight( true );
125 if ( notif
.$replacementPlaceholder
) {
126 // Grab the height of a placeholder that has not finished animating.
127 placeholderHeight
= notif
.$replacementPlaceholder
.height();
128 // Remove any placeholders added by a previous tagged
129 // notification that was in the middle of replacing another.
130 // This also makes sure that we only grab the placeholderHeight
131 // for the most recent notification.
132 notif
.$replacementPlaceholder
.remove();
133 delete notif
.$replacementPlaceholder
;
135 // Close the previous tagged notification
136 // Since we're replacing it do this with a fast speed and don't output a placeholder
137 // since we're taking care of that transition ourselves.
138 notif
.close( { speed
: 'fast', placeholder
: false } );
141 if ( placeholderHeight
!== undefined ) {
142 // If the other tagged notification was in the middle of replacing another
143 // tagged notification, continue from the placeholder's height instead of
144 // using the outerHeight of the notification.
145 outerHeight
= placeholderHeight
;
149 // Insert the new notification before the tagged notification(s)
150 .insertBefore( $tagMatches
.first() )
152 // Use an absolute position so that we can use a placeholder to gracefully push other notifications
153 // into the right spot.
154 position
: 'absolute',
155 width
: $notification
.width()
157 // Fade-in the notification
158 .animate( { opacity
: opacity
},
161 complete: function () {
162 // After we've faded in clear the opacity and let css take over
163 $( this ).css( { opacity
: '' } );
167 // Create a clear placeholder we can use to make the notifications around the notification that is being
168 // replaced expand or contract gracefully to fit the height of the new notification.
170 self
.$replacementPlaceholder
= $( '<div>' )
171 // Set the height to the space the previous notification or placeholder took
172 .css( 'height', outerHeight
)
173 // Make sure that this placeholder is at the very end of this tagged notification group
174 .insertAfter( $tagMatches
.eq( -1 ) )
175 // Animate the placeholder height to the space that this new notification will take up
176 .animate( { height
: $notification
.outerHeight( true ) },
178 // Do space animations fast
180 complete: function () {
181 // Reset the notification position after we've finished the space animation
182 // However do not do it if the placeholder was removed because another tagged
183 // notification went and closed this one.
184 if ( self
.$replacementPlaceholder
) {
185 $notification
.css( 'position', '' );
187 // Finally, remove the placeholder from the DOM
192 // Append to the notification area and fade in to the original opacity.
195 .animate( { opacity
: opacity
},
198 complete: function () {
199 // After we've faded in clear the opacity and let css take over
200 $( this ).css( 'opacity', '' );
206 // By default a notification is paused.
207 // If this notification is within the first {autoHideLimit} notifications then
208 // start the auto-hide timer as soon as it's created.
209 var autohideCount
= $area
.find( '.mw-notification-autohide' ).length
;
210 if ( autohideCount
<= notification
.autoHideLimit
) {
216 * Pause any running auto-hide timer for this notification
218 Notification
.prototype.pause = function () {
219 if ( this.isPaused
) {
222 this.isPaused
= true;
224 if ( this.timeout
) {
225 clearTimeout( this.timeout
);
231 * Start autoHide timer if not already started.
232 * Does nothing if autoHide is disabled.
233 * Either to resume from pause or to make the first start.
235 Notification
.prototype.resume = function () {
237 if ( !notif
.isPaused
) {
240 // Start any autoHide timeouts
241 if ( notif
.options
.autoHide
) {
242 notif
.isPaused
= false;
243 notif
.timeout
= setTimeout( function () {
244 // Already finished, so don't try to re-clear it
245 delete notif
.timeout
;
247 }, notification
.autoHideSeconds
* 1000 );
252 * Close/hide the notification.
254 * @param {Object} options An object containing options for the closing of the notification.
255 * These are typically only used internally.
256 * - speed: Use a close speed different than the default 'slow'.
257 * - placeholder: Set to false to disable the placeholder transition.
259 Notification
.prototype.close = function ( options
) {
260 if ( !this.isOpen
) {
264 // Clear any remaining timeout on close
267 options
= $.extend( {
272 // Remove the mw-notification-autohide class from the notification to avoid
273 // having a half-closed notification counted as a notification to resume
274 // when handling {autoHideLimit}.
275 this.$notification
.removeClass( 'mw-notification-autohide' );
277 // Now that a notification is being closed. Start auto-hide timers for any
278 // notification that has now become one of the first {autoHideLimit} notifications.
279 notification
.resume();
283 // Don't trigger any mouse events while fading out, just in case the cursor
284 // happens to be right above us when we transition upwards.
285 pointerEvents
: 'none',
286 // Set an absolute position so we can move upwards in the animation.
287 // Notification replacement doesn't look right unless we use an animation like this.
288 position
: 'absolute',
289 // We must fix the width to avoid it shrinking horizontally.
290 width
: this.$notification
.width()
292 // Fix the top/left position to the current computed position from which we
293 // can animate upwards.
294 .css( this.$notification
.position() );
296 // This needs to be done *after* notification's position has been made absolute.
297 if ( options
.placeholder
) {
298 // Insert a placeholder with a height equal to the height of the
299 // notification plus it's vertical margins in place of the notification
300 var $placeholder
= $( '<div>' )
301 .css( 'height', this.$notification
.outerHeight( true ) )
302 .insertBefore( this.$notification
);
305 // Animate opacity and top to create fade upwards animation for notification closing
311 duration
: options
.speed
,
312 complete: function () {
313 // Remove the notification
315 if ( options
.placeholder
) {
316 // Use a fast slide up animation after closing to make it look like the notifications
317 // below slide up into place when the notification disappears
318 $placeholder
.slideUp( 'fast', function () {
319 // Remove the placeholder
328 * Helper function, take a list of notification divs and call
329 * a function on the Notification instance attached to them
331 * @param {jQuery} $notifications A jQuery object containing notification divs
332 * @param {string} fn The name of the function to call on the Notification instance
334 function callEachNotification( $notifications
, fn
) {
335 $notifications
.each( function () {
336 var notif
= $( this ).data( 'mw.notification' );
345 * (don't call before document ready)
348 if ( !isInitialized
) {
349 isInitialized
= true;
350 $area
= $( '<div id="mw-notification-area"></div>' )
351 // Pause auto-hide timers when the mouse is in the notification area.
353 mouseenter
: notification
.pause
,
354 mouseleave
: notification
.resume
356 // When clicking on a notification close it.
357 .on( 'click', '.mw-notification', function () {
358 var notif
= $( this ).data( 'mw.notification' );
363 // Stop click events from <a> tags from propogating to prevent clicking.
364 // on links from hiding a notification.
365 .on( 'click', 'a', function ( e
) {
369 // Prepend the notification area to the content area and save it's object.
370 mw
.util
.$content
.prepend( $area
);
376 * Pause auto-hide timers for all notifications.
377 * Notifications will not auto-hide until resume is called.
380 callEachNotification(
381 $area
.children( '.mw-notification' ),
387 * Resume any paused auto-hide timers from the beginning.
388 * Only the first {autoHideLimit} timers will be resumed.
390 resume: function () {
391 callEachNotification(
392 // Only call resume on the first {autoHideLimit} notifications.
393 // Exclude noautohide notifications to avoid bugs where {autoHideLimit}
394 // { autoHide: false } notifications are at the start preventing any
395 // auto-hide notifications from being autohidden.
396 $area
.children( '.mw-notification-autohide' ).slice( 0, notification
.autoHideLimit
),
402 * Display a notification message to the user.
404 * @param {mixed} message The DOM-element, jQuery object, mw.Message instance,
405 * or plaintext string to be used as the message.
406 * @param {Object} options The options to use for the notification.
407 * See mw.notification.defaults for details.
409 notify: function ( message
, options
) {
411 options
= $.extend( {}, notification
.defaults
, options
);
413 notif
= new Notification( message
, options
);
418 preReadyNotifQueue
.push( notif
);
424 * The defaults for mw.notification.notify's options parameter
426 * A boolean indicating whether the notifification should automatically
427 * be hidden after shown. Or if it should persist.
430 * An optional string. When a notification is tagged only one message
431 * with that tag will be displayed. Trying to display a new notification
432 * with the same tag as one already being displayed will cause the other
433 * notification to be closed and this new notification to open up inside
434 * the same place as the previous notification.
437 * An optional title for the notification. Will be displayed above the
438 * content. Usually in bold.
448 * Number of seconds to wait before auto-hiding notifications.
454 * Maximum number of notifications to count down auto-hide timers for.
455 * Only the first {autoHideLimit} notifications being displayed will
456 * auto-hide. Any notifications further down in the list will only start
457 * counting down to auto-hide after the first few messages have closed.
459 * This basically represents the number of notifications the user should
460 * be able to process in {autoHideSeconds} time.
470 // Handle pre-ready queue.
472 while ( preReadyNotifQueue
.length
) {
473 notif
= preReadyNotifQueue
.shift();
478 mw
.notification
= notification
;
480 }( mediaWiki
, jQuery
) );