// Uncompressed script. DokuWikis JS compressor does not support ASI. To be compressed with https://closure-compiler.appspot.com/ (SIMPLE)
window.mobileTables = ((options) => {
options = options || {}
// A CSS query selector to find all tables to be transformed.
const selector = options.selector || "table"
// A callback to determine the index column of a table.
const parseColumnIndex = options.parseColumnIndex || (node => -1)
// Special schema values that are not repeated on the mobile table. Instead a cell with colspan = 2 is created.
const hideHeadings = options.hideHeadings || []
// Holds references to the original
elements to undo the transformation.
const tableMap = new WeakMap()
// Holds references to the transformed and | elements to undo the transformation.
const cellMap = new WeakMap()
// Indicates that a cell should be treated as part of the index column.
const indexColumn = Symbol("index")
// Indicates that the cell heading should not be shown. The resulting cell will span both columns.
const hiddenHeading = Symbol("hidden")
// Creates an array of header cells that can be cloned for each "row" of the mobile version of the table.
const extractSchema = (table, columnIndex) => {
const schema = []
// Get the schema from the first row.
const row = table.querySelector("tr")
let i = 0
for (let cell of row.children) {
const colSpan = cell.colSpan
let indexSpan = 0
// Add a cell one or more times to the schema (adjusting for colspan).
while (indexSpan < colSpan) {
if (i === columnIndex) {
schema.push(indexColumn)
} else if (hideHeadings.includes(cell.innerText.trim())) {
schema.push(hiddenHeading)
} else {
let td = document.createElement("td")
td.innerHTML = cell.innerHTML
schema.push(td)
}
i = i + 1
indexSpan = indexSpan + 1
}
}
return schema
}
const isTextCell = cell => cell.childNodes.length === 1 && cell.lastChild.nodeName === "#text"
// Move cell contents instead of cloning to keep attached event handlers (footnotes etc.).
const moveContent = (oldCell, newCell) => {
// Text nodes can just be copied.
if (isTextCell(oldCell)) {
newCell.innerText = oldCell.innerText
return false
}
//newCell.append(...oldCell.childNodes)
while (oldCell.firstChild) {
newCell.appendChild(oldCell.firstChild)
}
return true
}
const addRow = tbody => {
const tr = document.createElement("tr")
tbody.appendChild(tr)
return tr
}
// Adds a cell containing content moved from the original table.
const addCell = (tr, cell, colSpan) => {
const newCell = cell.cloneNode(false)
if (colSpan) {
newCell.colSpan = 2
}
if (moveContent(cell, newCell)) {
cellMap.set(cell, newCell)
}
tr.appendChild(newCell)
return newCell
}
const addHeaderCell = (tr, cell) => {
const newCell = document.createElement("th")
newCell.colSpan = 2
// Copy the CSS class for alignement etc.
newCell.className = cell.className
if (moveContent(cell, newCell)) {
cellMap.set(cell, newCell)
}
tr.appendChild(newCell)
//return newCell
}
const addNameCell = (tr, name) => {
let cell
if (isTextCell(name)) {
cell = document.createElement("td")
cell.innerText = name.innerText
} else {
cell = name.cloneNode(true)
}
tr.appendChild(cell)
//return cell
}
const buildTable = (table, schema) => {
const columnIndex = schema.indexOf(indexColumn)
// Create shallow copies of the table and tbody.
const newTable = table.cloneNode(false)
const tbody = table.querySelector("tbody").cloneNode(false)
newTable.appendChild(tbody)
// Check for rowspans that need to be skipped.
const rowSpans = new Array(schema.length).fill(0)
let skip = true
// Iterating all children of the | is not sufficient as there may be multiple elements inside .
for (let row of table.querySelectorAll("tr")) {
// Skip the first row (header).
if (skip) {
skip = false
continue
}
// A random header row appeared!
if (row.children.length === 1) {
addCell(addRow(tbody), row.firstElementChild, true)
// This resets row spans.
rowSpans.fill(0)
continue
}
// If there is an index column, create a header row.
const header = (columnIndex !== -1) ? addRow(tbody) : null
// Check for colspans that need to be converted to rowspans.
let i = 0
let rowSpan = 1
let colOffset = 0
for (let name of schema) {
if (rowSpans[i] > 0) {
rowSpans[i] = rowSpans[i] - 1
colOffset = colOffset + 1
i = i + 1
continue
}
if (row.children[i - colOffset] === undefined) {
console.log("mobileTables: Unsupported table layout:")
console.log(row)
break
}
if (i === columnIndex) {
// The index column has already been created above, add the content.
addHeaderCell(header, row.children[i - colOffset])
} else {
const tr = addRow(tbody)
if (name !== hiddenHeading) {
addNameCell(tr, name)
}
const colSpan = row.children[i - colOffset].colSpan
rowSpans[i] = row.children[i - colOffset].rowSpan - 1
if (rowSpan === 1) {
const newCell = addCell(tr, row.children[i - colOffset], name === hiddenHeading)
newCell.rowSpan = colSpan
}
rowSpan = (rowSpan === 1) ? colSpan : rowSpan - 1
if (rowSpan > 1) {
continue
}
}
i = i + 1
}
}
return newTable
}
const replaceWithDummy = table => {
// Create a dummy element to take the place of the table so we can modify it outside the document tree.
const dummy = document.createElement("div")
table.replaceWith(dummy)
return dummy
}
const transform = tables => {
tables = tables || document.querySelectorAll(selector)
let mutation = false
for (let table of tables) {
if (tableMap.has(table) || table.classList.contains("mobiletable-transformed")) {
return
}
const columnIndex = parseColumnIndex(table)
// Create the mobile table.
const dummy = replaceWithDummy(table)
const mobile = buildTable(table, extractSchema(table, columnIndex))
// Replace the original table and save it for later.
tableMap.set(mobile, table)
dummy.replaceWith(mobile)
mobile.classList.add("mobiletable-transformed")
mutation = true
}
return mutation
}
const undo = tables => {
tables = tables || document.querySelectorAll(selector)
let mutation = false
for (let table of tables) {
const original = tableMap.get(table)
if (original === undefined) {
//console.log("mobileTables: Cannot find original for table:")
//console.log(table)
continue
}
const dummy = replaceWithDummy(table)
// Move the cell contents back to the original table.
for (let cell of original.querySelectorAll("td, th")) {
const transformed = cellMap.get(cell)
if (transformed !== undefined) {
moveContent(transformed, cell)
}
}
dummy.replaceWith(original)
mutation = true
}
return mutation
}
return (isMobile, tables) => isMobile ? transform(tables) : undo(tables)
})({
selector: "div.page div.mobiletable table",
parseColumnIndex: node => {
const index = parseInt(node.parentElement.parentElement.getAttribute("data-column"), 10)
return (isNaN(index) || index < 0) ? -1 : index
},
hideHeadings: window.JSINFO["plugin_mobiletable_hideHeadings"] || []
})
window.checkMobileTables = () => {
const div = document.querySelector("div.mobiletable")
if (!div) {
return
}
const before = window.getComputedStyle(div, ":before")
.getPropertyValue("content")
.replace(/"|'/g, "")
if (window.mobileTables(before === "mobile") && window.location.hash) {
// Scroll to anchor after transformation.
window.location.hash = window.location.hash
}
}
// document.ready
(cb => ["complete", "interactive"].includes(document.readyState) ? setTimeout(cb, 0) : document.addEventListener("DOMContentLoaded", cb))(() => {
let resizeTimer
window.addEventListener("resize", () => {
if (resizeTimer) {
clearTimeout(resizeTimer)
}
resizeTimer = setTimeout(window.checkMobileTables, 200)
})
window.checkMobileTables()
})