GetShitDone-3
๐จ ํ๋ก์ ํธ ์ผ์ง
์ ๋ณด ์ ๋ฌ๋ณด๋จ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉฐ ๊ฒช์ ์ ๋ค, ๋๋ ์ ๋ค์ ๊ธฐ๋กํ ์ผ์ง
Project
: Get Shit Done
๋๋ง์ ToDo๋ฆฌ์คํธ๋ฅผ ๋ง๋ค๊ธฐ ์ํด ์์ํ ํ๋ก์ ํธ
Get Shit Done
์ ๊ทธ๋ฅ ๋ฅ์น๊ณ ํด
๋ผ๋ ์๋ฏธ๋ก ํ์์ ์ค์ค๋ก ๋ค์งํ๋ ๋ฌธ์ฅ์ธ๋ฐ, ํฌ๋๋ฆฌ์คํธ์ ์ด์ธ๋ฆฐ๋ค ์๊ฐํ์ฌ ํ๋ก์ ํธ๋ช
์ผ๋ก ์ ํ๊ฒ ๋์๋ค.
์ด๋ฒ ํ๋ก์ ํธ์ ๊ฐ์ฅ ํฐ ๋ชฉ์ ๊ณผ ์์๋ ์นํฉ ๋ฐ ๋ฐ๋ฒจ์ ํตํด ๊ฐ ๋ชจ๋์ ์น ์ปดํฌ๋ํธ
๋ก ๊ตฌ์ฑํ์ฌ ๋์ํ๊ฒ ํ๋ ๊ฒ์ด๋ค!!!
๐คช ์ปดํฌ๋ํธ ๋ก์ง
๋จผ์ ๋ค์ด๊ฐ๊ธฐ์ ์์ ์ปดํฌ๋ํธ๋ฅผ ๋๋ ๋ถ๋ถ์ ๋ค์ ๋ณด์๋ฉด, ํฌ๊ฒ ์๋์ ๊ฐ์ด ๋๋์๋ค.
- todoApp.js: ์ ์ฒด ์ฑ์ ๋ํ๋ธ๋ค.
- todoContainer.js: ์ฑ ๋ด์ ๋ฉ๋ชจ์ฅ์ ๋ํ๋ธ๋ค.(์ฌ๋ฌ๊ฐ์ ๋ฉ๋ชจ์ฅ์ ์์ฑํ๊ฒ ํ ์์ )
- todoToolbar.js: ๋ฉ๋ชจ์ ๋ํ ์กฐ์์ ํ๋ ํด๋ฐ ๋ถ๋ถ
- todoNote.js: ํด๋ฐ ์กฐ์์ ๋ฐ๋ผ ๋ฉ๋ชจ๋ฅผ ์ ๋ ฅํ ์ ์๋ input ๋ถ๋ถ
- todoList.js: ๋ฉ๋ชจ ์ ์ฒด๋ฅผ ๊ฐ์ธ๊ณ ์๋ ๋ถ๋ถ
- todoItem.js: ๋ฉ๋ชจ ์ปดํฌ๋ํธ
๐งญ todoNote
todoNote๋ item(memo)์ ์ถ๊ฐํ๊ธฐ ์ํ ์ปดํฌ๋ํธ๋ก memo์ ๋ด์ฉ์ ์ ๋ ฅํ๋ ๋ถ๋ถ์ด๋ค.
connectedCallback() {
// 1. Add ๋ฒํผ ํด๋ฆญ ์ ์๋ก์ด ๋ฉ๋ชจ ์์ฑ
const $noteAddButton = this.shadowRoot.querySelector('.note-add-button');
const $noteInput = this.shadowRoot.querySelector('.note-input');
$noteAddButton.addEventListener('click', (event) => {
// 1-1. input์ด ์์ ๋๋ ๋ฉ๋ชจ ์์ฑ ๋ง๊ธฐ
if (!$noteInput.value) return;
const $todoItem = document.createElement('todo-item');
$todoItem.dataset.containerTitle = this.dataset.containerTitle;
$todoItem.dataset.itemTitle = $noteInput.value;
this.nextElementSibling.shadowRoot.append($todoItem);
// 1-2. ๋ฉ๋ชจ ๊ฐฏ์ ๋ํ๊ธฐ
const $noteCount =
this.previousElementSibling.shadowRoot.querySelector('.count-item');
$noteCount.textContent = +$noteCount.textContent + 1;
// 1-3. Add ๋ฒํผ ๋นํ์ฑํ
$noteAddButton.style.opacity = '50%';
// 1-4. Add ์ฌ์ฉ์ ํ๋ ๊ธฐ๋ก
const $record = document.createElement('div');
$record.className = 'record';
$record.innerHTML = `<span class="record-important">@Jayden</span> added <span class="record-important">${$noteInput.value}</span> to <b>${this.dataset.containerTitle}</b>`;
const now = Date.now();
$record.dataset.timeMakeNote = now;
activityLog.push($record);
// 1-5. Add ๋ฒํผ ํด๋ฆญ ํ ์
๋ ฅ์นธ ์ด๊ธฐํ
$noteInput.value = '';
});
// 2. Cancel ๋ฒํผ ํด๋ฆญ ์ todoNote ๋ซ๊ธฐ
const $noteCancelButton = this.shadowRoot.querySelector(
'.note-cancel-button'
);
$noteCancelButton.addEventListener('click', (event) => {
this.style.display = 'none';
// 2-1. ๋ฉ๋ชจ ๋ซ์ ํ ์
๋ ฅ์นธ ์ด๊ธฐํ
$noteInput.value = '';
});
// 3. ๋ฉ๋ชจ ์
๋ ฅ ์ Add ๋ฒํผ ํ์ฑํ
$noteInput.addEventListener('input', (event) => {
$noteAddButton.style.opacity =
event.target.value.length === 0 ? '50%' : '100%';
});
}
-
1-1. input์ ๊ฐ์ด ์๋ ๊ฒฝ์ฐ๋ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ง ์๋๋ก
early return
์ ํ์ฉํ๋ค.- ์ด๋ฐ ํจํด์ ์์น ์์ ๊ฒฝ์ฐ์, ์์ ์ฝ๋๋ฅผ ์คํํ์ง ์๊ธฐ๋๋ฌธ์ ๋ฉ๋ชจ๋ฆฌ์ ์ธ ๋ฉด์ผ๋ก๋ ๊ต์ฅํ ์ ์ฉํ๋ ์์ฃผ ์ฌ์ฉํ๋๋ก ํ์!
-
1-2. ์ด์ ์ฅ์ todoToolbar์ ์๋ memo์ ๊ฐฏ์๋ฅผ ๋ํด์ฃผ๋ ๊ธฐ๋ฅ์ด๋ค. ์ด ๋๋ ๋คํํ(?) toolbar์ note ์ปดํฌ๋ํธ๊ฐ ํ์ ๊ด๊ณ๋ผ ์ ๊ทผํ๊ธฐ ํธํ์ง๋ง, ์ปดํฌ๋ํธ๋ฅผ ๋๋ฌด ๋ณต์กํ๊ฒ ๋๋๋ฉด ๋ ์ ๊ทผํ๊ธฐ ์ด๋ ค์ ์ ๊ฒ ๊ฐ๋ค.
-
1-4. ์ด ๋ถ๋ถ์ ์ต๊ทผ์ ์๋ก ์ถ๊ฐํ ๋ก์ง์ผ๋ก, ๋ฉ๋ชจ์ ๋ํ ์ถ๊ฐ/์์ /์ญ์ ์ ๋ํ ๊ธฐ๋ก์ ๋ณด์ฌ์ฃผ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํจ์ด๋ค.
-
1-5. ์ด๋ฐ ๊ฒ ๋์น๊ธฐ ์ฌ์ด ๊ฒ ๊ฐ๋ค. add ๋ฒํผ์ผ๋ก ๋ฉ๋ชจ๋ฅผ ์์ฑํ๊ณ ๋ค์ input์ ๊ฐ์ ๋น ๊ฐ์ผ๋ก ์ด๊ธฐํํด์ค๋ค. ๋ญ๊ฐ ์ด๋ฐ ๋ถ๋ถ์ด ์ปดํจํฐ์ด๊ธฐ์ ํ๋ํ๋ ๋ช ๋ นํด์ค์ผํ๋ค๊ณ ๋๋ผ๋ ๋ถ๋ถ์ด๋ค.ใ ใ
-
3 . ๋ฉ๋ชจ input์ ๊ฐ์ด ์๊ธฐ๋ฉด ์๊ฐ์ ์ผ๋ก ๋๋ ทํ๊ฒ ํ๊ณ ๋ค์ input๊ฐ์ ๊ธธ์ด๊ฐ 0์ด๋๋ฉด ๋ฐํฌ๋ช ํ๊ฒ ๋ง๋ ๋ค.
๐งญ todoItem
์ถ๊ฐํ ๋ก์ง์ด ์ ์ผ ๋ง์๋, ๋ฉ๋ชจ ๊ทธ ์์ฒด ์ปดํฌ๋ํธ์ธ todoItem!!! ๊ฑฐ๋์ ๋ฏธํ๊ณ ๋ฐ๋ก ์ฝ๋๋ก!!!
connectedCallback() {
// 1. item์ด ์์ฑ๋๊ณ DOM์ ์ถ๊ฐ๋ ๋ ๊ทธ ์์ ํ
์คํธ๋ฅผ ๋ฐ๊ฟ์ฃผ๊ธฐ
this.shadowRoot.querySelector('.item-content').textContent =
this.dataset.itemTitle;
// ์ถ๊ฐ) ๋ชจ๋ฌ ์ฐฝ input value์๋ ๊ฐ์ ๊ฐ ํ ๋น
this.shadowRoot.querySelector('.modal-input').value =
this.dataset.itemTitle;
// 2. item X button ํด๋ฆญ ์ item ์ญ์ ๋ฐ count ๋นผ๊ธฐ
const $itemDeleteButton = this.shadowRoot.querySelector(
'.item-delete-button'
);
$itemDeleteButton.addEventListener('click', (event) => {
// 2-0. ์ปจํ ๋ฉ์์ง false์ธ ๊ฒฝ์ฐ early return
if (!confirm('์ ํํ์ ์นด๋๋ฅผ ์ญ์ ํ์๊ฒ ์ต๋๊น?')) return;
// 2-1. item ์ญ์
this.remove();
// 2-2. count ๋นผ๊ธฐ
// โ
๋ค์: shadow dom ๋ฐ๊นฅ์ผ๋ก ๊ฐ๋ ๋ค๋ฅธ ๋ฃจํธ๋ ์๋์ง ํ์ธ
const containerTitle = this.dataset.containerTitle;
const $noteCount = document
.querySelector('todo-app')
.shadowRoot.querySelector(
`[
data-container-title=${containerTitle}
]`
)
.shadowRoot.querySelector('todo-toolbar')
.shadowRoot.querySelector('.count-item');
$noteCount.textContent = +$noteCount.textContent - 1;
// 2-3. ํ๋ ๊ธฐ๋ก์ ์ญ์ ํ๋ ์ถ๊ฐ
const $record = document.createElement('div');
$record.className = 'record';
$record.innerHTML = `<span class="record-important">@Jayden</span> deleted <span class="record-important">${this.dataset.itemTitle}</span> from <b>${this.dataset.containerTitle}</b>`;
const now = Date.now();
$record.dataset.timeMakeNote = now;
activityLog.push($record);
});
// 3. ์์ดํ
๋๋ธ ํด๋ฆญ ์ ์์ ๋ชจ๋ฌ ์์ฑ
this.addEventListener('dblclick', (event) => {
this.shadowRoot.querySelector('.item-modal-outer').style.display =
'block';
this.shadowRoot.querySelector('.item-modal-inner').style.display = 'flex';
});
// 4. ๋ชจ๋ฌ ์ฐฝ X ํด๋ฆญ ์ ๋ชจ๋ฌ ๋ซ๊ธฐ
const $modalCloseButton = this.shadowRoot.querySelector(
'.modal-close-button'
);
const $modalInner = this.shadowRoot.querySelector('.item-modal-inner');
const $modalOuter = this.shadowRoot.querySelector('.item-modal-outer');
$modalCloseButton.addEventListener('click', (event) => {
$modalInner.style.display = 'none';
$modalOuter.style.display = 'none';
$modalInner.querySelector('.modal-input').value = this.dataset.itemTitle;
});
// 5. ๋ชจ๋ฌ ์ฐฝ save button ํด๋ฆญ ์ ๋ด์ฉ ๋ณ๊ฒฝ
const $modalSaveButton =
this.shadowRoot.querySelector('.modal-save-button');
const $modalInput = this.shadowRoot.querySelector('.modal-input');
const $itemContent = this.shadowRoot.querySelector('.item-content');
$modalSaveButton.addEventListener('click', (event) => {
$modalInner.style.display = 'none';
$modalOuter.style.display = 'none';
// 5-1. ํ๋ ๊ธฐ๋ก์ ์์ ํ๋ ์ถ๊ฐ
const $record = document.createElement('div');
$record.className = 'record';
$record.innerHTML = `<span class="record-important">@Jayden</span> changed <b>${$itemContent.textContent}</b> to <span class="record-important">${$modalInput.value}</span> in <b>${this.dataset.containerTitle}</b>`;
const now = Date.now();
$record.dataset.timeMakeNote = now;
activityLog.push($record);
$itemContent.textContent = $modalInput.value;
});
}
- 2-0. ์ด ๋ถ๋ถ๋ ์์ฃผ ์ ์ฉํ๊ฒ ์ฌ์ฉํ
early return
ํจํด! - 2-2. ๋๋ฅผ ์ ์ผ ๊ณ ์์์ผฐ๋ ๋ ์โฆ ์ปดํฌ๋ํธ ๋ณ๋ก shadow dom์ด ํ์ฑ๋๋ค๋ณด๋, ์ปดํฌ๋ํธ์์ ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ฅผ ์ ํํ๋ ๊ฒ์ด ๋๋ฌด ๊น๋ค๋ก์ ๋ค. ํนํ๋, ์ง๊ธ๊ณผ ๊ฐ์ด ์ปดํฌ๋ํธ๊ฐ ์๋ก ๋ถ๋ชจ, ์์ ๊ด๊ณ์ผ ๋ ์์ ์ปดํฌ๋ํธ์ root ๋ ธ๋๊ฐ ๋ณธ์ธ์ด๋ค๋ณด๋ ๊ทธ ์๋ก ์ ๊ทผํ๋ ๊ฒ ๋ถ๊ฐ๋ฅํ๋ค.(๊ฐ๋ฅํ์ง ์ ๋ง ์ด์ฌํ ๋ค์ก์ง๋งโฆ shadow dom์ ๊ฐ๋ ์ ์๊ฐํด๋ณด๋ฉด ์๋๋ ๊ฒ ๋ง๋คโฆ) ๊ทธ๋์ ๊ฒฐ๊ตญ dataset์ ํตํด ์์์๋ถํฐ ํ๋ํ๋ ์ ๊ทผํ๋ ๋ฐฉ๋ฒ์ ์ ํํ๋ค. ์ถํ์ ์ด ๋ถ๋ถ์ ๋ค๋ฅด๊ฒ ํด๊ฒฐํ ์๋ ์๋์ง ๊ผญ ์๊ณ ์ถ๋ค.
- 3 . ์ด์ ๋ชจ๋ฌ ์ฐฝ์ ๋ง๋๋ ๋ถ๋ถ์ ์ฝ๊ฒ ์ฒ๋ฆฌํ ์ ์๊ฒ ๋์๋ค. ์ญ์ ๊ณ์ ํด๋ด์ผํ๋ค.
์ฝ๋ฉํธ๋ฅผ ๊ตณ์ด ๋จ๊ธฐ์ง ์์ ๋ถ๋ถ๋ค์ ์์ ์ค๋ช ํ ๋ถ๋ถ๋ค์ ํตํด ์ดํดํ ์ ์๊ฑฐ๋ ํฌ๊ฒ ์ด๋ ต์ง ์์๋ ๋ถ๋ถ์ด๋ผ ์๋ตํ์๋ค.(๋ฌผ๋ก ์ด ์ฝ๋๋ค์กฐ์ฐจ๋ ์ง์ ๋ถํ์ง๋งโฆ) ์ผ๋จ ๊ธฐ๋ฅ๊ตฌํ์ ์ด์ ์ ๋ง์ถฐ ํ๋ก์ ํธ๋ฅผ ๋ช๋ฒ ์งํํ๋ค๋ณด๋, ์ ์ ์ฝ๋ ์์ฒด์ ์ ๊ฒฝ์ ์ฐ๊ณ ์ถ์ด์ง๊ณ ๋๋ ์์๊ฐ๊ณ ์ถ์ ๊ฒ ๋ง์์ง๋ค. ์ฌ๋ ์์ฌ์ด ์ฐธ ๊ทธ๋ฐ ๊ฒ ๊ฐ๋ค. ์ฒ์ฒํ ๋ง์ ์ก๊ณ ๋ด๊ฐ ํ ์ ์๋ ์ ์์ ๋ ๋์๊ฐ๋ฉด ๋๋ค. ์ถ๊ฐ๋ก
๋๋๊ทธ ์ค ๋๋
๊ณผ๋คํฌ๋ชจ๋
๋ฅผ ๊ตฌํํด๋ณผ ์์ ์ด๋ค..!