Brief history of the ASF Board Agenda tool
The gold standard of server side web applications is Model, View, Controller. Early versions of this tool was not written that way: it was a CGI script that grew like a weed. Over time, some JQuery effects were added.
The first major rewrite was done using Angular.js and Bootstrap. These frameworks enabled me to do things I had never done before. It also required me to write code that watched for changes, and to ensure that changes were applied in place (specifically arrays and hashes could not be replaced, they had to be updated).
While Angular.js used terms like Directives, Filters, and Services, the overall effect was to impose a structure on the client side application. As with most things, this structure was both constraining and freeing.
The current rewrite replaces Angular.js with React.js. Gone is all watches and the need to update things in place. In its place is a policy of “rerender everything” whenever an event (a keystroke, a mouse click, a server side event) occurs. With React.JS, rerendering everything is efficient as React computes a delta and then only applies the delta to the DOM. React.JS does provide a suggested architecture, namely Flux, that minimizes the need to rerender everything, but in practice I have not found that necessary.
To illustrate, if you bring up the “Call to order” page and press and hold down the right arrow key, every page of the agenda will be flashed up and promptly replaced.
The overall resulting flow is as follows: when a page is fetched the response starts out with a pre-rendered representation (simple HTML), followed by the scripts needed to produce that page, followed by the data used by those scripts. This ensures that the data is presented promptly, then become reactive to input and events.
The resulting architecture isn’t MVC on either the client or the server. Instead, V and C get mushed together, and a unified client/server event stream is added.
Events are received from the server using Server Sent Events. This is widely implemented, and has a solid polyfill for browsers (most notably, IE) that haven’t implemented this standard. Its one way data flow is a good fit for React.js.
Events are generally triggered by actions on a client browser window somewhere (typically a mouse click) resulting in a HTTP GET or POST request being sent to the server, but can also be triggered by file system changes on the server (example: a cron job does a svn update, which causes the agenda to contain new data).
A single event-stream is maintained per browser, and that process is responsible for propagating updates to all tabs and windows. Events can be sent to all clients, or only clients authenticated with a given user id. This enables my pending updates to be immediately reflected on all of my tabs and windows but not affect others. The result of an event is to update one or more models, and then trigger a re-render.
Models on both the client and server are simple classes. Class methods operate on the entity as a whole (example: write the whole agenda to disk on the server, or provide an index for the agenda on the client). Instance methods refer to an individual item (example: an agenda item).
What’s left is React Components on the client and actions on the server.
React components have a render method. That method has full (read-only) access to client models, and can do if statements, iterate over result, and (generally minor) computations. More extensive computations should be refactored to other methods in the component when limited in scope to a single component, or to the client model otherwise. The one limitation that is enforced is that render methods can not directly or indirectly change state. A predefined life-cycle is defined. Other methods can be added, for example methods to handle onClick events.
These methods can trigger HTTP POST and GET requests (the convenience method I provide for the latter is called fetch instead). These run small scripts on the server that may update models, generate events, and return JSON.
Taken together, the current implementation is a lot more fun to develop and easier to maintain than prior versions. As an example, if it were decided that the moment the secretary clicked the ‘timestamp` button on the 'Call to order’ page, all comment buttons are to be removed from all windows and all comment modal dialogs are to be closed, this could be implemented using a single if statement as the event is already propagated, and a re-render is already triggered. All that would be required is to change the conditions under which the comment button appears.
The board agenda tool has been tested on Linux, Mac OS/X, Vagrant, and Docker. It contains a suite of tests.