Let Me Paint You a Picture
Flight is what Twitter is made with. Flight doesn’t get much press because its specialty isn’t fancy single page app demos with data binding, but for real world web apps built on primarily server-side codebases. Its design is entirely, and solely, components and events. There are no instance variables. There is no magic. Data components fire events to broadcast data, while UI components listen to data events and in turn fire their own UI events.
Flight components are exceptionally decoupled, in no way “take over” your page (unlike Angular’s
ng-app), and are by their decoupled nature very easy to test, to migrate to/from, and to refactor. The cognitive load of inheriting maintenance of existing Flight components or refactoring existing components is dramatically lower than what is possible with Backbone or Angular and you don’t end up leaking or duplicating domain logic into your app like you do with Backbone or any JS framework which includes models.
Option three sounds good to me. So how does Flight propose to deliver these lofty promises?
Everyone’s Talking, Everyone’s Listening
While you define your components in a style very much like regular classes (including have the
this context bound to your component in event handlers), it’s not possible for any component to reference instances of other components. This means you cannot tightly couple APIs, nor any of the related mistakes possible through well-intentioned designs that organically grow out of control. Components may only communicate through events, which are either scoped to the DOM node the component is attached to, or the
document. This is a Flight convention for broadcasting an event to anyone who might wish to hear it.
Due to this, a Flight component doesn’t know or care if it’s talking to another Flight component. There’s no expected interface, because there’s practically no interface at all. If you want a piece of your existing JS to send data to a Flight component, all it has to do is trigger an event with a name the component is listening for, and send the data (e.g.
The Hardest Problem in Computer Science
dataShoppingCart event name hints at the part Flight doesn’t solve for you — event naming. If every component is listening to and triggering all these events, how are you going to keep track of them? At least with a traditional instance-based API you can easily see what’s depending on what, and where the data is expected to come from. You must remember, however, Twitter made this for themselves. They’re not looking to make a framework that will guide beginners, they hire experienced developers and they have internal coding conventions which must be followed.
Coding conventions are exactly what prevents the event naming problem from getting out of hand. At the heart of it, there are two types of events — UI events whose names start with
ui, and data events whose names begin with
data event is always the broadcast of newly available data, while a UI event represents a user interaction. For more guidance on event naming, Tom Hamshere has some tips on naming Flight events from his experience migrating TweetDeck to Flight.
Separation of Concerns
This UI vs data delineation continues into the components themselves, and it is here that I see the biggest payoff from using Flight in the way it is intended. Twitter’s example components for Flight are split into two distinct groups,
components_data. Data components know nothing of the DOM, and UI components never touch the network. By extension, user events are therefore only ever handled in UI components, so you don’t get data processing methods (e.g. XHR form submission) starting with
e.preventDefault(), amongst other anti-patterns.
Entirely Practical Test-Driven Development
Flight Uses the DOM Rather Than Dominates It
Flight convinced me it was a great fit for real-world long-lived products the first time I used it (which was only a month ago). Myself and another developer on my team were working in parallel on an area of the UI which offers a tabbed collaborator selection, where the list of collaborators has search criteria that are applied asynchronously. I was implementing the tab panels and navigation as one UI component, and he was implementing the search controls and results list in another. When we were both finished I went to merge the two git branches, fully expecting both of our JS additions to be broken due to assumptions by the other.
It just worked!
Flight components don’t take any ownership of the DOM nodes to which they’re attached, nor make assumptions about the state of those nodes. There’s no temptation to tightly couple like
this.el from Backbone Views, no
ng-app from Angular — none of those possessive directives. Flight truly utilizes the DOM, it doesn’t mirror it, hijack it, or take an “our way or the highway” framework approach. Since then I’ve gone back and refactored the results list into its own component, and again this required no changes to the expectations or implementation of the neighbouring components. Our code is easy to test, and easy to change, and easy to understand for the next person who needs to read it.