Skip to content

Commit d903da2

Browse files
authored
Merge pull request #281 from Maluscat/main
Implement more getters and methods on HTMLElement, in accordance with the DOM spec
2 parents a3e41a9 + 64d4585 commit d903da2

File tree

3 files changed

+243
-49
lines changed

3 files changed

+243
-49
lines changed

README.md

+43-4
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ Node --|> TextNode
186186
Node ..> ClassList
187187
```
188188

189+
189190
## HTMLElement Methods
190191

191192
### trimRight()
@@ -216,9 +217,26 @@ Note: Use * for all elements.
216217

217218
Query closest element by css selector. `null` if not found.
218219

220+
### before(...nodesOrStrings)
221+
222+
Insert one or multiple nodes or text before the current element. Does not work on root.
223+
224+
### after(...nodesOrStrings)
225+
226+
Insert one or multiple nodes or text after the current element. Does not work on root.
227+
228+
### prepend(...nodesOrStrings)
229+
230+
Insert one or multiple nodes or text to the first position of an element's child nodes.
231+
232+
### append(...nodesOrStrings)
233+
234+
Insert one or multiple nodes or text to the last position of an element's child nodes.
235+
This is similar to appendChild, but accepts arbitrarily many nodes and converts strings to text nodes.
236+
219237
### appendChild(node)
220238

221-
Append a child node to childNodes
239+
Append a node to an element's child nodes.
222240

223241
### insertAdjacentHTML(where, html)
224242

@@ -298,6 +316,7 @@ Clone a node.
298316

299317
Get element by it's ID.
300318

319+
301320
## HTMLElement Properties
302321

303322
### text
@@ -312,7 +331,7 @@ Get escaped (as-is) text value of current node and its children. May have
312331

313332
### tagName
314333

315-
Get or Set tag name of HTMLElement. Notice: the returned value would be an uppercase string.
334+
Get or Set tag name of HTMLElement. Note that the returned value is an uppercase string.
316335

317336
### structuredText
318337

@@ -322,13 +341,33 @@ Get structured Text.
322341

323342
Get DOM structure.
324343

344+
### childNodes
345+
346+
Get all child nodes. A child node can be a TextNode, a CommentNode and a HTMLElement.
347+
348+
### children
349+
350+
Get all child elements, so all child nodes of type HTMLELement.
351+
325352
### firstChild
326353

327-
Get first child node. `undefined` if no child.
354+
Get first child node. `undefined` if the node has no children.
328355

329356
### lastChild
330357

331-
Get last child node. `undefined` if no child
358+
Get last child node. `undefined` if the node has no children.
359+
360+
### firstElementChild
361+
362+
Get the first child of type HTMLElement. `undefined` if none exists.
363+
364+
### lastElementChild
365+
366+
Get the first child of type HTMLElement. `undefined` if none exists.
367+
368+
### childElementCount
369+
370+
Get the number of children that are of type HTMLElement.
332371

333372
### innerHTML
334373

src/nodes/html.ts

+93-41
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export interface RawAttributes {
5151
}
5252

5353
export type InsertPosition = 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend';
54+
export type NodeInsertable = Node | string;
5455

5556
// https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements
5657
const Htags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup'];
@@ -643,29 +644,10 @@ export default class HTMLElement extends Node {
643644
* @return {Node} node appended
644645
*/
645646
public appendChild<T extends Node = Node>(node: T) {
646-
// remove the node from it's parent
647-
node.remove();
648-
this.childNodes.push(node);
649-
node.parentNode = this;
647+
this.append(node);
650648
return node;
651649
}
652650

653-
/**
654-
* Get first child node
655-
* @return {Node | undefined} first child node; or undefined if none
656-
*/
657-
public get firstChild(): Node | undefined {
658-
return this.childNodes[0];
659-
}
660-
661-
/**
662-
* Get last child node
663-
* @return {Node | undefined} last child node; or undefined if none
664-
*/
665-
public get lastChild(): Node | undefined {
666-
return arr_back(this.childNodes);
667-
}
668-
669651
/**
670652
* Get attributes
671653
* @access private
@@ -818,33 +800,46 @@ export default class HTMLElement extends Node {
818800
}
819801
const p = parse(html, this._parseOptions);
820802
if (where === 'afterend') {
821-
const idx = this.parentNode.childNodes.findIndex((child) => {
822-
return child === this;
823-
});
824-
resetParent(p.childNodes, this.parentNode);
825-
this.parentNode.childNodes.splice(idx + 1, 0, ...p.childNodes);
803+
this.after(...p.childNodes);
826804
} else if (where === 'afterbegin') {
827-
resetParent(p.childNodes, this);
828-
this.childNodes.unshift(...p.childNodes);
805+
this.prepend(...p.childNodes);
829806
} else if (where === 'beforeend') {
830-
p.childNodes.forEach((n) => {
831-
this.appendChild(n);
832-
});
807+
this.append(...p.childNodes);
833808
} else if (where === 'beforebegin') {
834-
const idx = this.parentNode.childNodes.findIndex((child) => {
835-
return child === this;
836-
});
837-
resetParent(p.childNodes, this.parentNode);
838-
this.parentNode.childNodes.splice(idx, 0, ...p.childNodes);
809+
this.before(...p.childNodes);
839810
} else {
840811
throw new Error(
841812
`The value provided ('${where as string}') is not one of 'beforebegin', 'afterbegin', 'beforeend', or 'afterend'`
842813
);
843814
}
844815
return this;
845-
// if (!where || html === undefined || html === null) {
846-
// return;
847-
// }
816+
}
817+
818+
/** Prepend nodes or strings to this node's children. */
819+
public prepend(...insertable: NodeInsertable[]) {
820+
const nodes = resolveInsertable(insertable);
821+
resetParent(nodes, this);
822+
this.childNodes.unshift(...nodes);
823+
}
824+
/** Append nodes or strings to this node's children. */
825+
public append(...insertable: NodeInsertable[]) {
826+
const nodes = resolveInsertable(insertable);
827+
resetParent(nodes, this);
828+
this.childNodes.push(...nodes);
829+
}
830+
/** Insert nodes or strings before this node. */
831+
public before(...insertable: NodeInsertable[]) {
832+
const nodes = resolveInsertable(insertable);
833+
const siblings = this.parentNode.childNodes;
834+
resetParent(nodes, this.parentNode);
835+
siblings.splice(siblings.indexOf(this), 0, ...nodes);
836+
}
837+
/** Insert nodes or strings after this node. */
838+
public after(...insertable: NodeInsertable[]) {
839+
const nodes = resolveInsertable(insertable);
840+
const siblings = this.parentNode.childNodes;
841+
resetParent(nodes, this.parentNode);
842+
siblings.splice(siblings.indexOf(this) + 1, 0, ...nodes);
848843
}
849844

850845
public get nextSibling(): Node | null {
@@ -909,13 +904,56 @@ export default class HTMLElement extends Node {
909904
}
910905
}
911906

912-
public get classNames() {
913-
return this.classList.toString();
907+
/** Get all childNodes of type {@link HTMLElement}. */
908+
public get children(): HTMLElement[] {
909+
const children = [];
910+
for (const childNode of this.childNodes) {
911+
if (childNode instanceof HTMLElement) {
912+
children.push(childNode);
913+
}
914+
}
915+
return children;
916+
}
917+
918+
/**
919+
* Get the first child node.
920+
* @return The first child or undefined if none exists.
921+
*/
922+
public get firstChild(): Node | undefined {
923+
return this.childNodes[0];
924+
}
925+
/**
926+
* Get the first child node of type {@link HTMLElement}.
927+
* @return The first child element or undefined if none exists.
928+
*/
929+
public get firstElementChild(): HTMLElement | undefined {
930+
return this.children[0];
914931
}
915932

916933
/**
917-
* Clone this Node
934+
* Get the last child node.
935+
* @return The last child or undefined if none exists.
918936
*/
937+
public get lastChild(): Node | undefined {
938+
return arr_back(this.childNodes);
939+
}
940+
/**
941+
* Get the last child node of type {@link HTMLElement}.
942+
* @return The last child element or undefined if none exists.
943+
*/
944+
public get lastElementChild(): HTMLElement | undefined {
945+
return this.children[this.children.length - 1];
946+
}
947+
948+
public get childElementCount(): number {
949+
return this.children.length;
950+
}
951+
952+
public get classNames() {
953+
return this.classList.toString();
954+
}
955+
956+
/** Clone this Node */
919957
public clone() {
920958
return parse(this.toString(), this._parseOptions).firstChild;
921959
}
@@ -1204,6 +1242,20 @@ export function parse(data: string, options = {} as Partial<Options>) {
12041242
return root;
12051243
}
12061244

1245+
/**
1246+
* Resolves a list of {@link NodeInsertable} to a list of nodes,
1247+
* and removes nodes from any potential parent.
1248+
*/
1249+
function resolveInsertable(insertable: NodeInsertable[]): Node[] {
1250+
return insertable.map(val => {
1251+
if (typeof val === 'string') {
1252+
return new TextNode(val);
1253+
}
1254+
val.remove();
1255+
return val;
1256+
});
1257+
}
1258+
12071259
function resetParent(nodes: Node[], parent: HTMLElement) {
12081260
return nodes.map((node) => {
12091261
node.parentNode = parent;

0 commit comments

Comments
 (0)