Skip to content

Commit

Permalink
Merge pull request #281 from Maluscat/main
Browse files Browse the repository at this point in the history
Implement more getters and methods on HTMLElement, in accordance with the DOM spec
  • Loading branch information
taoqf authored Nov 14, 2024
2 parents a3e41a9 + 64d4585 commit d903da2
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 49 deletions.
47 changes: 43 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ Node --|> TextNode
Node ..> ClassList
```


## HTMLElement Methods

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

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

### before(...nodesOrStrings)

Insert one or multiple nodes or text before the current element. Does not work on root.

### after(...nodesOrStrings)

Insert one or multiple nodes or text after the current element. Does not work on root.

### prepend(...nodesOrStrings)

Insert one or multiple nodes or text to the first position of an element's child nodes.

### append(...nodesOrStrings)

Insert one or multiple nodes or text to the last position of an element's child nodes.
This is similar to appendChild, but accepts arbitrarily many nodes and converts strings to text nodes.

### appendChild(node)

Append a child node to childNodes
Append a node to an element's child nodes.

### insertAdjacentHTML(where, html)

Expand Down Expand Up @@ -298,6 +316,7 @@ Clone a node.

Get element by it's ID.


## HTMLElement Properties

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

### tagName

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

### structuredText

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

Get DOM structure.

### childNodes

Get all child nodes. A child node can be a TextNode, a CommentNode and a HTMLElement.

### children

Get all child elements, so all child nodes of type HTMLELement.

### firstChild

Get first child node. `undefined` if no child.
Get first child node. `undefined` if the node has no children.

### lastChild

Get last child node. `undefined` if no child
Get last child node. `undefined` if the node has no children.

### firstElementChild

Get the first child of type HTMLElement. `undefined` if none exists.

### lastElementChild

Get the first child of type HTMLElement. `undefined` if none exists.

### childElementCount

Get the number of children that are of type HTMLElement.

### innerHTML

Expand Down
134 changes: 93 additions & 41 deletions src/nodes/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface RawAttributes {
}

export type InsertPosition = 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend';
export type NodeInsertable = Node | string;

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

/**
* Get first child node
* @return {Node | undefined} first child node; or undefined if none
*/
public get firstChild(): Node | undefined {
return this.childNodes[0];
}

/**
* Get last child node
* @return {Node | undefined} last child node; or undefined if none
*/
public get lastChild(): Node | undefined {
return arr_back(this.childNodes);
}

/**
* Get attributes
* @access private
Expand Down Expand Up @@ -818,33 +800,46 @@ export default class HTMLElement extends Node {
}
const p = parse(html, this._parseOptions);
if (where === 'afterend') {
const idx = this.parentNode.childNodes.findIndex((child) => {
return child === this;
});
resetParent(p.childNodes, this.parentNode);
this.parentNode.childNodes.splice(idx + 1, 0, ...p.childNodes);
this.after(...p.childNodes);
} else if (where === 'afterbegin') {
resetParent(p.childNodes, this);
this.childNodes.unshift(...p.childNodes);
this.prepend(...p.childNodes);
} else if (where === 'beforeend') {
p.childNodes.forEach((n) => {
this.appendChild(n);
});
this.append(...p.childNodes);
} else if (where === 'beforebegin') {
const idx = this.parentNode.childNodes.findIndex((child) => {
return child === this;
});
resetParent(p.childNodes, this.parentNode);
this.parentNode.childNodes.splice(idx, 0, ...p.childNodes);
this.before(...p.childNodes);
} else {
throw new Error(
`The value provided ('${where as string}') is not one of 'beforebegin', 'afterbegin', 'beforeend', or 'afterend'`
);
}
return this;
// if (!where || html === undefined || html === null) {
// return;
// }
}

/** Prepend nodes or strings to this node's children. */
public prepend(...insertable: NodeInsertable[]) {
const nodes = resolveInsertable(insertable);
resetParent(nodes, this);
this.childNodes.unshift(...nodes);
}
/** Append nodes or strings to this node's children. */
public append(...insertable: NodeInsertable[]) {
const nodes = resolveInsertable(insertable);
resetParent(nodes, this);
this.childNodes.push(...nodes);
}
/** Insert nodes or strings before this node. */
public before(...insertable: NodeInsertable[]) {
const nodes = resolveInsertable(insertable);
const siblings = this.parentNode.childNodes;
resetParent(nodes, this.parentNode);
siblings.splice(siblings.indexOf(this), 0, ...nodes);
}
/** Insert nodes or strings after this node. */
public after(...insertable: NodeInsertable[]) {
const nodes = resolveInsertable(insertable);
const siblings = this.parentNode.childNodes;
resetParent(nodes, this.parentNode);
siblings.splice(siblings.indexOf(this) + 1, 0, ...nodes);
}

public get nextSibling(): Node | null {
Expand Down Expand Up @@ -909,13 +904,56 @@ export default class HTMLElement extends Node {
}
}

public get classNames() {
return this.classList.toString();
/** Get all childNodes of type {@link HTMLElement}. */
public get children(): HTMLElement[] {
const children = [];
for (const childNode of this.childNodes) {
if (childNode instanceof HTMLElement) {
children.push(childNode);
}
}
return children;
}

/**
* Get the first child node.
* @return The first child or undefined if none exists.
*/
public get firstChild(): Node | undefined {
return this.childNodes[0];
}
/**
* Get the first child node of type {@link HTMLElement}.
* @return The first child element or undefined if none exists.
*/
public get firstElementChild(): HTMLElement | undefined {
return this.children[0];
}

/**
* Clone this Node
* Get the last child node.
* @return The last child or undefined if none exists.
*/
public get lastChild(): Node | undefined {
return arr_back(this.childNodes);
}
/**
* Get the last child node of type {@link HTMLElement}.
* @return The last child element or undefined if none exists.
*/
public get lastElementChild(): HTMLElement | undefined {
return this.children[this.children.length - 1];
}

public get childElementCount(): number {
return this.children.length;
}

public get classNames() {
return this.classList.toString();
}

/** Clone this Node */
public clone() {
return parse(this.toString(), this._parseOptions).firstChild;
}
Expand Down Expand Up @@ -1204,6 +1242,20 @@ export function parse(data: string, options = {} as Partial<Options>) {
return root;
}

/**
* Resolves a list of {@link NodeInsertable} to a list of nodes,
* and removes nodes from any potential parent.
*/
function resolveInsertable(insertable: NodeInsertable[]): Node[] {
return insertable.map(val => {
if (typeof val === 'string') {
return new TextNode(val);
}
val.remove();
return val;
});
}

function resetParent(nodes: Node[], parent: HTMLElement) {
return nodes.map((node) => {
node.parentNode = parent;
Expand Down
Loading

0 comments on commit d903da2

Please sign in to comment.