Skip to content

Commit a0e2c5b

Browse files
authored
Append HTML string literals with components (#26)
2 parents 8088622 + b527052 commit a0e2c5b

File tree

5 files changed

+78
-6
lines changed

5 files changed

+78
-6
lines changed

src/DocumentComponent.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Copyright © 2024 Cloudnode OÜ
3+
*
4+
* This file is part of @cldn/components.
5+
*
6+
* \@cldn/components is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
7+
* General Public License as published by the Free Software Foundation, either version 3 of the License,
8+
* or (at your option) any later version.
9+
*
10+
* \@cldn/components is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
11+
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12+
* for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public License along with @cldn/components.
15+
* If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
import {NodeComponent} from "./index.js";
18+
19+
/**
20+
* A {@link !DocumentFragment} component
21+
*/
22+
export class DocumentComponent extends NodeComponent<DocumentFragment> {
23+
public constructor(html?: string) {
24+
super(html ? document.createRange().createContextualFragment(html) : document.createDocumentFragment());
25+
}
26+
27+
/**
28+
* Template literal tag function that accepts HTML code with components in a
29+
* string literal
30+
*/
31+
public static tag(strings: TemplateStringsArray, ...components: NodeComponent<any>[]): DocumentComponent {
32+
const idPrefix = `tag-${crypto.randomUUID()}-`;
33+
const doc = new DocumentComponent(strings.reduce((acc, str, index) => acc + `${str}${index < components.length ? `<slot name="${idPrefix}${index - 1}"></slot>` : ""}`, ""));
34+
for (const [index, component] of components.entries())
35+
component.slot(idPrefix + index, doc.node);
36+
return doc;
37+
}
38+
}

src/ElementComponent.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* You should have received a copy of the GNU Lesser General Public License along with @cldn/components.
1515
* If not, see <https://www.gnu.org/licenses/>.
1616
*/
17-
import {NodeComponent} from "./NodeComponent.js";
17+
import {DocumentComponent, NodeComponent} from "./index.js";
1818

1919
/**
2020
* Non-readonly non-method keys
@@ -127,11 +127,21 @@ export abstract class ElementComponent<T extends Element> extends NodeComponent<
127127
}
128128

129129
/**
130-
* Set inner HTML
130+
* Append HTML.
131+
*
132+
* Any components (provided as `${...}` inside the HTML string literal)
133+
* remain fully functional and are appended (not just as HTML).
134+
* @example
135+
* component.html`<div>${new Component("button")
136+
* .text("Click me")
137+
* .on("click", () => console.log("clicked"))
138+
* }</div>`
139+
* // Event listeners etc. are preserved.
140+
* // Note the lack of parentheses.
141+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals Template literals (Template strings) - MDN}
131142
*/
132-
public html(html: string) {
133-
this.node.innerHTML = html;
134-
return this;
143+
public html(strings: TemplateStringsArray, ...components: NodeComponent<any>[]): this {
144+
return this.append(DocumentComponent.tag(strings, ...components));
135145
}
136146

137147
/**
@@ -167,6 +177,11 @@ export abstract class ElementComponent<T extends Element> extends NodeComponent<
167177
return super.on(type as any, listener, c as any);
168178
}
169179

180+
public override empty() {
181+
this.node.replaceChildren();
182+
return this;
183+
}
184+
170185
/**
171186
* Get this component's outer HTML
172187
*/

src/NodeComponent.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ export abstract class NodeComponent<T extends Node> {
3333
this.node = node;
3434
}
3535

36+
/**
37+
* Run a function in the context of this component
38+
* @param fn Provides this component as the first argument and `this`.
39+
*/
40+
public context(fn: (this: this, component: this) => this): this {
41+
fn.call(this, this);
42+
return this;
43+
}
44+
3645
/**
3746
* Insert component after the last child
3847
*/
@@ -86,4 +95,13 @@ export abstract class NodeComponent<T extends Node> {
8695
slotNode.replaceWith(this.node);
8796
return this;
8897
}
98+
99+
/**
100+
* Empty the component (remove children)
101+
*/
102+
public empty() {
103+
while (this.node.firstChild)
104+
this.node.removeChild(this.node.firstChild);
105+
return this;
106+
}
89107
}

src/TextComponent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* You should have received a copy of the GNU Lesser General Public License along with @cldn/components.
1515
* If not, see <https://www.gnu.org/licenses/>.
1616
*/
17-
import {NodeComponent} from "./NodeComponent.js";
17+
import {NodeComponent} from "./index.js";
1818

1919
/**
2020
* A text node component

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* If not, see <https://www.gnu.org/licenses/>.
1616
*/
1717
export {NodeComponent} from "./NodeComponent.js";
18+
export {DocumentComponent} from "./DocumentComponent.js";
1819
export {TextComponent} from "./TextComponent.js";
1920
export {ElementComponent} from "./ElementComponent.js";
2021
export {Component} from "./Component.js";

0 commit comments

Comments
 (0)