13  UI language

(ns oiiku.ui.impl.docs.ui-language
  (:require [scicloj.kindly.v4.kind :as kind]))

When working with our UI domain, we try to separate it from the business domain, the technical domain and have a translation layer. We call this the prepare layer, where business domain data is translated in a prepare stage to the language the UI domain understands.

13.1 A concrete example

When sending a message to the server containing an update, we use our API layer called Convey. Convey has a concept of InFlight and SlowRequest, where InFlight means the message has been sent and a response has not yet been received. SlowRequest means that the message has been InFlight for over 1000ms.

If this was fed directly into a widget, we would have tied together the UI layer and the network layer. If we found out later that we want a similar effect on the UI layer as we get for the InFlight status on messages, we would need to mimic the InFlight mechanism, instead of writing something new that works with what we need. To avoid this problem, a new state called Disabled is introduced, where a button can be Disabled (ie, you cannot interact with it) and how it is disabled is not something that the button decides.

Button has a UI language (Disabled), which is separate from what could cause it (InFlight). When the message becomes SlowRequest, we might want to show that in the button, in which case the UI State becomes Loading (shows a spinner).

To further complicate things we can have a composite of widgets that logically is tied together, e.g. a form. We want a UI language that can describe what happens with a form when a message is InFlight or is SlowRequest, which might or might not be the same as a Button inside.

13.2 UI Language Dictionary

Dictionary of UI Language words and what they mean.

13.2.1 Disabled

The widget is set into a disabled state, in which it can no longer receive input and it’s visually shown that the widget is disabled.

13.2.2 Read-only

The widget is set in a read-only state, in which it can no longer receive input, but does not show so visually.

13.2.3 Loading

The widget is set into a Loading state, in which it visually (i.e. a spinner) shows that it’s waiting for input from the app itself, e.g. a message response.

13.2.4 Closable

The widget is set to be Closable or not Closable. If it’s set to not be Closable you cannot close the widget. For modals this would mean that you cannot close the modal via either Escape or via some button click. For a menu it would mean you cannot move out of the menu.

13.3 Flowchart simple modal window

In this flowchart there is a modal window containing two buttons: Cancel and Save. When pressing Save, a network message is sent to the server and the Save button is disabled. The modal can no longer be dimissed with an Escape press, nor will the X button on the top right work.

(kind/mermaid
 "flowchart TD
  Store --> State
  State --> RenderModal{Render modal?}
  RenderModal -->|Yes| ModalPrepare{Modal prepare fn}
  RenderModal -->|No| Nothing
  ModalPrepare -->|InFlight| RenderDisabled(Set disabled)
  ModalPrepare -->|SlowRequest| RenderSlow(Set loading)
  ModalPrepare -->|Locked?| RenderLocked(Set locked)
  ModalPrepare -->|Pass| RenderNormal(Pass)
  RenderDisabled --> CancelButton(Cancel button)
  RenderLocked --> CancelButton
  RenderNormal --> CancelButton
  CancelButton --> CancelActions(Actions for Cancel button)
  CancelActions -->|Click - remove modal| Store
  RenderLocked --> Modal
  RenderNormal --> Modal
  Modal -->|Escape pressed| Escape{Locked?}
  Escape -->|Yes| Modal
  Escape -->|No - remove modal| Store
  RenderDisabled --> SaveButton(Save button)
  RenderSlow --> SaveButton
  RenderLocked --> SaveButton
  RenderNormal --> SaveButton
  SaveButton --> SaveActions(Actions for Save button)
  SaveActions -->|Network request| ConveyMessage
  ConveyMesssage -->|Set InFlight| Store
  ConveyMessage -->|Set timeout|Timeout
  Timeout -->|Triggered - set SlowRequest| Store
  SaveActions -->|Set locked| Store")
flowchart TD Store --> State State --> RenderModal{Render modal?} RenderModal -->|Yes| ModalPrepare{Modal prepare fn} RenderModal -->|No| Nothing ModalPrepare -->|InFlight| RenderDisabled(Set disabled) ModalPrepare -->|SlowRequest| RenderSlow(Set loading) ModalPrepare -->|Locked?| RenderLocked(Set locked) ModalPrepare -->|Pass| RenderNormal(Pass) RenderDisabled --> CancelButton(Cancel button) RenderLocked --> CancelButton RenderNormal --> CancelButton CancelButton --> CancelActions(Actions for Cancel button) CancelActions -->|Click - remove modal| Store RenderLocked --> Modal RenderNormal --> Modal Modal -->|Escape pressed| Escape{Locked?} Escape -->|Yes| Modal Escape -->|No - remove modal| Store RenderDisabled --> SaveButton(Save button) RenderSlow --> SaveButton RenderLocked --> SaveButton RenderNormal --> SaveButton SaveButton --> SaveActions(Actions for Save button) SaveActions -->|Network request| ConveyMessage ConveyMesssage -->|Set InFlight| Store ConveyMessage -->|Set timeout|Timeout Timeout -->|Triggered - set SlowRequest| Store SaveActions -->|Set locked| Store

13.4 Sequence diagram

(kind/mermaid
 "sequenceDiagram
  Modal ->> Save: Renders Save button
  Modal ->> Cancel: Renders Cancel button
  Save ->> EffectConvey: A click causes an effect to dispatch to Convey
  Save ->> ActionLockUI: A click locks the UI for the modal
  ActionLockUI ->> UIState: UI state is set to locked for the modal
  UIState ->> Cancel: Cancel button is locked and cannot be interacted with
  EffectConvey ->> Convey: The effect tells Convey to send a message to the server
  Convey ->> UIState: UI State is set to inflight for the message
  UIState ->> Prepare: Save is set to disabled
  EffectConvey ->> UIState: An update is happening
  Prepare ->> Save: Save is rendered as disabled
  Convey ->> Timeout: A timeout is set that can fire a slow request
  Timeout ->> UIState: UI State is set to slow-request for the message
  UIState ->> Prepare: Save is set to loading
  Prepare ->> Save: Save is rendered with a spinner
  Convey ->> UIState: A response is received and UI is unlocked and message is no longer in flight
  UIState ->> Modal: Re-render everything from the top")
sequenceDiagram Modal ->> Save: Renders Save button Modal ->> Cancel: Renders Cancel button Save ->> EffectConvey: A click causes an effect to dispatch to Convey Save ->> ActionLockUI: A click locks the UI for the modal ActionLockUI ->> UIState: UI state is set to locked for the modal UIState ->> Cancel: Cancel button is locked and cannot be interacted with EffectConvey ->> Convey: The effect tells Convey to send a message to the server Convey ->> UIState: UI State is set to inflight for the message UIState ->> Prepare: Save is set to disabled EffectConvey ->> UIState: An update is happening Prepare ->> Save: Save is rendered as disabled Convey ->> Timeout: A timeout is set that can fire a slow request Timeout ->> UIState: UI State is set to slow-request for the message UIState ->> Prepare: Save is set to loading Prepare ->> Save: Save is rendered with a spinner Convey ->> UIState: A response is received and UI is unlocked and message is no longer in flight UIState ->> Modal: Re-render everything from the top
source: bases/admin-web/src/oiiku/ui/impl/docs/ui_language.clj