There are 5 previous versions of this script.
Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)
// Copyright (C) 2008 Daniel Dawson
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can download a copy of the GNU General Public License at
// http://www.fsf.org/licensing/licenses/gpl.html or get a free printed
// copy by writing to the Free Software Foundation, Inc., 51 Franklin
// Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: http://www.greasespot.net/
// Then restart Firefox and revisit this script. A dialog should pop up
// asking you to confirm you really want to install the script. Click
// "install", and that's it; the next page you visit will be modified by
// the script (if it applies).
//
// To uninstall, go to Tools/Greasemonkey/Manage User Scripts,
// select "Table Floaters", and click Uninstall.
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name Table Floaters
// @namespace http://www.icehouse.net/ddawson/
// @description Keeps table headers visible; useful for long tables where you would otherwise scroll back and forth just to see what the columns mean.
// @author Daniel Dawson (ddawson at icehouse dot net)
// @version 1.3.4
// @date 2008-08-11
// @homepage http://www.icehouse.net/ddawson/software.html
// @copyright Licensed under GPL v2
// @include *
// ==/UserScript==
//
// Changes in 1.3.4:
// * Added logo.
//
// Changes in 1.3.3:
// * Updated list of style properties to be copied on cloning.
//
// Changes in 1.3.2:
// * Fixed bug in setup: adding anonymous function as event handler for the
// scroll event. Since all anonymous functions are distinct, another
// redundant copy would be added every time setup ran, causing performance
// to get gradually worse.
// * Added config to allow a delay after setup is called through the
// DOMSubtreeModified event, to improve performance, especially in Web 2.0
// applications.
//
// Changes in 1.3.1:
// * Fixed infinite loop in FF 3 caused by modifying the tree in function
// handling DOMSubtreeModified event (fixed by temporarily removing event
// handler in setup).
//
// Changes in 1.3:
// * Now compensates for user-adjusted font sizes.
// * Now rounds up header cell widths; fixes visual glitch with cell text
// wrapping inappropriately.
//
// Changes in 1.2.1:
// * Fixed this-qualification bug in clone-first-row-without-TH code.
//
// Changes in 1.2:
// * Added a nice little XUL configure dialog in place of the awkward use of
// GM userscript commands. Now just go to (Options/Tools) > Greasemonkey >
// User Script Commands > Configure Table Floaters...
// * Fixed an issue with floaters appearing on printouts by classing all
// floaters as 'tableFloaterCell' and inserting an !important rule applying
// to 'print' media.
//
// Changes in 1.1:
// * Merged fixed-positioning and absolute-positioning versions; you now
// choose the mode through a user script command (look under Tools >
// Greasemonkey).
// * Added the possibility to clone the first row of each table that defines
// neither a 'thead' nor any 'th' cells in the first row, as a last
// fallback. As this will catch tables that really aren't supposed to have
// a header (e.g. the vast number of pages using tables for grid layouts!),
// the option is disabled by default. Use the appropriate command if you
// want to try it.
//
// Changes in 1.0:
// * Fixed bug causing floater to disappear when cell has z-index 'auto' but
// ancestor has non-auto z-index.
//
// Changes in 0.3:
// * Eliminated error when none of a table's cells is cloned.
// * Now inserts clones at beginning of body, instead of at end;
// unfortunately, they still appear on top in FF 2 (seems to violate CSS
// 2.1).
// * Now handles headers for multiple tables concurrently. A header for a
// nested table is handled, so long as none of the ancestor tables has
// its own header.
//
// Changes in 0.2:
// * Now correctly handles theads with multiple rows.
// * Display now properly constrained so cloned cells never show below any of
// the real table cells (aside from delayed scroll events).
// * Tables with no body rows are no longer even considered.
//
// Known issues:
// * Text inside cloned cells is always top-aligned, regardless of style
// settings (vertical-align only applies inside line boxes and table
// cells). I know of no easy solution to this.
// * Floaters should be stacked only just above the rest of the flow;
// currently, if there are any fixed-position elements, the floaters will
// appear above them, instead of below as they should. I know of no
// solution to this.
// * Watch out for theads that are as high or nearly as high as the window;
// you won't be able to see anything else.
// * You have to wait until the page finishes loading to see the floaters,
// since page layout isn't finalized until then.
// * There are no events for style changes in DOM 2; thus, if a property is
// changed that affects one or more header cells, this script cannot
// react.
// * If any floaters are active, and you go to Print Preview, when you
// return, new floaters will be created, but the old copies will still be
// there where you left them, and they will be stuck in place and refuse to
// go away. Apparently, a 'resize' event has been triggered, but why hasn't
// the cleanup method been called?
// * Some floaters are positioned and/or sized incorrectly. One of these days
// I'll find out why and fix it. I suspect it involves table nesting.
//
// TODO:
// * Maybe handle headers on the left-hand side?
// * Maybe handle tfoot?
(function () {
const tableFloaters = {
checkForNestedTable: function (tableElem) {
var elem = tableElem.parentNode;
function eqElem (obj) { return obj == elem; }
for (; elem; elem = elem.parentNode)
if (elem && elem.nodeType == 1
&& (elem.tagName == 'table' || elem.tagName == 'TABLE')
&& this.processedTables.some(eqElem))
return true;
return false;
},
getElementOffset: function (elem) {
offset = { x: elem.offsetLeft, y: elem.offsetTop };
elem = elem.offsetParent;
while (elem != null) {
offset.x += elem.offsetLeft;
offset.y += elem.offsetTop;
elem = elem.offsetParent;
}
return offset;
},
getElementOffsetX: function (elem) {
offset = elem.offsetLeft;
elem = elem.offsetParent;
while (elem != null) {
offset += elem.offsetLeft;
elem = elem.offsetParent;
}
return offset;
},
getElementOffsetY: function (elem) {
offset = elem.offsetTop;
elem = elem.offsetParent;
while (elem != null) {
offset += elem.offsetTop;
elem = elem.offsetParent;
}
return offset;
},
copiedProps: [/*'azimuth',*/ 'background-attachment', 'background-color',
'background-image', 'background-position',
'background-repeat', 'border-bottom-style',
'border-bottom-width', 'border-collapse', 'border-image',
'border-left-color', 'border-left-style',
'border-left-width', 'border-right-color',
'border-right-style', 'border-right-width', 'border-spacing',
'border-top-color', 'border-top-style', 'border-top-width',
'bottom', 'box-shadow', 'caption-side', 'clear', 'clip',
'color', 'content', 'counter-increment', 'counter-reset',
/*'cue',*/ 'cursor', 'direction', 'display', /*'elevation',*/
'empty-cells', 'float', 'font-family', 'font-size-adjust',
'font-stretch', 'font-style', 'font-variant', 'font-weight',
'height', /*'ime-mode',*/ 'left', 'letter-spacing',
'line-height', 'list-style-image', 'list-style-position',
'list-style-type', 'margin-bottom', 'margin-left',
'margin-right', 'margin-top', 'marker-offset', /*'marks',*/
'max-height', 'max-width', 'min-height', 'min-width',
'opacity', 'orphans', 'outline-color', 'outline-offset',
'outline-style', 'outline-width', 'overflow', 'overflow-x',
'overflow-y', 'padding-bottom', 'padding-left',
'padding-right', 'padding-top', /*'page...', 'pause',
'pitch', 'play-during',*/ 'position', 'quotes',
/*'richness',*/ 'right', /*'size', 'speak', 'speech-rate',
'stress',*/ 'table-layout', 'text-align', 'text-decoration',
'text-indent', 'text-rendering', 'text-shadow',
'text-transform', 'top', 'unicode-bidi', 'vertical-align',
'visibility', /*'voice-family', 'volume',*/ 'white-space',
'window', 'width', 'word-spacing', '-moz-appearance',
'-moz-background-clip', '-moz-background-inline-policy',
'-moz-background-origin', '-moz-binding',
'-moz-border-bottom-colors', '-moz-border-end-color',
'-moz-border-end-style', '-moz-border-end-width',
'-moz-border-image', '-moz-border-left-colors',
'-moz-border-radius-bottomleft',
'-moz-border-radius-bottomright',
'-moz-border-radius-topleft', '-moz-border-radius-topright',
'-moz-border-right-colors', '-moz-border-start-color',
'-moz-border-start-style', '-moz-border-start-width',
'-moz-border-top-colors',
'-moz-box-align', '-moz-box-direction', '-moz-box-flex',
'-moz-box-flexgroup', '-moz-box-ordinal-group',
'-moz-box-orient', '-moz-box-pack', '-moz-box-shadow',
'-moz-box-sizing', '-moz-column-count', '-moz-column-gap',
'-moz-column-width', '-moz-column-rule-width',
'-moz-column-rule-style', '-moz-column-rule-color',
'-moz-float-edge', '-moz-force-broken-image-icon',
'-moz-image-region', '-moz-margin-end', '-moz-margin-start',
'-moz-opacity', '-moz-outline-color', '-moz-outline-offset',
'-moz-outline-radius-bottomleft',
'-moz-outline-radius-bottomright',
'-moz-outline-radius-topleft',
'-moz-outline-radius-topright', '-moz-padding-end',
'-moz-padding-start', '-moz-stack-sizing', '-moz-user-focus',
'-moz-user-input', '-moz-user-modify', '-moz-user-select'],
copyStyle: function (newElem, oldElem) {
var oldStyle = window.getComputedStyle(oldElem, null);
for each (var prop in this.copiedProps)
newElem.style.setProperty(
prop,
oldStyle.getPropertyValue(prop),
'important');
newElem.style.setProperty(
'font-size',
(parseFloat(oldStyle.getPropertyValue('font-size'))
*this.fontSizeCompensate + 'px'),
'important');
var newChildren = newElem.childNodes, oldChildren = oldElem.childNodes;
for (var i = 0; i < newChildren.length; i++)
if (newChildren[i].nodeType == 1)
this.copyStyle(newChildren[i], oldChildren[i]);
},
getFinalBG: function (elem) {
const bgProps = ['background-image', 'background-repeat',
'background-attachment', 'background-position',
'background-color'];
var theStyle = new Object();
for each (var prop in bgProps) {
var tElem = elem;
var val;
do {
var stl = window.getComputedStyle(tElem, null);
val = stl.getPropertyValue(prop);
tElem = tElem.parentNode;
} while (tElem && (val == 'inherit' || val == 'transparent'));
theStyle[prop] = val;
}
return theStyle;
},
getProperZIndex: function (elem) {
while (elem.tagName) {
var s =
window.getComputedStyle(elem, null).getPropertyValue('z-index');
if (s != 'auto') return Number(s)+1;
elem = elem.parentNode;
}
return 'auto';
},
cloneRow: function (row, cellList, cloneAll) {
var cells = row.cells;
for (var i = 0, j = 0; i < cells.length; i++) {
var tn = cells[i].tagName;
if (!cloneAll && tn != 'TH' && tn != 'th') continue;
cellList[j] = {
elem: cells[i].cloneNode(true),
left: this.getElementOffsetX(cells[i]),
top: 0
};
this.copyStyle(cellList[j].elem, cells[i]);
cellList[j].elem.setAttribute('class', 'tableFloaterCell');
with (cellList[j].elem.style) {
setProperty('display', 'none', 'important');
setProperty('position', this.useFixedPos ? 'fixed' : 'absolute',
'important');
setProperty('width',
Math.ceil(
parseFloat(window.getComputedStyle(cells[i],
null).width))+'px',
'important');
if (this.useFixedPos)
setProperty('top', '0px', 'important');
else
setProperty('left', '' + this.getElementOffsetX(cells[i]) + 'px',
'important');
setProperty('z-index', this.getProperZIndex(cells[i]), 'important');
var opaqueStyle = this.getFinalBG(cells[i]);
for (var prop in opaqueStyle)
setProperty(prop, opaqueStyle[prop], 'important');
}
cellList[j].elem.removeAttribute('id');
document.body.insertBefore(cellList[j].elem, this.firstElem);
j++;
}
if (!cloneAll && this.cloneFirstRowWithoutTH && cellList.length == 0)
this.cloneRow(row, cellList, true);
},
makeClonedHdrCells: function (tableElem) {
var cellList = [];
if (tableElem.tHead) {
var cellNum = 0;
var rows = tableElem.tHead.rows;
var firstPos = -1;
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var cells = row.cells;
for (var j = 0; j < cells.length; j++) {
var tn = cells[j].tagName;
if (tn != 'TD' && tn != 'td' && tn != 'TH' && tn != 'th') continue;
var offset = this.getElementOffset(cells[j]);
cellList[cellNum] = {
elem: cells[j].cloneNode(true),
left: offset.x
};
if (!this.useFixedPos) {
if (firstPos >= 0)
cellList[cellNum].top = offset.y - firstPos;
else {
firstPos = offset.y;
cellList[cellNum].top = 0;
}
}
this.copyStyle(cellList[cellNum].elem, cells[j]);
cellList[cellNum].elem.setAttribute('class', 'tableFloaterCell');
with (cellList[cellNum].elem.style) {
setProperty('display', 'none', 'important');
setProperty('position', this.useFixedPos ? 'fixed' : 'absolute',
'important');
setProperty('width',
Math.ceil(
parseFloat(window.getComputedStyle(cells[j],
null).width))
+ 'px',
'important');
if (this.useFixedPos) {
if (firstPos >= 0)
setProperty('top', '' + (offset.y - firstPos) + 'px',
'important');
else {
firstPos = offset.y;
setProperty('top', '0px', 'important');
}
} else
setProperty('left',
'' + this.getElementOffsetX(cells[i]) + 'px',
'important');
setProperty('z-index', this.getProperZIndex(cells[i]),
'important');
var opaqueStyle = this.getFinalBG(cells[j]);
for (var prop in opaqueStyle)
setProperty(prop, opaqueStyle[prop], 'important');
}
cellList[cellNum].elem.removeAttribute('id');
document.body.insertBefore(cellList[cellNum].elem, this.firstElem);
cellNum++;
}
}
} else if (tableElem.tBody) {
this.cloneRow(tableElem.tBody.rows[0], cellList, false);
} else {
this.cloneRow(tableElem.rows[0], cellList, false);
}
return cellList;
},
compensateUserFontSizeAdjust: function () {
var testElem = document.createElement('th');
testElem.style.setProperty('font-size', '100px', 'important');
this.fontSizeCompensate = (
100/parseFloat(window.getComputedStyle(testElem, null).fontSize));
},
setup: function (evt) {
document.removeEventListener('DOMSubtreeModified', callTFSetup, false);
if (this.tableInfo) this.cleanup();
this.compensateUserFontSizeAdjust();
this.tableInfo = [];
this.processedTables = [];
this.firstElem = document.body.firstChild;
this.lastElem = document.body.lastChild;
var tables = document.getElementsByTagName('table');
var numTables = tables.length;
for (var i = 0, j = 0; i < numTables; i++) {
if (this.checkForNestedTable(tables[i]) || !tables[i].tBodies[0]
|| tables[i].tBodies[0].rows.length == 0)
continue;
var numRows = tables[i].rows.length;
var topOffset = this.getElementOffsetY(tables[i].rows[0]);
var lastRowCell = tables[i].rows[numRows - 1].cells[0];
var lrcStl = window.getComputedStyle(lastRowCell, null);
var bottomOffset = this.getElementOffsetY(lastRowCell)
+ parseInt(lrcStl.borderTopWidth)
+ parseInt(lrcStl.height)
+ parseInt(lrcStl.borderBottomWidth);
var ti = {
tableTop: topOffset,
tableBot: bottomOffset,
clonedHdrCells: this.makeClonedHdrCells(tables[i])
};
if (ti.clonedHdrCells.length == 0) continue;
if (this.useFixedPos) {
var lastStl =
ti.clonedHdrCells[ti.clonedHdrCells.length - 1].elem.style;
ti.tableBot -=
(parseInt(lastStl.top) + parseInt(lastStl.borderTopWidth)
+ parseInt(lastStl.height)
+ parseInt(lastStl.borderBottomWidth) + 10);
} else {
var lastCell = ti.clonedHdrCells[ti.clonedHdrCells.length - 1];
var lastTop = lastCell.top;
var lastStl = lastCell.elem.style;
ti.tableBot -= (lastTop + parseInt(lastStl.borderTopWidth)
+ parseInt(lastStl.height)
+ parseInt(lastStl.borderBottomWidth) + 10);
}
this.tableInfo.push(ti);
this.processedTables.push(tables[i]);
j++;
}
document.addEventListener('scroll', this.callHandleScroll, false);
this.handleScroll();
if (evt && evt.type == 'DOMSubtreeModified' && this.delayAfterDSTM > 0)
window.setTimeout(
function () {
document.addEventListener('DOMSubtreeModified', callTFSetup,
false);
}, this.delayAfterDSTM);
else
document.addEventListener('DOMSubtreeModified', callTFSetup, false);
},
callHandleScroll: function (evt) { tableFloaters.handleScroll(); },
handleScroll: function () {
var scrollX = window.scrollX, scrollY = window.scrollY;
var ti = this.tableInfo;
for each (var tbl in ti) {
if (scrollY >= tbl.tableTop && scrollY <= tbl.tableBot) {
if (this.useFixedPos)
for each (var cell in tbl.clonedHdrCells) {
cell.elem.style.setProperty('left',
'' + (cell.left - scrollX) + 'px',
'important');
cell.elem.style.setProperty('display', 'block', 'important');
}
else
for each (var cell in tbl.clonedHdrCells) {
cell.elem.style.setProperty('top',
'' + (scrollY + cell.top) + 'px',
'important');
cell.elem.style.setProperty('display', 'block', 'important');
}
} else
for each (var cell in tbl.clonedHdrCells)
cell.elem.style.setProperty('display', 'none', 'important');
}
},
cleanup: function () {
for (var i = 0; i < this.tableInfo.length; i++) {
var cells = this.tableInfo[i].clonedHdrCells;
for (var j = 0; j < cells.length; j++) {
var cell = cells[j].elem;
cell.parentNode.removeChild(cell);
}
}
}
};
function callTFSetup (evt) { tableFloaters.setup(evt); }
window.addEventListener('load', callTFSetup, false);
window.addEventListener('resize', callTFSetup, false);
document.addEventListener('DOMSubtreeModified', callTFSetup, false);
var printHideStl = document.createElement('style');
printHideStl.appendChild(
document.createTextNode('\
@media print{.tableFloaterCell{display:none!important}}'));
document.getElementsByTagName('head')[0].appendChild(printHideStl);
function makeConfigWindow (menuLbl, cfgDescriptor) {
// This URI creates a XULDocument when opened in a window.
const initXULDoc = 'data:application/vnd.mozilla.xul+xml,' +
encodeURIComponent('\
<?xml version="1.0" encoding="iso-8859-15"?>\
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>\
<window class="dialog" \
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" \
xmlns:html="http://www.w3.org/1999/xhtml"/>');
var win;
// Event handler for radiogroup interaction.
function handleBRGCommand (evt) {
var target = evt.currentTarget, cfgVar = target.id,
cfgVal = target.wrappedJSObject.selectedIndex; // Why must I unwrap?
cfgDescriptor.cfgObj[cfgVar] = cfgVal;
GM_setValue(cfgVar, Boolean(cfgVal));
setTimeout(callTFSetup, 1, null);
}
// Event handler for checkbox interaction.
function handleCBCommand (evt) {
var target = evt.currentTarget, cfgVar = target.id,
cfgVal = target.wrappedJSObject.checked; // Why must I unwrap?
cfgDescriptor.cfgObj[cfgVar] = cfgVal;
GM_setValue(cfgVar, Boolean(cfgVal));
setTimeout(callTFSetup, 1, null);
}
// Event handler for number interaction.
function handleNumChange (evt) {
var target = evt.currentTarget, cfgVar, cfgVal, min, max;
cfgVar = target.id;
cfgVal = parseInt(target.wrappedJSObject.value); // Why must I unwrap?
min = parseInt(target.getAttribute('min'));
max = parseInt(target.getAttribute('max'));
if (isNaN(cfgVal))
cfgVal = GM_getValue(cfgVar);
else if (cfgVal < min)
cfgVal = min;
else if (cfgVal > max)
cfgVal = max;
target.wrappedJSObject.value = cfgVal;
cfgDescriptor.cfgObj[cfgVar] = cfgVal;
GM_setValue(cfgVar, cfgVal);
setTimeout(callTFSetup, 1, null);
}
// Called asynchronously after the window is opened.
function populateCfgWindow () {
var doc = win.document, winElem = doc.documentElement;
doc.title = cfgDescriptor.title;
with (winElem) {
setAttribute('id', cfgDescriptor.id);
setAttribute('title', cfgDescriptor.title);
setAttribute('orient', 'vertical');
setAttribute('align', 'start');
}
var hb = doc.createElement('hbox');
var img = doc.createElement('image');
img.setAttribute('src', cfgDescriptor.logoSrc);
var lbl = doc.createElement('label');
lbl.setAttribute('value', cfgDescriptor.title);
hb.appendChild(img);
hb.appendChild(lbl);
winElem.appendChild(hb);
for each (var item in cfgDescriptor.structure) {
var cfgVal = cfgDescriptor.cfgObj[item.cfgVar];
switch (item.type) {
case 'boolRadioGrp':
var cont;
if (item.caption) {
cont = doc.createElement('groupbox');
var capt = doc.createElement('caption');
capt.setAttribute('label', item.caption);
cont.appendChild(capt);
winElem.appendChild(cont);
} else
cont = winElem;
var rg, rf = doc.createElement('radio'),
rt = doc.createElement('radio');
rf.setAttribute('label', item.labels[0]);
if (!cfgVal) rf.setAttribute('selected', 'true');
rt.setAttribute('label', item.labels[1]);
if (cfgVal) rt.setAttribute('selected', 'true');
with (rg = doc.createElement('radiogroup')) {
setAttribute('id', item.cfgVar);
allowEvents = true;
addEventListener('command', handleBRGCommand, false);
appendChild(rf);
appendChild(rt);
selectedIndex = cfgVal ? 1 : 0;
}
cont.appendChild(rg);
break;
case 'checkbox':
var cb;
with (cb = doc.createElement('checkbox')) {
setAttribute('label', item.label);
setAttribute('checked', cfgVal);
setAttribute('id', item.cfgVar);
addEventListener('command', handleCBCommand, false);
}
winElem.appendChild(cb);
break;
case 'number':
var hb = doc.createElement('hbox'),
tb = doc.createElement('textbox'),
lb = doc.createElement('label');
with (tb) {
setAttribute('type', 'number'); // For FF 3 only
setAttribute('min', item.min); // For FF 3 only
setAttribute('max', item.max); // For FF 3 only
setAttribute('increment', item.increment); // For FF 3 only
setAttribute('value', cfgVal);
setAttribute('id', item.cfgVar);
setAttribute('tooltiptext', item.tooltip);
setAttribute('flex', 1);
addEventListener('change', handleNumChange, false);
}
hb.appendChild(tb);
with (lb) {
setAttribute('control', item.cfgVar);
setAttribute('value', item.label);
setAttribute('tooltiptext', item.tooltip);
setAttribute('flex', 4)
}
hb.appendChild(lb);
winElem.appendChild(hb);
break;
// ... Other types might be added later
default:
GM_log('Unknown configuration element type');
}
}
}
for each (var item in cfgDescriptor.structure) {
var cfgVal = GM_getValue(item.cfgVar, item.defaultVal);
cfgDescriptor.cfgObj[item.cfgVar] = cfgVal;
}
GM_registerMenuCommand(
menuLbl,
function () {
win = open(initXULDoc, cfgDescriptor.title,
'width=' + cfgDescriptor.width
+ ',height=' + cfgDescriptor.height);
// Workaround: the window always contains about:blank until the script
// terminates, so I use a callback to get the real document.
setTimeout(populateCfgWindow, 0);
});
}
makeConfigWindow(
'Configure Table Floaters...',
{
cfgObj: tableFloaters,
id: 'tableFloaters-config',
logoSrc: 'data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAVNQTFRF\
AAAAAAIEAAMFAwMDAAUKAAcMAAkQCQkJAAsSCgoKAA0WAA0YAA8bCA4UBQ8WBw8WBhAYCBEY\
CBEZEBAQCBIZABQkERERAhQiCRMbEhISChQcExMTDxYbFRUVABksAhkrFhYWAhosDhgfABsx\
FxcXGBgYBRwuGxsbHBwcACM/ASM+ASQ/Hx8fICAgISEhGSMqIiIiAClIIyMjJCQkJiYmJycn\
KCgoKioqADRcLCwsADVcADVeLS0tAjZdMjIyADxrNTU1NjY2Nzc3ODg4Ozs7PDw8PT09AEqD\
AE2JR0dHSUlJTExMTk5OT09PAF6nUFBQU1NTVFRUVVVVAHDHaGhoaWlpbW1tbm5ub29vAJD/\
enp6gICAhoaGh4eHiYmJj4+Pl5eXn5+fpqamrq6ur6+vx8fH0NDQ0dHR09PT2dnZ2tra5+fn\
8vLy8/Pz9PT0/f39////hfEGvAAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMA\
AAFbAAABWwFy1guMAAAAB3RJTUUH2AgLCjsuODufAwAAAStJREFUOMvNz9k3AnEYxvGffU+W\
MkpkyYwlYiRrm7I0SLTQqkWFxP9/5Tcz79PJcePK6Xvzfi6eMwvrMlGDfVDvMNTNGDM9UZfH\
0NotNP9jcASttwYL9p62wSm00Rqs3lvbBge/n7AYNrPRB8q3Ay1fQDNnU6x/ZHp7ySIIwsS4\
0SBojU0ajBosc1ez/DNt1+GgX02W/JQkayd4HjXzgf1OiWh53RHK7dWvErPywf5rvVZVK6Sq\
VKqgnVr9zckHh82GXilDaGRK+v1o7vLBySdVzkLZMiSrgy+qkoNyFUgdOJ+peBSKxiGRD7aK\
VDIGxZKQ+JdX/MNAeqSUEBRSoBU+2Huh8gkokYc2O+QvHDdUwAN5ApCNDzzvVDENpYuQq0P+\
YshFiQ7IIUID32z1TbD++YXiAAAAAElFTkSuQmCC',
title: 'Table Floaters configuration',
width: 400,
height: 400,
structure: [
{ type: 'boolRadioGrp', cfgVar: 'useFixedPos',
caption: 'Floating method',
labels: [ 'Use scrollable floaters (fast but jittery)',
'Use fixed floaters (smooth but slow)' ],
defaultVal: true },
{ type: 'boolRadioGrp', cfgVar: 'cloneFirstRowWithoutTH',
caption: 'Agressiveness',
labels: [ 'No floaters without THEAD/TH',
'Clone first rows even without THEAD/TH' ],
defaultVal: false },
{ type: 'number', cfgVar: 'delayAfterDSTM',
label: 'Delay after dynamic change',
tooltip: 'Delay this many milliseconds after responding to a '
+ 'dynamic change in content, before responding again (FF 3 users '
+ 'may want to set a nonzero value)',
min: 0, max: 10000, increment: 100, defaultVal: 0 }]});
})();
