primefaces / primereact

The Most Complete React UI Component Library
https://primereact.org
MIT License
6.68k stars 1.01k forks source link

DataTable: Sticky header #3396

Closed bhkangw closed 1 year ago

bhkangw commented 1 year ago

Describe the feature you would like to see added

Sorry if I missed it but I don't see a way to freeze the DataTable header row.

frozenValue appears to take an array of rows but not sure how it would work with freezing the Header?

Describe the solution you'd like

There are ways to freeze columns and rows, but I don't see any documentation on freezing the Header row. I'm guessing it likely already exists but maybe including it in the documentation if it does! If not, perhaps a way to specify the header in frozenValue or a separate prop for freezeHeader

bhkangw commented 1 year ago

I also tried adding p-frozen-column as a className under DataTable and Column components which states in the docs "Frozen column header" but it did not work as expected

melloware commented 1 year ago

So you want the header row to be sticky? Basically like this how PrimeFaces does it? https://www.primefaces.org/showcase/ui/data/datatable/sticky.xhtml?jfwid=51db7

bhkangw commented 1 year ago

@melloware yes exactly!

kbeeveer46 commented 1 year ago

@bhkangw You can easily to this with CSS by setting a header class name like this. If you scroll down the page the headers are always stickied to the top of the page. This is how I do it in my app.

<Column headerClassName="stickyToTopTableHeaders" />

.stickyToTopTableHeaders {
    position: sticky;
    background-color: #fff;
    z-index: 1100;
}
melloware commented 1 year ago

We should add a Showcase example showing this sticky usage.

bhkangw commented 1 year ago

@bhkangw You can easily to this with CSS by setting a header class name like this. If you scroll down the page the headers are always stickied to the top of the page. This is how I do it in my app.

<Column headerClassName="stickyToTopTableHeaders" />

.stickyToTopTableHeaders {
    position: sticky;
    background-color: #fff;
    z-index: 1100;
}

@kbeeveer46 thanks for your reply but I wasn't able to get it working - here's a code sandbox with your suggestions. Let me know if I missed something

kbeeveer46 commented 1 year ago

@bhkangw You can easily to this with CSS by setting a header class name like this. If you scroll down the page the headers are always stickied to the top of the page. This is how I do it in my app.

<Column headerClassName="stickyToTopTableHeaders" />

.stickyToTopTableHeaders {
    position: sticky;
    background-color: #fff;
    z-index: 1100;
}

@kbeeveer46 thanks for your reply but I wasn't able to get it working - here's a code sandbox with your suggestions. Let me know if I missed something

@bhkangw

  1. Remove responsiveLayout from the table
  2. Add top: 0 to the CSS (sorry, forgot to add this to my last comment) Here's a working example: https://codesandbox.io/s/charming-snyder-727lco?file=/src/demo/DataTableDemo.js

I don't use menu filterDisplay in my app. I use the row option. So I'm not sure if the menu option will still work well with this CSS. I also don't use responsiveLayout in my app, either. I'm not entirely sure what that does. My tables seem to work fine on mobile and desktop so I never had the need to use that property.

bhkangw commented 1 year ago

@kbeeveer46 still not working unfortunately. This is what I see in your code sandbox, the header is still not sticky and the table formatting is morphed

Screen Shot 2022-10-03 at 3 12 54 PM
kbeeveer46 commented 1 year ago

@kbeeveer46 still not working unfortunately. This is what I see in your code sandbox, the header is still not sticky and the table formatting is morphed Screen Shot 2022-10-03 at 3 12 54 PM

@bhkangw You are viewing the mobile version of the table. Your monitor resolution + the size of the window is automatically putting the table into mobile mode. Mobile mode doesn't have table headers (they're on the left side of the table and not the top).

Click on the 3rd icon in the top right of your screen shot and it will open that window in full screen mode. Here's what the non-mobile mode looks like and you can see the headers are stickied to the top as I scrolled half way down the page. image

melloware commented 1 year ago

Great work @kbeeveer46 i think we definitely need to add a showcase example

bhkangw commented 1 year ago

Thanks @kbeeveer46 -- was able to get it working!

To anyone viewing this issue, I found the width where the table would collapse to mobile stacked view was unnecessarily wide. So I set breakpoint="240px" which prevents the stacked view in smaller windows. Code sandbox

Side note: @melloware is there a way to move the DataTable horizontal scrollbar to the top of the table? I tried css workarounds like this but it's not applying as the scrollbar is part of the component. I can also create a separate request!

melloware commented 1 year ago

Hmm I believe that scroll bar is rendered by the browser on overflow and I don't think it's location can be changed but I could be wrong. I have seen it done with hacks like this : https://stackoverflow.com/a/2274892

melloware commented 1 year ago

I took at look at your demo and you don't need to use headerClassName="stickyHeader" at all. You can do it with just CSS like this.

.p-datatable .p-datatable-thead > tr > th {
  position: sticky;
  top: 0;
  background-color: #fff;
  z-index: 1100;
}

See my Code Sandbox: https://codesandbox.io/s/loving-currying-brsqjw?file=/src/demo/DataTableDemo.js

kbeeveer46 commented 1 year ago

I took at look at your demo and you don't need to use headerClassName="stickyHeader" at all. You can do it with just CSS like this.

.p-datatable .p-datatable-thead > tr > th {
  position: sticky;
  top: 0;
  background-color: #fff;
  z-index: 1100;
}

See my Code Sandbox: https://codesandbox.io/s/loving-currying-brsqjw?file=/src/demo/DataTableDemo.js

Yes, that is correct. I try to avoid editing the Prime React CSS directly because then my changes are overwritten if I ever upgrade to a new version. And in my case, all my tables were different and also have other content above them which also needed to be stickied (I use top: 75px on my headers so they are stickied below that content. I took the 75px out of my last comment and forgot to add 0px back before submitting the comment which is why it didn't work for the other user at first).

There are many different ways to sticky the headers and I think your solution is probably best for users who only have basic requirements. In my case, I needed a bit more control over how each table stickied the headers. That's when using the headerClassName works well.

melloware commented 1 year ago

But you can just put an ID on the table like id="hello" and then make your CSS custom like...

#hello.p-datatable .p-datatable-thead > tr > th {
  position: sticky;
  top: 0;
  background-color: #fff;
  z-index: 1100;
}

To only take effect on that single table. I always prefer CSS to adding anything in code but to each his/her own!

bhkangw commented 1 year ago

Sorry for the additional pestering but I noticed with these changes the horizontal scrolling now occurs outside of the table contents. So what I'm observing now is the table header no longer "sticks" but scrolls horizontally. This makes the header and any navigation continue scrolling while it should stay fixed.

Screen Shot 2022-10-04 at 1 29 33 PM Screen Shot 2022-10-04 at 1 30 00 PM

Code sandbox

What use to happen is while the table's contents would scroll horizontally, the header/parent would stay fixed

Screen Shot 2022-10-04 at 1 41 02 PM

Sandbox where it's working

Any fix to prevent this?

bhkangw commented 1 year ago

@bhkangw

  1. Remove responsiveLayout from the table
  2. Add top: 0 to the CSS (sorry, forgot to add this to my last comment) Here's a working example: https://codesandbox.io/s/charming-snyder-727lco?file=/src/demo/DataTableDemo.js

I don't use menu filterDisplay in my app. I use the row option. So I'm not sure if the menu option will still work well with this CSS. I also don't use responsiveLayout in my app, either. I'm not entirely sure what that does. My tables seem to work fine on mobile and desktop so I never had the need to use that property.

@kbeeveer46 what responsiveLayout="scroll" does is it allows horizontal scrolling within the contents of the table. By disabling it, the unintended side effect I'm seeing is that is scrolling now occurs on the whole page - leaving parents like the DataTable header component "behind" when scrolling. Is there a workaround that doesn't require removing responsiveLayout?

melloware commented 1 year ago

I answered in your other ticket. This is not possible with HTML. These two features cannot be combined.

bhkangw commented 1 year ago

Ok thank you @melloware for the response

nguyenhaimd commented 1 year ago

Late to the party but I have the same issue. I tried adding CSS below to index.css but it's still not floating.

responsivelayout was set to scroll and I did remove it from the datatable. Also, is it possible to make the footer sticky too ?

.p-datatable .p-datatable-thead > tr > th {
  position: sticky;
  top: 0;
  background-color: #fff;
  z-index: 1100;
}

The render portion for the app I am modifying is in index.js

const root = ReactDOM.createRoot(document.getElementById('root'));``

root.render(
  <React.StrictMode>
    <ReportDashboard />
  </React.StrictMode>
);
<div className="card">
                    <DataTable id='report-table' value={data} ref={dt} header={header} filters={filters} filterDisplay="menu"
                    globalFilterFields={selectedColumns.map(c=>c.key)} onFilter={(e) => setFilters(e.filters)}
                    paginator rows={25} resizableColumns columnResizeMode="expand" showGridlines={showGridlines} onValueChange={newData => setFilteredData(newData)}
                    paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown"
                    currentPageReportTemplate="Showing {first} to {last} of {totalRecords}" rowsPerPageOptions={[10,25,50,100]}
                    >
                    {selectedColumns.map( col => {
                        return <Column {...getColumnProps(col)} />
                    })}
                </DataTable>
                <Sidebar visible={modal==='settings'} position="right" className="p-sidebar-md" onHide={() => setModal(null)} >
                    <h3>Settings</h3>
                    <div className="col-12">
                        <label htmlFor="showGridlines" className="mr-2">Show Gridlines</label>
                        <Checkbox inputId='showGridlines' onChange={e => setShowGridlines(e.checked)} checked={showGridlines}></Checkbox>
                    </div>
                    <div className="col-12">
                         <label htmlFor="datetimeFormat" className="mr-2">Date/time format</label>
                        <Dropdown value={datetimeFormat} options={datetimeFormats} onChange={(e) => setDatetimeFormat(e.value)} placeholder="Select a format"/>
                    </div>
                </Sidebar>
            </div>
        </div>
    );
}

export default Table

Any help is greatly appreciated. Thanks for your time.

nguyenhaimd commented 1 year ago

I got it to work by removing the lines below resizableColumns columnResizeMode="expand"

Any idea how I can make the footer sticky also ?

melloware commented 1 year ago

In PrimeReact 9.2.1 my CSS above won't work because it makes responsiveLayout="scroll" the default and sticky cannot be used with scrollable tables.

nguyenhaimd commented 1 year ago

Luckily our app is still on 8.7.2 is there any workaround for that in 9.2.1 ?

melloware commented 1 year ago

Luckily our app is still on 8.7.2 is there any workaround for that in 9.2.1 ?

I would have to spend some time looking at it but I am sure there is a workaround to disable scrollable tables.

nguyenhaimd commented 1 year ago

@melloware thanks for your time and prompt response, any suggestions for making the footer sticky also ?

melloware commented 1 year ago

@melloware thanks for your time and prompt response, any suggestions for making the footer sticky also ?

When you say "footer" do you mean the paginator? Can you fork my sandbox here and show me what you are trying to do? https://codesandbox.io/s/loving-currying-brsqjw?file=/src/demo/DataTableDemo.js

nguyenhaimd commented 1 year ago

Yes. Exactly. I would like to make the paginator sticky.

Anyway we could get the header to be sticky with resizeableColumns ?

melloware commented 1 year ago

resizableColumns probably makes the table scrollable and see above why the HTML5 spec doesn't allow that.

As for paginator to make it sticky just do this and tweak the top value to suit your needs.

.p-paginator {
  position: sticky;
  top: 55px;
  z-index: 1101;
}
nguyenhaimd commented 1 year ago

I forked your sandbox with the footer update in datatabledemo.css but couldn't seem to get it sticky https://codesandbox.io/s/boring-solomon-j5v5go?file=/src/demo/DataTableDemo.css

melloware commented 1 year ago

try this one: https://codesandbox.io/s/loving-currying-brsqjw?file=/src/demo/DataTableDemo.css:965-1057

i made it have a top paginator so you can see it stick.

melloware commented 1 year ago

Working Demo for 9.2.1: https://codesandbox.io/s/flamboyant-dawn-nvd7oc?file=/src/demo/DataTableDemo.css

mertsincan commented 1 year ago

In addition to @melloware solution, you can also try this style;

...
.stickyTable.p-datatable > .p-datatable-wrapper {
  overflow: visible;
}
.stickyTable.p-datatable
  > .p-datatable-wrapper
  > .p-datatable-table
  > .p-datatable-thead {
  position: sticky;
  top: 75px; /* paginator's height */
  z-index: 1100;
}
.p-paginator {
  position: sticky;
  top: 0px;
  height: 75px; /* you can change this line according to your needs */
  background-color: #fff;
  z-index: 1101;
}
nguyenhaimd commented 1 year ago

Thank you @melloware and @mertsincan is it possible to float the very top header also where you can select a keyword and country?

How about floating the paginator on the bottom instead ?

const renderHeader = () => {
    return (
      <div className="flex justify-content-between align-items-center">
        <h5 className="m-0">Customers</h5>
        <span className="p-input-icon-left">
          <i className="pi pi-search" />
          <InputText
            value={globalFilterValue}
            onChange={onGlobalFilterChange}
            placeholder="Keyword Search"
          />
        </span>

        <Dropdown
          value={selectedCountry}
          options={countries}
          onChange={onCountryChange}
          optionLabel="name"
          filter
          showClear
          filterBy="name"
          placeholder="Select a Country"
        />
      </div>
    );
  };
melloware commented 1 year ago

Yep: https://codesandbox.io/s/flamboyant-dawn-nvd7oc?file=/src/demo/DataTableDemo.css

nguyenhaimd commented 1 year ago

When I click on the filter icon on a column or the country dropdown, it show up behind the floating headers. Is there a way to have it appear in front of the floating headers so I can actually see it ?

Are we able to move those elements (Customers, keyword search and country dropdown) into the paginator ?

melloware commented 1 year ago

No idea about moving to paginator but you need to mess with the z-index in that CSS. Try dropping it to 100 instead of 1100.

mertsincan commented 1 year ago

Also, you can try to change z-index and set appendTo="self" to Dropdown component; https://codesandbox.io/s/bitter-pond-5i04c6?file=/src/demo/DataTableDemo.css

Are we able to move those elements (Customers, keyword search and country dropdown) into the paginator ?

I think you can use paginatorTemplate property; https://primereact.org/datatable/#paginator_template

melloware commented 1 year ago

Update for 9.5.0 I had to create this style:

    .stickyTable.p-datatable>.p-datatable-wrapper {
        overflow: visible;
    }

    .stickyTable.p-datatable>.p-datatable-wrapper>.p-datatable-table>.p-datatable-thead {
        position: sticky;
        top: 55px;
        z-index: 10;
    }

Then use it on the table with className="stickyTable"

espezaliate commented 7 months ago

Is there any way to do this without overflow: visible;? My table is quite long and I want to avoid scrolling whole page since it scrolls table settings out of view.

melloware commented 7 months ago

scrollable tables can NOT be sticky. Its not us its how HTML5 sticky works. Scrolling by definition means the header can't be static and sticky.

espezaliate commented 7 months ago

It seems to be enough to put the datatable in a container that has overflow: auto to stop it from overflowing on the page. However my TreeSelect header still scrolls out of view in the x axis.