til

day26

paulaner80 2022. 1. 18. 06:20
반응형

 

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);

'til' 카테고리의 다른 글

new Day2 (BottomNavigationBar 사용법)  (0) 2022.06.10
new Day 1  (0) 2022.06.09
day25  (0) 2022.01.14
day 24  (0) 2022.01.13
day23  (0) 2022.01.13