Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapping table column spec #1220

Merged
merged 31 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1ddda56
Initial spec
m-akinc May 1, 2023
afc2dda
Link from main table spec
m-akinc May 1, 2023
8a2c5a9
Updates
m-akinc May 1, 2023
5149672
Update
m-akinc May 1, 2023
dffcc0a
Updates
m-akinc May 1, 2023
1c7876a
Add code for ellipsized text tooltip
m-akinc May 1, 2023
d72e90b
Updates
m-akinc May 2, 2023
b5b80ea
some feedback
m-akinc May 3, 2023
2af533d
Big revamp
m-akinc May 4, 2023
9e9061f
Change files
m-akinc May 4, 2023
a00ff8e
Address most of Molly's feedback
m-akinc May 4, 2023
fa90d2a
Address some feedback
m-akinc May 5, 2023
28582a6
not throwing errors
m-akinc May 5, 2023
f813463
Expand on sorting
m-akinc May 5, 2023
40ebb7a
Fixes
m-akinc May 5, 2023
eaf9b00
Split key property into multiple type-specific props
m-akinc May 8, 2023
3195be9
Add column validation section to columns HLD
m-akinc May 8, 2023
6bbf1df
Updates
m-akinc May 8, 2023
a0ad5bd
Updates
m-akinc May 15, 2023
9047098
Rename default property to default-mapping
m-akinc May 15, 2023
ebd2ff3
Update column validation documentation
m-akinc May 19, 2023
986aefb
Updates
m-akinc May 19, 2023
57448bb
Update validation documentation
m-akinc May 19, 2023
7e27c21
Update sorting section
m-akinc May 23, 2023
4cbd0bb
Update packages/nimble-components/src/table/specs/table-column-specs/…
m-akinc May 24, 2023
9a39440
Merge branch 'users/makinc/icon-column-spec' of https://github.com/ni…
m-akinc May 24, 2023
643cd8f
Clearly lay out sort options
m-akinc May 24, 2023
c81af5b
Update
m-akinc May 24, 2023
6ba69f5
Update packages/nimble-components/src/table/specs/table-column-specs/…
m-akinc May 30, 2023
9977915
Update packages/nimble-components/src/table/specs/table-column-specs/…
m-akinc May 30, 2023
74cee98
Merge branch 'main' into users/makinc/icon-column-spec
m-akinc May 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "Mapping table column spec",
"packageName": "@ni/nimble-components",
"email": "[email protected]",
"dependentChangeType": "none"
}
1 change: 1 addition & 0 deletions packages/nimble-components/src/table/specs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ The various APIs/features of the `nimble-table` will be split up amongst several
- List the set of column providers that Nimble will provide and provide their respective APIs where unique (e.g., formatter for DateTime column)
- [TableColumnText](table-column-specs/table-column-text-field.md)
- [TableColumnAnchor](table-column-specs/table-column-anchor-hld.md)
- [TableColumnMapping](table-column-specs/table-column-mapping.md)
- Headers
- Define the anatomy of headers in the table DOM
- What is the component to use for interaction? Outline Button? Ghost button?
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
# Mapping Table Column

## Overview

The `nimble-table-column-mapping` is a component that supports rendering specific number, boolean, or string values as mapped text. `nimble-table-column-icon` is a specialized version of `nimble-table-column-mapping` that instead maps values to icons and/or spinners and has a minimal, fixed width. The actual mappings are defined by child elements `nimble-mapping-icon`, `nimble-mapping-spinner`, and `nimble-mapping-text`.

### Background

[Icon column type issue](https://github.com/ni/nimble/issues/1013)

[Boolean column type issue](https://github.com/ni/nimble/issues/1103)

### Features

- Supported input:
- string
- number
- boolean
- Supported output:
- Text
- Icon
- Spinner
- (empty)

### Non-goals

- Non-Nimble icon support
- Arbitrary icon colors
- Hyperlink icons
- Mixed text and icons
- Non-icon, non-spinner Nimble components

---

## Design

Below is an example of how these elements would be used within a `nimble-table`:

```HTML
<nimble-table>
<nimble-table-column-icon field-name="status" key-type="string">
Status
<nimble-mapping-icon key="fail" icon="nimble-icon-xmark" severity="error" label="Failed"></nimble-mapping-icon>
<nimble-mapping-icon key="error" icon="nimble-icon-xmark" severity="error" label="Errored"></nimble-mapping-icon>
<nimble-mapping-icon key="pass" icon="nimble-icon-check" severity="success" label="Passed"></nimble-mapping-icon>
<nimble-mapping-spinner key="running" label="Running"></nimble-mapping-spinner>
</nimble-table-column-icon>
<nimble-table-column-mapping field-name="errorCode" key-type="number">
Error Code
<nimble-mapping-text key="1" label="A bad thing happened"></nimble-mapping-text>
<nimble-mapping-text key="2" label="A worse thing happened"></nimble-mapping-text>
<nimble-mapping-text key="3" label="A terrible thing happened"></nimble-mapping-text>
</nimble-table-column-mapping>
<nimble-table-column-icon field-name="archived" key-type="boolean">
Archived
<nimble-mapping-icon key="true" icon="nimble-icon-database" label="Archived"></nimble-mapping-icon>
</nimble-table-column-icon>
</nimble-table>
```

Each column contains mapping elements that define what to render when the cell's value matches the given `key` value.

When none of the given mappings match the record value for a cell, that cell will be empty. Alternatively, if one of the mappings has the `default-mapping` attribute, it will match when no other mappings have. This is equivalent to the `placeholder` configuration we provide on `nimble-table-column-text` and `nimble-table-column-anchor`.

The column will translate its contained mapping elements into a map that is stored in the `columnConfig`.

Validation will be performed to ensure each mapping's key value can be converted to the `key-type` of the column. If not, an error flag will be set on the column's validation object. Note that whenever an error flag is set on the column's validation object, a generic `invalidColumnConfiguration` flag is also set on the table, putting it in an invalid state as well.

If multiple mappings in a column have the same key, an error flag will be set on the column's validity object.

If an invalid `icon` value is passed to `nimble-mapping-icon`, an error flag will be set on the column's validity object. An invalid `icon` value is any element that cannot be resolved or that does not derive from `Icon`.

`nimble-table-column-icon` supports only `nimble-mapping-icon` and `nimble-mapping-spinner` as mapping elements. `nimble-table-column-mapping` supports only `nimble-mapping-text`. Unsupported mappings will result in an error flag being set on the column's validity object.

Text in a grouping header or in the cell will be ellipsized and gain a tooltip when the full text is too long to display.

**Alternatives:**
m-akinc marked this conversation as resolved.
Show resolved Hide resolved

An earlier version of this spec proposed mapping elements with `template` elements as content instead of relying on attribute configuration. The template element would define the mapped html to render. We would impose restrictions on the types of supported elements that could be provided in the template.

Pros:

- It would not require updates to the API if we needed to support new types of mapped content (e.g. icon with text), or if the mapped content itself got new configuration options (e.g. a scaling factor for icons).
- Undefined element types caught at compile time.

Cons:

- Verbose. Requires user to create `template` element and wrap text in `span`s for styling purposes.
- Requires difficult validation to ensure only supported elements are present in the `template`.
- Could allow users to provide inline styling.
- Blazor: cannot put Blazor components inside `template`--must use raw Nimble elements without type safety

### API

#### Icon column element:

_Component Name_

- `nimble-table-column-icon`

_Props/Attrs_

- `field-name`: string
- `key-type`: 'string' | 'number' | 'boolean'
- `pixel-width`: number (set to the desired fixed column width, else will use a default fixed width)

_Content_

- column title (text or icon)
- 1 or more `nimble-mapping-icon` or `nimble-mapping-spinner` elements

#### General mapping column element:

_Component Name_

- `nimble-table-column-mapping`

_Props/Attrs_

- `field-name`: string
- `key-type`: 'string' | 'number' | 'boolean'
- `fractional-width`: number (defaults to 1)
- `min-pixel-width`: number (defaults to minimum supported by table)

_Content_

- column title (text or icon)
- 1 or more `nimble-mapping-text` elements

#### Mapping element (icon):

_Component Name_

- `nimble-mapping-icon`

_Props/Attrs_

- `key`: string | number | boolean | undefined
- `icon`: string - name of the Nimble icon element
- `severity`: string - one of the supported enum values. Controls color of the icon.
- `label`: string - localized value used as the accessible name and `title` of the icon. Will also be displayed in the group header.
- `default-mapping`: boolean - presence causes this mapping to be used when no others match the value

#### Mapping element (spinner):

_Component Name_

- `nimble-mapping-spinner`

_Props/Attrs_

- `key`: string | number | boolean | undefined
- `label`: string - localized value used as the accessible name and `title` of the spinner. Will also be displayed in the group header.
- `default-mapping`: boolean - presence causes this mapping to be used when no others match the value

#### Mapping element (text):

_Component Name_

- `nimble-mapping-text`

_Props/Attrs_

- `key`: string | number | boolean | undefined
- `label`: string - display text
- `default-mapping`: boolean - presence causes this mapping to be used when no others match the value

### Anatomy

#### `nimble-table-column-mapping`
m-akinc marked this conversation as resolved.
Show resolved Hide resolved

```HTML
<template slot="${x => x.columnInternals.uniqueId}">
<slot
${slotted('mappings')}
name="mapping">
</slot>
<span class="header-content">
<slot></slot>
</span>
</template>
```

Cell view:

The cell view relies on the matched mapping to provide a template to render.

```HTML
html<TableColumnMappingCellView>`${x => x.getMappingToRender().cellViewTemplate}`
```

Group header view:

Similarly, the group header view relies on the matched mapping to provide a template to render.

```HTML
m-akinc marked this conversation as resolved.
Show resolved Hide resolved
html<TableColumnMappingHeaderView>`${x => x.getMappingToRender().groupHeaderViewTemplate}`
```

#### `nimble-mapping-*`

```HTML
<template slot="mapping"></template>
```

#### `nimble-mapping-icon`

`mapping.cellViewTemplate`:

```HTML
<${this.icon}
title="${x => x.label}"
aria-label="${x => x.label}"
severity="${x => x.severity}">
</${this.icon}>
```
m-akinc marked this conversation as resolved.
Show resolved Hide resolved

`mapping.groupHeaderViewTemplate`:

```HTML
<${this.icon}
title="${x => x.label}"
aria-label="${x => x.label}"
severity="${x => x.severity}">
</${this.icon}>
<span
${ref('span')}
@mouseover="${x => {
x.isValidContentAndHasOverflow = !!x.label && x.span!.offsetWidth < x.span!.scrollWidth;
}}"
@mouseout="${x => {
x.isValidContentAndHasOverflow = false;
}}"
title=${x => (x.isValidContentAndHasOverflow ? x.label : null)}>
${x => x.label}
</span>`;
```

### Grouping

Grouping will be based on the record value. The grouping header will display the rendered icon/spinner/text. In the case of an icon/spinner, it will also be followed by the `label` text. This will disambiguate cases where multiple record values map to the same icon (assuming the labels are different).

For values that do not match any mapping, we will display the raw data value. While this introduces inconsistency, it seems preferable to the alternative, which is having multiple, separate groupings with a blank header (well, with just the item count in parens). Even in the case where there is a default mapping, we would still end up with separate groups with the identical default mapped icon and/or text, which is just as bad.

Text in a grouping header will be ellipsized and gain a tooltip if there is not enough room to display it all.

### Sorting

Sorting will be based on the record value. For boolean and number values, a basic sort (just using basic comparison/equality operators) is the clear choice. For string values, it is less clear. In the case where the strings are enum values, they are likely to be non-localized, English strings. In that case, even if there is a semantically meaningful sort order (i.e. "NOT_STARTED" < "RUNNING" < "DONE"), we can't sort that way. Settling for a basic sort in this case seems just as reasonable as anything else. If the strings are localized, it's more likely the client would use a text column instead. If we did want to support a different sorting method for strings than booleans/numbers, it would be awkward to implement. We would have update the column's sort method when the data changes, and the column would have to determine the data type for its field, which might require looking at multiple records (e.g. if the first didn't define that field). For these reasons, we will just use a basic sort for all supported data types (string, boolean and number).
m-akinc marked this conversation as resolved.
Show resolved Hide resolved

For icons, if multiple values map the to same icon, it is possible that sorting will result in the instances of a certain icon not being all together in one span of rows. The user may think they should be all together, but this is a corner case that we can't/shouldn't do anything about.
m-akinc marked this conversation as resolved.
Show resolved Hide resolved

### Sizing

`nimble-table-column-icon` will support only a fixed width. We will introduce a new mixin for fixed-width support that exposes a `pixel-width` property. The default value will be the minimum supported by the table, which is still significantly larger than the width of an icon.

`nimble-table-column-mapping` will support fixed or fractional widths. If `pixel-width` is set, the column will have a fixed width, otherwise it defaults to a fractional width of 1. The client may configure `fractional-width` and/or `min-pixel-width`.

### Angular integration

Angular directives will be created for the column components and the mapping components. No component has form association, so a `ControlValueAccessor` will not be created.

### Blazor integration

Blazor wrappers will be created for the components. Columns will be generic in the type of the key, and will cascade that type parameter to contained mapping elements (see [`CascadingTypeParameter`](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/generic-type-support?view=aspnetcore-7.0#cascaded-generic-type-support)):

```HTML
<NimbleTableColumnMapping TKey=int Field="NumberData">
<NimbleMappingText Key="1" Label="foo"></NimbleMappingText>
<NimbleMappingText Key="2" Label="bar"></NimbleMappingText>
</NimbleTableColumnMapping>
```

### Visual Appearance

The cell view (and group header view) will be responsible for styling the templates returned by the mappings. This will include alignment and spacing (`--ni-nimble-small-padding`). Unfortunately, because the mappings cannot provide CSS to go with the templates they return, some implementation knowledge will leak from the mappings to the cell/group header views. For example, we must set `flex-shrink: 0` on all elements so that icons do not get smaller in a group header when the adjacent label takes up all the space.
rajsite marked this conversation as resolved.
Show resolved Hide resolved
m-akinc marked this conversation as resolved.
Show resolved Hide resolved

---

## Implementation

### States

N/A

### Accessibility

Text, icons, and spinner are not interactive and cannot receive keyboard focus. These items already have proper accessibility roles, and we will set accessible names (`aria-label`) based on the `label` value provided by the client.

### Globalization

All text will be provided by the client and is expected to be localized.

### Security

N/A

### Performance

N/A
m-akinc marked this conversation as resolved.
Show resolved Hide resolved

### Dependencies

None

### Test Plan

- Unit tests will be written verifying the usual component expectations, plus:
- renders mapping matching the cell value (string, number, and boolean)
- nothing rendered when value matches no mappings
- validation error when non-unique mapping keys exist
- validation error when multiple mappings marked as default
- validation error when mapping key is null and not marked default
- validation error when invalid icon name given
- validation error when icon column has a `nimble-mapping-text` element
- grouping header for icon column includes label
- Verify manually that the column content appears in the accessibility tree and can be read by a screen reader.
- Verify manually that several mapping columns with thousands of elements scrolls performantly.
- Visual Chromatic tests will be created

### Tooling

N/A

### Documentation

Documented in Storybook

---

## Open Issues
m-akinc marked this conversation as resolved.
Show resolved Hide resolved
Loading