erfangc / gigagrid Goto Github PK
View Code? Open in Web Editor NEWMassively performant, multi-layered React.js table widget Written in TypeScript
License: MIT License
Massively performant, multi-layered React.js table widget Written in TypeScript
License: MIT License
How can I use a special sort function (not lexicographical) on one column, so that when the user clicks on the header of that column, it gets sorted accordingly to that function?
I tried to use customSortFn but it does not seem to be used when clicking on the header.
Right now we restrict the canvas size to the viewport (or component parent) size
We should allow the actual canvas to be bigger should the user specify long columns or there are many columns. Enable scrollX when that happens.
Only show them if needed
Similar to ReactTable, we want the ability to perform infinitely scrolling on the data, i.e. load data as the user scrolls through it.
This will need to be implemented carefully. the last iteration with ReactTable produced counter intuitive scrollbar behaviors.
In this implementation, we should make sure the scrollbar is scaled correctly for the data in the table from the beginning. i.e. the scrollbar should be as tall as there are data in the rasterized tree (even if those data is un-rendered)
Similar to the react-data-grid
project, I propose we use placeholder unstyled cells to represent unloaded cells. This will stretch the body down and automatically scale the scrollbar.
We should leverage tr > th[col-span]
features of the HTML table spec and allow users to decorate column headers with additional layers of headers
we need to decide on the input format we would accept from the user and then just render them as additional tr
in the header
with the appropriate col-spans
I am assuming the scrollbar width is 17px
in this code https://github.com/erfangc/GigaGrid/blob/master/src/components/GigaGrid.tsx#L139
we need to determine it dynamically so it works for different browsers and OS
Similar to some of the other table options out there, we should provide users with the option to edit cells natively in the table.
Cell edit actions will trigger some user defined callback (if provided) and if the return value from the callback is true
we proceed to update the cell and re-render the table.
it might be wise to keep the original data immutable and simply add the edits in a mask layer.
The mask layer can consequently be implemented as a stack to allow undo
s ... or we don't have to be that complicated
We should add a locale
key to formatInstruction
so in the format()
function, a number would be formatted per an optional locale. I will raise an appropriate PR.
@caguthrie your recent upgrade from table to DIV has made a lot of behaviors possible
to give ourselves more performant table do you think it is a good idea to leverage the width
property of ColumnDef
instead of assigning a dynamic CSS, which requires manual jQuery action?
size
on ColumnDef
w%
where w = 1 / visibleColumns.length
overflow
on the cells to hidden, to ensure alignment across rowsA column's width is a state that dictates how wide each column is. We already have this width
property in GigaGrid.
This view fits into the prop/state driven view of React ... the render()
function of the main component under this proposal would still be a pure function of its props/state
and we can render everything in 1 cycle - thus improving performance
If we ever need to reason about how wide columns ought to be, we avoid having to deal with direct DOM manipulation in our logic
It is my belief that if a user wants to see overflowing content in a cell, they will intuitively want to drag a handle
At which point, a drag action on cell borders is an action to change its width ... so we dispatch that action, and the entire app re-renders ... all very 'React-isc' without resorting to complicated lifecycle methods and direct DOM manipulation
please let me know what you think!
There are two benefits
1 - we no longer need to override ReduceStore
's isEqual()
method b/c Immutable objects can be compared by reference to test equality
2 - we avoid manually copying parts of the state that were not changed in an action, e.g. we avoid
handleXAction(state, action) {
return {
p1: state.p1
p12: state.p2 // needlessly copying reference
...
pNew: action.pNew
}
}
See: Immutable.js
th
with colspan
does not appear to play nicely with custom width during column sync
further more, we need to consider how to re-adjust column groups when leaf level columns can be removed from state.columns
due to subtotaling and other factors ...
How should child components (i.e. Row) communicate user triggered events to parent Components (the table)?
There are two approaches that I can see (and are suggested by Facebook)
// parent.render()
<Child onCellClick={e=>this.onCellClick(e)} />
// child,render()
<td onClick={this.props.onCellClick} />
Would the assignee of this issue please research and enumerate the pros/cons of either approach or suggest the alternative?
At @caguthrie 's request
I am really not sure how to do this ...
These may hold the key
https://www.npmjs.com/package/font-awesome-webpack
https://gist.github.com/Turbo87/e8e941e68308d3b40ef6
but I have not been able to get it to work ..
Only some of the rows get rendered into the table.
We have the ability to detect container width per #7, but once the component has been rendered and the user change the viewport dimension, we are not updating bodyWidth
. This behavior should be addressed (and optionally turned off in props).
TreeRasterizer.rasterizeTree()
is being called effective from render()
which means we are rasterizing each time the table is updated.
This might not be a good thing if we frequently make updates to the table state that does not require full rasterization of the tree (such as scrolling).
Therefore, it is best if we put this logic into GigaStore
. the various action handlers will decide if re-rasterization is necessary.
We are currently unable to test in travis due to a known issue with jspm/github rate limit. It appears there is a work around and we will have to investigate
As does this Doc
It appears sub menus have the same z-index as their parent, which is a problem when they overlap
we should write some code to detect parent z-index and stay 1 above it using something like
ReactDOM.findDOMNode(this).parentElement
We need to know how to format generic cells (especially numeric or currency / date)
This should be a property of ColumnDef
i.e.
columnDef: ColumnDef = {
formatInstructions: {
roundTo: 2, // round decimal to
multiplier: 100 // multiple cell value by this constant
separator: true // enable "," separator for long numbers, i.e. 1,000,000
}
}
Feature:
Add a new prop that is either a JSON
1.
{
"children": [ {} ]
}
OR
Tree
objectand instead of using TreeBuilder
on props.data
we just the passed in Tree directly
@shriniwasayyer14 which one is preferred for you?
We should create a WYSIWYG style XLS exports using rich table formatting
Since XLS accept in-line table styles and we already have the the table markup, this shouldn't be monumentally difficult. (we can build off rasterized data like we did in ReactTable)
Current version in npm only has a file in the dist/
folder which includes dependancies such as jQuery. Let's have package.json
point to a lib/index.ts
file which would be the untry point for loading this when using things like webpack. This will solve potentially duplicating dependancies by a developer and reduce bundled file size
Subtotal only works for String columns not for Number columns
We want the same behavior as ReactTable, i.e. to allow users to specify the buckets to bucket Number fields by
ex:
User enters: 1,2,3,4,5,6
Then rows are bucketed accordingly
Arjun has helped us decide to use stylus
A basic design is in place, see src/styles/giga-grid.styl
This design is inspired by Google's Material Design
We need to continue to style the table as we assemble the table for its initial release!
Add a resize handle to each column header (not column group headers ... that sounds complicated)
As the user drag this handle, the column header automatically resizes (i.e. pushing or pull the adjacent column to make itself fit)
Once the drag stops, we re-render the table with the new width so all the cell's width update as well
React exposes almost all DOM events and I have had some success with resizable widgets in the past. This should not be the most difficult.
We are computing column width based on container width for now, OR optionally taking explicit width set by the user. However, when either of these values becomes smaller than the elements in the headers, the table can look quite ridiculous.
I propose a way to derive the correct min-width
on each column using rem
units.
Similar to most other Javascript or style plugins, we need to let the users know how to use this widget in various environments and run time setups
I will include this in the examples page for now, which we will evolve into the github.io page
Replicate the cell/row select feature from ReactTable
activate callback when these events happen
we should dispatch a row select
or cell select
event to the store. the store will invoke the proper externally provided callback
Would make props simpler. I will create a PR for this.
We need to create a script to publish the distribution files to npm
and write the d.ts
if necessary
Replicate the Filtering feature from ReactTable
GigaStore
will process the event and update the tree
Row
s in the tree@caguthrie and I discussed offline today,
the best way to achieve Fixed Header scrolling effect is to render 2 tables and control cell width and height explicitly
however, if the user does not provide column width explicitly, we should derive it in an intelligent manner
here is the decision tree / pseudo logic I propose
if (user_provide_explicit_column_width)
table_width = sum(column_width)
if (user_provide_table_width && no_explicit_column_width)
column_width = table_width / num_columns; // equalize column width
if (user_provide_neither)
table_width = parent_width; // determined in componentDidMount()
column_width = table_width / num_columns; // equalize column width
Create an StoreFactory
that produces a Flux store which delegates state management to a series of Server End Points instead of the client. This allow us to navigate arbitrarily deep graphs without resorting to loading all of that data upfront, which can accumulate to become tens of megabytes.
The new StoreFactory
must produce a flux store such that for every flux action, there is an appropriate handler to mutate the grid's state. Some of these handlers will fully delegate to server end end points. (sorting becomes tricky, and might still have to be done client-side)
Note StoreFactory
is truly a factory, not a store! it will produce a store
The StoreFactory
should take as inputs:
columnDefs
so it may know how to aggregate and sort/GET /server-tree/expand?sectorPath={colTag}:{value},{colTag2}:{value}
The ServerStore
instance should provide access to a async action creator (i.e. a higher-order function returns a function which dispatches two-actions instead of a single action) to handle what is now known as TOGGLE_COLLAPSE
events.
The first action in this async action creator should be to market the node dispatching the action as loading. This action creator then promptly makes an Ajax call to the server URL to fetch additional data
The second action should dispatch an action that will mutate the Tree in the ServerStore
. This mean's the store
must learn to handle this new action type and call TreeRasterizer
as appropriate.
The server must satisfy the following contract:
DetailRow
or SubtotalRow
(or some using some static, predefined schema)SubtotalAggregator
should NEVER be used by GigaGrid if the underlying flux store is a Server StoreMost recent experiments revealed that td
do not play nice with height/width + overflow
to strictly enforce these CSS properties at render time we have to not use td
second, we need to make rowHeight
a prop to allow maximum flexibility. Once we make rowHeight
user-defined (with default fallback to 35px
) we have to enforce it in the table. This is probably also easier with div
and span
than td
I'm using GigaGrid inside a tab component and also I update it with live data.
Therefore when the user expands a rows group clicking on the (+) icon, I need to know it, so that when the user goes to a different tab and then comes back to the original one he finds the grid with the same groups expanded as before.
GigaGrid has the property initiallyExpandedSubtotalRows, with it I can tell the grid that a list of row groups must be expanded to show the rows contained in them.
But I would neeed a property "onRowExpand", similar to onRowClick, so that when the user expands a rows group I can keep track of it and later call initiallyExpandedSubtotalRows on that row group.
Is it possible to have it?
Grid throws some console errors and strange behaviors are observed. Will take a look at a fix
I created TestUtils.ts
to serve mock/sample data for testing purposes
This class has grown big due to the new test cases written. We need to organize it so it does not confuse devs in the future
right now to support fixed header we divided the grid into a top and a bottom table element
to ensure alignment we restrict the width of each th
and td
element. however, we need a mechanism to display things like dropdown menus
this is a good resources: https://css-tricks.com/popping-hidden-overflow/
if not, we can consider using fixed
positioning although that is definitely not preferred
It should be easy given we are using TypeScript and can just take the main Props interface as our public API
However, it is worth noting that there should be ways to automatically generate documentation and it might be good to explore those ...
In addition to Resizable columns, it would really cool if the user can simply drag a column from one position to another by simply dragging the column header itself. This allow the user to reconfigure the column at will.
The entire header should be the draggable handle. The drag event simply mutates the index of the dragged column in the columns
array.
note columns
is not a state now! we may have to make it a state.
GigaGrid displays 'NaN' when the underlying data are null
or (empty). GigaGrid needs to be enhanced to display (empty) instead in this case.
Text wraps as a result which looks counter-intuitive for a menu ... we should make these submenus or menus expand to be as big as the longest single-line item
Explore using various CSS techniques
For targeting UMD and without bundling React itself, we use webpack
However, for webpack to work, we had to install the same jspm dependencies in npm under node_modules
. I believe we can write the webpack.config.js
file to smartly read package.json > jspm > dependencies
and populate the resolve
section on an automated basis
This would eliminate the need to keep track of two sets of dependencies
Consider using systemjs to load files into the browser when testing. This could save us from tediously having to add the src
script files one-by-one.
For production deployment, we can concatenate the modules together with something like systemjs builder, because our dist
still needs to be a single ES5 file.
The problem I see with this, which hopefully is trivial to resolve: how do we load 3rd party libraries through systemjs
. Can we leverage it in phantomjs
for testing with karma
as well?
In Intellij, once we added the karma-jspm plugin, the IDE no longer rerun test when assets change ... not sure if this is a known bug or a config change is needed
On the command-line, if we run karma
manually, src changes are being watched BUT phantomjs
cannot be "captured" if a failing test is not corrected in seconds ... I wonder what can we do to make karma with jspm as good as karma by itself ...
@erfangc per discussion, in each cell, input cell value can be Null, currently it's output is default to NaN%
(it used to be0.00
).
We want to config each input Null
mapping to a blank cell in GigaGrid, not NaN%
. Thanks.
similar to ReactTable
there should be additional default aggregation methods for when rows are subtotaled. currently there is only sum and average, we should add count / range etc ...
if custom aggregation callback is passed on the column def by the user, use that instead
If cell contents becomes too long, they simply push the parent .. we need to stop this behavior as our entire table work off calculated width / height
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.