WebGL Tutorial
and more

DOMUtils Test

撰写时间:2025-02-11

修订时间:2025-04-05

为便捷生成各种DOMs提供便利工具。

便捷生成表格

const { DOMUtils } = await import('/js/esm/DOMUtils.js'); let ths = ['a', 'b', 'c', 'd']; let cells = [ [1, 2, 3, 4], [5, 6, 7, 8] ]; let str = DOMUtils.GenTableStr(ths, cells); pc.log('%o', str);

使用二维数组来表示多行数据。

将标题数据与主体数据分离至不同的数组中,可解决HTML的表格格式代码与数据耦合度过高的问题,从而让我们专注于表格数据的独立变化。

单元格的跨行及跨列

表格单元格的跨行及跨列耦合度更高,且会引起后续行或列的连锁反应。编写跨行或跨列的表格代码往往成为一个噩梦。

跨列: 影响到当前行当前列的后面的列。colspan的值表示当前单元格共占多少列。

跨行: 影响到当前列的下面的行。rowspan的值表示当前单元格共占多少行。

ABCDEF
123456
22345
323456
423456
52345
623456

为解决此问题,可在上节的基础上,将跨行或跨列的需求以特定的格式编写进相应的数组中。

使用[data, row, col]的形式来指定需要跨行或跨列的情况,data表示单元格内容,row表示跨多少行,col表示跨多少列。

因此,['string', 0, 2]表示单元格内容为string,跨2列;[4, 2]表示单元格内容为4,跨2行。

const { DOMUtils } = await import('/js/esm/DOMUtils.js'); let ths = ['A', 'B', 'C', 'D', 'E']; let cells = [ [1, ['string', 0, 2], [4, 2], 45], [2, 6, 8, 9], [3, [6, 3], 7, 32, 4], [4, 2, [3, 2], 5], [5, 2, 3] ]; let str = DOMUtils.GenTableStr(ths, cells); pc.log('%o', str);

这样可达到非常直观的效果。以后若要修改所跨的行列也更加便捷。

将多行拆分为多列显示

有时,表格的行太多而列太少,为充分利用水平方向上的空间,可考虑分成多列显示,即,将一些过长的行剪下来,放至右边而成为并排的列。

依先排列还是先排行,分为列优先及行优先两种方式。

列优先

const { DOMUtils } = await import('/js/esm/DOMUtils.js'); const { NumUtils } = await import('/js/esm/NumUtils.js'); let ths = ['Row Id', 'A', 'B', 'C']; let dataColsNum = ths.length - 1; let rows = 15; let nums = NumUtils.GetIntsInRange(1, 100, dataColsNum * rows); let numIndex = 0; let cells = []; for (let rowIndex = 0; rowIndex < rows; rowIndex++) { cells.push([rowIndex, nums[numIndex++], nums[numIndex++], nums[numIndex++]]); } pc.group('Normal layout'); let str = DOMUtils.GenTableStr(ths, cells); pc.log('%o', str); pc.groupEnd(); pc.group('Fold into 4 cols'); let splitIntoColsNum = 4; str = DOMUtils.GenTableStr(ths, cells, splitIntoColsNum); pc.log('%o', str); pc.groupEnd();

将一个15行的表格,转变为每行显示4组数据,从而同时兼顾了美观及空间的需求。

上述效果为列优先column majored)的方式,即先在垂直方向上按顺序显示一部分行的数据,再添加新列并按顺序显示另一部分行的数据,直至最后一行数据。

行优先

下面实现行优先row majored),即先确定每行共有多少列,再按原数组的顺序逐行往下排。

const { DOMUtils } = await import('/js/esm/DOMUtils.js'); const { NumUtils } = await import('/js/esm/NumUtils.js'); let ths = ['Row Id', 'A', 'B', 'C']; let dataColsNum = ths.length - 1; let rows = 7; let nums = NumUtils.GetIntsInRange(1, 100, dataColsNum * rows); let numIndex = 0; let cells = []; for (let rowIndex = 0; rowIndex < rows; rowIndex++) { cells.push([rowIndex, nums[numIndex++], nums[numIndex++], nums[numIndex++]]); } pc.group('Normal layout'); let str = DOMUtils.GenTableStr(ths, cells); pc.log('%o', str); pc.groupEnd(); pc.group('Fold into 4 cols'); let splitIntoColsNum = 4; str = DOMUtils.GenTableStr(ths, cells, splitIntoColsNum, false); pc.log('%o', str); pc.groupEnd();

手工指定切割行的范围

如果数据有内在规律,则可手工指定各行的边界。

const { DOMUtils } = await import('/js/esm/DOMUtils.js'); const { getBinStr, getHexStr } = await import('/js/esm/BinUtils.js'); let ths = ['Num', 'Bin', 'Hex']; let cells = []; for (let num = 0; num < 16; num++) { cells.push([num, getBinStr(num), getHexStr(num)]); } pc.group('Normal layout'); let str = DOMUtils.GenTableStr(ths, cells); pc.log('%o', str); pc.groupEnd(); pc.group('By specifying row delimiters'); str = DOMUtils.GenTableStr(ths, cells, [[0, 1], [2, 3], [4, 7], [8, 15]]); pc.log('%o', str); pc.groupEnd();

第一列组有效数位为1位,第二列组有效数位为2位,第三列组有效数位为3位,第四列组有效数位为4位。

这种方式,仍为列优先,但左边的列无须填满即可另起新列。

Node.insertAfter

编写代码

DOM中,Node只有insertBefore方法,没有insertAfter方法。DomUtils将直接在Node的原型上添加此方法。

Node.prototype.insertAfter = function(insertElement, referenceElement) { if (!insertElement) { throw new Error(`Argument 1 ('node') to Node.insertAfter must be an instance of Node`); } this.insertBefore(insertElement, referenceElement?.nextSibling); };

上面最后一行代码也可改写为:

Node.prototype.insertBefore.call(this, insertElement, referenceElement.nextSibling);

但这里由于调用者均为同一对象,因此没必要。

需注意的是,上面代码不能使用lambda的形式:

Node.prototype.insertAfter = (insertElement, referenceElement) => { // this would not be Node };

在采用lambda的形式中,this将指向调用此方法时所在环境的对象,而不是Node对象。

测试

下面测试客户端调用。第一种情况,如果insertElement为空值时:

const {} = await import('/js/esm/DOMUtils.js'); // generate DOM tree first pc.appendHTMLStr(`

This is the only child node.

`); let parentNode = pc.querySelector('#parent'); let refNode = parentNode.querySelector('p'); parentNode.insertAfter(null, refNode);

此时抛出异常,与Node.insertBefore方法的逻辑一致,正确。

第二种情况,如果referenceElement为空值时:

const {} = await import('/js/esm/DOMUtils.js'); // generate DOM tree first pc.appendHTMLStr(`

This is the only child node.

`); let parentNode = pc.querySelector('#parent'); let refNode = parentNode.querySelector('p'); let newP = document.createElement('p'); newP.textContent = 'This is the new paragraph.'; parentNode.insertAfter(newP, null);

此时将新元素插入为父节点的最后一个节点,与Node.insertBefore方法的逻辑一致,正确。

第三种情况,当两个参数均不为空值时:

const {} = await import('/js/esm/DOMUtils.js'); // generate DOM tree first pc.appendHTMLStr(`

p1

p2

p3

`); let parentNode = pc.querySelector('#parent'); let map = { 1: '1st', 2: '2nd', 3: '3rd' }; for (let i = 1; i <= 3; i++) { let newDiv = document.createElement('div'); newDiv.textContent = `This div is expected to be inserted after the ${map[i]} paragraph.`; let refNode = parentNode.querySelector(`p:nth-of-type(${i})`); parentNode.insertAfter(newDiv, refNode); }

3个新节点均正确地插入到相应的节点之后。

AppendCSSFile

AppendCSSFile将一个CSS文件添加到head标签,或ShadowRoot

const { DOMUtils } = await import('/js/esm/DOMUtils.js'); const { applyColorTheme } = await import('/js/esm/color-scheme.js'); let iframe, doc, htmlElement; function appendIframe() { iframe = document.createElement('iframe'); pc.appendHTMLStr(`
`); pc.querySelector('#target').appendChild(iframe); pc.log('%O', iframe); } function initIframe() { doc = iframe.contentDocument; htmlElement = doc.firstElementChild; applyColorTheme(htmlElement); doc.head.innerHTML = ` `; doc.body.insertAdjacentHTML('afterbegin', `

This is a sample paragraph.

`); } appendIframe(); initIframe(); DOMUtils.AppendCSSFile(doc, 'demo.css'); pc.listDOMTree(htmlElement, (node) => { let title = node.tagName.toLowerCase(); if (title === 'link') { return `${title}: href="${node.href}"`; } return title; });

DOMTree中应可看到所添加的CSS文件,被插入到scriptstyle标签(若有)之前。