-
-
Save theinvensi/e1aacc43bb5a3d852e2e85b08cf85c8a to your computer and use it in GitHub Desktop.
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) |
thanks bro you are the best
Very epic, thanks
Is there support for tfoot
as well?
greattttttttttttttttttt
Works like a charm! Thank you!!!
You just don't know how much of a savior you are sir! Thank you!!!!
You save my life, Thank u very much
Thanks a lot!!! you're my life saver!!! Kudos 🙏🙏🙏
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!
Thanks alot
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.
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?
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);
}
}
So great ! Thanks a lot !
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?
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
.
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
Works great, thank you!
Thank you
Thank you! For me in the current 0.5.0-beta2 version it wans't working when tables weren't in one specific layout (in code it says table should be parent of the element). So here is the version that looks for tables everywhere (any layout):
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 = []
// Check all the tables on the current page
const tablesOnPage = pageElement.querySelectorAll("table");
// Check which tables have sources
for (let table of tablesOnPage) {
if (table.dataset.ref) {
const sourceTable = chunker.source.querySelector(`[data-ref='${table.dataset.ref}']`);
if (sourceTable) {
this.splitTablesRefs.push(table.dataset.ref);
}
}
}
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)
great !