Monday, July 19, 2010

Declarative UI interactions using Reactive Extensions for Javascript

We are creating a page, with a lot of controls, that are talk to each other. For example "When DD1 (DropDown 1) changes, change T1 (TextBox 1), T2, reload U1 (UpdatePanel 1), but only when DD1 and DD2 has changed, update T3).
Using imperative style, your's attempt will defiantly will end with a lot of nested callbacks, and it will be very problematic to "read" original interaction scheme, from the code.

Rx will simplify a bit this problem. It will allow you to describe interactions (and data flow) between these controls, in declarative style.

Let's try to create a page, that contains 2 input text boxes, and will compute the result and write it, to the third text box. The result, will appear, after 1 second, to simulate Ajax call. If we have made many changes, than only last result will be shown to us, instead of displaying all intermediate results. And no events will be triggered, until we sat all initial values, to the controls.

Here is resulting marble diagram (red bars – are ajax calls):

marble1 

So, the diagram shows, that T3 is calculated as T1 + T2. Calculation is made on the server, using ajax calls, and we need to recalculate T3, each time T1 or T2 changes. If Text changes, while calculation is in process (red bar), then it's result should be ignored, and new call made, to recalculate T3 with most recent data. And last thing, during initialization, no events will be fired, but when we "turn on the switch", each text box will fire the event, with it's value.

Implementation

Sample page markup:

JavaScript:

Notes:

@2 – Latch is a sort of a global "power on" switch, when we call Latch.OnComplete(), messages will start cycling around
@3 – AttachToChangeAndSetValue – it's functions that returns observable on control's change event, also, it does some "plumbering", I'll describe it later
@18, @20 – Two observables, Text1Changed and Text2Changed on marble diagram
@21 – Rx CombileLatest expression, that actually do that "value management" that is shown with arrows on a diagram
@27-28 – Actually doing calculations, and simulating Ajax call with duration of 1000ms, returns Observable, with fires, as soon, as value is ready (Ajax call returned, or 1000ms passed)
@32 – Ensures that only latest calculation result will be displayed, all earlier results will be ignored
@43-44 – Starts Computations, by releasing latch

Latch implementation

Function AttachToChangeAndSetValue creates observable from html events, but before returning this observable, it is preceded by Latch observable (@11-15 return Latch.Concat(source)). Semantic of Concat function, says that all events from source will be ignored, until Latch source is depleted. Now, we can freely change control's state (in initialization phase), without any side effects. But, as soon, as we call Latch.OnCompleted(); (@43), Concat will allow to pass change events from source.

At line @42, Latch is instructed to fire dummy event, value from this event is overwritten with control's default value (@11-14), this is for setting default values of control, and to set system to "on - tw" state (on the marble diagram).

Throwing our obsolete ajax responses

Lines @25-30 Maps update requests, to the source of sources of ajax responses. Don't be scared, it's just a list of events, and each of them, will fire when ajax response will return.
For simplification, we don't use ajax, but timer instead. Rx.Observable.Timer(1000) returns an observable, that will fire once (after specified interval). These items are represented as a red bars, on the marble diagram.
Switch function, at line @32, takes this source of sources of ajax responses, and wait until most recent event will fire, this will guarantee, that we will display data from latest recalculation.