Skip to content
This repository has been archived by the owner on Feb 24, 2022. It is now read-only.

Latest commit

 

History

History
72 lines (51 loc) · 9.37 KB

vision.md

File metadata and controls

72 lines (51 loc) · 9.37 KB

Vision

Adaptive user interfaces

End users of academic software typically have very specialised workflows, so it makes sense that the UI for such software can accommodate a wide range of needs. Furthermore, academic software is in a unique disposition:

  • It is usually developed in relatively few man-hours.
  • It must be maintained for much longer than most other software.
  • There is often little incentive/budget to further develop the software past the point of delivery.

Anticipating the varied needs of both current and future end users, UIs built with Stucco aim for a high degree of adaptability:

  1. Components should be tangible: Most of the components can be reordered, resized, or otherwise customised by the end user to fit their individual workflow.
  2. Components should be contextual: Many components will visually connect with nearby elements by morphing their styling accordingly.
  3. Devices are different: The components are responsive and will adapt to different screen sizes. Alternative input methods like keyboard and touch gestures are also supported.

The library focuses specifically on frontend development for data-centric systems. While it is highly modular, no special effort has been made to support a custom branded experience. Knowing that fashion is fleeting, the default designs of the components do not necessarily follow all the trends of the day, but rather lean towards a more utilitarian, evergreen style. Whenever a choice had to be made, form always yields to function.

Accessibility semantics

Full accessibility is really hard to get right in web applications. The aim of this library is to have any UI built with it be broadly accessible. To this effect, the components mostly follow the WAI-ARIA Authoring Practices:

  1. Markup should be semantic: Semantic HTML is used throughout (when possible) and appropriate ARIA attributes are set whenever they're required.
  2. Keyboard input must be equal Every component can be operated in a predictable way when using a keyboard instead of a mouse (also to the benefit of keyboard power users).
  3. Layout should be declarative: ARIA Landmark Regions are used as the basis of layouts in Stucco, making the process entirely declarative whilst coercing an accessible HTML structure.
    • This allows for precise control of the tab order by ensuring that the DOM order corresponds to the visual layout presented to the end user.
    • Focusing purely on semantics also allows the developer (or the end user) to hot swap the basic layout for a different one.
  4. Terminology should be clear: The component names try to match the official W3C terminology as much as possible, e.g. the tabs component consists of a tab-list and the currently selected tab-panel. The deliberate use of well-established names aids developers using this library.

For these reasons, accessibility should not be seen as an obstacle to overcome, but rather as a set of semantic constraints that work in synergy with the goal of creating an adaptive UI.

All the components in the library should - in principle - comply with the EU Web Accessibility Directive and the relevant Danish law. In practice, this means compliance with WCAG 2.1 which is the current guideline from the World Wide Web Consortium. Only the contextual parts that cannot be automatically deduced are made the responsibility of the developer. Runtime assertions serve as a helpful enforcement mechanism during development.

Shared component state

In more typical UI libraries (e.g. re-com), you construct stateful components from one or more values - usually from dereferenced state - along with associated callback functions for handling mutation. Components then create a closure around their mutable inner state and (sometimes) exchange data with other components using callbacks functions, though only to the extent that the developer has explicitly defined.

In Stucco, you generally construct stateful components with a reference to a piece of state and no callbacks. From the developer's perspective, the components may be considered a variation of Form-2 components where the developer injects the inner state as a function argument. Reagent's RAtoms, RCursors, Reactions (with on-set defined), and Wrappers can all be used directly with the UI components of this library. You don't need any special functions or macros.

To facilitate component integration, the shape of the injected state is very generic, enabling many UI components to accept the same state. Most callback functions are unnecessary when components have this kind of direct access to shared state. And as with the accessibility enforcement mentioned above, runtime assertions continuously validate component state during development.

Mutable interfaces: an emergent advantage

Stucco features extensive drag-and-drop of interface elements. One example of this might be dragging a tab somewhere else, e.g. to another tabbed interface on the same page. When injecting mutable state - such as RAtoms - into a Stucco component, the RAtoms can in fact survive a component being removed from the page and re-rendered somewhere else on the same page. The approach to state facilitates the adaptive interface.

In this way, complex interface elements composed of stateful reagent components are able to preserve their state while being moved around on the page. The only additional code needed is wrapping the initial state with (r/atom ...). You do not even need to keep a reference to the state. The implication here is that you can still inline the input args if you don't care in particular about inspecting the state.

It's possible to initialise Stucco components using plain data too. The components behave like typical, stateful reagent components, creating a temporary (fully internal) RAtom to contain the component state. However, using the components in this way does not allow for the above-mentioned advantage.

Motivation

I was motivated by the following considerations:

  1. Components should connect through state: Stateful components are simpler to integrate if they are allowed read-write access to the same state. The component APIs benefit from reduced ceremony due to a de-emphasis on callback functions.
  2. Component state should be generic: There are many benefits to having similarly shaped state in use across different component types. It imposes simplicity and a separation of concerns, while facilitating adhoc communication between discrete UI components by using the data itself as a protocol.
  3. All state should be exposed: Having access to most state through a single deref - as is common in the ClojureScript world - makes for simple debugging. If it's beneficial to expose the majority of the state why not expose state hidden away behind function closures too?

The Python principle of "we are all consenting adults here" is relevant here. This idea is also widely practiced in the Clojure/ClojureScript world, just not when it comes to stateful reagent components.

Of course, many "dumb" components are actually completely stateless. These are not the main concern of Stucco.

Trade-offs

  • Using the library in an opinionated frontend architecture like Re-frame is slightly less ergonomic.
  • The user is no longer required to write call back functions, but code that relies on side-effects of callback functions is now more complicated to write (e.g. maybe you need a wrapper or a watch function).
  • The internal state of the stateful components is no longer encapsulated inside a function closure. The user should take care not to mess with the state by accident, for example by having components making incompatible changes to the same state.
  • Function parameters cannot be destructured inline which obfuscates the component APIs somewhat. This is one key advantage of a more traditional approach.

Re-frame is a great idea that solves many hurdles by attempting to totally separate business logic from DOM mutation. Unfortunately, it doesn't have a good story when it comes to creating stateful components using its core abstractions.

In fact, the architecture that Re-frame imposes on the developer basically disincentivises making components with internal state. Re-frame strongly prefers to have all data transformations occur in subscriptions or as part of the state machine represented by the graph of Re-frame events. Components in Re-frame should be as dumb as possible and hook directly into the business logic by emitting events and dereferencing subscriptions.

Developers that want to use stateful components from a library such as Stucco can still do so in Re-frame (since it's just a layer on top of reagent), but doing so is slightly antithetical to Re-frame's overall design.