Skip to content

Commit

Permalink
Implement selectors: getElement* & querySelector*
Browse files Browse the repository at this point in the history
  • Loading branch information
b-fuze committed Jul 29, 2020
1 parent e524455 commit 38a6765
Show file tree
Hide file tree
Showing 8 changed files with 2,020 additions and 6 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ deno run --allow-read basic.ts
Deno DOM has two backends, WASM and native (not functional yet). You can use the
respective by importing either `deno-dom-wasm.ts` or `deno-dom-native.ts`.

# Credits
- html5ever developers for the HTML parser
- nwsapi developers for the selector parser

76 changes: 76 additions & 0 deletions src/dom/document.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { setLock, getLock } from "../constructor-lock.ts";
import { Node, NodeType, Text, Comment } from "./node.ts";
import { Element } from "./element.ts";
import { DOM as NWAPI } from "./nwsapi-types.ts";

export class DOMImplementation {
constructor() {
Expand Down Expand Up @@ -99,6 +100,7 @@ export class Document extends Node {
#lockState = false;
#documentURI = "about:blank"; // TODO
#title = "";
#nwapi = NWAPI(this);

constructor() {
super(
Expand Down Expand Up @@ -136,6 +138,10 @@ export class Document extends Node {
return false;
}

get compatMode(): string {
return "CSS1Compat";
}

get documentElement(): Element | null {
for (const node of this.childNodes) {
if (node.nodeType === NodeType.ELEMENT_NODE) {
Expand Down Expand Up @@ -172,6 +178,76 @@ export class Document extends Node {
createComment(data?: string): Comment {
return new Comment(data);
}

querySelector(selectors: string): Element | null {
return this.#nwapi.first(selectors, this);
}

querySelectorAll(selectors: string): Element[] {
return this.#nwapi.select(selectors, this);
}

// TODO: DRY!!!
getElementById(id: string): Element | null {
for (const child of this.childNodes) {
if (child.nodeType === NodeType.ELEMENT_NODE) {
if ((<Element> child).id === id) {
return <Element> child;
}

const search = (<Element> child).getElementById(id);
if (search) {
return search;
}
}
}

return null;
}

getElementsByTagName(tagName: string): Element[] {
return <Element[]> this._getElementsByTagName(tagName.toUpperCase(), []);
}

private _getElementsByTagName(tagName: string, search: Node[]): Node[] {
for (const child of this.childNodes) {
if (child.nodeType === NodeType.ELEMENT_NODE) {
if ((<Element> child).tagName === tagName) {
search.push(child);
}

(<any> child)._getElementsByTagName(tagName, search);
}
}

return search;
}

getElementsByTagNameNS(_namespace: string, localName: string): Element[] {
return this.getElementsByTagName(localName);
}

getElementsByClassName(className: string): Element[] {
return <Element[]> this._getElementsByClassName(className, []);
}

private _getElementsByClassName(className: string, search: Node[]): Node[] {
for (const child of this.childNodes) {
if (child.nodeType === NodeType.ELEMENT_NODE) {
if ((<Element> child).classList.contains(className)) {
search.push(child);
}

(<any> child)._getElementsByClassName(className, search);
}
}

return search;
}

hasFocus(): boolean {
return true;
}
}

export class HTMLDocument extends Document {
Expand Down
98 changes: 96 additions & 2 deletions src/dom/element.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getLock } from "../constructor-lock.ts";
import { Node, NodeType } from "./node.ts";
import { fragmentNodesFromString } from "../deserialize.ts";
import { Node, NodeType, Text } from "./node.ts";

export class DOMTokenList extends Set<string> {
contains(token: string): boolean {
Expand Down Expand Up @@ -56,6 +56,8 @@ export class Element extends Node {
public classList = new DOMTokenList();
public attributes: NamedNodeMap & {[attribute: string]: string} = <any> new NamedNodeMap();

#currentId = "";

constructor(
public tagName: string,
parentNode: Node | null,
Expand All @@ -72,15 +74,27 @@ export class Element extends Node {

for (const attr of attributes) {
this.attributes[attr[0]] = attr[1];

switch (attr[0]) {
case "class":
this.classList = new DOMTokenList(attr[1].split(/\s+/g));
break;
case "id":
this.#currentId = attr[1];
break;
}
}

this.tagName = tagName.toUpperCase();
}

get className(): string {
return Array.from(this.classList).join(" ");
}

set className(className: string) {
// TODO
// TODO: Probably don't replace the current classList
this.classList = new DOMTokenList(className.split(/\s+/g));
}

get outerHTML(): string {
Expand Down Expand Up @@ -177,12 +191,33 @@ export class Element extends Node {
}
}

get id(): string {
return this.#currentId || "";
}

set id(id: string) {
this.setAttribute(id, this.#currentId = id);
}

getAttribute(name: string): string | null {
return this.attributes[name] ?? null;
}

setAttribute(name: string, value: any) {
this.attributes[name] = "" + value;

if (name === "id") {
this.#currentId = value;
}
}

hasAttribute(name: string): boolean {
return this.attributes.hasOwnProperty(name);
}

hasAttributeNS(_namespace: string, name: string): boolean {
// TODO: Use namespace
return this.attributes.hasOwnProperty(name);
}

get nextElementSibling(): Element | null {
Expand Down Expand Up @@ -230,5 +265,64 @@ export class Element extends Node {

return prev;
}

// TODO: DRY!!!
getElementById(id: string): Element | null {
for (const child of this.childNodes) {
if (child.nodeType === NodeType.ELEMENT_NODE) {
if ((<Element> child).id === id) {
return <Element> child;
}

const search = (<Element> child).getElementById(id);
if (search) {
return search;
}
}
}

return null;
}

getElementsByTagName(tagName: string): Element[] {
return <Element[]> this._getElementsByTagName(tagName.toUpperCase(), []);
}

private _getElementsByTagName(tagName: string, search: Node[]): Node[] {
for (const child of this.childNodes) {
if (child.nodeType === NodeType.ELEMENT_NODE) {
if ((<Element> child).tagName === tagName) {
search.push(child);
}

(<Element> child)._getElementsByTagName(tagName, search);
}
}

return search;
}

getElementsByClassName(className: string): Element[] {
return <Element[]> this._getElementsByClassName(className, []);
}

getElementsByTagNameNS(_namespace: string, localName: string): Element[] {
// TODO: Use namespace
return this.getElementsByTagName(localName);
}

private _getElementsByClassName(className: string, search: Node[]): Node[] {
for (const child of this.childNodes) {
if (child.nodeType === NodeType.ELEMENT_NODE) {
if ((<Element> child).classList.contains(className)) {
search.push(child);
}

(<Element> child)._getElementsByClassName(className, search);
}
}

return search;
}
}

8 changes: 7 additions & 1 deletion src/dom/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ export class Node extends EventTarget {
}

_setOwnerDocument(document: Document | null) {
this.#ownerDocument = document;
if (this.#ownerDocument !== document) {
this.#ownerDocument = document;

for (const child of this.childNodes) {
child._setOwnerDocument(document);
}
}
}

get ownerDocument() {
Expand Down
14 changes: 14 additions & 0 deletions src/dom/nwsapi-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import NWDom from "./nwsapi.js";
import { Element } from "./element.ts";
import { Document } from "./document.ts";

export const DOM: (doc: Document) => {
ancestor(selector: string, context: Element | Document, callback?: (element: Element) => void): Element | null,
first(selector: string, context: Element | Document, callback?: (element: Element) => void): Element | null,
match(selector: string, context: Element | Document, callback?: (element: Element) => void): boolean,
select(selector: string, context: Element | Document, callback?: (element: Element) => void): Element[],
byId(id: string, from: Element | Document): Element[],
byTag(tag: string, from: Element | Document): Element[],
byClass(tag: string, from: Element | Document): Element[],
} = <any> NWDom;

Loading

0 comments on commit 38a6765

Please sign in to comment.