- 쩨리쩨리님의 달력 만들기 : https://jerryjerryjerry.tistory.com/26
- 왼쪽 To Do List 부분 카피한 디자인 : https://dribbble.com/shots/20082214-Contact-page-Untitled-UI
미리보기 : https://ohikmyeong.github.io/cdtd-2022/
깃허브 주소 : https://github.com/OhIkmyeong/cdtd-2022
소요시간 : 이틀 (전과정 포함 약 8~9시간)
HTML
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
|
<!DOCTYPE html>
<html lang="ko">
<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">
<title>달력만들기-2022</title>
<link rel="stylesheet" type="text/css" href="./css/main.css"/>
<script src="./js/main.js" type="module"></script>
</head>
<body>
<div id="wrap-cdtd">
<!-- TO DO LIST -->
<section id="wrap-td">
<!-- 선택한 날짜 출력 -->
<article id="td-date">
<!-- 오늘일시 -->
<div id="ic-today">TODAY</div>
<!-- -->
<p id="td-date-year">YYYY</p>
<p id="td-date-day">DAY</p>
<span id="td-date-month">MM</span><span id="td-date-date">DD</span>
</article>
<!-- 카운트 -->
<article id="td-count">
<!-- 카운트-텍스트 -->
<div id="td-count-text">
할 일을 등록해보세요!
</div><!-- td-count-text -->
<!-- 카운트-막대그래프 -->
<div id="td-count-graph">
<div id="td-count-graph-bar">
<div id="td-count-graph-num">0%</div>
</div>
</div><!-- td-count-graph -->
</article><!-- td-count -->
<!-- 작성된 할 일 목록 출력 -->
<ul id="td-list"></ul><!-- td-list -->
<!-- 할 일 입력 -->
<form id="form-add-td">
<input type="text" id="ipt-add-td" title="할 일 입력" placeholder="Write something TO DO" autocomplete="off"/>
<button id="btn-add-td" title="할 일 등록">SUBMIT</button>
</form><!-- form-td-list -->
</section><!-- wrap-td -->
<!-- CALENDAR -->
<section id="wrap-cd">
<!-- 달력 상단 -->
<article id="cd-head">
<button id="btn-mm-prev" title="지난달"></button>
<div>
<p id="cd-head-yy">YYYY</p>
<p id="cd-head-mm-num">MM</p>
<p id="cd-head-mm-eng">MM-ENG-FULL</p>
</div>
<button id="btn-mm-next" title="다음달"></button>
</article><!-- cd-head -->
<!-- 달력 -->
<table id="cd">
<caption>달력</caption>
<colgroup>
<col style="width:calc(100% / 7);">
<col style="width:calc(100% / 7);">
<col style="width:calc(100% / 7);">
<col style="width:calc(100% / 7);">
<col style="width:calc(100% / 7);">
<col style="width:calc(100% / 7);">
<col style="width:calc(100% / 7);">
</colgroup>
<thead>
<tr>
<th>SUN</th>
<th>MON</th>
<th>TUE</th>
<th>WED</th>
<th>THU</th>
<th>FRI</th>
<th>SAT</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</section><!-- wrap-cd -->
</div><!-- wrap-cdtd -->
</body>
</html>
|
cs |
CSS는 생략
main.js
1
2
3
4
5
6
7
8
9
10
|
import { Calendar } from "./Calendar.js";
import { ToDoList } from "./ToDoList.js";
(async function(){
const TODO = new ToDoList();
const CAL = new Calendar();
await TODO.init(CAL);
CAL.init(TODO);
})();
|
cs |
FN.js
1
2
3
4
5
6
7
8
9
10
11
|
/**
* Date 객체를 받아서 String으로 반환한다
* @param date
* @returns yyyy-mm-dd
*/
export function return_date_string(date){
const yyyy = date.getFullYear();
const mm = String(date.getMonth() + 1).padStart(2,"0");
const dd = String(date.getDate()).padStart(2,"0");
return `${yyyy}-${mm}-${dd}`;
}//return_date_string
|
cs |
ToDoList.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
|
import { return_date_string } from "./FN.js";
/** 📌TO DO LIST 관련 */
export class ToDoList {
/** TO DO LIST 관련 */
constructor(){
this.$icToday = document.getElementById('ic-today');
this.$year = document.getElementById('td-date-year');
this.$month = document.getElementById('td-date-month');
this.$date = document.getElementById('td-date-date');
this.$day = document.getElementById('td-date-day');
this.$countText = document.getElementById('td-count-text');
this.$countBar = document.getElementById('td-count-graph-bar');
this.$countNum = document.getElementById('td-count-graph-num');
this.$tdList = document.getElementById('td-list');
this.$form = document.getElementById('form-add-td');
this.$formIpt = document.getElementById('ipt-add-td');
this.$formBtn = document.getElementById('btn-add-td');
this.today = null;
this.currDate = null;
this.days = ["SUN","MON","TUE","WED","THU","FRI","SAT"];
this.data = null;
this.CAL = null;
}//constructor
/**
* 최초 화면 로드시
* @param {Class}CAL 캘린더 클래스 받아오기
*/
async init(CAL){
//캘린더 클래스 받아오기
this.CAL = CAL;
//데이터 설정
this.data = await fetch('./json/data.json').then(res=>res.json()).then(json=>json.todo);
//오늘 날짜 설정
this.today = return_date_string(new Date());
//화면 변경 총괄
this.display_all(this.today);
//form에 이벤트 추가
this.$form.addEventListener('submit',(e)=>{
//등록시
this.on_submit(e);
//카운트 수정
this.display_count();
//캘린더에 표시 수정
this.CAL.change_rmn_done(this.currDate);
});
//to do list에 이벤트 추가(삭제 가능하도록);
this.$tdList.addEventListener('click',(e)=>{
switch(e.target.tagName){
case "BUTTON" : {
/* 삭제버튼 */
//아이템 지우고 데이터에서도 삭제
this.delete_td_item(e.target);
//카운트 수정
this.display_count();
//캘린더에 표시 수정
this.CAL.change_rmn_done(this.currDate);
}break;
case "INPUT" : {
/* 체크박스 */
//데이터에서 수정
this.modify_data(e.target);
//카운트 수정
this.display_count();
//캘린더에 표시 수정
this.CAL.change_rmn_done(this.currDate);
}break;
}//switch
});
}//init
/**
* 화면 바꾸기 총괄
* @param {String}dateString yyyy-mm-dd
*/
display_all(dateString){
//현재 보여줄 날짜 변경
this.change_curr_date(dateString);
//날짜 표시 변경
this.display_date_info(dateString);
//오늘 아이콘 표시
this.is_today(dateString);
//오늘의 할 일 목록 표시
this.fill_td_list(dateString);
//오늘의 할 일 성과 표시
this.display_count();
}//display_all
/**
* TO DO LIST에 표시되는 날짜를 변경합니다
* @param {String}dateString yyyy-mm-dd
*/
change_curr_date(dateString){
this.currDate = dateString
}//change_curr_date
/**
* 날짜 변경
* @param {String}dateString yyyy-mm-dd
*/
display_date_info(dateString){
const [yyyy,mm,dd] = dateString.split('-');
this.$year.textContent = yyyy;
this.$month.textContent = mm;
this.$date.textContent = String(dd).padStart(2,"0");
const day = this.days[new Date(dateString).getDay()];
this.$day.textContent = day;
}//display_date_info
/**
* 오늘이면 아이콘 표시
* @param {String}dateString yyyy-mm-dd
* */
is_today(dateString){
const bool = dateString == this.today;
this.$icToday.classList.toggle("off", !bool);
}//is_today
/**
* form에 내용 등록시
*/
on_submit = e => {
e.preventDefault();
const val = this.$formIpt.value.trim();
if(!val) return;
//ipt내용 비우고 다시 포커스 주기
this.$formIpt.value = "";
this.$formIpt.focus();
//리스트에 아이템 추가
const $item = this.make_td_item(val,false);
this.$tdList.appendChild($item);
this.$tdList.scrollTo({
top : this.$tdList.scrollHeight,
behavior : "smooth"
})
//this.data에 추가
this.add_data(val,false);
}//on_submit
/**
* 날짜로부터 할 일들을 받아와서 todoList에 채워줍니다
* @param {String} dateString yyyy-mm-dd
*/
fill_td_list(dateString){
//비우고
this.$tdList.innerHTML = '';
//DOM 추가
const items = this.data[dateString];
if(!items?.length) return;
const $frag = document.createDocumentFragment();
items.forEach(item => {
const {val,done} = item;
$frag.appendChild(this.make_td_item(val,done));
});
this.$tdList.appendChild($frag);
}//fill_td_list
/**
* 할 일 등록
* @param {String}val
* @param {Boolean}done
* @returns {DOM} LI.td-list-item
*/
make_td_item(val,done = false){
//DOM 추가
const $li = document.createElement('LI');
const $lbl = document.createElement('LABEL');
const $chk = document.createElement('INPUT');
const $ic = document.createElement('SPAN');
const $txt = document.createElement('P');
const $btnDel = document.createElement('BUTTON');
$li.classList.add('td-list-item');
$lbl.classList.add('lbl-chk');
$chk.type = "checkbox";
$chk.checked = done;
$ic.classList.add('ic');
$txt.classList.add('td-list-item-txt');
$txt.textContent = val;
$btnDel.classList.add('btn-del');
$btnDel.title = "삭제";
$lbl.appendChild($chk);
$lbl.appendChild($ic);
$lbl.appendChild($txt);
$li.appendChild($lbl);
$li.appendChild($btnDel);
return $li;
}//make_td_item
/**
* 할 일 아이템 삭제
* @param {DOM} $btnDel
*/
delete_td_item($btnDel){
const $li = $btnDel.parentElement;
const val = $li.getElementsByClassName('td-list-item-txt')[0].textContent;
//DOM 제거
this.$tdList.removeChild($li);
//data에서도 삭제
this.remove_data(val);
}//delete_td_item
/**
* 할 일 계산
*/
display_count(){
const $$li = Array.from(this.$tdList.children);
const len = $$li.length;
//오늘의 할 일 없을 때
if(!len){
this.$countText.textContent = '할 일을 등록해보세요!';
this.$countBar.style.width = '0%';
this.$countNum.textContent = '0%';
return;
}
const done = $$li.filter($li => {
const $ipt = $li.querySelector('INPUT');
if($ipt.checked) return $li;
}).length;
const per = parseInt((done / len).toFixed(2) * 100);
this.$countText.innerHTML = per == 100 ? '👍 <strong>대단해요! 할 일을 전부 해냈습니다!</strong>' :`<strong>${len}</strong>개의 할 일 중 <strong>${done}</strong>개를 달성하였습니다.`;
this.$countBar.style.width = `${per}%`;
this.$countNum.textContent = `${per}%`;
}//display_count
/**
* this.data에 새 아이템을 등록합니다
* @param {String}val value
* @param {Boolean}done
*/
add_data(val,done){
const obj = {
val : val,
done : done ?? false
}
if(!this.data[this.currDate]) this.data[this.currDate] = [];
this.data[this.currDate].push(obj);
console.log(this.data);
}//add_data
/**
* data에서도 제거
* @param {String} val value
*/
remove_data(val){
if(!this.data[this.currDate]) return;
const idx = this.data[this.currDate].findIndex(item=>item.val == val);
if(idx < 0) return;
this.data[this.currDate].splice(idx,1);
console.log(this.data);
}//remove_data
/**
* 체크박스시 data의 값을 수정합니다
* @param {DOM}$ipt input[type="checkbox"]
*/
modify_data($ipt){
const val = $ipt.parentElement.getElementsByClassName('td-list-item-txt')[0].textContent;
const done = $ipt.checked;
const idx = this.data[this.currDate].findIndex(item=>item.val == val);
if(idx < 0) return;
this.data[this.currDate][idx].done = done;
}//modify_data
}//class-ToDoList
|
cs |
Calendar.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
|
import { return_date_string } from "./FN.js";
/** 📌달력 관련 */
export class Calendar {
/** 달력 관련 */
constructor(){
this.$head = document.getElementById('cd-head');
this.$year = document.getElementById('cd-head-yy');
this.$monthNum = document.getElementById('cd-head-mm-num');
this.$monthEng = document.getElementById('cd-head-mm-eng');
this.$tbody = document.getElementById('cd').getElementsByTagName('TBODY')[0];
this.today = return_date_string(new Date());
this.curr = this.today;
this.mm = ["January","February","March","April","May","June","July","August","Septemper","October","November","December"];
this.TODO = null;
}//constructor
/**
* 최초 화면 로드시
* @param {Class} TODO To Do List 클래스 받아오기
*/
init(TODO){
//todo 클래스 받아오기
this.TODO = TODO;
//상단날짜, 캘린더 채우기
this.display_all(this.today);
//좌우 버튼에 이벤트 달기
this.$head.addEventListener('click',this.on_click_prev_next);
//tbody에 이벤트 달기
this.$tbody.addEventListener('click',this.on_click_btn_date);
}//init
/**
* 캘린더, 상단 날짜 전체 변경
* @param {String}dateString yyyy-mm-dd
*/
display_all(dataString){
//tbody reset
this.reset_tbody();
//상단 날짜 표기
this.display_cd_head(dataString);
//달력 채우기
this.draw_calendar(dataString);
}//display_all
/** reset tbody */
reset_tbody(){
this.$tbody.innerHTML = '';
}//reset_tbody
/**
* 상단의 날짜 표기
* @param {String}dateString yyyy-mm-dd
*/
display_cd_head(dateString){
const [yyyy,mm] = dateString.split('-');
this.$year.textContent = yyyy;
this.$monthNum.textContent = mm;
this.$monthEng.textContent = this.mm[Number(mm) - 1];
}//display_cd_head
/**
* 📌달력 날짜 채우기
* @param {String} dateString yyyy-mm-dd
* @memo https://jerryjerryjerry.tistory.com/26
*/
draw_calendar(dateString){
const [cy,cm,cd] = dateString.split('-');
const start = new Date(cy,Number(cm)-1,1);
const last = new Date(cy, cm, 0);
let cnt = start.getDay();
let $tr = this.$tbody.insertRow();
//날짜 첫 요일까지 빈칸 삽입
for(let k=0; k<cnt; k++) $tr.insertCell();
//날짜 그리기
for(let i=1; i<=last.getDate(); i++){
cnt++;
const $td = $tr.insertCell();
const dataString = `${cy}-${cm.padStart(2,"0")}-${String(i).padStart(2,"0")}`;
//날짜 버튼 추가
const $btn = document.createElement("BUTTON");
$btn.classList.add('cd-cell');
$btn.textContent = i;
$btn.dataset.dateInfo = dataString;
if( dataString == this.today) $btn.classList.add('today');
$td.appendChild($btn);
//남아있는일,한 일 표시
const thisTodo = this.TODO.data[dataString];
if(thisTodo?.length){
const rmn = thisTodo.filter(obj=>!obj.done).length;
const done = thisTodo.length - rmn;
if(rmn){
const $rmn = document.createElement('DIV');
$rmn.classList.add('cd-td-num');
$rmn.classList.add('rmn');
$rmn.textContent = rmn;
$rmn.title = `${rmn}개의 업무가 남아있습니다.`;
$td.appendChild($rmn);
}
if(done){
const $done = document.createElement('DIV');
$done.classList.add('cd-td-num');
$done.classList.add('done');
$done.textContent = done;
$done.title = rmn ? `${done + rmn}개중 ${done}개 완료!` : `전부 완료!`;
$td.appendChild($done);
}
}//if
//줄바꿈,토요일
if(cnt % 7 == 0){
$tr = this.$tbody.insertRow();
$btn.classList.add('saturday');
}//if
//일요일
if(cnt % 7 == 1) $btn.classList.add('sunday');
}//for
}//draw_calendar
/**
* tbody의 날짜 버튼을 클릭시
* @param {Event}e
*/
on_click_btn_date = (e) =>{
if(e.target.tagName != "BUTTON") return;
//클래스 "on" toggle
const $BTN = e.target;
const $$btn = this.$tbody.querySelectorAll('BUTTON');
$$btn.forEach($btn => $btn.classList.toggle('on', $btn == $BTN));
//to do list에 반영
const dataString = $BTN.dataset.dateInfo;
this.TODO.display_all(dataString);
}//on_click_btn_date
/**
* 캘린더의 표시 수정
*/
change_rmn_done(dataString){
const $btn = this.$tbody.querySelector(`[data-date-info="${dataString}"]`)
if(!$btn) return;
const $cell = $btn.parentElement;
const thisTodo = this.TODO.data[dataString];
const rmn = thisTodo.filter(item => !(item.done)).length;
const done = thisTodo.length - rmn;
const $rmn = $cell.getElementsByClassName('rmn')[0];
const $done = $cell.getElementsByClassName('done')[0];
//남아있는거 처리
if(!rmn){
$rmn && $cell.removeChild($rmn);
}else{
if($rmn){
$rmn.textContent = rmn;
}else{
const $rmnNew = document.createElement('DIV');
$rmnNew.classList.add('cd-td-num');
$rmnNew.classList.add('rmn');
$rmnNew.textContent = rmn;
$cell.appendChild($rmnNew);
if($done) $cell.insertBefore($rmnNew,$done);
}
}//if-rmn
//해결한거 처리
if(!done){
$done && $cell.removeChild($done);
}else{
if($done){
$done.textContent = done;
}else{
const $doneNew = document.createElement('DIV');
$doneNew.classList.add('cd-td-num');
$doneNew.classList.add('done');
$doneNew.textContent = done;
$cell.appendChild($doneNew);
}
}//if-done
}//change_rmn_done
/**
* 달력 바꾸기 (전,후) 버튼 클릭시
*/
on_click_prev_next = e => {
if(e.target.tagName != "BUTTON") return;
const dir = [...e.target.id.matchAll(/(btn\-mm\-)(\w*)/g)][0][2];
const prevNext = dir == "next" ? 1 : -1;
const [yyyy,mm] = this.curr.split('-');
const newDate = new Date(yyyy, Number(mm - 1) + prevNext, 1);
this.curr = return_date_string(newDate);
this.display_all(this.curr);
}//on_click_prev_next
}//class-Calendar
|
cs |
'CSS&JS > ⚡Thinkers' 카테고리의 다른 글
[JS]바닐라 자바스크립트로 slick slider 간단히 구현해보기 (0) | 2023.01.20 |
---|---|
[JS]Draggable & Resize 모달창 (0) | 2022.12.19 |
[CSS,JS] 3d-carousel slider(캐러샐 슬라이더) (0) | 2022.10.24 |
[CSS,JS] 타임 슬라이더 (0) | 2022.10.11 |
[vanilla JS] 바닐라 자바스크립트로 무한 슬라이더 만들기. v1.0 (0) | 2022.10.11 |