17,097
edits
(Sticky headers: Try ResizeObserver instead of MutationObserver (cannot observe a computed style change)) |
(Rewrite sticky headers code - Corrects issue with sticky headers within overflown tables, and only updates the relevant tables' headers upon resize) |
||
Line 521: | Line 521: | ||
//[This is the end of the section stolen from https://oldschool.runescape.wiki/w/MediaWiki:Gadget-highlightTable-core.js] | //[This is the end of the section stolen from https://oldschool.runescape.wiki/w/MediaWiki:Gadget-highlightTable-core.js] | ||
/ | // Sticky headers for tables | ||
function | |||
var | // Returns a list of header rows within a sticky table | ||
function getStickyTableHeaders(element) { | |||
var rv = []; | |||
for (var rowIdx = 0; rowIdx < 10; rowIdx++) { | |||
var rowElem = element.getElementsByClassName('headerRow-' + rowIdx.toString()); | |||
if (rowElem.length === 0) { | |||
break; | |||
} | |||
rv.push(rowElem[0]); | |||
} | } | ||
return rv; | |||
var | } | ||
var | |||
if ( | // Given a table element, sets the headers' 'top' property as required | ||
function setStickyHeaderTop(element) { | |||
var isOverflown = false; | |||
var parentElem = element.parentElement; | |||
if (parentElem !== undefined) { | |||
isOverflown = (parentElem.scrollHeight > parentElem.clientHeight || parentElem.scrollWidth > parentElem.clientWidth); | |||
} | } | ||
var | // Determine the height of the MediWiki header, if it is always visible at the top of the page. | ||
// If the parent div to the table is overflowing, then the header's top position is set in | |||
// relation to that parent element and this can be skipped | |||
if ( | var headHeight = 0; | ||
if (!isOverflown) { | |||
if ( | var headElem = document.getElementById('mw-header-container'); | ||
if (headElem !== undefined) { | |||
var headStyles = getComputedStyle(headElem); | |||
if ((headStyles !== undefined) && (headStyles.position !== 'static')) { | |||
headHeight = headElem.offsetHeight; | |||
} | } | ||
} | |||
} | |||
var cumulativeRowHeight = 0; | |||
var headElems = getStickyTableHeaders(element); | |||
for (var rowIdx = 0; rowIdx < headElems.length; rowIdx++) { | |||
// Find each header row in sequence. When found, set or remove the 'top' attribute as | |||
// required. If not found, then break | |||
var headElem = headElems[rowIdx]; | |||
var cellElems = headElem.getElementsByTagName('th'); | |||
var topPos = headHeight + cumulativeRowHeight; | |||
// Iterate over all header cells for the current header row | |||
for (var cellIdx = 0; cellIdx < cellElems.length; cellIdx++) { | |||
var cell = cellElems[cellIdx]; | |||
if ((isOverflown) && (cell.style.top !== undefined)) { | |||
// If the table has overflown, then unset the 'top' attribute | |||
cell.removeAttribute('top'); | |||
} | } | ||
if ( | else { | ||
// Otherwise, set the 'top' attribute with the appropriate position | |||
cellElems[cellIdx].style.top = topPos.toString() + 'px'; | |||
for (var j = 0; j < | } | ||
} | |||
cumulativeRowHeight += headElem.offsetHeight - 1; | |||
} | |||
} | |||
// Initialize observers for stickyHeader tables. These enable attributes of table headers to be | |||
// adjusted as required when various elements are resized | |||
function initStickyObservers() { | |||
if (ResizeObserver !== undefined) { | |||
// If the headers are resized, then the header's top position (particularly the second | |||
// header) may need to be set again | |||
var obvHeaderResize = new ResizeObserver( | |||
function(entries) { | |||
var st = []; | |||
for (var i = 0; i < entries.length; i++) { | |||
var headerRow = entries[i].target; | |||
var stickyTable = headerRow.parentElement.parentElement; | |||
if (!st.includes(stickyTable)) { | |||
st.push(stickyTable); | |||
} | |||
} | |||
for (var j = 0; j < st.length; j++) { | |||
setStickyHeaderTop(st[j]); | |||
} | } | ||
} | |||
); | |||
// If the parent div to a table is overflowing, then the header's top position needs to | |||
// be set in relation to the top of that parent element | |||
var obvOverflow = new ResizeObserver( | |||
function(entries) { | |||
for (var i = 0; i < entries.length; i++) { | |||
var tableParent = entries[i].target; | |||
// The child elements will contain the table we want to set sticky headers for | |||
var stickyTables = tableParent.children; | |||
for (var j = 0; j < stickyTables.length; j++) { | |||
var stickyTable = stickyTables[j]; | |||
if (stickyTable.classList.contains('stickyHeader')) { | |||
setStickyHeaderTop(stickyTable); | |||
} | |||
} | |||
} | |||
} | |||
); | |||
var stickyTables = document.getElementsByClassName('stickyHeader'); | |||
for (var i = 0; i < stickyTables.length; i++) { | |||
var stickyTable = stickyTables[i]; | |||
// Observe the table's parent for content overflows | |||
obvOverflow.observe(stickyTable.parentElement); | |||
var headElems = getStickyTableHeaders(stickyTable); | |||
for (var j = 0; j < headElems.length; j++) { | |||
// Observe the table's header rows for resizing | |||
obvHeaderResize.observe(headElems[j]); | |||
} | } | ||
} | } | ||
Line 560: | Line 636: | ||
} | } | ||
function initStickyHeaders() { | |||
var stickyTables = document.getElementsByClassName('stickyHeader'); | |||
var | if (stickyTables.length > 0) { | ||
if ( | |||
var elemArticle = document.getElementsByTagName('article'); | var elemArticle = document.getElementsByTagName('article'); | ||
for (i = 0; i < stickyTables.length; i++) { | |||
var stickyTable = stickyTables[i]; | |||
// Sticky headers do not function well when Tabber containers/article tags. | |||
// Therefore identify any stickyHeader tables within these containers | |||
// and remove the stickyHeader class | |||
for (j = 0; j < elemArticle.length; j++) { | |||
if (elemArticle[j].contains(stickyTable)) { | |||
stickyTable.classList.remove('stickyHeader'); | |||
} | } | ||
} | |||
if (stickyTable.classList.contains('stickyHeader')) { | |||
// If the table is still sticky, initialize header positions | |||
setStickyHeaderTop(stickyTable); | |||
} | } | ||
} | } | ||
// Initialize | |||
// Initialize observers | |||
initStickyObservers(); | |||
// Reset sticky header positions when the window resizes, as this may | // Reset sticky header positions when the window resizes, as this may | ||
// affect visibility of fixed elements at the top of the page | // affect visibility of fixed elements at the top of the page | ||
$(window).resize(function() { setStickyHeaderTop( | $(window).resize( | ||
function() { | |||
var stickyTables = document.getElementsByClassName('stickyHeader'); | |||
for (i = 0; i < stickyTables.length; i++) { | |||
setStickyHeaderTop(stickyTables[i]); | |||
} | |||
}); | |||
} | } | ||
} | |||
$(document).ready(function () { | |||
// Table sticky headers | |||
initStickyHeaders(); | |||
}); | }); |