-
Notifications
You must be signed in to change notification settings - Fork 6
Sticky header using intersection observer for recordset page #2608
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
base: master
Are you sure you want to change the base?
Conversation
…into sticky-header-intersection-observer
…into sticky-header-intersection-observer
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.
The implemented solution is not responsive and therefore we cannot merge it as is.
The vertical position of the sticky header is calculated on the initial load and remains the same. So if the user squeezes the page or remove a banner/alert (which changes the height of the top section), the sticky header shows up in the wrong spot.
Also there's no reason to keep the height in the React state. It's only used for manipulating the position of sticky header and not used in the render logic, so we shouldn't add it to state.
Other changes that should be applied:
- Please remove
.last-run.json
. - Revert the docker file changes that you did.
- Revert the changes in
locators/recordset.ts
. Instead of using filter, we just need to change the selector to use the:not(.sticky-header)
stynax. You can just revert you changes. Once we think this PR is ready, I'll take care of test cases myself.
…or recordsettable
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.
We had a call about this and I'm going to summarize what we talked about here:
The attachContainerHeightSensors
function already does the calculations that we need and so we shouldn't add another resizeSensor
for doing similar calculations.
Instead we can add a callback as an argument to attachContainerHeightSensors
which would tell us about the height changes. And then we can use that callback for setting the top
of the sticky header.
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.
With the latest changes, the sticky header is now properly adjusting as the available space above the table changes. The width of the headers are also adjusting properly.
But there are still two functionality issues that I mentioned in my comments and I'll summarize here:
-
The
top
calculation is not working properly for the popups. It's not honoring the vertical space outside of the modal, so the sticky header shows up on top of the page size dropdown. -
This implementation is forcing React to rerender the recordset component and all its children after each height/width change. This can cause performance issues on busier pages.
By the way, please update this branch with the latest changes of the main branch.
@@ -33,13 +33,16 @@ type RecordsetTableProps = { | |||
* Determines if both horizontal scrollbars should always be visible, or if only one should appear at a time. | |||
*/ | |||
showSingleScrollbar?: boolean, | |||
// Determines the top for the sticky header |
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.
Use JSDoc style of comment like the previous prop that you see. So
/**
* Determines the top for the sticky header
*/
headerTop?: number,
position: absolute !important; | ||
} | ||
.sticky-header { |
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.
Please rename this to chaise-table-sticky-header
to be more specific.
@@ -423,6 +495,15 @@ const RecordsetTable = ({ | |||
}) | |||
} | |||
|
|||
const renderHeader = () => { | |||
return( | |||
<tr> |
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.
Fix the indentation to match the rest of the file.
@@ -22,7 +23,7 @@ import Tooltip from 'bootstrap/js/dist/tooltip'; | |||
* | |||
* TODO offsetHeight is a rounded integer, should we use getBoundingClientRect().height in this function instead? | |||
*/ | |||
export function attachContainerHeightSensors(parentContainer?: any, parentContainerSticky?: any, useDocHeight?: boolean) { | |||
export function attachContainerHeightSensors(parentContainer?: any, parentContainerSticky?: any, useDocHeight?: boolean, onContainerHeightChange?: (dimensions: any) => void) { |
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.
Don't use any
. Create a proper type here and for the state variable in src/components/recordset/recordset.tsx
import that type:
export type ContainerHeightSensorDimensions = {
/**
* explain what it is here
*/
top: number;
}
Also this line is now too long (you should see it being highlighted as a warning on VSCode if you have eslint installed). So please break it into multiple lines.
@@ -53,6 +54,7 @@ export function attachContainerHeightSensors(parentContainer?: any, parentContai | |||
appContent.style.overflowY = 'auto'; | |||
appContent.style.height = ((parentUsableHeight / windowRef.innerHeight) * 100) + 'vh'; | |||
container.style.height = 'unset'; | |||
appContent.classList.add('app-content-container-scrollable'); |
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.
Add this to CLASS_NAMES
in src/utils/constants.ts
.
@@ -534,7 +612,14 @@ const RecordsetTable = ({ | |||
</div> | |||
{/* This div will be used as the target (end of table) for the intersection observer to hide the | |||
top scrollbar when the bottom one is visible */} | |||
<div className='dummy-table-end-div' ref={tableEndRef}/> | |||
<div className='dummy-table-end-div' ref={tableEndRef} /> | |||
{config.displayMode.indexOf(RecordsetDisplayMode.RELATED) !== 0 && <div className='sticky-header' id='sticky-header' ref={stickyHeaderRef}> |
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.
Create a variable for config.displayMode.indexOf(RecordsetDisplayMode.RELATED) !== 0
at the beginning of this component. Call it something like enableStickyHeader
. And then use this boolean for all the different places that you have the sticky header logic.
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.
Remove the id
attribute.
if (onContainerHeightChange) { | ||
//To notify height change | ||
onContainerHeightChange({ | ||
top: containerSticky.offsetHeight + parentContainerSticky.offsetHeight, |
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.
Instead of recalculating this, is it possible to change the code above to first calculate the top. And then sets the container height using this?
What you have is not working properly for popups. You can test this on isa:dataset
table (https://dev.derivacloud.org/chaise/recordset/#1/isa:dataset@sort(accession::desc::). For example the Title
facet is a good example.
const syncWidths = () => { | ||
if (stickyHeaderRef.current && tableRef.current) { | ||
|
||
const originalThs = tableRef.current.querySelectorAll('tbody > tr > td'); |
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.
Why is this selecting td
s? and not th
?
@@ -367,7 +369,8 @@ const RecordsetInner = ({ | |||
} | |||
|
|||
// handle the scrollable container | |||
const resizeSensors = attachContainerHeightSensors(parentContainer, parentStickyArea); | |||
const resizeSensors = attachContainerHeightSensors(parentContainer, parentStickyArea, undefined, setTopPanelDimensions); |
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.
The more I think about it, the more I don't like this solution. Since the callback is called on each height or width change, React is rerendering recordset component and all its children on each height/width change. So this solution would never work on record page, and it can slow the page down on long recordset pages.
As I mentioned before, we're just manipulating the styles attached to the elements, so there's no reason to involve React state in this. Instead of calling setTopPanelDimensions
and passing the state variable to RecordsetTable
, is there anyway that we can pass the top
value from here? Maybe attach it as a data-*
attribute to an element and then grab it from that page?
This PR includes code changes to ensure the table header remains sticky while scrolling content on the recordset page using the Intersection Observer.
Issues Identified During Testing:
The included changes address these dependency errors