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

POC: use react portals for cell editors #1369

Merged
merged 16 commits into from
Nov 20, 2018
Merged

Conversation

amanmahajan7
Copy link
Contributor

@amanmahajan7 amanmahajan7 commented Nov 4, 2018

This approach fixes the following problems

  • Editor is never hidden as it is rendered outside the grid
  • Currently the editor is using fixed position that requires resetting the editor's position when window is scrolled. This is no longer needed as editor is absolutely positioned.
  • Additional padding is automatically added when an autocomplete editor is opened on the last row

Fixed a few more issues with frozen columns

  • Copy/Drag/Selection masks positions
  • Mask now uses absolute positioning
  • Removed unused props

This PR however requires a major version as it works with React 16. I think it is about time we drop support for React 15

Demo
https://www.useloom.com/share/45cf203bf19e4027bbb3081dec51d0ad

el = document.createElement('div');

componentDidMount() {
editorRoot.appendChild(this.el);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this makes sense and can fix some of those annoying issues that arise from using fixed positioning

Copy link
Contributor Author

@amanmahajan7 amanmahajan7 left a comment

Choose a reason for hiding this comment

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

@malonecj I fixed some issues with frozen columns, let me know what you think

@@ -218,9 +218,13 @@ class Canvas extends React.PureComponent {
};

setScrollLeft = (scrollLeft) => {
if (this.interactionMasks && this.interactionMasks.setScrollLeft) {
Copy link
Contributor Author

@amanmahajan7 amanmahajan7 Nov 7, 2018

Choose a reason for hiding this comment

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

Using the same approach to fix mask's position

Copy link
Contributor

Choose a reason for hiding this comment

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

since you are touching this file, maybe we can also add some simple doc above the function? like

/**
 * what does scrollLeft do
 * @param scrollLeft number
 */

@@ -246,28 +250,23 @@ class Canvas extends React.PureComponent {
return this.props.rowHeight * rowIdx;
}

getSelectedRowHeight = (rowIdx) => {
Copy link
Contributor Author

@amanmahajan7 amanmahajan7 Nov 7, 2018

Choose a reason for hiding this comment

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

I believe this logic was added to support variable height rows. This adds additional complexity and it is currently not used by Copy and Drag masks so I have removed it. Wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

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

by saying variable height, do you mean row1 is 50 and row2 is 35 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is correct. This is a great feature to have but we need to handle all the masks (Copy and Drag). This currently does not work with them and has some edge cases so I removed it

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree that it adds complexity. When starting the work on the Interaction Mask, I did it under the assumption that RDG only supports fixed row height. However, this is not exactly true as it is possible to have dynamic row height using custom row renderers. I have seen a lot of cases out in the wild where this is happening. Removing this, is going to cause a lot of strange display issues for those cases

Supporting dynamic row height is not trivial, and this current implementation was really just a stop gap to support these non documented edge cases. I would prefer to leave this here and think of a better solution that to completely remove it

@@ -414,9 +414,6 @@ class Canvas extends React.PureComponent {
onCellRangeSelectionCompleted={this.props.onCellRangeSelectionCompleted}
scrollLeft={this._scroll.scrollLeft}
scrollTop={this._scroll.scrollTop}
prevScrollLeft={this.props.prevScrollLeft}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This props do not seem to be used anywhere

Copy link
Contributor

Choose a reason for hiding this comment

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

I think they can be safely removed.

}
};

setMaskScollLeft = (mask, position, scrollLeft) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This logic adjust the mask position during scroll

return { height, top, width: column.width, left, zIndex };
};
import CellMask from './CellMask';
import { getSelectedDimensions } from '../utils/SelectedCellUtils';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This component can be really simplified if we remove getSelectedRowHeight and getSelectedRowTop

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed but I can see some users getting upset that their variable row height grids no longer work. We need to think of a better solution to handle dynamic row heights

@amanmahajan7 amanmahajan7 removed the WIP label Nov 7, 2018
@amanmahajan7 amanmahajan7 requested a review from qili26 November 15, 2018 15:21
Copy link
Contributor

@qili26 qili26 left a comment

Choose a reason for hiding this comment

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

Couple comments.

scrollLeft: PropTypes.number,
scrollTop: PropTypes.number
scrollTop: PropTypes.number,
position: PropTypes.object.isRequired
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not that familiar with the really old syntax, should we add the shape of the position?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea, I will do that in a separate PR to keep this simple.

@@ -132,15 +118,15 @@ class EditorContainer extends React.Component {
return <CustomEditor ref={this.setEditorRef} {...editorProps} />;
}

return <SimpleTextEditor ref={this.setEditorRef} column={this.props.column} value={this.getInitialValue()} onBlur={this.commit} rowMetaData={this.getRowMetaData()} onKeyDown={() => {}} commit={() => {}}/>;
return <SimpleTextEditor ref={this.setEditorRef} column={this.props.column} value={this.getInitialValue()} onBlur={this.commit} rowMetaData={this.getRowMetaData()} onKeyDown={() => { }} commit={() => { }} />;
Copy link
Contributor

Choose a reason for hiding this comment

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

should we lift the onKeyDown={() => { }} commit={() => { }} to the component like this.emptyCall?

const { width, height, column } = this.props;
const zIndex = isFrozen(column) ? zIndexes.FROZEN_EDITOR_CONTAINER : zIndexes.EDITOR_CONTAINER;
const style = { position: 'fixed', height, width, zIndex, transform: this.calculateTransform() };
const { width, height, column, position } = this.props;
Copy link
Contributor

Choose a reason for hiding this comment

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

This position under this context in render() might be confusing when the dev first look at the code, maybe we can rename it just in this render function? Because position is a valid attr in style, while we are assigning ...position to the style as well. But I am OK if we don't change this.

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 have removed position and added left/top props instead

@@ -218,9 +218,13 @@ class Canvas extends React.PureComponent {
};

setScrollLeft = (scrollLeft) => {
if (this.interactionMasks && this.interactionMasks.setScrollLeft) {
Copy link
Contributor

Choose a reason for hiding this comment

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

since you are touching this file, maybe we can also add some simple doc above the function? like

/**
 * what does scrollLeft do
 * @param scrollLeft number
 */

@@ -246,28 +250,23 @@ class Canvas extends React.PureComponent {
return this.props.rowHeight * rowIdx;
}

getSelectedRowHeight = (rowIdx) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

by saying variable height, do you mean row1 is 50 and row2 is 35 ?

if (this.selectionMask) {
const { left, top } = this.selectionMask.getBoundingClientRect();
const { scrollLeft, scrollTop } = document.documentElement;
this.editorPosition = { left: left + scrollLeft, top: top + scrollTop };
Copy link
Contributor

Choose a reason for hiding this comment

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

I noticed you didn't use this window.pageYOffset in the transform function that I added in the merged PR. Is it because of the this.selectionMask ref? Because the original PR added a window scroll event listener, so that when the grid is at far bottom with window scroll bar and the user drags the window level scroll bar, the mask position will be refreshed. For this InteractionMasks, will the position be refreshed when the window level scroll bar is dragged?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

window.pageYOffset is not needed anymore as the editor is now absolutely positioned instead of fixed so it will just work. We however need to make sure scrollleft works for frozen columns and setScrollLeft handles that logic

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

column={getSelectedColumn({ selectedPosition, columns })}
scrollLeft={scrollLeft}
scrollTop={scrollTop}
{...{
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe it's better to clean this code like

const restProps = { ...getSelectedDimensions({ selectedPosition, rowHeight, columns, scrollLeft }), ...this.editorPosition };
isEditorEnabled && (
  <EditorContainer 
     existing codes
     {...restProps} 
  />
);

Copy link
Contributor

@malonecj malonecj left a comment

Choose a reason for hiding this comment

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

Looking good Aman. And thanks for uploading the video. I think this is close to being ready. Some minor comments

};

onPressEnter = () => {
this.commit({key: 'Enter'});
this.commit({ key: 'Enter' });
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can refactor this to use constants instead of magic strings

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a KeyCodes file that needs to be moved to the common folder. I will address this in a separate PR to keep this one simple

return { height, top, width: column.width, left, zIndex };
};
import CellMask from './CellMask';
import { getSelectedDimensions } from '../utils/SelectedCellUtils';
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed but I can see some users getting upset that their variable row height grids no longer work. We need to think of a better solution to handle dynamic row heights

setScrollLeft = (scrollLeft) => {
const { selectionMask, copyMask, state: { selectedPosition, copiedPosition } } = this;
this.setMaskScollLeft(selectionMask, selectedPosition, scrollLeft);
this.setMaskScollLeft(copyMask, copiedPosition, scrollLeft);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this handle the case of cell range selection as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This does not handle cell ranges. Cell range selection probably should only be applied either to the frozen section or to scrollable section. This will require some investigation.

@@ -414,9 +414,6 @@ class Canvas extends React.PureComponent {
onCellRangeSelectionCompleted={this.props.onCellRangeSelectionCompleted}
scrollLeft={this._scroll.scrollLeft}
scrollTop={this._scroll.scrollTop}
prevScrollLeft={this.props.prevScrollLeft}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think they can be safely removed.

- Add back dynamic row logic
Copy link
Contributor Author

@amanmahajan7 amanmahajan7 left a comment

Choose a reason for hiding this comment

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

I have fixed the logic to calculate dynamic heights for copy and drag masks. There are a few known issues with the current implementation

  • Select range functionality does not handle dynamic heights. This never worked so I am not fixing it in this PR
  • Scrolling on cell navigation does not work with dynamic heights. This is due to this logic . I am not sure what is the best fix here.
  • Placeholder heights are incorrect for dynamic height rows.

I think we should change rowHeight to a function and it will become really simple to support dynamic heights. Let me know what do you think.

};

onPressEnter = () => {
this.commit({key: 'Enter'});
this.commit({ key: 'Enter' });
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a KeyCodes file that needs to be moved to the common folder. I will address this in a separate PR to keep this one simple

if (wrappedRow) {
return wrappedRow.row;
}
return this.rows[i];
};

getSelectedRowTop = (rowIdx) => {
getRowTop = (rowIdx) => {
Copy link
Contributor Author

@amanmahajan7 amanmahajan7 Nov 19, 2018

Choose a reason for hiding this comment

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

ReactDOM.findDOMNode() has been deprecated so using an instance method to get row' height/top

isGroupedRow: this.isGroupedRowSelected(),
innerRef: this.setSelectionMaskRef
};
getSelectedDimensions = (selectedPosition) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

getSelectedDimensions is used to calculate cell dimensions for all the mask components

@amanmahajan7
Copy link
Contributor Author

Here is the current implementation

https://www.useloom.com/share/10f73e7d0cd54dcc9c82037fe1706ffb

@amanmahajan7 amanmahajan7 merged commit 264045f into master Nov 20, 2018
@amanmahajan7 amanmahajan7 deleted the am-editor-portal-poc branch November 20, 2018 17:26
rossjp pushed a commit to rossjp/react-data-grid that referenced this pull request Dec 14, 2018
* POC: use react portals for cell editors

* Delete obsolete tests

* Fix selection/copy/drag masks positions on frozen columns

* Simplify selectionMask position logic
Remove unused props

* Fix unit tests

* Remove getSelectedRowTop

* Fix unit tests

* Fix unit tests in FF

* Remove position prop in favor of left/top props

* MInor cleanup

* Remove fdescribe and fix unit tests

* Remove empty event handlers

* Add some comments

* - Fix row heights for copy and drag masks
- Add back dynamic row logic
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants