UI with ReactJS and ImmutableJS
Background
Evolution of Javascript in kent/src
Before JS: just the CGI
JS inlined in HTML
AJAX and JQuery: manipulating the DOM
MV*
An abandoned experiment
ReactJS & JSX
Simplicity of top-down render, made efficient
JSX: HTML-ish with the power of JS
Lifecycle methods of React components
React mixins
The Flux architecture: why not?
ImmutableJS
The new architecture
Bootstrapping: the CGI prints minimal HTML
ImModel: monolithic UI state model using ImmutableJS
Handling user actions
Handling AJAX responses
Undo and redo
ImModel mixins
View: React components using ImmutableJS
Avoid internal state when possible
Using UI state from ImModel
Passing user actions to ImModel
JQueryUi: oil & water with React, but still too useful to drop
The CGI speaks JSON now
One nice feature of Javascript is its object literal notation because it is able to express nested data structures reasonably compactly and, with pretty-printing, in a fairly human-readable form. Javascript data structures without loops or function references can easily be serialized into JSON, a more restricted notation that is easy to parse. That makes JSON an ideal interchange format for data that's not too big when working with Javascript and some other language, in our case C.
The UI model sends JSON-encoded requests to the server/CGI, and expects JSON responses. For example, if the user selects a different clade, the model formulates this command as a Javascript object:
{ changeClade: { newValue: "rodent" } }
js/model/lib/cart.js makes a request to the CGI with the param cjCmd
set to the CGI-encoded JSON string representing that object. Now the CGI needs to
- recognize the
cjCmd
param -- that means output will be JSON, not HTML or text - decode and parse
cjCmd
's JSON value into a C data structure - act on the command(s) as specified in the data structure and write out any reponse data as JSON.
The new lib module hg/inc/cartJson.h provides a function cartJsonExecute
that does part 2 (using src/inc/jsonParse.h) and also part 3 for common commands like changing clade/genome/database or retrieving track & table menu data (using hdb, cart, trackDb etc and src/inc/jsonWrite.h). (For a list of those common commands, see the source code for cartJsonNew
.) It also pushes & pops a warning handler that accumulates warning messages into a string that will be added to the response JSON, e.g. warning: "ftp server timed out etc..."
, that the UI can then present to the user or not. CGIs create a cartJson object, and can plug in their own part 3's by writing handler functions with this signature:
typedef void CartJsonHandler(struct cartJson *cj, struct hash *paramHash); /* Implementation of some command; paramHash associates parameter names with * jsonElement values. */
and then registering them with cartJsonRegisterHandler
.
Here is an example use of cartJson by a CGI that needs one app-specific command, getThingie. First the CGI defines a handler function:
static void getThingie(struct cartJson *cj, struct hash *paramHash) /* Write JSON for an object with some info */ { char *foo = cartJsonOptionalParam(paramHash, "foo"); // ... compute ... use cj->cart if we need the cart ... jsonWriteObjectStart(cj->jw, "thingie"); jsonWriteString(cj->jw, "title", fooDerivative); jsonWriteString(cj->jw, "url", etc); jsonWriteObjectEnd(cj->jw); }
Then when the CGI detects the special parameter cjCmd
, it creates a cartJson object, registers the handler function, and calls cartJsonExecute
to do the rest (including printing out the Content-Type header).
static void doCartJson() /* Perform UI commands to update the cart and/or retrieve cart vars & metadata. */ { struct cartJson *cj = cartJsonNew(cart); cartJsonRegisterHandler(cj, "getThingie", getThingie); cartJsonExecute(cj); } void doMiddle(struct cart *theCart) /* Depending on invocation, either respond to an ajax request or display the main page. */ { cart = theCart; if (cgiOptionalString(CARTJSON_COMMAND)) doCartJson(); else doMainPage(); }