User:MSchottlender-WMF/Notifications UI Architecture

This is an overview of the new Echo notifications UI architecture. The scope of this page is strictly the JavaScript front-end UI.

General structure
The new Echo UI architecture was designed with the MVC model in mind. In general, it has three main components:
 * Controller - The main action-setter of the code. It is responsible for setting up and calling actions through to the API and for building and manipulating the models.
 * Models - These are data components. They are roughly divided into two types - simple and complex.
 * Simple models - The simple models only contain pieces of data without much manipulation. They have setters and getters, and exist to serve as a convenient end-point to request consistent information throughout the different layers.
 * Complex models - The complex models contain pieces of data, but they can also manipulate it, format it, and emit events based on requested actions.
 * View/UI - These are the view elements that draw widgets on the screen. They listen to events from the model and populate themselves accordingly. Their actions are called through the controller.

MVC behavior
As pointed out, the codebase now behaves under the MVC architecture. The order of action should remain strictly in one direction:
 * The widget is calling the controller for an action.
 * The controller calls the API and manipulates the models.
 * The models emit events (primarily an 'update' event) that the widgets listen to.
 * The widgets keep a reference to the model so they can listen to events and read its current state. Widgets should not manipulate the model directly.

Events
In general, the system defines 'update' as its main event. All models emit this event to demonstrate that they have changed, and the prospective widgets listen to this event to update themselves.

The update event
The 'update' event can be emitted from different layers in the system, to update a different scope.

Examples: The scope of the update event can also be demonstrated in the filters and pagination models. In the Special:Notifications page, the mw.echo.ui.PaginationWidget listens to the mw.echo.dm.PaginationModel, which emits an 'update' event when its information is updated. The pagination widget then uses this event to update its state and decide whether to display certain buttons ('forward' / 'backwards' and 'home').
 * An update event from the ModelManager will cause the full NotificationsListWidget (the whole popup list) to be repopulated.
 * An update event from the NotificationsList data model will cause the specific bundled/group widget to update itself. In the case of CrossWiki notifications, each of the sub-groups listen to its own NotificationsList according to its source. When the Cross-wiki item is expanded, the actual notification items are populated into the model, which then emits an 'update' event, and causes the sub-group list widget to populate itself.

The same idea is true for the FiltersModel and the mw.echo.ui.ReadStateButtonSelectWidget which allows the user to choose a display of 'read', 'unread' and 'all' notifications in the Special:Notifications page.

Other events
For the most part, the code is trying to consistently use the 'update' event. However, there are several cases where that is either not practical or not straight forward. In those cases, we use other events that the widgets can listen to.

Examples include: Note that in the two cases above, we want a very specific, scoped response from the widget. If these behaviors would have been covered with an 'update' event, the entire widget would have re-populated itself, which in these cases is an overkill.
 * discard event, emitted by mw.echo.dm.NotificationsList - In some cases (like in Cross-wiki notifications, and in future local bundles) we want to permanently remove an item in case it is read. This is done through the 'discard' method, which emits a 'discard' event for the widget to deal with. In general, this is also done because there may be a difference in behavior when an item is discarded between cross-wiki notifications and local bundles.
 * removeSource event, emitted by mw.echo.dm.CrossWikiNotificationItem - If an entire cross-wiki source becomes empty (after all of its items are marked as read and discarded) then the source itself will be removed. The widget must recognize this to be able to respond accordingly.

The model manager
The model manager is responsible to be an entry-point and management for all the data models that the controller and widgets require. It serves as both a repository of required models as well as a single entity the widgets can listen to 'update' events on when the entire structure is rebuilt.

The model manager has these models:
 * Notifications models
 * FiltersModel
 * PaginationModel

Notifications model
Notification models represent different types of notifications and their data. There can be, in principle, two types of notification models: General local notifications are stored in a local notifications list.
 * NotificationsList - contains an array of notification items
 * NotificationGroupList - contains an array of notification lists

Cross-wiki notifications are all stored in a single data object (mw.echo.dm.CrossWikiNotificationItem) that contains a NotificationsGroupList. Each group represents a remote source.

Special:Notifications page has a NotificationsInboxWidget that works similarly to the cross-wiki notifications widget, except in its NotificationsGroupList each group represents a single day of notifications.

Initially, cross-wiki notifications are populated with only their groups, leaving the notification items empty. When the widget itself is expanded, another request is sent to the server to also fill in the actual items in each group.

Pagination model
The pagination model is an extremely simple model that retains information about pagination (obviously). Every API request returns with a "continue" value, which the pagination model then stores according to the logical page it knows about. The widget can then update itself based on whether a "next" or "previous" page exists, and the controller can request the relevant "continue" value for the API request.

Filters model
The filters model retains information about relevant filters for the Special:Notifications page, like readState ('read', 'unread' and 'all') and source and page information, in case the user is looking at remote notifications.

How the code works
In general, an entire scaffolding is built for each of the popups (alerts and messages) and for the special page. The special page has slightly different behavior for the models, but uses the same base structure.

This section will outline how each of the popups are initialized and how they work:
 * 1) Basic Initialization
 * 2) Asynchronous building of the scaffolding
 * 3) Sending an initial API request
 * 4) Requesting and building all resource loader modules
 * 5) Building the models
 * 6) Popup opening
 * 7) Populating the notifications list

Initialization
Since notifications load for all registered users, the initialization is done in two parts. See ext.echo.init.js for the initialization. When the button is clicked, the following is loaded: The controller takes the API request that has already started and uses it to build the models and send them to the Model Manager.
 * A minimal JavaScript script is initialized to listen to click events on either of the buttons.
 * When one of the buttons is clicked, the full code - including all required modules and libraries - are loaded.
 * An instance of mw.echo.api.EchoApi is initialized and a request is made to fetch relevant notifications
 * ResourceModule loads ext.echo.ui (desktop or mobile) and creates:
 * mw.echo.dm.UnreadNotificationsCounter
 * mw.echo.dm.ModelManager
 * mw.echo.Controller
 * and an mw.echo.ui.NotificationBadgeWidget it then uses to replace the current badge in the DOM.

The badge widget creates a NotificationsListWidget, which listens to the ModelManager for an update event. Once that is fired, it populates itself.