Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"parser": "babel-eslint",
"plugins": [
"prettier"
"prettier",
"html"
],
"env": {
"browser": true,
Expand Down
241 changes: 223 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ named `<regular-table>`,
which renders a regular HTML `<table>` to a `fixed` position within a scollable
viewport. Only visible cells are rendered and queried from a natively `async`
virtual data model, making `regular-table` ideal for enormous or remote data
sets. Use it to build high-performance Data Grids,
sets. Use it to build Data Grids,
Spreadsheets, Pivot Tables, File Trees, or anytime you need:

* Just a regular `<table>`.
Expand Down Expand Up @@ -89,43 +89,64 @@ to write a simple _virtual_ data model to access `DATA` and `COLUMN_NAMES`
indirectly.

```javascript
const COLUMN_NAMES = [
"Column 1 (number)",
"Column 2 (string)",
"Column 3 (boolean)",
];

const DATA = [
[0, 1, 2, 3, 4, 5],
["A", "B", "C", "D", "E", "F"],
[true, false, true, false, true, false]
];
```

When clipped by the scrollable viewport, you may end up with a `<table>` of just
a rectangular region of `DATA`:

<table>
<tbody>
<tr>
<td>0</td>
<td>A</td>
</tr>
<tr>
<td>1</td>
<td>B</td>
</tr>
</tbody>
</table>

Here's a simple _virtual_ data model for this example, the function
`getDataSlice()`. This function is called by your `<regular-table>` whenever it
needs more data, with coordinate arguments, `(x0, y0)` to `(x1, y1)`. Only
this region is needed to render the viewport, so `getDataSlice()` returns
this rectangular `slice` of `DATA`, as well as overall dimensions the overall
dimensions of `DATA` itself ( `num_rows`, `num_columns`), for sizing the
virtual scroll area:
this rectangular `slice` of `DATA`:

```javascript
function getDataSlice(x0, y0, x1, y1) {
const data = DATA.slice(x0, x1).map(col => col.slice(y0, y1));
const column_indices = COLUMN_NAMES.slice(x0, x1);
const num_columns = DATA.length;
const num_rows = DATA[0].length;

return {
num_rows,
num_columns,
column_indices,
data
};
}
```

For the window (0, 0) to (2, 2), `getDataSlice()` would generate an Object
like this, containing the `data` slice, as well as the overall dimensions of
`DATA` itself ( `num_rows`, `num_columns`), for sizing the scroll area.

```json
{
"num_rows": 26,
"num_columns": 3,
"data": [
[0, 1],
["A", "B"]
]
}
```

To render this virtual data model to a regular HTML `<table>`, all you need to
do is register this data model via the `setDataModel()` method:

Expand All @@ -143,12 +164,6 @@ scroll, more data will be fetched from `getDataSlice()`, and parts of the
<regular-table>

<table>
<thead>
<tr>
<th>Column 1 (number)</th>
<th>Column 2 (string)</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
Expand Down Expand Up @@ -203,6 +218,196 @@ self.addEventListener("message", async (event) => {
});
```

## Column and Row Headers

`regular-table` can also generate Hierarchial Row and Column Headers, using
`<th>` elements which layout in a `fixed` position within the virtual table.
It can generate Column Headers (within the `<thead>`), or Row Headers (the first
children of each `tbody tr`), via the `column_headers` and `row_headers`
properties (respectively) of your data model's `Response` object.


<table>
<thead>
<tr>
<th>Column 1 (number)</th>
<th>Column 2 (string)</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>A</td>
</tr>
<tr>
<td>1</td>
<td>B</td>
</tr>
</tbody>
</table>

This can be renderered with `column_headers`, a two dimensional `Array` which
must be of length `x1 - x0`.
one `Array` for every column in your `data` window. A modified
`getDataSlice()` which describes this `<table>`:

```javascript
const COLUMN_HEADERS = [
["Column 1 (number)"],
["Column 2 (string)"],
["Column 3 (boolean)"],
];

function getDataSlice(x0, y0, x1, y1) {
const data = DATA.slice(x0, x1).map(col => col.slice(y0, y1));
const column_headers = COLUMN_NAMES.slice(x0, x1);
const num_columns = DATA.length;
const num_rows = DATA[0].length;

return {
column_headers,
num_rows,
num_columns,
data
};
}
```

Samples response:

```json
{
"num_rows": 26,
"num_columns": 3,
"data": [
[0, 1],
["A", "B"]
],
"column_headers": [
["Column 1 (number)"],
["Column 2 (string)"]
]
}
```

Resulting HTML:

```html
<regular-table>

<table>
<thead>
<tr>
<th>Column 1 (number)</th>
<th>Column 2 (string)</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>A</td>
</tr>
<tr>
<td>1</td>
<td>B</td>
</tr>
</tbody>
</table>

</regular-table>
```

## Hierarchial/Group Headers

`regular-table` supports multiple `<tr>` of `<th>`, and also uses `colspan` and
`rowspan` to merge simple consecutive names, which allows description of simple
Row and Column Group Hierarchies such as this:


<table>
<thead>
<tr>
<th colspan="2" rowspan="2"></th>
<th colspan="2">Colgroup 1</th>
</tr>
<tr>
<th>Column 1</th>
<th>Column 2</th>
</tr>
</thead>
<tbody>
<tr>
<th rowspan="2">Rowgroup 1</th>
<th>Row 1</th>
<td>0</td>
<td>A</td>
</tr>
<tr>
<th>Row 2</th>
<td>1</td>
<td>B</td>
</tr>
</tbody>
</table>

A sample response object that renders this `<table>` would look like this:

```json
{
"num_rows": 26,
"num_columns": 3,
"data": [
[0, 1],
["A", "B"]
],
"row_headers": [
["Rowgroup 1", "Row 1"],
["Rowgroup 1", "Row 2"]
],
"column_headers": [
["Colgroup 1", "Column 1"],
["Colgroup 1", "Column 2"]
]
}
```

Note that in the rendered HTML below, for these Row and Column `Array`,
repeated elements in a sequence will be automatically merged via `rowspan` and
`colspan` attributes. In this example, e.g. `"Rowgroup 1"` will only output
to one `<th>` node in the resulting `<table>`:

```html
<regular-table>

<table>
<thead>
<tr>
<th colspan="2" rowspan="2"></th>
<th colspan="2">Colgroup 1</th>
</tr>
<tr>
<th>Column 1</th>
<th>Column 2</th>
</tr>
</thead>
<tbody>
<tr>
<th rowspan="2">Rowgroup 1</th>
<th>Row 1</th>
<td>0</td>
<td>A</td>
</tr>
<tr>
<th>Row 2</th>
<td>1</td>
<td>B</td>
</tr>
</tbody>
</table>

</regular-table>
```

## Development

First install `dev_dependencies`:
Expand Down
26 changes: 10 additions & 16 deletions examples/2d_array.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,30 @@
<regular-table></regular-table>

<script>

const COLUMN_NAMES = [
["Column 1 (number)"],
["Column 2 (string)"],
["Column 3 (boolean)"],
];
const COLUMN_NAMES = [["Column 1 (number)"], ["Column 2 (string)"], ["Column 3 (boolean)"]];

const DATA = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25],
["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"],
Array.from(Array(26).keys()).map(x => x % 2 === 0)
Array.from(Array(26).keys()).map((x) => x % 2 === 0),
];

function getDataSlice(x0, y0, x1, y1) {
const data = DATA.slice(x0, x1).map(col => col.slice(y0, y1));
const column_indices = COLUMN_NAMES.slice(x0, x1);
const data = DATA.slice(x0, x1).map((col) => col.slice(y0, y1));
const column_headers = COLUMN_NAMES.slice(x0, x1);
const num_columns = DATA.length;
const num_rows = DATA[0].length;

return {
num_rows,
num_columns,
column_indices,
data
column_headers,
data,
};
}

const table = document.getElementsByTagName('regular-table')[0];
table.setDataModel(getDataSlice);

const table = document.getElementsByTagName("regular-table")[0];
table.setDataModel(getDataSlice);
</script>

</body>
Expand Down
Loading