Jetpack/Collections

From MozillaWiki
Jump to navigation Jump to search

Collections

This describes a future module that will have a coherent set of collection/list classes, with a common API, similar to java.util.Collection.

Functionality

  • Base API implemented by all collections
    • similar to java.util.Collection probably
    • with observers to allow you to subscribe to changes and be notified when items are added or remoted
    • Ability to specify identity and sorting for items, e.g. "if |id| property matches, it's the same item" and "sort on |name| property" or "if a.name > b.name, then a > b"
  • Operators on the collections
    • Operate on whole lists, e.g. allItems = add(serverItems, localItems);
    • Follow the observers
    • add
    • subtract
    • sort
  • Some basic collection implementations
    • Array - ordered list of items, integer-indexed - based on JS Array
    • Map - key/value pair - based on JS Object properties
    • OrderedMap - allows fast lookup
    • Set
    • OrderedMap
    • Others can live in third-party modules, but if they adhere to the common APi, they would fit in nicely
  • UI
    • Listbox, tree node childred etc. all could accept such lists
    • E.g. listbox.showList(allItems)
    • The UI then updates automatically based on changes of the underlying list, and the programmer doesn't have to think about it.
    • The API of the UI then wouldn't need "add/remoteItem" functions.
  • Other classes
    • Pretty much anything that takes a list in Jetpack could be using this API, at least optionally.

Example

Show only those server items which are not in local items, i.e. only offer new stuff

var serverItems = new JArray();
serverItems.add(itemA);
serverItems.add(itemB);
var localItems = listAllMyItems(path);
var offerItems = subtract(serverItems, localItems);
var listbox = E("itemsList");
listbox.showList(offerItems);

That's all already. Now, when items are added to serverItems, they automatically appear in the list UI (without any further app code), *unless* they are in localItems.

That's because the subtract operator automatically subscribed to changes in serverItems. If you then later do serverItems.add(itemC), the subtract operator would check whether the same items is in localItem, and only if it's not, it would add it to offerItems. listbox.showList() in turn automatically subscribed to changes in offerItems, gets notified about the new item there, and shows it. All of that would happen just with the above code lines, there is no additional code needed to follow updates.

Of course, if you wanted to show both serverItems and localItems in the UI, you would do |add()| instead of |substract()|.

This means that you don't need additional wiring to make the UI update after the user (or server) did add/remove item actions, you only need to update the underlying lists.

API

(extract, full docs will be in module)

Collection

Base class for all lists.

  * Adds one item to the list
  * @param item {Object} any JS object
 add : function(item)
 
  * Removes one item from the list
  * @param item {Object} any JS object
 remove : function(item)
 
  * Removes all items from the list.
 clear : function()
 
  * The number of items in this list
  * @returns {Integer} (always >= 0)
 get length()
 
  * Whether there are items in this list
  * @returns {Boolean}
 get isEmpty()
 
  * Checks whether this item is in the list.
  * @returns {Boolean}
 contains : function(item)
 
  * Returns all items contained in this list,
  * as a new JS array (so calling this can be expensive).
  *
  * If the list is ordered, the result of this function
  * is ordered in the same way.
  *
  * While the returned array is a copy, the items
  * are not, so changes to the array do not affect
  * the list, but changes to its items do change the
  * items in the list.
  *
  * @returns {Array} new JS array with all items
 contents : function(item)
 
  * Pass an object that will be called when
  * items are added or removed from this list.
  * If you call this twice for the same observer, the second is a no-op.
  * @param observer {CollectionObserver}
 registerObserver : function(observer)
 
  * undo |registerObserver|
  * @param observer {CollectionObserver}
 unregisterObserver : function(observer)

KeyValueCollection

A collection where entries have a key or label or index.

Examples of subclasses: Array (key = index), Map

  * Sets the value for |key|
  * @param key
 set : function(key, item)
 
  * Gets the value for |key|
  * If the key doesn't exist, returns |undefined|.
  * @param key
 get : function(key)
 
  * Remove the key and its corresponding value item.
  * undo set(key, item)
 removeKey : function(key)
 
  * @returns {Boolean}
 containsKey : function(key)
 
  * Searches the whole list for this |value|.
  * and if found, returns the (first) key for it.
  * If not found, returns undefined.
  * @returns key
 getKeyForValue : function(value)

CollectionObserver

  * Called after an item has been added to the list.
  * @param item {Object} the removed item
  * @param coll {Collection} the observed list. convenience only.
 added : function(item, list)
 
  * Called after an item has been removed from the list
  *
  * TODO should clear() call removed() for each item?
  * Currently: yes.
  * Alternative: separate cleared()
  *
  * @param item {Object} the removed item
  * @param coll {Collection} the observed list. convenience only.
 removed : function(item, coll)

Operators

  * Returns a collection that contains all values from coll1 and coll2.
  * If the same item is in both coll1 and coll2, it will be added only once.
  *
  * @param coll1 {Collection}
  * @param coll2 {Collection}
  * @returns {Collection}
 function addColl(coll1, coll2)
 
  * Returns a collection that contains all values from coll1 and coll2.
  * If the same item is in both coll1 and coll2, it will be added twice.
  *
  * @param coll1 {Collection}
  * @param coll2 {Collection}
  * @returns {Collection}
 function addCollWithDups(coll1, coll2)
 
  * Returns a collection that contains all values from collBase,
  * apart from those in collSubtract.
  *
  * @param collBase {Collection}
  * @param collSubtract {Collection}
  * @returns {Collection}
 function subtractColl(collBase, collSubtract)
 
  * Returns a new collection that is sorted based on the |sortFunc|.
  *
  * @param coll {Collection}
  * @param sortFunc(a {Item}, b {Item})
  *     returns {Boolean} a > b
  * @returns {Collection}
 function sortColl(coll, sortFunc)

Implementation

  • Ben Bucksch will provide API, base implementation, and operators
  • M.-A. Darche will provide OrderedMap and possibly Set and OrderedSet
  • Others are welcome to make UI elements support these collections.

Feedback needed

Comparation of items

How to specify identity and sorting for items, e.g. "if |id| property matches, it's the same item" and "sort on |name| property" or "if a.name > b.name, then a > b"

Options:

  • base class for items -- convenient, but doesn't allow to collect objects not supporting this API, also doesn't allow to specify different sorting based on situation
  • Operators and Set and Ordered* collections take functions that can compare the objects -- cumbersome, because it needs to be specified for every use
  • specify id and sortBy properties or -- more convenient
  • specify isSameObject() and isGreaterThan() functions - more flexible