enhancement
major
minor
major
minor
minor
#29506
A dashboard tile cannot be the entry point it looks like: neither <tile> nor <card> carries an action
Reported from the Consulting application on 8.0.0-alpha7 (react view UI, Issue #94).
The problem
A cockpit tile that shows a count is the thing a reader aims at when that count is what they came for. Today it cannot respond: neither the tile nor the card inside it can carry an action.
CardElement.Config -> TITLE, VARIANT, PADDING TileElement.Config -> ID, WIDTH, ROW_SPAN, CONTENT
Our project view showed what that costs. It had four cockpit tiles - //Documents 13//, //Phases 4//, //Tasks 29//, //Stakeholders 5// - and, because the tiles could not open anything, four toolbar buttons with the same four names beside them. So every part of the project was offered twice, the toolbar ended up with five equally weighted buttons and therefore no primary action, and the tile that carried the number did nothing when clicked. Users click the number.
What exists today
A <button> inside the <card>, since <button> takes any ViewCommand as its action and NavigatePushCommand is one:
{{{#!xml <tile id="tasks" width="half">
<card padding="COMPACT">
<title><en>Tasks</en></title>
<text input="taskCount"/>
<button appearance="LINK">
<action class="com.top_logic.layout.view.tiles.NavigatePushCommand" view="consulting/tasks.view.xml" .../>
</button>
</card>
</tile> }}}
That is what we shipped, and it is a real improvement - count and way are in one place now. It is not the same thing, though:
- The target is a link a few dozen pixels wide, not the tile. The large, obvious surface stays inert, so the cheap mis-click is still the likely one.
- The tile grows by the height of a row. We had to set padding="COMPACT" on all five cards to keep the object's own fields above the fold.
- The card ends up with the same word three times over - heading, value, and a link that has to name its destination to be understandable on its own ("Open tasks").
Suggested shape
An action on <tile> rather than on <card>, since the tile is the surface:
{{{#!xml <tile id="tasks" width="half" >
<action class="com.top_logic.layout.view.tiles.NavigatePushCommand"
view="consulting/tasks.view.xml"
bind-input-to="project"
input="project"
/>
<card>
<title><en>Tasks</en></title>
<text input="taskCount"/>
</card>
</tile> }}}
with no action being today's behaviour, so nothing existing changes. Points worth deciding in the design:
- The tile needs the affordances of a control, not just a click handler: a hover state, a visible focus ring, activation by Enter and Space, and an accessible name - presumably the card's own title, which is the only label a reader sees.
- <executability> should work as it does on toolbar commands (<null-input-disabled/> is what our four pushes use), and a non-executable tile should read as such rather than silently swallowing the click.
- Nested interactive content has to be defined: a clickable tile that also contains a button or a link is the case that breaks first. Either forbid the combination or stop the event at the inner control.
- A tile that is a target should be distinguishable from one that is not - our //Client// tile is pure display and sits beside four that are not.
Why not build it in the application
An own UIElement would mean re-creating the card and tile rendering from outside the framework, together with the focus handling, the keyboard activation and the ARIA wiring - the same reasoning as in #29491 and #29500. Our application deliberately does not.
Related
- #29501 - activate a table row by double click or Enter. Same class of problem: the surface a user aims at is not the one that acts.
- #29500 - no overlay master-detail.
- #29475 - no configurable column width on <table>.
Lösung
A <tile> of a <dashboard> takes an optional <action class="..."/>: any ViewCommand, configured exactly like the action of a <button> or the <on-activate> of a <table>, with input, <executability>, observed-types and <label>. A tile without an action behaves as before.
Behaviour of a tile with an action
- The whole tile is the target: a click anywhere on it runs the command. A click that starts on an interactive control inside the tile - a button, a link, an input, an ARIA widget - is left to that control, so a card that also holds a button keeps working.
- The tile is a control for the keyboard and for assistive technology: it is a tab stop, Enter and Space run the command, it shows a focus ring, and it carries an accessible name - the action's <label> when configured, otherwise "Open" combined with the title of the card or panel inside the tile.
- The tile follows the command's executability the way a toolbar button does. While the rules disable the command, the tile shows no hover, ignores the click and gives the rule's reason as its tooltip. While the rules hide the command, the tile is a plain display tile.
- A tile with an executable action answers the pointer: the cursor is a pointer and the tile is raised while hovered. The engine draws no glyph over the content; a visible mark is placed by the tile's content itself.
- In the dashboard's edit mode (drag to reorder) the action is suspended.
Card header slot and icon element
- <card> takes <header-content>: elements rendered at the right edge of the card header, after the title - an icon marking the card as an entry point, a badge, a button.
- The new <theme-icon image="css:..."/> element renders a theme icon (font icon or picture resource) on its own, decorative or named by its tooltip. (The tag icon is taken by the icon property of several elements with a default container and cannot be a content tag.)
Technical notes
- TileElement.Config#getAction() and the ViewCommandModel built from it by ViewCommandModel.forCommand, attached and detached with the dashboard control; TitledElement (implemented by CardElement and PanelElement) supplies the title for the accessible-name fallback.
- ReactDashboardControl.Tile carries the command model; the dashboard control offers an activate client command (ActivateTileArguments) and republishes the tile descriptors (action: {label, disabled, tooltip}) on a state change of a model.
- The client (TLDashboard) renders a visually hidden but focusable <button> inside an actionable tile for the name, the keyboard and the disabled state; the tile itself handles the pointer, with the interactive-descendant test shared with the table (interactive.ts).
- IconElement / ReactIconControl / TLIcon.
- React demo (tl-demo-react): the project frame is a cockpit dashboard (Project tile display-only, Milestones and Notes tiles as entry points with a chevron in the card header, the Notes tile refused with a reason while the project has no notes and enabled the moment a note is created). The project drill-down uses row activation instead of toolbar "Open" commands, the milestone list is a pushed frame (also for display targets), and projects, milestones and notes can be created and deleted.