Straightening our Backbone: A lesson in event-driven UI development
code.mixpanel.com
code.mixpanel.com
I encourage any JavaScript developer to seriously take a look at ClojureScript + React + core.async for front end development. Not a silver bullet, and there's the learning curve of a new language, but definitely better tools to build the foundation for an asynchronous & inherently complex UI. If the problem/project you're solving/working on is not trivial, the payoffs are there.
DOM interactions are callback oriented. That's how the DOM is built. That's why people write callbacks at first place. Why would you want people to work differently? the api is what it is. You might think the api is broken, well, it's not the front end developers fault, they didn't write the spec.
> but definitely better tools to build the foundation for an asynchronous & inherently complex UI.
React and CO are DOM abstractions. Which is fine if you know how the DOM works at first place. The problem is most people don't bother learning how the damn thing really works.
But at the end of the end of day, no front end developer can escape from JavaScript or the DOM. When things break,you still have a big JavaScript stack trace with DOM errors to debug,language A or B,framework X,Y,Z or not.
Rather than manipulating or calling components from your mediator, you could tell a coordinator that coordinates services. Services are then where you're housing your business logic. This allows you to structure these service calls and account for execution order.
>View initialization and render are separate steps
yes
> no events are fired during the app’s initial bootstrap/render process
sure
> on a given UI screen, a single top-level mediator takes on all responsibilities of communication/event-dispatching between subviews
> In practice, the code of the Orchestrator view becomes the centralized location and source of truth for all inter-widget communication in its purview
no?
There's a reason why actor systems are a thing. You can't just stuff everything into a central object like it's a flat key/value store. It's better (in my opinion) to form a hierarchy of responsibility. The "Orchestrator" should hire some subordinates and delegate work to them in an effective manner.
You guys must have an insane amount of business logic in your web app for any given UI screen (which as I'm understanding it is different menu options I guess? because otherwise a single UI screen is your entire app). The main takeaway in what I'm saying is a router shouldn't be confused with a component that executes business logic. It's doing too much.
hmm, seems like the diagram wasn't very clear on that point. there's only one asynchronous action there: making a network request back to the server for fresh data, which by its nature has to be an async op. everything else is synchronous (including Backbone's event handlers).
> The "Orchestrator" should hire some subordinates and delegate work to them in an effective manner.
this is precisely what's happening: the orchestrator basically does nothing but delegate between subviews and the model layer, ensuring that messages are passed around as appropriate. the text probably didn't make clear enough that this is supposed to happen at any sufficiently complex portion of the UI ("This pattern is repeatable: any sufficiently large or complex area of an app (such as a single route) may hold its own mediator to encapsulate the details of its UI interactions and communicate with the Model layer with a small, well-defined API"). we don't really consider interactions between UI components to be "business logic;" if the 'mediator' started turning into an uber-object with many responsibilities we would probably look at breaking it into services (or putting the right bits back into the model layer), but we try pretty rigorously to avoid premature abstractions...
We pretty much write desktop apps in this style but run into problems in some complex situations, so I am parsing this for clues to where we may be letting problems sneak in. The rule that programmatic UI updates never raise events may be such a clue, indicating some views are doing too much direct interaction with both local state and subviews.
Question: are there rules to apply to this architecture to help with data modularity up and across the view tree? A lot of the work around React right now has to do with creating techniques to avoid passing state across levels that don't directly depend on it. For example, communicating an event two levels up without involving the intervening parent or communicating across subviews that don't share a common parent. As I read the OP, you have to pass the event all the way up and the state all the way down, coupling the dataflow and triggering rerenders pretty much all along the path.
[1] https://www.youtube.com/watch?v=ZM6wXoFTY3o&feature=youtu.be
I have essentially ported Unix itself onto the web at https://www.urdesk.net . You can say that the site itself is a kind of "mega-app", but given that it is an entire OS-in-a-browser that typically "installs" in < 2 seconds, I think it can be forgiven. There is also an app that is a kind of "Hello World" for AI that uses the Speech Recognition and Voice Synth capabilities of the Chrome browser. To hop directly into the AI, you can check out https://www.urdesk.net/desk?intro=bertie
The site is now Chrome-only, since I am pretty much going all-in with the whole AI theme, and none of the other vendors include the necessary Javascript API's.
EDIT: Already fixed with a nice friendly error message!
If by “web” you mean “an up-to-date version of Chrome”, which was what the site told me was the only thing it worked with.
As far as backbone goes, we've had success using marionette.js to standardize the way we write front-end components and implement common types of view patterns. Curious to see if anyone else has found it to be useful.
It's a definite boon to our development proces, and it has made our solutions easier to maintain. Can't ever see us go back from this approach.
The point is that javascript tends to lead one down an event driven route. But you should only use events if you want to decouple things. Cohesion is also very important in design because it makes things simpler and more encapsulated. You need to decide what the boundaries of your component are. In this case I want the calendar to be the component, not it's subviews. So within the calendar I will make direct function calls, there are less events in the system and it is easier to think about and debug.
This is where you have to decide how much coupling to introduce to balance comprehension vs future needs of the project
that said, in practice our UIs contain relatively few one-off components, and it often requires less dev friction simply to use the standard event pattern than to weigh borderline cases to shave off a little indirection at the cost of tighter coupling. the problem of "too many events in the system" is avoided by letting complicated components handle events from their own subviews and not just blindly bounce everything down to a global mediator. e.g. if no other views have to know about the 4 dropdowns and slider within MyWidget (and they usually don't), then MyWidget can handle all those subview events itself and simply present a single unified 'i've been updated' event for other consumers. essentially narrow the public apis between different actors in the overall system.
I love Backbone for its simplicity but I think most problems come from the use of the views' render function, which encourages the habit to have a _centralized_ way to refresh theirs content.
This is, again, a simple way to think about the process, but often it is not the most performant (and this is what has driven to the clever virtual DOM diff-ing of the recent frameworks).
I normally use what I call 'micro-rendering' functions, a way to change the appearance of a view focusing on the change of single state properties.
For some reason the css doesn't get applied to your page when I look at it in firefox.