Thunderbird/NextGeneration/Collections
Author: Ben Bucksch (Please check with the author before modifying)
A recurring pattern across the model of many components is to have a list of objects.
- For example, a message folder has a list of messages and a list of other sub-folders.
- An address book is basically just a list of address book cards.
For most of these lists, it is important to be notified of changes, i.e. that they are observable. Most of these lists also need add and remove functions.
Instead of re-creating these common functions, we will use a collection library that creates a standard API and implementation, which allows addition/removal, observing, and higher-level functions like merging lists.
The UI list widgets can then directly subscribe to these observers, and automatically update. The concrete application UI only needs to define how to render a single item, which list to render.
This will dramatically simplify the API and implementation of the entire application.
- For example, the address book API needs to have only 2 properties: The name of the AB and the list of cards. The list is the generic collection API and allows addition and removal of cards.
- Likewise, the account manager only needs a property with the list of accounts, which is a standard collection.
For the API, see:
Example code for main window
To illustrate the power of the concept, here is some example code for the 3-pane main window. This sample contains almost all code necessary to display the account list, folder list and message list, and to react to mouse clicks selecting items and loading them. Missing are the generic list widget implementation and the code to display an individual item.
function start() { try { gAccountListE = new Fastlist(E("account-list")); gFolderListE = new Fastlist(E("folder-list")); gMessageListE = new Fastlist(E("message-list")); gAccountListE.showCollection(accountsManager.accounts); gAccountSelectionObserver.onSelectedItem(null); gFolderSelectionObserver.onSelectedItem(null); gAccountListE.selectedCollection.registerObserver(gAccountSelectionObserver); gFolderListE.selectedCollection.registerObserver(gFolderSelectionObserver); gMessageListE.selectedCollection.registerObserver(gMessageSelectionObserver); } catch (e) { showError(e); } } window.addEventListener("load", start, false); var gAccountSelectionObserver = new SingleSelectionObserver(); gAccountSelectionObserver.onSelectedItem = function(account) { gFolderListE.showCollection(account ? account.folders : new ArrayColl()); }; var gFolderSelectionObserver = new SingleSelectionObserver(); gFolderSelectionObserver.onSelectedItem = function(folder) { gMessageListE.showCollection(folder ? folder.messages : new ArrayColl()); }; var gMessageSelectionObserver = new SingleSelectionObserver(); gMessageSelectionObserver.onSelectedItem = function(message) { if (message) { showMessage(message); } else { // show start page } };
What's happening here is:
- The accounts UI list widget is populated with the accounts list in the model.
- The generic list widget automatically subscribes to changes in the accounts list, and will immediately display any new accounts being added or removed. The same is true for folders and messages.
- The generic list widget also handles selections using mouse and keyboard.
- We instantiate an SingleSelectionObserver for each of the UI list widgets, and let it listen to selections.
- We define what should happen when the user selects an account, a folder and a message.
- If the user selects an account, we display its folders in the folder list.
- If the user select a folder, we display its messages in the message list.
- If the user selects a message, we display the message.
This is most the UI code necessary for basic message reading.
If new messages arrive, or the user creates a new folder, the UI will be automatically updated. That's all in the above code, thanks to the collection classes and the generic list widget.
Smart folders
Smart folders that update immediately when the underlying folders change are also possible with the Collection classes. Given that Collections can merge and subtract collections and dynamically update the result list of these operations, displaying a smart folder is just a matter of assigning the right collection to the message list.
For example, to show all mails from 2 inboxes:
var globalInbox = mergeColl(account1.inbox.messages, account2.inbox.messages); gMessageListE.showCollection(globalInbox);
As new mails are incoming, they will immediately appear, without any further code.
For example, to show all mails in AllMail that are not in Sent folder:
var incomingMails = allMailFolder.messages.subtract(sentFolder.messages); gMessageListE.showCollection(incomingMails);
As you move mails to the Sent folder, they will immediately disappear from the list.