반응형
index.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<section id = "target" data-viewmodel="wrapper">
<h2 data-viewmodel="title"></h2>
<section data-viewmodel="contents"></section>
</section>
</body>
<script src="./index.js"></script>
</html>
index.js
const type = (target, type) => {
if (typeof type == "string") {
if (typeof target != type) throw `invalid type ${target} : ${type}`;
} else {
if (!(target instanceof type)) throw `invalid type ${target} : ${type}`;
}
return target;
};
const ViewModel = class {
static #private = Symbol();
static get(data) {
return new ViewModel(this.#private, data); //this.#private 입니다.
}
styles = {};
attributes = {};
properties = {};
events = {};
constructor(checker, data) {
if (checker != ViewModel.#private) throw `use ViewModel.get()`; //ViewModel.#private 입니다.
Object.entries(data).forEach(([k, v]) => {
console.log(" K : " + k);
switch (k) {
case "styles":
this.styles = v;
break;
case "attributes":
this.attributes = v;
break;
case "properties":
this.properties = v;
break;
case "events":
this.events = v;
default:
this[k] = v; //default를 빼먹지 말것. 없으면 viewmodel2의 wrapper, title, content를 처리하지 못한다.
}
});
Object.seal(this); //value를 바꿀 순 있지만 key를 추가할 수는 없음
}
};
const BindItem = class {
el;
vmCat;
constructor(el, vmCat, _0 = type(el, HTMLElement), _1 = type(vmCat, "string")) {
this.el = el;
this.vmCat = vmCat;
Object.freeze(this);
}
};
const Binder = class {
#item = new Set();
add(v, _ = type(v, BindItem)) {
this.#item.add(v);
}
render(viewModel, _ = type(viewModel, ViewModel)) {
this.#item.forEach((bindItem) => {
console.log(" bindItem.vmCat : " + bindItem.vmCat);
const vm = type(viewModel[bindItem.vmCat], ViewModel);
const el = bindItem.el;
Object.entries(vm.styles).forEach(([k, v]) => (el.style[k] = v));
Object.entries(vm.attributes).forEach(([k, v]) => (el.attribute[k] = v));
Object.entries(vm.properties).forEach(([k, v]) => (el[k] = v));
Object.entries(vm.events).forEach(([k, v]) => (el[`on${k}`] = (e) => v.call(el, e, viewModel)));
});
}
};
const Scanner = class {
scan(el, _ = type(el, HTMLElement)) {
const binder = new Binder();
// this.checkItem(binder, el);
let target;
const stack = [el];
while ((target = stack.pop())) {
this.checkItem(binder, target);
if (target.firstElementChild) stack.push(target.firstElementChild); //if 체크 해야한다.
if (target.nextElementSibling) stack.push(target.nextElementSibling); //if 체크 해야한다.
}
return binder; //바안더를 리턴한다.
}
checkItem(binder, el) {
const vmCat = el.getAttribute("data-viewmodel");
if (vmCat) {
binder.add(new BindItem(el, vmCat));
}
}
};
const getRandom = () => parseInt(Math.random() * 150) + 100;
const viewModel2 = ViewModel.get({
isStop: false,
changeContent() {
this.wrapper.styles.background = `rgb(${getRandom()},${getRandom()},${getRandom()})`;
this.contents.properties.innerHTML = this.wrapper.styles.background; //contents안에 properties안에 innerHTML가 있다.
binder.render(viewModel2);
},
wrapper: ViewModel.get({
styles: { with: "50%", background: "#ffa", cursor: "pointer" },
events: {
click(e, vm) {
vm.isStop = true;
},
},
}),
title: ViewModel.get({
properties: { innerHTML: "title" },
}),
contents: ViewModel.get({
properties: { innerHTML: "Contents" },
}),
});
const scanner = new Scanner();
const binder = scanner.scan(document.querySelector("#target"));
binder.render(viewModel2); //제어역전
const f = () => {
viewModel2.changeContent();
binder.render(viewModel2); //없어도 될것 같은데..
if (!viewModel2.isStop) requestAnimationFrame(f);
};
requestAnimationFrame(f);