-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
308 lines (301 loc) · 8.8 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
const err = (v) => {
throw v;
};
// production할때는 console.log로 변경. 이방법 많이씀.
const Task = class {
static load(json) {
const task = new Task(json.title, json.isCompleted);
return task;
}
static get(title) {
return new Task(title);
}
toJSON() {
return this.getInfo();
}
constructor(title, isCompleted = false) {
this.title = title;
this.isCompleted = isCompleted;
}
setTitle(title) {
this.title = title;
// return new Task(title, this.isCompleted);
}
toggle() {
this.isCompleted = !this.isCompleted;
// return new Task(this.title, !this.isCompleted);
}
getInfo() {
return { title: this.title, isCompleted: this.isCompleted };
}
// isEqual(task) {
// return task.title === this.title && task.isCompleted === this.isCompleted;
// }
};
() => {
let isOkay = true;
const task = new Task('test1');
isOkay = task.getInfo().title === 'test1' && task.getInfo().isCompleted === false;
console.log('test1', isOkay);
task.toggle();
isOkay = task.getInfo().title === 'test1' && task.getInfo().isCompleted === true;
console.log('test2', isOkay);
};
// 상태값으로 boolean 안좋아. 그래서 일반적으로 열거형씀.
// 근데 자스는 없어. 그냥 클래스로 만들어
// 가변형에대한 의사결정. 값을가질지 객체를가질지.
// immutable은 참조 안전성. 다중 스레드에서 새객체니까 중복참조도 없어져. 값 컨텍스트
// mutable과 immutable은 처음에 정해야돼. 의사결정
// 객체의 실체는 값이 아니다. 메모리의 참조값.
// 값 컨텍스트 때문에
// 객체지향인데 this.id=uuid() 이런건 잘못된거야ㅣ
// 클래스 인스턴스도 값 컨텍스트를 따르도록 조종할 수 있다. 디자인이 중요한거야
// 객체지향에서 내장을 깔필요가 없어. 내장 까야된다면 잘못된 코드. ㅇ
// 호스트가 중요하지 객체는 중요하지 않다. 그냥 호스트한테 잘 가면된다.
const Folder = class extends Set {
// 팩토리 패턴. 생성에대한 지식을 바깥으로 노출하고 싶지 않기 때문에 존재.
static load(json) {
const folder = new Folder(json.title);
json.tasks.forEach((t) => {
folder.addTask(Task.load(t));
});
return folder;
}
static get(title) {
return new Folder(title);
}
toJSON() {
return { title: this.title, tasks: this.getTasks() };
}
constructor(title) {
super();
this.title = title;
// this.tasks = new Set();
}
// move는 remove and add야.
// return err를 통해서 개발모드와 배포모드모두 그다음작업을 막을수있다.
moveTask(task, folderSrc) {
if (super.has(task) || !folderSrc.has(task)) return err('...');
folderSrc.removeTask(task);
this.addTask(task);
}
// addTask(title) {
// this.tasks.add(new Task(title));
// }
addTask(task) {
if (!task instanceof Task) err('invalid task');
super.add(task);
}
removeTask(task) {
if (!task instanceof Task) err('invalid task');
super.delete(task);
}
// 대칭성. 켄트백. add할때 title만받는데 remove할때 title만 받으면 이상해져.
getTasks() {
return [...super.values()];
}
// 범용적인 명사 동사 등은 프레임워크나 상위 개발자들이 만들어놓는다. 우리는 구체화된 변수명을 사용하자. ex) getList. but component는 도메인적인 맥락 제거하는게 좋을때도있음.
getTitle() {
return this.title;
}
// 내적 동질성.(자식이 우선이라 부모꺼는 호출안돼)
add() {
err('...');
}
delete() {
err('...');
}
clear() {
err('...');
}
values() {
err('...');
}
};
() => {
let isOkay = true;
const task = new Task('test1');
const folder = new Folder('folder1');
isOkay = folder.getTasks().length === 0;
console.log('test1', isOkay);
folder.addTask(task);
isOkay = folder.getTasks().length === 1 && folder.getTasks()[0].getInfo().title === task.title;
console.log('test2', isOkay);
folder.addTask(task);
isOkay = folder.getTasks().length === 1 && folder.getTasks()[0].getInfo().title === task.title;
console.log('test3', isOkay);
};
const App = class extends Set {
static load(json) {
const app = new App();
json.forEach((f) => {
app.addFolder(Folder.load(f));
});
return app;
}
toJSON() {
return this.getFolders();
}
constructor() {
super();
}
addFolder(folder) {
if (!folder instanceof Task) err('invalid task');
super.add(folder);
}
removeFolder(folder) {
super.delete(folder);
}
getFolders() {
return [...super.values()];
}
add() {
err('...');
}
delete() {
err('...');
}
clear() {
err('...');
}
values() {
err('...');
}
};
const Renderer = class {
constructor(app) {
this.app = app;
}
render() {
this._render();
}
_render() {
throw err('must be overrided');
}
};
const el = (tag) => document.createElement(tag);
const DomRenderer = class extends Renderer {
constructor(parent, app) {
super(app);
this.taskEl = [];
const [folder, task] = Array.from(parent.querySelectorAll('ul'));
const [load, save] = Array.from(parent.querySelectorAll('button'));
// create를 먼저해라. view, list, delete 순서.
load.onclick = (e) => {
const v = localStorage['todo'];
if (v) {
this.app = App.load(JSON.parse(v));
this.render();
}
};
save.onclick = (e) => {
localStorage['todo'] = JSON.stringify(this.app);
};
this.folder = folder;
this.task = task;
this.currentFolder = null;
parent.querySelector('nav>input').addEventListener('keyup', (e) => {
if (e.keyCode !== 13) return;
const v = e.target.value.trim();
if (!v) return;
// 쉴드패턴. 위에서 다 튕겨내고 아래는 동작하는 로직만있는거. 화이트 블랙처럼
// const folder = new Folder(v);
const folder = Folder.get(v);
this.app.addFolder(folder);
e.target.value = '';
this.render();
});
parent.querySelector('header>input').addEventListener('keyup', (e) => {
if (e.keyCode !== 13 || !this.currentFolder) return;
const v = e.target.value.trim();
if (!v) return;
// const task = new Task(v);
const task = Task.get(v);
this.currentFolder.addTask(task);
e.target.value = '';
this.render();
});
}
_render() {
const folders = this.app.getFolders();
let moveTask, tasks;
if (!this.currentFolder) this.currentFolder = folders[0];
let oldEl = this.folder.firstElementChild,
lastEl = null;
folders.forEach((folder) => {
let li;
if (oldEl) {
li = oldEl;
oldEl = oldEl.nextElementSibling;
} else {
li = el('li');
this.folder.appendChild(li);
oldEl = null;
}
lastEl = li;
li.innerHTML = folder.getTitle();
li.style.cssText = `font-size: ${this.currentFolder === folder ? '20px' : '12px'}`;
li.onclick = () => {
this.currentFolder = folder;
this.render();
};
li.ondrop = (e) => {
e.preventDefault();
folder.moveTask(moveTask, this.currentFolder);
};
li.ondragover = (e) => {
e.preventDefault();
};
});
if (lastEl) {
while ((oldEl = this.task.firstElementChild)) {
this.task.removeChild(oldEl);
this.taskEl.push(oldEl);
}
}
if (!this.currentFolder) return;
tasks = this.currentFolder.getTasks();
if (tasks.length === 0) {
while (this.task.firstElementChild) {
this.task.removeChild(this.task.firstElementChild);
}
} else {
(oldEl = this.task.firstElementChild), (lastEl = null);
tasks.forEach((t) => {
let li;
if (oldEl) {
li = oldEl;
oldEl = oldEl.nextElementSibling;
} else {
li = this.taskEl.length ? this.taskEl.pop() : el('li');
this.task.appendChild(li);
oldEl = null;
}
lastEl = li;
const { title, isCompleted } = t.getInfo();
li.setAttribute('draggable', true);
li.innerHTML = (isCompleted ? 'completed ' : 'process ') + title;
li.addEventListener('click', (e) => {
// e.preventDefault();
t.toggle();
this.render();
});
li.addEventListener('dragstart', (e) => {
// e.preventDefault();
moveTask = t;
});
});
if (lastEl) {
while ((oldEl = lastEl.nextElementSibling)) {
this.task.removeChild(oldEl);
this.taskEl.push(oldEl);
}
}
}
}
};
new DomRenderer(document.querySelector('main'), new App());
// 전체 렌더는 그냥 그리면 돼. 데이터만 다루는거야. 어차피 라이브러리들이 해주는거야 그거는.
// 51.38
// 풀링. 객체 재활용
// 증분 랜더링