Skip to content

Instantly share code, notes, and snippets.

@theinvensi
Last active October 25, 2024 19:21
Show Gist options
  • Save theinvensi/e1aacc43bb5a3d852e2e85b08cf85c8a to your computer and use it in GitHub Desktop.
Save theinvensi/e1aacc43bb5a3d852e2e85b08cf85c8a to your computer and use it in GitHub Desktop.
pagedjs-repeat-table-header
class RepeatTableHeadersHandler extends Paged.Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller)
this.splitTablesRefs = []
}
afterPageLayout(pageElement, page, breakToken, chunker) {
this.chunker = chunker
this.splitTablesRefs = []
if (breakToken) {
const node = breakToken.node
const tables = this.findAllAncestors(node, "table")
if (node.tagName === "TABLE") tables.push(node)
if (tables.length > 0) {
this.splitTablesRefs = tables.map(t => t.dataset.ref)
let thead = node.tagName === "THEAD" ? node : this.findFirstAncestor(node, "thead")
if (thead) {
let lastTheadNode = thead.hasChildNodes() ? thead.lastChild : thead
breakToken.node = this.nodeAfter(lastTheadNode, chunker.source)
}
this.hideEmptyTables(pageElement, node)
}
}
}
hideEmptyTables(pageElement, breakTokenNode) {
this.splitTablesRefs.forEach(ref => {
let table = pageElement.querySelector("[data-ref='" + ref + "']")
if (table) {
let sourceBody = table.querySelector("tbody > tr")
if (!sourceBody || this.refEquals(sourceBody.firstElementChild, breakTokenNode)) {
table.style.visibility = "hidden"
table.style.position = "absolute"
let lineSpacer = table.nextSibling
if (lineSpacer) {
lineSpacer.style.visibility = "hidden"
lineSpacer.style.position = "absolute"
}
}
}
})
}
refEquals(a, b) {
return a && a.dataset && b && b.dataset && a.dataset.ref === b.dataset.ref
}
findFirstAncestor(element, selector) {
while (element.parentNode && element.parentNode.nodeType === 1) {
if (element.parentNode.matches(selector)) return element.parentNode
element = element.parentNode
}
return null
}
findAllAncestors(element, selector) {
const ancestors = []
while (element.parentNode && element.parentNode.nodeType === 1) {
if (element.parentNode.matches(selector)) ancestors.unshift(element.parentNode)
element = element.parentNode
}
return ancestors
}
layout(rendered, layout) {
this.splitTablesRefs.forEach(ref => {
const renderedTable = rendered.querySelector("[data-ref='" + ref + "']")
if (renderedTable) {
if (!renderedTable.getAttribute("repeated-headers")) {
const sourceTable = this.chunker.source.querySelector("[data-ref='" + ref + "']")
this.repeatColgroup(sourceTable, renderedTable)
this.repeatTHead(sourceTable, renderedTable)
renderedTable.setAttribute("repeated-headers", true)
}
}
})
}
repeatColgroup(sourceTable, renderedTable) {
let colgroup = sourceTable.querySelectorAll("colgroup")
let firstChild = renderedTable.firstChild
colgroup.forEach((colgroup) => {
let clonedColgroup = colgroup.cloneNode(true)
renderedTable.insertBefore(clonedColgroup, firstChild)
})
}
repeatTHead(sourceTable, renderedTable) {
let thead = sourceTable.querySelector("thead")
if (thead) {
let clonedThead = thead.cloneNode(true)
renderedTable.insertBefore(clonedThead, renderedTable.firstChild)
}
}
nodeAfter(node, limiter) {
if (limiter && node === limiter) return
let significantNode = this.nextSignificantNode(node)
if (significantNode) return significantNode
if (node.parentNode) {
while ((node = node.parentNode)) {
if (limiter && node === limiter) return
significantNode = this.nextSignificantNode(node)
if (significantNode) return significantNode
}
}
}
nextSignificantNode(sib) {
while ((sib = sib.nextSibling)) { if (!this.isIgnorable(sib)) return sib }
return null
}
isIgnorable(node) {
return (
(node.nodeType === 8)
|| ((node.nodeType === 3) && this.isAllWhitespace(node))
)
}
isAllWhitespace(node) {
return !(/[^\t\n\r ]/.test(node.textContent))
}
}
Paged.registerHandlers(RepeatTableHeadersHandler)
@axessweb
Copy link

great !

@zackwong97
Copy link

thanks bro you are the best

@anteeek
Copy link

anteeek commented Aug 19, 2023

Very epic, thanks

@TheCardinalSystem
Copy link

Is there support for tfoot as well?

@Zarky2k2
Copy link

greattttttttttttttttttt

@cathyhax
Copy link

Works like a charm! Thank you!!!

@kevinguto
Copy link

You just don't know how much of a savior you are sir! Thank you!!!!

@adepranaya
Copy link

You save my life, Thank u very much

@DaisukiDaYo
Copy link

Thanks a lot!!! you're my life saver!!! Kudos 🙏🙏🙏

@RobertBouillon
Copy link

RobertBouillon commented Mar 4, 2024

Works great most of the time. Nested repeated headers causes some sections to be missing from both pages, though :(

EDIT: nevermind - it's a bug with pagedJS, not this script!

@DiesuaY
Copy link

DiesuaY commented Mar 11, 2024

Thanks alot

@MoamenAbdelsattar
Copy link

Thanks, I haven't tested yet but I don't understand where the function layout is called. It's not a part of the documented API of hooks.

@mersa2024
Copy link

How do I get this to work? I add it to my code, but it doesn't do anything??? Is there something I need to add to the CSS?

@pawelcwiek
Copy link

pawelcwiek commented May 23, 2024

Thanks!
one note: When the table is moved to a new page (because the table does not fit on the previous page), the header appears twice.
you can avoid this by adding a condition.

  repeatTHead(sourceTable, renderedTable) {
    let thead = sourceTable.querySelector('thead');
    if (thead && renderedTable.firstChild.tagName !== 'THEAD') {
      let clonedThead = thead.cloneNode(true);
      renderedTable.insertBefore(clonedThead, renderedTable.firstChild);
    }
  }

@0110wdj
Copy link

0110wdj commented Aug 9, 2024

So great ! Thanks a lot !

@jkbgbr
Copy link

jkbgbr commented Oct 20, 2024

Dear all,

Seeing everybody thanking for this I think it works, but I have absolutely no experience with js and so far no success in using this code.

What I tried is: simply putting it in the section right after the paged.polyfill.js section like this:

<script src="js/paged.polyfill.js"></script>
<script type="text/javascript">

class RepeatTableHeadersHandler extends Paged.Handler {
    constructor(chunker, polisher, caller) {
.
.
.
</script>

And of course I added a table in the html with a header like this but longer:

<table>
  <tr>
    <th>Firstname</th>
    <th>Lastname</th>
    <th>Age</th>
  </tr>
  <tr>
    <td>Jill</td>
    <td>Smith</td>
    <td>50</td>
  </tr>
</table>

Could anybody point me in the right direction please?

@TheCardinalSystem
Copy link

TheCardinalSystem commented Oct 21, 2024

Dear all,

Seeing everybody thanking for this I think it works, but I have absolutely no experience with js and so far no success in using this code.

What I tried is: simply putting it in the section right after the paged.polyfill.js section like this:

<script src="js/paged.polyfill.js"></script>
<script type="text/javascript">

class RepeatTableHeadersHandler extends Paged.Handler {
    constructor(chunker, polisher, caller) {
.
.
.
</script>

And of course I added a table in the html with a header like this but longer:

<table>
  <tr>
    <th>Firstname</th>
    <th>Lastname</th>
    <th>Age</th>
  </tr>
  <tr>
    <td>Jill</td>
    <td>Smith</td>
    <td>50</td>
  </tr>
</table>

Could anybody point me in the right direction please?

Add me on Discord and I can walk you through it. My username is cardinalsystem.

@tobias-stein
Copy link

tobias-stein commented Oct 25, 2024

Great work, thanks for sharing.

It's repeating the the first table header twice for me.
I think it does that when you have

    thead {
      display: table-header-group; /* Repeat header */
    }

    tbody {
      display: table-row-group;
    }

in your css

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment