This page takes the position that on-display events, as commonly seen in JavaScript-rendered HTML and in the construction of GUI shell applications, are 'harmful' in the sense that their potential for abuse (even when not actually abused) holds us back from achieving many beneficial UserInterface features. Further, this page explains weaker alternatives to on-display events to achieve the same features without the disadvantages or potential for abuse.
First a few working definitions:
- on-display event: a programmer-defined behavior with non-local side-effects triggered by a change in display state of a document, form, or other widget for a GraphicalUserInterface.
- In this case non-local side-effects means any communications that affects something outside the immediate document being displayed. This includes communications with a server, but it also includes manipulations of other parts of the document. (cf. SideEffect)
- See "Internal Vs. External" below for a discussion about this.
- display states may include such as minimized, maximized, resized, occluded, displayed, foreground, background, position on screen, etc. More critically, deciding display state is the prerogative of the browser or application shell. Changes in display state may occur as a result of user or server input to remote applications.
On-display events are commonly used to refresh data, start threads or events to keep that data up-to-date, etc. Most of these uses are relatively harmless. The problem with on-display events is not their harmless uses, but rather is their potential for abuse. This is similar to how having
SideEffects in functions prevents arbitrary
CompileTime PartialEvaluation, or how being
TuringComplete introduces complex concerns of divergence when composing programs.
I posit that all common, harmless uses may be performed without relying upon on-display events. But I'll come back to describing those alternatives, later.
Let's assume then that applications are using on-display events to their full extent, to what I consider to be abuse-cases of on-display events:
- SideEffects that manipulate other parts of the application. For example, the display of one form directly manipulating a checkbox widget in another form.
- SideEffects that manipulate state in an application-server.
From these abuse-cases and more general principles, we can make the following observations:
- A widget or form with on-display events is not reusable or re-composable. That is, the widget is tied inexorably to its environment due to references to other parts of the application and the server. This contributes to SpaghettiCode and BigBallOfMud applications.
- Widgets or forms referenced by on-display events from other widgets are not reusable or re-composable. That is, the widgets are fully defined only by side-effects from their environment. This contributes to SpaghettiCode and BigBallOfMud applications.
- The state of a service and application has a direct dependency on the order in which application windows and widgets are opened, and the order in which they are drawn. This a vector for UI permutation bugs (bugs that only appear when windows are opened, closed, resized in certain orders), implementation-dependence (bugs that appear because of variation in widget-draw-order between implementations), and RaceConditions (bugs that appear due to parallel display-event operations).
- Due to the possibility for UI permutation bugs, testing is complicated a great deal: one must try many different permutations for activities to be confident of an application or document that uses on-display events.
- The on-display events are controlled by the browser or shell rather than by the programmer of the application or server. This makes it difficult to control when they fire. So if on-display events are used, they can easily interact with other parts of the application or server in ways that were not predicted. Without on-display events, the programmer needs only consider user-inputs and the server, the former of which can be controlled (e.g. by greying out input options) and the latter of which can largely be predicted.
- Resistance to caching and compilation: one cannot, in general, cache or precompile forms and documents for later display, since the coincident timing of on-display events and the side effects thereof can be important to determining what to display.
- Because widgets aren't fully defined except by on-display events from other widgets, accessibility options (e.g. transforms for blind persons or mobile phones) are difficult to provide in general.
- If the network is disrupted, even briefly, the application may end up in an inconsistent state due to failed communications with server over a display transition. This is not robust or resilient.
- If the browser or shell fails or crashes, the remote services may be left in an inconsistent state due to failed display-event transitions. This is not robust or resilient.
- Sharing of applications, for server consistency, must share a common display-state, potentially including exact same size and position. This cannot be done except by sharing a whole virtual screen. This does not compose well with desktop systems, is generally more expensive, and makes stylistic integration (i.e. changing widget-sets based on the desktop) difficult. It's also inconvenient: simply because an application is shared doesn't mean that all users want to have it displayed with the same display-state.
- Continual events on resize and other display-state operations doesn't scale well to very large numbers of applications, or to persistent systems where applications are almost never 'closed'. Essentially, it is due to on-display events that ZoomableUserInterface for applications is impractical.
- As an extension of sharing, applications are not wholistically composable. The ability to compose one application within another is simply a form of sharing. Composition of this sort is useful for mashups, application portals, and other designs.
To these, we can add the general problems associated with constructor events (
ConstructorsAreEvil).
Alternatives:
- Declarative descriptions of data sources by use of side-effect free queries. This allows the application to get the data at need, and to cache the data, and to refresh the data periodically (or possibly subscribe to refresh continuously). More declarative approaches avoid many of the permutations issues and limit SideEffects to compiler-defined events that can be cleaned up and regenerated automatically, thus resulting in far more resilience against network disruption. Use this instead of on-display events to keep data up-to-date.
- FunctionalReactiveProgramming: define display-state as a function of other properties in a stratified manner, and define other properties in terms of display-states. This allows one to manipulate and relate display-state of many things without harming composition beyond the minimal amount necessary.
This page could use some coded examples. Things like SpaghettiCode are often in the eye of the beholder.
SpaghettiCode isn't something that can be shown in a toy example. It's a scaling problem.
When I've seen SpaghettiCode, usually I can find a way to describe why I feel it's spaghetti. Or, at least raise it as a question, such as "why did you use approach A here, but approach B here? Why not use the same; they do essentially the same thing." Or, "Why so many levels of indirection between this and this? What kind of change were you anticipating?"
But more to the point, you have to explicitly show X succeeding where Y fails if you claim something is better than something else. You cannot just rely on "trust me, I'm smart" because everybody thinks they are smart.
''I don't rely on "trust me, I'm smart". I rely on "You're smart too. Run a few example problems if you can't see it right away." The proof that on-display events contribute to SpaghettiCode simply reduces to demonstration that SideEffects and the global coupling necessary for the specified 'on-display event' to provide these SideEffects each contribute to SpaghettiCode and BigBallOfMud.
Internal Vs. External
Re: In this case non-local side-effects means any communications that affects something outside the immediate document being displayed. This includes communications with a server, but it also includes manipulations of other parts of the document. (cf. SideEffect)
But often the client and the server are a partnership. The "distance" is only an unfortunate happen-stance or condition that exists for some architectural reason. Whether we consider it "internal" or "external" is relative. It's called being flexible and adaptive. Sometimes we want to treat things as external and sometimes internal, depending on the need. It's virtualization in action.
It is 'relative' to "the immediate document being displayed". This was made explicit in the definition. A document being displayed might be a form, or even just a single checkbox inside a form. If a checkbox inside a form had an 'on_display()' event that could modify the rest of the form, or (even more global) the server, then doing so would qualify as non-local side-effects because the side-effects aren't 100% 'local' to the checkbox. Just about the only 100% local side-effect would be setting the checkbox value or stylistic options.
As far as that "partnership" goes, it is useful to model the client as providing a view of an application provided by the server, as opposed to providing the application. Why is it useful? Because thinking about views of applications allows you to model multiple views of applications, of shared views, of migrating views, of composed views, of most-recent-updated views, of zoomable views, of mashup or relational-query views (GooglifyDeepMenus), and so on. Why is that useful? Because those are GuiEngineGoals that are not being achieved in your current design. I suspect they cannot readily be achieved without restricting the role of a client in its 'partnership' with the server to providing, for example, 'just a view' of the application. (Not that maintaining views is a small challenge or anything. That's still a very significant role.)