mediawiki-extensions-Echo/modules/viewmodel/mw.echo.dm.List.js
Moriel Schottlender 1ac72cc01a Split alerts and messages in Echo
Split the notifications into 'alert' and 'message' badget with two
different flyouts. Also clean up styling and module behavior.

** Depends on ooui change Id4bbe14ba0bf6c for footers in popups.
** Depends on ooui change Ie93e4d6ed5637c for fixing a bug in
   inverted icons.

** MobileFrontend must also be updated to support the new modules
   in this patch  I168f485d6e54cb4067

In this change:
* Split notifcations into alert and messages and display those in
  two different badges.
* Create two separate flyout/popups for each category with their
  notifications.
* Create a view-model to control notification state and emit events
  for both the popup and the badge to intercept and react to.
* Clean up module load and distribution:
  * Create an ext.echo.ui module for javascript-ui support and ooui
    widgets.
  * Create an ext.echo.nojs module that unifies all base classes that
    are needed for both nojs and js support, that the js version
    builds upon.
  * Create a separate ext.echo.logger module as a singleton that can
    be called to perform all logging.
* Clean up style uses
  * Move the special page LESS file into nojs module so all styles
    load properly even in nojs mode.
  * Transfer some of the styling from JS to LESS for consistency.
  * Make the 'read more' button load already with the styles it
    needs to look like a button, since its behavior is similar in
    nojs and js vesions, but before its classes were applied only
    by the js, making it inconsistent and also making its appearance
    'jump' from a link to a button.
* Delete and clean up all old and unused files.
* Moved 'Help.png' icon from modules/overlay to modules/icons for
  later use.

Bug: T108190
Change-Id: I55f440ed9f64c46817f620328a6bb522d44c9ca9
2015-09-02 15:36:37 -07:00

273 lines
7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

( function ( $, mw ) {
/**
* Echo List mixin
*
* @mixin
* @abstract
* @constructor
* @param {Object} config Configuration options
*/
mw.echo.dm.List = function mwFlowDmList( config ) {
// Configuration initialization
config = config || {};
this.items = [];
// Store references to items by their ids
this.itemsById = {};
this.aggregateItemEvents = {};
};
/* Events */
/**
* @event add Items have been added
* @param {mw.echo.dm.NotificationItem[]} items Added items
* @param {number} index Index items were added at
*/
/**
* @event remove Items have been removed
* @param {mw.echo.dm.NotificationItem[]} items Removed items
*/
/**
* @event clear All items have been removed
*/
/* Methods */
/**
* Get all items
*
* @return {mw.echo.dm.NotificationItem[]} Items in the list
*/
mw.echo.dm.List.prototype.getItems = function () {
return this.items.slice( 0 );
};
/**
* Get an item by its id
* @param {string} id Item id
* @return {mw.echo.dm.NotificationItem} Item
*/
mw.echo.dm.List.prototype.getItemById = function ( id ) {
return this.itemsById[ id ];
};
/**
* Get the index of a specific item
*
* @param {mw.echo.dm.NotificationItem} item Requested item
* @return {number} Index of the item
*/
mw.echo.dm.List.prototype.getItemIndex = function ( item ) {
return this.items.indexOf( item );
};
/**
* Get number of items
*
* @return {number} Number of items in the list
*/
mw.echo.dm.List.prototype.getItemCount = function () {
return this.items.length;
};
/**
* Check if a list contains no items.
*
* @return {boolean} Group is empty
*/
mw.echo.dm.List.prototype.isEmpty = function () {
return !this.items.length;
};
/**
* Aggregate the events emitted by the group.
* Taken from oojs-ui's OO.ui.GroupElement#aggregate
*
* When events are aggregated, the group will listen to all contained items for the event,
* and then emit the event under a new name. The new event will contain an additional leading
* parameter containing the item that emitted the original event. Other arguments emitted from
* the original event are passed through.
*
* @param {Object.<string,string|null>} events An object keyed by the name of the event that should be
* aggregated (e.g., click) and the value of the new name to use (e.g., groupClick).
* A `null` value will remove aggregated events.
* @throws {Error} An error is thrown if aggregation already exists.
*/
mw.echo.dm.List.prototype.aggregate = function ( events ) {
var i, len, item, add, remove, itemEvent, groupEvent;
for ( itemEvent in events ) {
groupEvent = events[ itemEvent ];
// Remove existing aggregated event
if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
// Don't allow duplicate aggregations
if ( groupEvent ) {
throw new Error( 'Duplicate item event aggregation for ' + itemEvent );
}
// Remove event aggregation from existing items
for ( i = 0, len = this.items.length; i < len; i++ ) {
item = this.items[ i ];
if ( item.connect && item.disconnect ) {
remove = {};
remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
item.disconnect( this, remove );
}
}
// Prevent future items from aggregating event
delete this.aggregateItemEvents[ itemEvent ];
}
// Add new aggregate event
if ( groupEvent ) {
// Make future items aggregate event
this.aggregateItemEvents[ itemEvent ] = groupEvent;
// Add event aggregation to existing items
for ( i = 0, len = this.items.length; i < len; i++ ) {
item = this.items[ i ];
if ( item.connect && item.disconnect ) {
add = {};
add[ itemEvent ] = [ 'emit', groupEvent, item ];
item.connect( this, add );
}
}
}
}
};
/**
* Add items
*
* @param {mw.echo.dm.NotificationItem[]} items Items to add
* @param {number} index Index to add items at
* @chainable
* @fires add
*/
mw.echo.dm.List.prototype.addItems = function ( items, index ) {
var i, len, item, event, events, currentIndex, existingItem, at;
if ( items.length === 0 ) {
return this;
}
// Support adding existing items at new locations
for ( i = 0, len = items.length; i < len; i++ ) {
item = items[i];
existingItem = this.getItemById( item.getId() );
// Check if item exists then remove it first, effectively "moving" it
currentIndex = this.items.indexOf( existingItem );
if ( currentIndex >= 0 ) {
this.removeItems( [ existingItem ] );
// Adjust index to compensate for removal
if ( currentIndex < index ) {
index--;
}
}
// Add the item
if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) {
events = {};
for ( event in this.aggregateItemEvents ) {
events[ event ] = [ 'emit', this.aggregateItemEvents[ event ], item ];
}
item.connect( this, events );
}
// Add by reference
this.itemsById[ item.getId() ] = items[i];
}
if ( index === undefined || index < 0 || index >= this.items.length ) {
at = this.items.length;
this.items.push.apply( this.items, items );
} else if ( index === 0 ) {
at = 0;
this.items.unshift.apply( this.items, items );
} else {
at = index;
this.items.splice.apply( this.items, [ index, 0 ].concat( items ) );
}
this.emit( 'add', items, at );
return this;
};
/**
* Remove items
*
* @param {mw.echo.dm.NotificationItem[]} items Items to remove
* @chainable
* @fires remove
*/
mw.echo.dm.List.prototype.removeItems = function ( items ) {
var i, len, item, index, remove, itemEvent,
removed = [];
if ( items.length === 0 ) {
return this;
}
// Remove specific items
for ( i = 0, len = items.length; i < len; i++ ) {
item = items[ i ];
index = this.items.indexOf( item );
if ( index !== -1 ) {
if (
item.connect && item.disconnect &&
!$.isEmptyObject( this.aggregateItemEvents )
) {
remove = {};
if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
}
item.disconnect( this, remove );
}
this.items.splice( index, 1 );
// Remove reference by Id
delete this.itemsById[ item.getId() ];
}
}
this.emit( 'remove', removed );
return this;
};
/**
* Clear all items
*
* @fires clear
*/
mw.echo.dm.List.prototype.clearItems = function () {
var i, len, item, remove, itemEvent;
// Remove all items
for ( i = 0, len = this.items.length; i < len; i++ ) {
item = this.items[ i ];
if (
item.connect && item.disconnect &&
!$.isEmptyObject( this.aggregateItemEvents )
) {
remove = {};
if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
}
item.disconnect( this, remove );
}
}
this.items = [];
this.itemsById = {};
this.emit( 'clear' );
return this;
};
}( jQuery, mediaWiki ) );