5 // The #mw-notification-area div that all notifications are contained inside.
7 // Number of open notification boxes at any time
8 openNotificationCount
= 0,
10 preReadyNotifQueue
= [],
11 rAF
= window
.requestAnimationFrame
|| setTimeout
;
14 * A Notification object for 1 message.
16 * The underscore in the name is to avoid a bug <https://github.com/senchalabs/jsduck/issues/304>.
17 * It is not part of the actual class name.
19 * @class mw.Notification_
20 * @alternateClassName mw.Notification
22 * @constructor The constructor is not publicly accessible; use mw.notification#notify instead.
23 * This does not insert anything into the document (see #start).
26 function Notification( message
, options
) {
27 var $notification
, $notificationTitle
, $notificationContent
;
29 $notification
= $( '<div class="mw-notification"></div>' )
30 .data( 'mw.notification', this )
31 .addClass( options
.autoHide
? 'mw-notification-autohide' : 'mw-notification-noautohide' );
34 // Sanitize options.tag before it is used by any code. (Including Notification class methods)
35 options
.tag
= options
.tag
.replace( /[ _\-]+/g, '-' ).replace( /[^\-a-z0-9]+/ig, '' );
37 $notification
.addClass( 'mw-notification-tag-' + options
.tag
);
44 // Sanitize options.type
45 options
.type
= options
.type
.replace( /[ _\-]+/g, '-' ).replace( /[^\-a-z0-9]+/ig, '' );
46 $notification
.addClass( 'mw-notification-type-' + options
.type
);
49 if ( options
.title
) {
50 $notificationTitle
= $( '<div class="mw-notification-title"></div>' )
51 .text( options
.title
)
52 .appendTo( $notification
);
55 $notificationContent
= $( '<div class="mw-notification-content"></div>' );
57 if ( typeof message
=== 'object' ) {
58 // Handle mw.Message objects separately from DOM nodes and jQuery objects
59 if ( message
instanceof mw
.Message
) {
60 $notificationContent
.html( message
.parse() );
62 $notificationContent
.append( message
);
65 $notificationContent
.text( message
);
68 $notificationContent
.appendTo( $notification
);
70 // Private state parameters, meant for internal use only
71 // isOpen: Set to true after .start() is called to avoid double calls.
72 // Set back to false after .close() to avoid duplicating the close animation.
73 // isPaused: false after .resume(), true after .pause(). Avoids duplicating or breaking the hide timeouts.
74 // Set to true initially so .start() can call .resume().
75 // message: The message passed to the notification. Unused now but may be used in the future
76 // to stop replacement of a tagged notification with another notification using the same message.
77 // options: The options passed to the notification with a little sanitization. Used by various methods.
78 // $notification: jQuery object containing the notification DOM node.
81 this.message
= message
;
82 this.options
= options
;
83 this.$notification
= $notification
;
87 * Start the notification. Called automatically by mw.notification#notify
88 * (possibly asynchronously on document-ready).
90 * This inserts the notification into the page, closes any matching tagged notifications,
91 * handles the fadeIn animations and replacement transitions, and starts autoHide timers.
95 Notification
.prototype.start = function () {
96 var options
, $notification
, $tagMatches
, autohideCount
;
105 openNotificationCount
++;
107 options
= this.options
;
108 $notification
= this.$notification
;
111 // Find notifications with the same tag
112 $tagMatches
= $area
.find( '.mw-notification-tag-' + options
.tag
);
115 // If we found existing notification with the same tag, replace them
116 if ( options
.tag
&& $tagMatches
.length
) {
118 // While there can be only one "open" notif with a given tag, there can be several
119 // matches here because they remain in the DOM until the animation is finished.
120 $tagMatches
.each( function () {
121 var notif
= $( this ).data( 'mw.notification' );
122 if ( notif
&& notif
.isOpen
) {
123 // Detach from render flow with position absolute so that the new tag can
124 // occupy its space instead.
127 position
: 'absolute',
128 width
: notif
.$notification
.width()
130 .css( notif
.$notification
.position() )
131 .addClass( 'mw-notification-replaced' );
137 .insertBefore( $tagMatches
.first() )
138 .addClass( 'mw-notification-visible' );
140 $area
.append( $notification
);
142 // This frame renders the element in the area (invisible)
144 $notification
.addClass( 'mw-notification-visible' );
149 // By default a notification is paused.
150 // If this notification is within the first {autoHideLimit} notifications then
151 // start the auto-hide timer as soon as it's created.
152 autohideCount
= $area
.find( '.mw-notification-autohide' ).length
;
153 if ( autohideCount
<= notification
.autoHideLimit
) {
159 * Pause any running auto-hide timer for this notification
161 Notification
.prototype.pause = function () {
162 if ( this.isPaused
) {
165 this.isPaused
= true;
167 if ( this.timeout
) {
168 clearTimeout( this.timeout
);
174 * Start autoHide timer if not already started.
175 * Does nothing if autoHide is disabled.
176 * Either to resume from pause or to make the first start.
178 Notification
.prototype.resume = function () {
180 if ( !notif
.isPaused
) {
183 // Start any autoHide timeouts
184 if ( notif
.options
.autoHide
) {
185 notif
.isPaused
= false;
186 notif
.timeout
= setTimeout( function () {
187 // Already finished, so don't try to re-clear it
188 delete notif
.timeout
;
190 }, notification
.autoHideSeconds
* 1000 );
195 * Close the notification.
197 Notification
.prototype.close = function () {
200 if ( !this.isOpen
) {
205 openNotificationCount
--;
207 // Clear any remaining timeout on close
210 // Remove the mw-notification-autohide class from the notification to avoid
211 // having a half-closed notification counted as a notification to resume
212 // when handling {autoHideLimit}.
213 this.$notification
.removeClass( 'mw-notification-autohide' );
215 // Now that a notification is being closed. Start auto-hide timers for any
216 // notification that has now become one of the first {autoHideLimit} notifications.
217 notification
.resume();
220 notif
.$notification
.removeClass( 'mw-notification-visible' );
222 setTimeout( function () {
223 if ( openNotificationCount
=== 0 ) {
224 // Hide the area after the last notification closes. Otherwise, the padding on
225 // the area can be obscure content, despite the area being empty/invisible (T54659). // FIXME
227 notif
.$notification
.remove();
229 notif
.$notification
.slideUp( 'fast', function () {
238 * Helper function, take a list of notification divs and call
239 * a function on the Notification instance attached to them.
243 * @param {jQuery} $notifications A jQuery object containing notification divs
244 * @param {string} fn The name of the function to call on the Notification instance
246 function callEachNotification( $notifications
, fn
) {
247 $notifications
.each( function () {
248 var notif
= $( this ).data( 'mw.notification' );
257 * Must only be called once, and not before the document is ready.
262 var offset
, $window
= $( window
);
264 $area
= $( '<div id="mw-notification-area" class="mw-notification-area mw-notification-area-layout"></div>' )
265 // Pause auto-hide timers when the mouse is in the notification area.
267 mouseenter
: notification
.pause
,
268 mouseleave
: notification
.resume
270 // When clicking on a notification close it.
271 .on( 'click', '.mw-notification', function () {
272 var notif
= $( this ).data( 'mw.notification' );
277 // Stop click events from <a> tags from propogating to prevent clicking.
278 // on links from hiding a notification.
279 .on( 'click', 'a', function ( e
) {
283 // Prepend the notification area to the content area and save it's object.
284 mw
.util
.$content
.prepend( $area
);
285 offset
= $area
.offset();
288 function updateAreaMode() {
289 var isFloating
= $window
.scrollTop() > offset
.top
;
291 .toggleClass( 'mw-notification-area-floating', isFloating
)
292 .toggleClass( 'mw-notification-area-layout', !isFloating
);
295 $window
.on( 'scroll', updateAreaMode
);
302 * @class mw.notification
307 * Pause auto-hide timers for all notifications.
308 * Notifications will not auto-hide until resume is called.
310 * @see mw.Notification#pause
313 callEachNotification(
314 $area
.children( '.mw-notification' ),
320 * Resume any paused auto-hide timers from the beginning.
321 * Only the first #autoHideLimit timers will be resumed.
323 resume: function () {
324 callEachNotification(
325 // Only call resume on the first #autoHideLimit notifications.
326 // Exclude noautohide notifications to avoid bugs where #autoHideLimit
327 // `{ autoHide: false }` notifications are at the start preventing any
328 // auto-hide notifications from being autohidden.
329 $area
.children( '.mw-notification-autohide' ).slice( 0, notification
.autoHideLimit
),
335 * Display a notification message to the user.
337 * @param {HTMLElement|HTMLElement[]|jQuery|mw.Message|string} message
338 * @param {Object} options The options to use for the notification.
339 * See #defaults for details.
340 * @return {mw.Notification} Notification object
342 notify: function ( message
, options
) {
344 options
= $.extend( {}, notification
.defaults
, options
);
346 notif
= new Notification( message
, options
);
351 preReadyNotifQueue
.push( notif
);
359 * The defaults for #notify options parameter.
362 * A boolean indicating whether the notifification should automatically
363 * be hidden after shown. Or if it should persist.
366 * An optional string. When a notification is tagged only one message
367 * with that tag will be displayed. Trying to display a new notification
368 * with the same tag as one already being displayed will cause the other
369 * notification to be closed and this new notification to open up inside
370 * the same place as the previous notification.
373 * An optional title for the notification. Will be displayed above the
374 * content. Usually in bold.
377 * An optional string for the type of the message used for styling:
378 * Examples: 'info', 'warn', 'error'.
389 * Number of seconds to wait before auto-hiding notifications.
395 * Maximum number of notifications to count down auto-hide timers for.
396 * Only the first #autoHideLimit notifications being displayed will
397 * auto-hide. Any notifications further down in the list will only start
398 * counting down to auto-hide after the first few messages have closed.
400 * This basically represents the number of notifications the user should
401 * be able to process in #autoHideSeconds time.
411 // Handle pre-ready queue.
413 while ( preReadyNotifQueue
.length
) {
414 notif
= preReadyNotifQueue
.shift();
419 mw
.notification
= notification
;
421 }( mediaWiki
, jQuery
) );