Coder Social home page Coder Social logo

finos / regular-table Goto Github PK

View Code? Open in Web Editor NEW
326.0 17.0 37.0 3.92 MB

A regular <table> library, for async and virtual data models.

License: Apache License 2.0

JavaScript 95.51% HTML 0.80% Less 3.68%
javascript data-visualization table jpmorganchase

regular-table's Introduction

regular-table

FINOS active badge NPM Version NPM Version Build Status

A Javascript library for the browser, regular-table exports a custom element named <regular-table>, which renders a regular HTML <table> to a sticky 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 Data Grids, Spreadsheets, Pivot Tables, File Trees, or anytime you need:

  • Just a regular <table>.
  • Virtually rendered for high-performance.
  • async data model handles slow, remote, enormous, and/or distributed backends.
  • Easy to style, works with any regular CSS for <table>.
  • Small bundle size, no dependencies.

Examples


Documentation

What follows functions as a quick-start guide, and will explain the basics of the Virtual Data Models, Styling and Interaction APIs. Complete API docs and documented examples are also available.

Installation

Include via a CDN like JSDelivr:

<script src="https://cdn.jsdelivr.net/npm/regular-table"></script>
<link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/regular-table/dist/css/material.css"
/>

Or, add to your project via yarn:

yarn add regular-table

... then import into your asset bundle.

import "regular-table";
import "regular-table/dist/css/material.css";

<regular-table> Custom Element

regular-table exports no symbols, only the <regular-table> Custom Element which is registered as a module import side-effect. Once loaded, <regular-table> can be used just like any other HTMLElement, using regular browser APIs:

const regularTable = document.createElement("regular-table");
document.body.appendChild(regularTable);

... or from regular HTML:

<regular-table></regular-table>

... or from your library of choice, as long as it supports regular HTML! Here's an example for React/JSX:

const App = () => <regular-table></regular-table>;
ReactDOM.render(<App />, document.getElementById("root"));

.setDataListener() Virtual Data Model

Let's start with with a simple data model, a two dimensional Array. This one is very small at 3 columns x 6 rows, but even for very small data sets, regular-table won't read your entire dataset at once. Instead, we'll need to write a simple virtual data model to access DATA and COLUMN_NAMES indirectly.

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, rather than the entire set. A simple viewport 2x2 may yield this <table>:

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

Here's a an implementation for this simple virtual data model, 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. For the window (0, 0) to (2, 2), getDataSlice() would generate an Object as above, containing the data slice, as well as the overall dimensions of DATA itself ( num_rows, num_columns), for sizing the scroll area. To render this virtual data model to a regular HTML <table>, register this data model via the setDataListener() method:

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

regularTable.setDataListener(getDataSlice);

This will render your regular HTML <table> ! Your DOM will look something like this, depending on the size of your viewport. Notice there are fewer rows and columns in the resulting HTML, e.g. the column Column 3 (boolean) - as you scroll, more data will be fetched from getDataSlice(), and parts of the <table> will redrawn or extended as needed.

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

virtual_mode Option

regular-table supports four modes of virtual scrolling, which can be configured via the virtual_mode optional argument. Note that using a virtual_mode other than the default "both" will render the entire <table> along the non-virtual axis(es), and may cause rendering performance degradation.

  • "both" (default) virtualizes scrolling on both axes.
  • "vertical" only virtualizes vertical (y) scrolling.
  • "horizontal" only virtualizes horizontal (x) scrolling.
  • "none" disable all scroll virtualization.
table.setDataListener(listener, { virtual_mode: "vertical" });

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. 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.

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

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:

Colgroup 1
Column 1 Column 2
Rowgroup 1 Row 1 0 A
Row 2 1 B
{
    "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, 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>.

Header merging can be disabled with the merge_headers option.

metadata Data-Aware Styling

A dataListener may also optionally provide a metadata field in its response, a two dimensional Array of the same dimensions as data. The values in this field will accompany the metadata records returned by regular-table's getMeta() method (as described in the next section).

{
    "num_rows": 26,
    "num_columns": 3,
    "data": [
        [-1, 1],
        ["A", "B"]
    ],
    "metadata": [
        ["pos", "neg"],
        ["green", "red"]
    ]
}

Rendering Options

Additional rendering options which can be set on the object returned by a setDataListener callback include:

  • column_header_merge_depth: number configures the number of rows to include from colspan merging. This defaults to header_length - 1.
  • row_height: number configures the pixel height of a row for virtual scrolling calculation. This is typically auto-detected from the DOM, but can be overridden if needed.
  • merge_headers: "column" | "row" | "both" | "none" configures whether equivalent, contiguous <th> elements are merged via rowspan or colspan for "row" and "column" respectively (defaults to "both").

async Data Models

With an async data model, it's easy to serve getDataSlice() remotely from node.js or re-implement the JSON response protocol in any language. Just return a Promise() from, or use an async function as an argument to, setDataListener(). Your <regular-table> won't render until the Promise is resolved, nor will it call your data model function again until the current call is resolved or rejected. The following async example uses a Web Worker, but the same principle applies to Web Sockets, readFile() or any other asynchronous source. Returning a Promise blocks rendering until the Web Worker replies:

// Browser

let callback;

worker.addEventListener("message", (event) => {
    callback(event.data);
});

regularTable.setDataListener((...viewport) => {
    return new Promise(function (resolve) {
        callback = resolve;
        worker.postMessage(viewport);
    });
});
// Web Worker

self.addEventListener("message", async (event) => {
    const response = await getDataSlice.apply(null, event.data);
    self.postMessage(response);
});

.addStyleListener() and getMeta() Styling

regular-table can be styled trivially with just regular CSS for <table>.

// Zebra striping!
regular-table tr:nth-child(even) td {
    background: rgba(0, 0, 0, 0.2);
}

However, CSS alone cannot select on properties of your data - if you scroll this example, the 2nd row will always be the striped one. Some other data-reliant style examples include:

  • Styling a specific column in the virtual data set, as <td> may represent a different column based on horizontal scroll position.
  • Styling cells by value, +/-, heatmaps, categories, etc.
  • Styling cells based on data within-or-outside of the virtual viewport, grouping depth, grouping categories, etc.

To make CSS that is virtual-data-model-aware, you'll need to use addStyleListener(), which invokes a callback whenever the <table> is re-rendered, such as through API invocations of draw() and user-initiated events such as scrolling. Within this optionally async callback, you can select <td>, <th>, etc. elements via regular DOM API methods like querySelectorAll().

// Only select row_headers!
table.addStyleListener(() => {
    for (const th of table.querySelectorAll("tbody th")) {
        style_th(th);
    }
});

Once you've selected the <td> and <th> you want to paint, getMeta() will return a MetaData record of information about the HTMLElement's virtual position. This example uses meta.x, the position in data-space, to make virtual-scroll-aware zebra striping.

function style_th(th) {
    const meta = table.getMeta(th);
    th.classList.toggle("zebra-striped", meta.x % 2 === 0);
}
.zebra-striped {
    background-color: rgba(0, 0, 0, 0.2);
}

.invalidate()

To prevent DOM renders, <regular-table> conserves DOM calls like offsetWidth to an internal cache. When a <td> or <th>'s width is modified within a callback to .addStyleListener(), you must indicate to <regular-table> that its dimensions have changed in order to invalidate this cache, or you may not end up with enough rendered columns to fill the screen!

A call to invalidate() that does not need new columns only imparts a small runtime overhead to re-calculate virtual width per async draw iteration, but should be used conservatively if possible. Calling invalidate() outside of a callback to .addStyleListener() will throw an Error.

table.addStyleListener(() => {
    for (const th of table.querySelectorAll("tbody th")) {
        th.style.maxWidth = "20px";
    }
    table.invalidate();
});

.addEventListener() Interaction

<regular-table> is a normal HTMLElement! Use the regular-table API in concert with regular DOM API methods that work on other HTMLElement to create advanced functionality, such as this example of virtual row select:

const selected_rows = [];

table.addEventListener("mousedown", (event) => {
    const meta = table.getMeta(event.target);
    if (meta && meta.y >= 0) {
        selected_rows.push(meta.y);
        table.draw();
    }
});

table.addStyleListener(() => {
    for (const td of table.querySelectorAll("td")) {
        const meta = table.getMeta(td);
        td.classList.toggle("row-selected", selected_rows.includes(meta.y));
    }
});

Advanced examples can be found in the examples directory, and in the bl.ocks example gallery.

Scrolling

Because of the structure of the HTML <table> element, <td> elements must be aligned with their respective row/column, which causes default <regular-table> to only be able to scroll in increments of a cell, which can be irregular when column data is of different lengths. Optionally, you may implement sub-cell scrolling in CSS via <regular-table> slotted CSS variables. The provided material.css theme does exactly this, or you can implement this in any custom style by importing the sub_cell_scrollling.css stylesheet explicitly:

<link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/regular-table/dist/css/sub-cell-scrolling.css"
/>

Pivots, Filters, Sorts, and Column Expressions with perspective

regular-table is natively compatible with perspective, a WebAssembly streaming visualization engine. By using a perspective.Table as a Virtual Data Nodel, it becomes simple to achieve user-driven row and column pivots, filters, sorts, and column expressions, as well as charts and persistent layouts, from high-frequency updating data.

Development

First install dev_dependencies:

yarn

Build the library

yarn build

Run the test suite

yarn test

Start the example server at http://localhost:8080/examples/

yarn start

OpenSSF

The Regular Table project achieves the "Passing" Open Source Security Foundation (OpenSSF) Best Practices status.

License

This software is licensed under the Apache 2.0 license. See the LICENSE and AUTHORS files for details.

regular-table's People

Contributors

debajitdatta2k avatar jhawk avatar lavakush07 avatar neilslinger avatar nminhnguyen avatar patricio-mancini avatar t3rrym avatar telamonian avatar texodus avatar thejuanandonly99 avatar timkpaine avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

regular-table's Issues

Problems when increasing column size by dragging

Bug Report

Steps to Reproduce:

  1. Open one of the examples
  2. Click to drag one of the column header resizers
  3. Drag resizer to the right (try to make column larger)

Expected Result:

Smoothly animated column resize

Actual Result:

Many console errors of the form:

TypeError: Cannot read property 'style' of undefined
    at HTMLElement._on_resize_column_move (events.js:178)
    at async HTMLElement.i.value (utils.js:101)

In some (most?) cases the column resize will still succeed, but the errors seem to prevent the animation/visual feedback from working correctly.

Environment:

osx 10.14, chromium 76

Additional Context:

The issue seems to be the recent removal of vcidx from th metadata. One possible resolution would be to remove the second "optimized" branch from this conditional:

https://github.com/jpmorganchase/regular-table/blob/f60a572904ee753624f9516182ff23a6562d0562/src/js/events.js#L168-L181

On a pivoted table, the group tree column doesn't remember resizes

Bug Report

Steps to Reproduce:

  1. Go to the perspective homepage
  2. Change the perspective viewer plugin to the datagrid
  3. Resize the group tree column (first column)
  4. Click on any of the other column headers to sort

Expected Result:

The column resize to be maintained after the sort

Actual Result:

The column resize is lost after the sort

Horizontal scroll returns one step when the next column is large

Bug Report

Steps to Reproduce:

  1. Create a spreadsheet.
  2. Create a column with a large string inside that will not be displayed in the first frame.
  3. Scroll slowly to it.
    horizontal-scroll

Expected Result:

Horizontal scroll moves smoothly.

Actual Result:

Horizontal scroll returns one step.

A simple way to specify a generic tree column

I've been messing around with the tree functionality in regular-table. The existing tree support is based on row_pivots, which is cumbersome for specifying a generic tree whose complete structure you don't necessarily know in advance. I think(?) there may also be at least some trees that you can't specify in terms of row pivots.

So far my best idea is that if a row can be expanded, the user's data model should add a / to the end of its final path element, and also supply expand and collapse functions (as per _on_toggle).

I'm working on a branch right now to implement the above, but am absolutely open to more clever suggestions.

Add Support for Content Types like NodeList

Feature Request

Description of Problem:

I'm trying to convert existing tables to regular-table. These tables use multiple HTML elements like buttons with hover. When multiple elements are not wrapped in a single HTMLElement they can currently not be added to a td.

Potential Solutions:

I've currently monkey patched this:

  } else if (val instanceof NodeList) {
    td.textContent = "";
    
    Array.prototype.forEach.call(val, (node) => {
      td.appendChild(node.cloneNode(true));
    });
  } else {

To stay in line with the rest this should probably only use appendChild without clone, but this would require a clone in the data listener, as these nodes are lost when they leave the view.

A more general alternative would a custom callback. This would maybe also allow usage of removeChild to keep a single instance of the nodes, which may be faster.

Simple repro of height bug when trying to get a `<regular-table>` to participate in a flex layout

The repro

I came up with a simple repro of the issue I was having in #115:

https://bl.ocks.org/telamonian/00c777c2194e11f43bd7ade294095aa1

The "bug"

The <regular-table> element renders as zero height.

Description

In the above example, there's an #outer flex box that contains two children:

  • a simple <span> with some text content
  • a <regular-table> with ~15,000 data rows

In order to get a <regular-table> element to participate in a flex layout, you have to change it's position (or wrap it in a <div> and change the position of that) to something other than absolute (I think maybe it has to be position: relative, not 100% sure). As soon as you change position, though, the <regular-table> disappears. The only resolution seems to be to set an explicit height in px on at least one of the parent elements of the <regular-table>. In theory, the height of the <regular-table> should be taken care of by the flex layout.

Is this a bug or not? If it is a bug, how do we fix it?

My sense is that there's a tweak to some virtual size calculation somewhere in scroll_body.js that will allow for height to be controlled by the flex layout

Support for explicitly specified row heights

Feature Request

Description of Problem:

As per the conversation here, one interesting feature that @lumino/datagrid has that regular-table is currently lacking is support for explicitly specifying row height.

For at least some set of possible "fat" rows (eg double-height, img with set height, etc), it seems reasonable that the <regular-table> author would know in advance what the "height anomaly" would be for a given row. It would be neat if we could support this use case.

Potential Solutions:

The ideal solution would allow for:

  • correct calculation of virtual scroll position, such that there is parity in behavior, thumb size, etc between the virtual scrollbar of a <regular-table> and the scrollbar of an eagerly rendered vanilla <table>
  • preservation of current regular-table virtual rendering, in which only the for-real visible rows get rendered

As implemented in datagrid, the idea is that you can deal with the various complications introduced by variable row height in O(n) time, where n is the count of rows with custom heights that are affected by a given operation (eg see here). For rendering, n would only be the count of currently visible non-default-height rows.

Non-solutions:

This feature is distinct from the bug discussed in #96, whereby if any row is larger than the default 19px it causes the breakage of the calculation of number of rows to render. The solution for #96 just has to prevent the complete breakage of an affected <regular-table> (eg it doesn't have to completely fix the virtual scroll behavior, etc). Also hopefully there will be a reasonable solution for #96 that won't require explicitly known/specified heights

Table size calculation breaks when you let words wrap in a td

If you let a td be taller than regular-table expects, the scroll position miscalculates.

table.addStyleListener(() => {
  for (const td of table.querySelectorAll("td")) {
    td.style["white-space"] = "normal";
    td.style["word-wrap"] = "break-word";
  }
});

make it optional to merge row_headers with same data

Feature Request

Description of Problem:

When using row headers, sometimes I do not want the headers to be hierarchical. However, I still want to maintain the sticky positioning of the row header columns, which is why I would like to use them.

For example, I may want to see row headers like so:

Country City
USA Atlanta
USA SF
Canada Ontario

Potential Solutions:

It would be nice if there was a setting for the rows to enable/disable the th merging based on shared values.

An alternative that I've considered is implementing these "row headers" as normal td elements within the table, but then I have to go through the exercise of trying to reimplement the sticky behavior. And I suspect there will be problems with making that work with the virtual scroll properly.

default yarn install fails

Bug Report

Steps to Reproduce:

run

yarn

Postinstall task of sharp (dependency of literally-cli tries to run native build of vips, fails for me(macos)

../src/common.cc:23:10: fatal error: 'vips/vips8' file not found

I can install with --ignore-scripts and go on to yarn build and all seems ok

On a wide table, last two columns don't show if the window size is almost as wide as table

On a wide table, the last two columns don't show, nor does a horizontal scroll show, if the window size is almost as wide as table. To get the scroll bar, you need to shrink the window size to cover the last two columns on the right and then the scroll will show, giving access to the last two columns. But unless you know those columns exist, you don't know to resize the window to grab these columns. In more concrete terms, if you have a ten column table and the window size allows 8 columns to be displayed, the 9th and 10th are hidden and no horizontal scroll appears. If you expand the window, they are there. If you shrink the window to 7 or less columns visible, the scroll appears giving you access to columns 7, 8, 9 and 10.

This is occurring with 0.4.1, 0.4.2 and latest Chrome on Mac, as well as an older chrome that comes with QT5 on linux.
Attached are three screen shots. I have a test case but attaching it is not working out well. I can retry that if need be. The styling added by me is minimal and there is nothing on the page but the table to interfere.

Happy to add anything to aid in discovery of what is going wrong. I can post the test case if need be.

Here is the actual right corner of the table. 33 is the 3rd to last column data. The strings are the 2nd to last column. The larger numbers are the last column.
Screen Shot 2022-01-25 at 4 53 58 PM
Here is the missing scroll bar at the bottom to move the table to left to get to last two right most columns. You can see part of the 2nd to last column here and still no scroll bar to get to the right.
Screen Shot 2022-01-25 at 4 53 46 PM
Shrinking the window enough allows you to get the scroll, which would allow one to get to the right most columns.
Screen Shot 2022-01-25 at 4 53 19 PM

using {virtual_mode: "vertical"} and "none" causes containing web page to fail to load

Bug Report

Steps to Reproduce:

I'm trying out the new virtual_mode option for setDataListener:

https://github.com/jpmorganchase/regular-table/blob/6dbee4ffe1b3c1342ae3903a39456aa9d5131cda/src/js/index.js#L272-L279

The both and horizontal options seem to work fine, but trying to use either vertical or none causes the webpage containing the <regular-table> to fail to load.

Expected Result:

vertical and none functioning as described in #125

Actual Result:

image

Before that error pops up, the page will try to load for about 30 sec (while displaying only a white screen, with no loaded sources visible in devtools).

Environment:

[email protected]

Additional Context:

This bug is happening in a regular table included in my <tree-finder-panel> element

Make the regular-table element accessible by default

Making grids and treegrids accessible is difficult, and requires a particular structure and set of annotations that conforms to the WAI-ARIA standard. It would be nice if we could make sure to provide consumers of regular-table with an accessible grid element by default.

refs for flat grids:

refs for treegrids:

general refs:

Datagrid Feature Comparison Matrix

Feature Request

Description of Problem:

regular-table offers some unique features and a simple API compared to many Data Grid libraries available. It would be helpful to see a more granular breakdown of how regular-table compares.

These features make regular-table unique in this regard:

  • It's just a <table>!
  • Virtualized rendering for high performance.
  • async friendly for easy remote data models.
  • Easy to style, works with vanilla CSS for <table>.
  • Small. npm bundle size
  • Simple API.

Potential Solutions:

From #3, which lists many such alternatives that would be a good place to start:

curated lists of grid/table projects:

vanilla javascript + DOM (similar to this project):

React-based:

for Angular/Vue:

other:

Draw only style updates without data updates

Support Question

Is there a way or an API to invoke only the style listeners without invoking the data listeners. The table.draw API updates both the data as well as style.

Sometimes to add more interactivity to the table (such as highlighting the column containing the cell on hover) requires recalculating the styles of the cells. Currently I achieve this by calling table.draw but this also invokes the data callback which adds an unnecessary overhead. This ends up being a laggy experience if I am hovering through multiple cells very quickly.

Table with width 100%

Support Question

Is there a trick to use regular-table with table { width: 100% }? I tried, because the table to replace used this, but then the initial render displays only two columns because view_state.viewport_width immediately exceeds the container โ€“ the first column has the full width after is is drawn:

https://github.com/jpmorganchase/regular-table/blob/8327abfd1f987721f18ab565702840b1c3938c1a/src/js/table.js#L171

After the first redraw this is fixed and behaves as expected.

Working arounds this with

table.addStyleListener(() => {
  table.getElementsByTagName('table')[0].style.width = '100%';
});

seems to work, but slightly changes the layout: the last column gets the remaining space instead of a more even distribution.

What are best practices for 3rd party libraries that want to build components on top of regular-table?

I am currently experimenting with building a more heavy-duty/dedicated tree-browser on top of regular-table over at github.com/telamonian/tree-finder, and I imagine (what with our high media profile, and all) that there will be interest from other parties as well.

With that in mind, at least from a documentation perspective, what should we consider to be the best practice for extending the 'regular-table' custom element? I think(?) there's roughly three choices:

  1. subclassing/extend-ing the RegularTableElement class (this is dependent on what we decide about #60)
  2. composition of the 'regular-table' custom element (within another custom element, a React component, etc)
  3. collections of static functions to help set up data listeners, style listeners, etc

I've been experimenting with 1., but inheritance in general seems to be out of fashion these days. In asking this question I'm somewhat inspired/motivated by the React folks, who have taken a hard line on composition over inheritance when it comes to extending custom components.

Should RegularTableElement be explicitly marked as `final`?

Currently it is possible for a 3rd party library to subclass RegularTableElement (the class backing our regular-table custom element), though it is a bit awkward:

import "regular-table";

customElements.whenDefined('regular-table').then(() => {
  const RegularTableElement = customElements.get('regular-table');

  class RegularFooElement extends RegularTableElement {
    ...
  }

  customElements.define('regular-foo', RegularFooElement);
});

We should either:

  1. Make this way easier by export-ing RegularTableElement at the top level of the regular-table module. We should then also add a short guide to the docs about subclassing RegularTableElement and using it to create new custom elements (and also maybe some warnings about not calling new RegularTableElement directly).

  2. @texodus Are there good reasons why RegularTableElement should be treated as final (ie not subclass-able)? If so, we should explicitly and loudly declare RegularTableElement to be final in the docs (and also explain why), and possibly also mark it as such in the code. I know there's no standard way to mark code as final in js, but there seem to be at least a few workable methods.

I'm leaning towards 1. I've been playing around all day with subclassing and otherwise extending 'regular-table', and it seems to work pretty good. So far the only hiccup I've run into is that any styling with a selector for the 'regular-table' tag (such as most of the styling in material.less) won't apply to subclassed custom elements, since the tag is different.

regular-table doesn't update on scroll in firefox

Bug Report

Steps to Reproduce:

Open the two billion rows example in a firefox browser, then scroll down and to the right a little

Expected Result:

The same thing as on chrome:

image

Actual Result:

The only thing that shows up is the initial table on load:

image

Environment:

OSX 10.14, firefox 77 and 78

Additional Context:

NA

The "namespace" of the css classes doesn't match the name of this project

Currently, the "namespace" used to prefix all of our css classes is "pd-". It's not clear what "pd" is supposed to stand for, and it doesn't match up with the "regular-table" project name. This makes it somewhat more confusing to write and handle styles for "regular-table" elements.

Here's the current list of unique "pd-foo" classNames used across the codebase (as determined by grep -Roh "pd-[-a-z]*" src | sort -u):

pd-cell-clip
pd-column-header-icon
pd-column-resize
pd-date
pd-datetime
pd-float
pd-group-header
pd-group-leaf
pd-group-name
pd-integer
pd-row-header-icon
pd-scroll-container
pd-scroll-table-clip
pd-selected
pd-string
pd-tree-container
pd-tree-group
pd-virtual-pane
pd-virtual-panel

I vote that we refactor all of these to instead start with "rt-". Since this will be a breaking change w.r.t. the css of external project, if/when we pull this refactor in we should also do a v0.2.0 release. This will prevent anyone with an eg "regular-table": "^0.1.6" dependency spec from accidentally picking up these changes prematurely.

This is not the most critical change, and it will cause a bit of pain for 3rd party devs. Still, I think this needs doing, and better we should do it now than later.

Also, if there's actually a really good reason for using "pd-" that I've simply missed, feel free to disregard this issue.

Not loading certain fonts (eg material icons) in built CSS

Currently, some of the fonts in our styling are broken. For example:

image

Those "add" and "remove" text blocks are actually supposed to be an icon ligature supplied by the material icons font.

The issue seems to be with the less build (so lessc, I guess?). Manually adding the font imports to the built dist/css/material.css fixes the issue. I'll look into a fix for this.

How to have different header row height than body row height?

Support Question

Hi, in the editable data grid example on Perspective's website, the column header row has a height of 36px while the table rows have a height of 23px:
CleanShot 2023-09-19 at 14 38 50@2x

Despite this difference, the virtualization works properly and you can scroll to all rows within the window.

I have tried to accomplish something similar using regular-table, but any time my header row is bigger than my table rows, the table does not render properly. It ends up rendering less rows than fill the available scrolling space.

I have tried looking at the underlying perspective data grid code to figure out how its accomplishing this different header size, but I can't seem to find the solution.

Any guidance on how to render a column header height that is different from the table body row height, without breaking the row virtualization? See this video of the broken behavior:

CleanShot.2023-09-19.at.14.42.16.mp4

`RegularTableElement.scrollTo` shadows base `HTMLElement.scrollTo` method

I took a first stab at writing a typescript type declaration file (regular-table.d.ts). In the RegularTableElement class declaration, I had to leave out:

scrollTo(x: number, y: number, ncols: number, nrows: number): void;

This is because the above is not compatible with the signature of the base method HTMLElement.scrollTo, as defined in typescript's standard lib.dom.d.ts:

interface ScrollToOptions extends ScrollOptions {
    left?: number;
    top?: number;
    behavior?: "auto" | "smooth";
}

...

    scrollTo(options?: ScrollToOptions): void;
    scrollTo(x: number, y: number): void;

This is likely to be a pain point for anyone trying to consume regular-table in a typescript project (at the least, they won't be able to call scrollTo on a RegularTableElement instance without a cast).

Potential fixes:

  • (easy) Change the name of scrollTo. For example, we could use scrollToCell
  • (more complex) Add explicit handling of arguments to scrollTo

Prefetching rows from remote source

Is there a way to prefetch rows from a remote source?
It seems that setting setDataListener gets called whenever the viewport changes. When scrolling this generates a lot of calls to the server. What is the best way to implement a caching mechanism to load data in pages (e.g. load 100 rows instead of just the 25 of the viewport) and have the table generate a call only when needed, or when approaching the edge of the prefetched data?

two_billion_rows example scrolls no less than ~2000 rows at a time

Not sure if this is a bug, or intended behavior (or if I'm just missing something obvious).

Steps to Reproduce:

Clone this repo, then:

yarn install
yarn start

then navigate to the two_billion_rows example.

Expected Result:

For the example to only scroll 1 row at a time (I guess? That might not be the best behavior either) for every press of the up/down keys or tic of the scroll wheel.

Actual Result:

I was playing around with the two_billion_rows example, and I noticed that the least you can scroll (ie on a single press of up/down or on a single click of the mousewheel) is 2364 rows at a time. This appears to be the behavior of any sufficiently long regular-table. This creates a UX problem, as there doesn't seem to be a way to scroll, eg, the 1000th row into view, much less a way to position the 1000th row in a specific location on my screen.

The implementation of this behavior is in the _calculate_row_range function. I think there's three questions here:

  • What's the intended UX for scrolling to a specific row?
  • Does it make sense for regular-table to be able to scroll several thousands rows on a single keypress/scroll wheel tic? Or should it max out at 2-3?
  • Given the above UX considerations, what needs to change in the implementation (if anything)?

How do I add a regular-table to a flex layout without breaking it?

Support Question

I'm creating a panel element that has a few widgets at the top and then a <regular-table> on the bottom. I want to use a flex-column layout for the panel, like so:

image

Following suggestions online, I tried setting the CSS to the following:

.myapp-panel {
  display: flex;
  flex-flow: column;
}

.myapp-breadcrumbs {
  height: 16px;
  width: 100%;
}

.myapp-filter {
  height: 16px;
  width: 100%;
}

regular-table.myapp-regular-table {
  flex: 1;
}

and then I ran into trouble. regular-table ships a stylesheet in its shadow root that applies position: absolute to its :host (ie the regular-table element itself). position: absolute does not seem to be compatible with having display: flex in the parent panel; these styles together fling the regular-element out of the visible area of the document.

Next I tried changing the position styling of the regular-table by adding more CSS:

regular-table.myapp-regular-table {
  flex: 1;
  position: relative;
}

However, changing the position: absolute styling seems to completely break regular-table; on initial render it only displays one or two rows, and it seems to break in stranger ways as well:

image

(where the heck does that nub come from?)

Any suggestions on how I can achieve my desired layout (or reasonable facsimile) without breaking regular-table would be greatly appreciated.

For more context, here's a qualitative description of what I'm trying to achieve in terms of layout:

  • The .myapp-panel element serves as the outer container for the other elements. Its height and width should each be able to grow to fill available space. Ideally, either of height/width should alternatively be fixed without breaking any child element behavior and/or CSS
  • The .myapp-breadcrumbs element should have a fixed height, and it's width should grow to fit its parent's width
  • The .myapp-filter elements should be the same (fixed height, grow width)
  • The .myapp-regular-table element should grow in height to fill the remainder of the panel, and its width should be set by the usual regular-table autosizing logic, up to the width of the containing panel

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.