-
Notifications
You must be signed in to change notification settings - Fork 357
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
feat(Table): support inline editing in PF4 #1227
Conversation
@@ -52,7 +52,7 @@ const defaultProps = { | |||
isDisabled: false, | |||
isReadOnly: false, | |||
type: 'text', | |||
value: null, | |||
value: undefined, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to allow uncontrolled components like TableTextInput
@@ -0,0 +1,8 @@ | |||
// TODO: better sharing util components between modules |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is copied from @react-core internal. Does someone have any recommendations how to share utility classes between modules?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added #1232 for this. If it merges you can:
import { debounce } from '@patternfly/react-core';
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks
onClick: (event) => onRowClick(event, row, props) | ||
row, | ||
rowProps: props, | ||
onMouseDown: (event) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onMouseDown is needed to register clicks before onChange/onBlur changes the row (which discards the onClick)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems like kind of a hack, but works. I wonder if proposing an upstream PR to reactabular in parallel to allow hooks into onChange
onBlur
would be a good idea?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where would the hooks be? I am not sure something like this would help.
The idea of this is to get ahead of cell onBlur callbacks. Because of the cell's onBlur callback, the cell will be scrapped and then constructed again. And then we get a chance to fire callbacks (captured here) on our new instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is a hack nevertheless :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One note, can this function be moved outside of this component? So it would be easier to read the component when debugging?
}; | ||
|
||
export const TableEditConfirmation = { | ||
NONE: 'NONE', // TODO: do edit buttons make sense on the left? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we include more combinations of edit buttons? E.g. on the left?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This question is better to be asked any UX person @mcarrano any thoughts on this?
}; | ||
|
||
// TODO: shouldComponentUpdate? | ||
// TableConfirmButtonsRow.shouldComponentUpdate = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not really sure yet if shouldComponentUpdate is needed? It was present in the pf3 component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As long as you are not trying to calculate re-renders for you we don't really need to use shouldComponentUpdate
privateRepo: false | ||
} | ||
// isEditing: true, | ||
// activeEditCell: 3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
possibility to select editing programatically
Pull Request Test Coverage Report for Build 4563
💛 - Coveralls |
Added issue 1228 for this. |
case TableEditConfirmation.ROW: | ||
if (isEditing) { | ||
editStyle = { | ||
background: '#def3ff', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you replace all color references with actual color tokens from @patternfly/react-tokens
(ex consumption here )?
you can also find these and their default mappings here:
http://patternfly-react.surge.sh/patternfly-4/styles/tokens/
If these colors don't exist, maybe we can subtitute an existing pf-next color?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I am planning to do the css cleanup
This seems like a great idea; however, we are avoiding adding anything to react-core or the core packages (which include react-table) that do not have backing PatternFly patterns and documentation. Would it be possible to move this to a new @ patternfly / {name of packaqge} - extension package so we can consider including it into core PatternFly at a later date? |
@@ -2,13 +2,14 @@ import React from 'react'; | |||
import { Body } from 'reactabular-table'; | |||
import PropTypes from 'prop-types'; | |||
import { TableContext } from './Table'; | |||
import '../../../internal/closestPolyfill'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a sideEffect
and will break webpack 4 (well not be imported in production)., It is also generally a bad idea to pollute prototypes for the user. This should be an opt-in pollyfill by the consumer. We can still export it and if they want/need it they can use it. Also since by default PF4 is excluding IE support (a workaround is coming for consumers needing it) the closest polyfill is not needed.
If we do for sure want to keep this in the lib it would be better to do something like:
function closest(element, selector) {
if (typeof element.closest === 'function') {
return element.closest(selector);
}
if (document.documentElement.contains(el)) {
while (el && el.nodeType === 1) {
if (el.matches(selector)) return el;
el = el.parentElement || el.parentNode;
}
}
return null;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know. I just assumed it would be the same as PF3 so I copied it from there. Removed
export const matches = | ||
Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; | ||
|
||
if (!Element.prototype.matches) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed at all. This is supported by all major browsers including IE9+
return null; | ||
} | ||
|
||
if (!Element.prototype.closest) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment in packages/patternfly-4/react-table/src/components/Table/Body.js
onRowClick(event, row, props); | ||
if (onEditCellChanged) { | ||
// edit cell after rerender | ||
setTimeout(onEditCellChanged, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems kind of "hacky" with upcoming react releases it is possible that the rerender may not have actually taken place here. will that be ok? I can't tell from the code if that will cause issues or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case we would really have to contribute to reactabular and fix these issues. Yup it is hacky, but there are not many other solutions.
Sorry for late response because of the PTO. I get the idea of putting bigger features to separate packages. Btw, do you mean patternfly next patterns or https://www.patternfly.org/pattern-library/forms-and-controls/inline-edit/ ? |
ac5c96e
to
f8864c5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice extension! I like how you are passing different editors for different edits. That is really great approach. Few nitpicks, but overall good PR!
onClick: (event) => onRowClick(event, row, props) | ||
row, | ||
rowProps: props, | ||
onMouseDown: (event) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One note, can this function be moved outside of this component? So it would be easier to read the component when debugging?
hidden={isExpanded !== undefined && !isExpanded} | ||
/> | ||
); | ||
const hasConfirmationButtons = (editConfirmationType, { isEditing, isTableEditing, isFirst, isLast }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These function with switch can be converted to object, this approach is much more extensible.
const tableMapper = {
[TableEditConfirmation.TABLE_TOP]: ({ isTableEditing, isFirst }) => isTableEditing && isFirst,
[TableEditConfirmation.TABLE_BOTTOM]: ({ isTableEditing, isFirst }) => isTableEditing && isLast,
[TableEditConfirmation.ROW]: ({ isEditing }) => isEditing,
[TableEditConfirmation.NONE]: () => false
}
You would then use it like
const hasConfirmationButtons = (editConfirmationType, props) => {
tableMapper[editConfirmationType] && tableMapper[editConfirmationType](props);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are right, it looks much cleaner now
} | ||
}; | ||
|
||
export const areButtonsOnTop = (editConfirmationType, { isLast }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similiar approach as with the previous function.
let editStyle; | ||
|
||
if (isTableEditing) { | ||
switch (editConfirmationType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly I don't really like switches, they tend to be really complex, hard to read and even harder to extend. Please consider using object literal with functions.
} | ||
const { editConfirmationType, onEditConfirmed, onEditCanceled } = editConfig; | ||
|
||
let confirmButtons; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to assign to variable if we are not gonna do anything with it and just return it. It's much cleaner to just return from if statement. When the if is not satisfied function will return undefined nevertheless.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather remove it too, but I kept that because of eslint/prettier. I am not sure how do we stand on that?
}; | ||
|
||
export const TableEditConfirmation = { | ||
NONE: 'NONE', // TODO: do edit buttons make sense on the left? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This question is better to be asked any UX person @mcarrano any thoughts on this?
}; | ||
|
||
// TODO: shouldComponentUpdate? | ||
// TableConfirmButtonsRow.shouldComponentUpdate = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As long as you are not trying to calculate re-renders for you we don't really need to use shouldComponentUpdate
) => { | ||
const { rowData, columnIndex } = additionalData; | ||
|
||
let result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to assign to result if we are only returning it. You can add return instead of assign to each if statements and it wil be the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are right, thanks
@suomiy what @dgutride is proposing is kinda understandable. We want to ship table component as pure as possible with only few enhancers and if consumers want to use inline edit they'd import |
f8864c5
to
18067b4
Compare
Ok, I guess it makes sense to separate it. I will look into that. |
PatternFly-React preview: https://1227-pr-patternfly-react-patternfly.surge.sh |
@suomiy if you need some special variables to be passed to core implementation that is fine, that code won't be used. But if we split this into another package we have really great way of importing custom packages and promote other teams to contribute and use some other packages (even written outside of PF world) |
645f600
to
e2ce121
Compare
18a037b
to
47b70e6
Compare
I've removed the Do not merge label since it's in its own package now @dgutride |
dd96fd9
to
1d8b7f2
Compare
Fixed TS and added support for rendering This PR is ready for final reviews (sibling #1520 was merged). |
getting these here locally now...can we fix them first?
|
1d8b7f2
to
931e249
Compare
fixed, seems like we can't have dynamic object keys (reactjs/react-docgen#317 (comment)) + added a comment about this issue to the code as the constants are now hardcoded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
react-docs look good and tests pass locally. Please just update the patternfly dependency, but will go ahead and approve. nice work!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good! Thank you for this PR!
931e249
to
21ca9d8
Compare
21ca9d8
to
dfb18fd
Compare
updated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @suomiy , great work on this!
Hi, I tried to tackle inline editing with reactabular. But it is still not perfect and a bit hacky.
My main goal was to prevent this behavior: kubevirt/web-ui-components#9, but other issues are still present.
This is a due to the reactabular architecture, where we cannot expect our cells to behave in react-like way and have to implement workarounds introduced by this PR. It is inspired by https://github.com/reactabular/react-edit and uses onBlur and autoFocus on input elements
There is one issue with gatsby? where the whole table page is refreshed and the table component is called by costructor. This happens on a dropdown selection in
Editable table With Inline Edit Row
live site: https://1227-pr-patternfly-react-patternfly.surge.sh/patternfly-4/components/inlineedit
TODO:
Current Issues:
@priley86 @karelhala