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

HLD brainstorming - Formatted text table columns #1054

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
848b33c
Duplicate table-column-text spec
jattasNI Jan 31, 2023
b1ec4fc
Link from main spec
jattasNI Jan 31, 2023
5631729
Initial thoughts
jattasNI Feb 2, 2023
d422606
Rename and add alternatives
jattasNI Feb 13, 2023
e26ec21
update link
jattasNI Feb 13, 2023
4282adb
link to HLD
jattasNI Feb 13, 2023
fe2d888
formatting
jattasNI Feb 13, 2023
ae254ee
label alternatives
jattasNI Feb 13, 2023
71c7cb5
remove date time example
jattasNI Feb 13, 2023
7eb1ada
updates
jattasNI Feb 14, 2023
ff973af
formatting
jattasNI Feb 14, 2023
79edfd1
cleanup
jattasNI Feb 14, 2023
438b570
lint fix
jattasNI Feb 14, 2023
d6e5474
data -> setData
jattasNI Mar 1, 2023
2b3a0dd
Merge branch 'main' into number-column-hld
jattasNI Mar 1, 2023
889491a
Formatting and address some review feedback
jattasNI Mar 1, 2023
9d7db1d
Better rationale for app-specific formatting logic
jattasNI Mar 1, 2023
d673866
Options for formatting API
jattasNI Mar 1, 2023
336939b
Enum example
jattasNI Mar 1, 2023
9c56c8f
More requirements
jattasNI Mar 1, 2023
817ced3
Wording
jattasNI Mar 1, 2023
f9e3fe6
Feedback from Meyer conversation
jattasNI Mar 2, 2023
6f5a076
Formatting and clarification
jattasNI Mar 2, 2023
5f4ca03
Clarify Blazor recommendation
jattasNI Mar 2, 2023
5586759
prettier-fix
jattasNI Mar 2, 2023
fe92ed0
Change files
jattasNI Mar 2, 2023
84e96c4
Boolean text use case and column type
jattasNI Mar 8, 2023
0cd4789
Merge branch 'main' into number-column-hld
jattasNI Apr 7, 2023
250292d
Solidifying proposals
jattasNI Apr 12, 2023
c4a44c8
HLD updates
jattasNI May 2, 2023
3688838
Merge branch 'main' into number-column-hld
jattasNI May 2, 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
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 @@ -104,6 +104,7 @@ The various APIs/features of the `nimble-table` will be split up amongst several
- What column gets used for sorting?
- 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)
- [TableColumnNumber](table-column-specs/table-column-formatted-text.md)
- Headers
- Define the anatomy of headers in the table DOM
- Require specific component type (i.e. do we need to create a `nimble-table-header`)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# Formatted Text Columns

## Overview

Clients will wish to display non-string text data in table columns for use cases like the following:

1. integer data like counts, formatted to be displayed with no trailing decimals ("4", "100")
2. floating point data formatted to display values in standard ways ("3.1415", "1.04E47", "Infinity", -0.03)
3. a mix of the above with formatting determined by the application ("1.000", "-0.030", "1024.000")
4. numeric values with a static unit string appended before or after (e.g. "$4.23" or "15%")
5. numeric values with custom unit logic. Examples:
- a file size column that could show the value 1000 as "1000 bytes" but the value 1024 as "1KB"
- an elapsed time column that could show 63 seconds as "00:01:03" or "1 minute, 3 seconds"
6. enum values formatted as localized strings (0 -> "Fail", 1 -> "Pass")
7. date/time values formatted in various ways ("October 27", "yesterday", "2023-12-28 08:27")

In all of the above cases:

- data should be sortable and groupable by its actual numeric value, not the string representation
- text styling like font and alignment should be provided by Nimble
- columns should support i18n behaviors like decimal separators, date/time formats, and localized content
- there should be an option to show "placeholder" text if no value is specified

We may not choose to support all of the above initially but we should design our solutions with these use cases in mind.

### Background

[Number column work item](https://github.com/ni/nimble/issues/1011)

[Date/time column work item](https://github.com/ni/nimble/issues/1014)

[Table Column API](../table-columns-hld.md)

[Table Spec](../README.md)

[Table Column Text Spec](./table-column-text-field.md)

[Visual Design Spec](https://xd.adobe.com/view/5b476816-dad1-4671-b20a-efe796631c72-0e14/specs/)

### Non-goals

- Editable numbers. This is not supported by the text column yet either.
- Customizing the styling or alignment of the column content. This is not supported by the text column yet either.

---

## Design

### Alternatives

Below are different alternatives to solve these use cases. Some alternatives will work better for certain use cases and worse for others. We may choose to implement a few of these alternatives in order to provide a great experience for all use cases. See below for an initial proposal.

At this stage, code examples are meant to be illustrative pseudo-code, not proposed APIs.

#### Alternative 1: Use `table-column-text`

With the changes proposed in [HLD for programmatically sorting columns](https://github.com/ni/nimble/pull/1049) to allow a column to be sorted by a different data field than the one being used for display, many of the above use cases could be met with minor changes to the existing text column. Clients would write custom logic to populate their data with a new string field that contains formatted values. Then they would configure the table to display that string field while sorting by the original numeric field.

```html
<nimble-table>
<nimble-table-column-text
operational-data-field-name="progress"
field-name="formattedProgress"
>
Progress
<nimble-table-column-text>`
</nimble-table>
```

```ts
const originalData = [{ progress: 0.1 }, { progress: 0.2 }];
const tableData = originalData.map(x => {
progress: x.progress;
formattedProgress: x ? `${100 * x.progress}%` : undefined;
});
table.data = tableData;
jattasNI marked this conversation as resolved.
Show resolved Hide resolved
```

**Pros:**

- formatted data is specified up front, guaranteeing fast scroll performance
- powerful; clients can format data however they want, including via browser APIs which are i18n-friendly or via server-side logic

**Cons:**

- Increased memory usage and data update time from clients pre-populating data with field for each formatted column
jattasNI marked this conversation as resolved.
Show resolved Hide resolved
- Added complexity of writing procedural code even for simple formatting use cases
- Potential cross-app inconsistency if formatting code isn't shared

**Implementation Cost:**

- Exposing `operational-data-field-name` to be set by client code rather than column definition

#### Alternative 2: Client specifies formatting function

When configuring a column, clients could provide a callback function that converts data of any supported type into a formatted string.

There isn't a good way to set a function as an attribute value on a column, so the function would be specified in JS code. One possible mechanism would be for clients to override an abstract base class and register a new column type:

```html
<nimble-table>
<my-app-progress-column
field-name="progress"
>
Progress
<my-app-progress-column>`
</nimble-table>
```

```ts
class MyAppProgressColumn : NimbleFormattedTextColumnBase<number> {
public override format(value: number) : string {
return `${100 * value}%`;
}

public override shouldUsePlaceholder(value: number | undefined) : boolean {
return value === undefined;
}
}

MyAppProgressColumn.registerColumn('my-app-progress-column');
```

Some of this is prototyped in the [number-column-prototype branch](https://github.com/ni/nimble/compare/main...number-column-prototype?expand=1).

Other variants of this idea include:

1. setting the formatting function as a property on a column element.
jattasNI marked this conversation as resolved.
Show resolved Hide resolved
2. setting an [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) object as a property on a column element.
jattasNI marked this conversation as resolved.
Show resolved Hide resolved

**Pros:**

- Small memory footprint and fast data update time because formatting function is called on-demand
- Powerful; clients can format data however they want, including via browser APIs which are i18n-friendly

**Cons:**

- Possible reduced scroll performance because formatting function is called on-demand
- Requires JS code to do formatting which is less convenient in frameworks like Blazor
- Potential cross-app inconsistency if formatting code isn't shared
jattasNI marked this conversation as resolved.
Show resolved Hide resolved

**Implementation Cost:**

- Expose mechanism for providing format function
jattasNI marked this conversation as resolved.
Show resolved Hide resolved

#### Alternative 3: Nimble provides column implementation for common use cases

For common use cases we could provide column types that expose simplified formatting APIs:

```html
<nimble-table>
<nimble-table-column-numeric
field-name="progress"
digits-width=2
suffix="%"
>
Progress
<nimble-table-column-numeric>`
</nimble-table>
```

**Pros:**

- Easy for clients to use since configuration is declarative
- Consistent formatting across apps

**Cons:**

- Requires Nimble team to design simple but powerful formatting and i18n APIs
- Can't solve some use cases like app-specific formatting logic

**Implementation Cost:**

- API design and implementation for each new column type

#### Alternative 4: Client provides custom column implementation for each use case

Nimble already has a mechanism for clients to provide custom columns by deriving from a base class, specifying the data fields / template / styling, and registering the column type with Nimble. We could ask clients to use this mechanism for text column types.

**Pros:**

- Zero implementation cost to Nimble team

**Cons:**

- Higher burden on clients to specify template, styling, etc in JS
- Potential for inconsistent text styling
- Potential cross-app inconsistency if formatting code isn't shared

### Strawman Proposal

For the sake of discussion my initial proposal is:

1. For columns that require app-specific formatting logic I'm leaning towards "Client specifies formatting function" over "Use `table-column-text`" because it seems more like the API that app developers would expect (perhaps I'm biased by previous implementations). I'd like to do performance profiling to see how it impacts scroll performance before committing to this direction.
Copy link
Contributor

@atmgrifter00 atmgrifter00 Feb 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the possible proposed solution outlined in the "Client specifies formatting function" section is at odds with what an app developer would expect for an API, in that they would be expected to create a custom column implementation. The first variant, I think, is what clients typically expect, and resembles what Ag-grid provides.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this open since I haven't addressed it (it's one level more detailed than I'm seeking agreement on at this moment, though still a good discussion to have).

I like the proposed approach of deriving from an abstract element class to define a new column type because it makes it easy to associate the formatting function with the column definition in JS code (they're in the same class). The approach of setting a formatting function via a property on the column element requires you to get a reference to the column in JS/TS/C# code which seemed more annoying to me (especially in Blazor).

2. I would also like to provide a small number of built-in column types to save clients from having to write JS code. These could offer limited or no configuration to start since we don't have clear requirements yet. Initially this might be just `nimble-table-column-numeric` with default `toString()` formatting and no unit support. Later we could add support for basic formatting and also add column types for date/time or enums, but i18n considerations might make it hard to expose a clean attribute API for these.

jattasNI marked this conversation as resolved.
Show resolved Hide resolved
### API

_Component Name_

_*Props/Attrs*_

_Type Reference_

### Anatomy

### Angular integration

### Blazor integration

### Visual Appearance

---

## Implementation

### Alternatives considered

### States

### Accessibility

### Globalization

### Security

### Performance

### Dependencies

### Test Plan

### Tooling

### Documentation

---

## Open Issues

1. API to configure text justification. Our working decision is that numeric column text and headers should be right aligned. Any alternatives we choose that might display numeric data will need a way to configure this. We'll update the HLD with a recommendation once we reach consensus on which alternatives to pursue (you're welcome to comment with ideas now though).