Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Component based rendering #67

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
42 changes: 42 additions & 0 deletions games/lockpicking/src/js/components/AppComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Component } from "./Component.js";
import { TestComponent1 } from "./test-component-1.js";

export class App extends Component {
constructor() {
const options = {
components: {
TestComponent1,
},
};
super(options);
}

render(selector = "") {
this.findParentNode(selector);
super.render();
}

findParentNode(selector) {
const parentNode = document.querySelector(selector);
if (!parentNode) {
throw new Error(`Parent node with selector ${selector} is not found`);
}
this.parentNode = parentNode;
}

renderNode() {
const node = this.createNode();
this.parentNode.appendChild(node);
}

html() {
return `
<div>
<div>Self</div>
<!-- TestComponent1 -->
<!-- test-component-2 -->
<!-- TestComponent1 -->
</div>
`;
}
}
89 changes: 89 additions & 0 deletions games/lockpicking/src/js/components/Component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { uniqueId } from "../utils/uniqueId.js";
import { isObjectEmpty } from "../utils/isObjectEmpty.js";

const COMPONENT_ID_ATTRIBUTE = "component-id";

export class Component {
constructor(options = {}) {
this.name = this.constructor.name;
this.el = null;
this.id = null;
this.props = options.props || {};
this.components = options.components || {};
this.parentNode = options.parentNode || {};
}

render() {
this.isComponentDeclarationExists();
this.renderSelf();
this.renderComponents();
}

isComponentDeclarationExists() {
// console.log("Component", this.name, this.parentNode.childNodes);
}

renderSelf() {
this.renderNode();
this.el = document.querySelector(
`[${COMPONENT_ID_ATTRIBUTE}="${this.id}"]`
);
console.log(this.el);
}

createNode() {
const temporaryNode = document.createElement("div");
temporaryNode.innerHTML = this.html();

const htmlElements = [...temporaryNode.childNodes].filter(
(childNode) => childNode instanceof HTMLElement
);

if (!htmlElements[0]) {
console.warn("Element for rendering in the component not found");
return;
}

this.id = uniqueId();
const node = htmlElements[0];
node.setAttribute(COMPONENT_ID_ATTRIBUTE, this.id);

return node;
}

renderNode() {
for (const childNode of this.parentNode.childNodes) {
if (!(childNode instanceof Comment)) {
continue;
}
const isTargetComment = childNode.nodeValue.trim() === this.name;
if (isTargetComment) {
const node = this.createNode();
childNode.replaceWith(node);
}
}
}

renderComponents() {
if (isObjectEmpty(this.components)) {
return;
}

const comments = {};
for (const node of this.el.childNodes) {
if (node instanceof Comment) {
comments[node.nodeValue.trim()] = node;
}
}

for (const componentName in this.components) {
this.renderComponent(this.components[componentName], comments);
}
}

renderComponent(component, comments) {
new component({
parentNode: this.el,
}).render();
}
}
14 changes: 14 additions & 0 deletions games/lockpicking/src/js/components/test-component-1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component } from "./Component.js";

// This class should just create node. And parent node will replace corresponding comment with this node
export class TestComponent1 extends Component {
constructor(options) {
super(options);
}

html() {
return `
<div class="test-component-1">Test component 1</div>
`;
}
}
3 changes: 3 additions & 0 deletions games/lockpicking/src/js/pages/test-components.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { App } from "../components/AppComponent.js";

new App().render("#app");
6 changes: 6 additions & 0 deletions games/lockpicking/src/js/utils/isObjectEmpty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function isObjectEmpty(obj) {
for (const key in obj) {
return false;
}
return true;
}
28 changes: 28 additions & 0 deletions games/lockpicking/src/test-components.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=EB+Garamond:wght@500;800&display=swap"
rel="stylesheet"
/>

<link rel="stylesheet" href="./styles/app.css" />
<title>Document</title>

<style>
body {
color: #fff;
}
</style>
</head>
<body>
<div id="app"></div>

<script type="module" src="./js/pages/test-components.js"></script>
</body>
</html>