r11 - 13 Jul 2007 - 05:49:07 - MimiYinYou are here: OSAF >  Projects Web  >  CosmoZeroDotSeven > HighLevelCosmoUIEventsProposal

Cosmo UI High Level Events Notification Framework Proposal

Introduction

As the Cosmo UI gets richer and richer, we have more and more independent and semi-independent UI elements (which I will call widgets - which are often but not necessarily Dojo widgets) and other code structures. By independent I mean that there is little or no dependencies on other structures at the same level of abstraction. Two examples are the calendar canvas and the item detail view. These elements are different views to the same data. If one element makes a change to that data, the other element needs to know about it.

The Problem

One way to solve this problem is for each element to let the other know when there it has made a change to the data is a simple, but not scalable approach. For two elements this is no big deal maybe. But if we add another, the original widgets have to be modified so that they can let this new widget know about item updates. And if we add three.....well you get the idea - we have situation where all n widgets need to talk to all other n -1 widgets. Not to mention all the dependencies between supposedly independent widgets that you are introducing.

The Solution

It would be nice if there was one place which notified all the interested parties when (for example) an item is updated. Better still, elements would register themselves as interested parties, so that the the system could accommodate new elements.

Introducing Dojo Topics

Dojo's topics is just such a system - an object subscribes to a topic its interested in, and a method calls publish whenever it wants to broadcast a message on that topic. Now whenever that topic is published, all subscribers get whatever callback they subscribed with invoked. The code might look something like this:

function CalendarCanvas(){ ... this.handleItemChanges = function(message){ updateCanvas(message.item); } dojo.event.topic.subscribe("ItemChanges", this, this.handleItemChanges); ... } function DetailForm(){ ... this.service = new ServiceCalls(); this.submitForm = function(){ validate(); service.save(this.item); } this.handleItemChanges = function(message){ updateDetailForm(message); } dojo.event.topic.subscribe("ItemChanges", this, this.handleItemChanges); ... } function ServiceCalls(){ ... this.save = function(item){ try { //talk to server here ... dojo.event.topic.publish("ItemChanges", new ItemChangedMessage("updated", item)); } catch (error e){ dojo.event.topic.publish("ItemChanges", new ItemChangedMessage("updated", item, error)); } } ... }

When the detail form is submitted (via a call to DetailForm.submitForm()), it in turn calls the service method ServiceCalls.save(). The service method publishes an ItemChangedMessage on the "ItemChanges" topic. If the service call was successful (no exception was thrown) the updated item is put into the Message object as well as the type of change (here, "updated" - other possibilities are "created" and "deleted".) If there was an error, the error object is placed in the message object as well.

Now that the message has been published, all the subscribers to "ItemChanges" will receive this message and be able to update themselves accordingly.

Proposal

Overview

The proposal then is to use dojo's topics as the basis for our high level event notification system. High Level events include changes to data, user actions that affect other views (changing currently selected item), application-wide errors (disconnecting from the server) and others.

Well Defined
The messages that are sent via topics will be well-defined and documented, so that future elements can take advantage of them. Ad-hoc topics or messages should not be created, and existing ones should be either formalized or removed.

Independence
The ideal situation is that no widgets have any direct dependencies on each other and communicate anonymously with each other through topics. Inasmuch as is possible within the preview time-frame as many inter-widget dependencies will be removed and be replaced by this new mechanism. Also, no new inter-widget dependencies will be introduced without a good reason and discussion amongst the team.

When not to...
Topics should not be used where simple method dispatching would do just as well if not better. In other words, if your topic is very case-specific and you only expect one subscriber ever, please re-consider whether or not to use topics.

Service Layer - Backwards Compatability
For the most part, Topics will not affect the API of existing service calls. In other words "getEvents()" is still called "getEvents()" and takes the same parameters (of course, there will be API changes for other reasons, but not because of topics.)

Service Layer - When topics get published
This may be obvious, but Messages will get published on a topic AFTER a service call has been completed - successfully or not.

Whodunnit? It's preferable that the publishing of a particular message happens at one place in the code, so there is no ambiguity as to "who" should do the calling. For some messages, this is pretty clear: since all saving of items happens at the service layer, the service layer should be publishing topics.

Loading an item(s) is not an Event
Loading items will not cause a message to be published. Travis and I agonized over this, but for performance reasons and philosophical reasons we don't think this is a good idea. The rule for model related messages is that messages only get published when something changes - the fact that something loaded is not a change.

Definitions

High Level Event
An event that can be defined semantically, rather than by a particular user action that initiated it. Easier to explain by example: "User clicked save" is not a high level event, but "Item was updated is". "Item was clicked on" is not a high level event but "Item was selected is".

Message
A message is the object which contains information about the event that occurred.

Topic
Topics are a way of grouping different types of messages so that objects only respond to messages that they are interested. Each topic is just an arbitrary string.

Publish
To publish a message is to invoke all the callbacks of all the registered subscribers on the topic of that message.

Subscribe
To subscribe is to register for callbacks for messages that are published on a specific topic.

Implementation

Anatomy of a Message

The base Message prototype should have the following properties:

Message

  • topic - the topic on which the message should get published

Messages which invoke RPC methods have the following additional properties mixed in:

ServiceMessage

  • rid - The id of the asynchronous request.

Finally, let's examine a concrete message (i.e not an abstract one.) Since changes in data are the main drivers for this functionality, let's examine the message objects that would be needed for notifying different parts of the application about data changes. First we need an ItemChangedMessage?:

ItemChangedMessage inherits from Message, mixins: ServiceMessage

  • topic = "ItemChanges" <-- always the same for all ItemChangedMessages?
  • rid = "123124124" <-- some integer
  • type = "updated" <-- either "updated", "created" or "deleted"
  • item = <<object>> <-- the item in its updated form (null if deletion)
  • itemUid = "asd1231241" <-- some string. Why do we need this AND item if item has the UID in it? Deletions.

We also need a message for errors that come back from the service layer:

ServiceErrorMessage inherits from Message, mixins: ServiceMessage

  • topic = "ItemChanges" <-- always the same for all ServiceErrorMessages?
  • rid = "123124124" <-- the rid of the request that failed
  • error = <<error>> <-- An error object, with stack trace if it's a server side message, error message, etc.

Retrofitting with Existing Code, Widgets

The existing code works well, and has been well tested. Preview is just around the corner. How will we possibly have time to re-write all the existing code to use topics instead of their own callbacks on service methods?

The answer is...we don't. We leave the existing code for handling callbacks after service methods complete more or less in place as is. The exception is that we remove the bits in those callbacks which modify other widgets. For example, after an event has been dragged in the calendar canvas widget, the canvas (presumably) has a callback for when the object successfully saves. This callback will update it's own ui, but also others potentially like the detail form. We leave the part in which updates itself, but remove the bits which update the detail form and any other UI elements.

Now that we've done that we add handlers for the ItemChanges? topic in each of the handlers. Since there is existing code which handles events that widget itself propagated, the first thing you do in each handler is ignore messages which are published as a result of service calls of that widget. Something like this:

function handleItemChangedMessage(message){ if (myRequestIdMap.containsKey(message.rid)){ return; } else { //handle stuff here } }

So the upshot is that we're using almost of all the existing code, rewriting very little, and only adding the functionality to handle messages from other widgets.

Note: The above might be a little tricky because I would imagine that the async registry code probably gets rid of the rId after the callback is done, so when the topic handler fires it won't find the rId in the registry and won't realize that it's widget was the propagator of that event. Hmmmm. We could keep old rid's around, and periodically get rid of them using a timer or something. Need to talk to mde about all that.

Suggested Topics and Messages

Topic Message Description
ItemChanges?   For notifications about creations, deletions and updates of data
  ItemChangedMessage?  
CollectionChanges?   For notifications about collection CUD (create, update, delete) and addition/removal of items to/from collections
  CollectionChangedMessage?  
ApplicationEvents?   For notifications about high level Cosmo UI events
  ModalDialogDisplayMessage? Used to notify other widgets that a modal dialog box is being displayed, or undisplayed. Yes, it's sort of a weird non-semantic low-level thing but it's needed so that widgets can show/hide scroll bars which tend to "shine through" our dialog boxes on some platforms.
  ItemSelectedMessage? Notifies others that an item was selected
  CollectionSelectedMessage? Notifies others that a different collections was selected
  ViewDateRangeChangedMessage? Let's other widgets know that the view date range has changed
Edit | WYSIWYG | Attach | Printable | Raw View | Backlinks: Web, All Webs | History: r11 < r10 < r9 < r8 < r7 | More topic actions
 
Open Source Applications Foundation
Except where otherwise noted, this site and its content are licensed by OSAF under an Creative Commons License, Attribution Only 3.0.
See list of page contributors for attributions.