GetShitDone-4(final)
๐จ ํ๋ก์ ํธ ์ผ์ง
์ ๋ณด ์ ๋ฌ๋ณด๋จ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉฐ ๊ฒช์ ์ ๋ค, ๋๋ ์ ๋ค์ ๊ธฐ๋กํ ์ผ์ง
Project
: Get Shit Done
๋๋ง์ ToDo๋ฆฌ์คํธ๋ฅผ ๋ง๋ค๊ธฐ ์ํด ์์ํ ํ๋ก์ ํธ
Get Shit Done
์ ๊ทธ๋ฅ ๋ฅ์น๊ณ ํด
๋ผ๋ ์๋ฏธ๋ก ํ์์ ์ค์ค๋ก ๋ค์งํ๋ ๋ฌธ์ฅ์ธ๋ฐ, ํฌ๋๋ฆฌ์คํธ์ ์ด์ธ๋ฆฐ๋ค ์๊ฐํ์ฌ ํ๋ก์ ํธ๋ช
์ผ๋ก ์ ํ๊ฒ ๋์๋ค.
์ด๋ฒ ํ๋ก์ ํธ์ ๊ฐ์ฅ ํฐ ๋ชฉ์ ๊ณผ ์์๋ ์นํฉ ๋ฐ ๋ฐ๋ฒจ์ ํตํด ๊ฐ ๋ชจ๋์ ์น ์ปดํฌ๋ํธ
๋ก ๊ตฌ์ฑํ์ฌ ๋์ํ๊ฒ ํ๋ ๊ฒ์ด๋ค!!!
๐คฎ drag & drop
๋๋์ด ๋๋๊ทธ ์ค ๋๋์ด๋ค.
์์งํ ํ๋ก์ ํธ ์ฒ์ ์์ ๋, ์น ์ปดํฌ๋ํธ์ ๋ํด ์ดํดํ๋ ค๊ณ ๋
ธ๋ ฅํ ๋๋ง ํด๋ โ๋๋๊ทธ ์ค ๋๋์ ๊ตฌํํ๊ธฐ ํ๋ค๊ฒ ๋คโ ์๊ฐํ๋ค.
์ฒ์์ ์์ API์์ด ๊ตฌํ์ ํด๋ณด๋ คํ์๋, ๋ญ๊ฐ ๊ทธ ๋๋์ ์ข ์๊ฒ ๋๋ฐโฆ ๊ณ์ ์๋ํด๋ณด๋ค๊ฐ ๊น๋ํ๊ฒ API๋ฅผ ์ด์ฉํ์ฌ ๊ตฌํํด๋ณด์ ๋ง์ ๋จน์๋ค.
๊ทผ๋ฐ ์ฌ๊ฑธ, API๋ฅผ ํตํด ๊ตฌํํ๋ ๊ฒ๋ ์ฌ์ด ์ผ์ด ์๋์๋โฆ!
์ด ๋ชจ๋ ์ด๋ ค์์ ์ํ์ Shadow DOM ์์์ด์ใทโฆ(๋ฌผ๋ก ์ปดํฌ๋ํธ๋ฅผ ์ผ๋ง๋ ์ ๋๋๊ณ ์ญํ ์ ๋ถ์ฌํ๋ ๊ฒ ์์คํ์ง ์ ์ ์์์ง๋ง ใ )
๋จผ์ ๋๋๊ทธ ์ค ๋๋ API๋ฅผ ํตํด ๊ธฐ๋ฅ์ ๊ตฌํํ ๋๋, ๊ฐ๊ฐ์ ์ปดํฌ๋ํธ๊ฐ ๋
๋ฆฝ์ ์ธ DOM์ ๊ฐ์ ธ๋ฒ๋ฆฌ๋ ๋ฐ๋์ drag ํน์ drop ์ด๋ฒคํธ ์ ๋๋๊ทธํ ํ๊ฒ๊ณผ ๋๋๋ ๊ณต๊ฐ์ ํ๊ฒํ๋ ๊ฒ ๋ฒ๊ฑฐ๋ก์ ๋ค.
๐ถ๏ธ ๋งค์ฐ๋งค์ฐ ์ง์ ๋ถํ ์ฝ๋๋ค์ด ๋ฑ์ฅํ๋ ์ฃผ์
1. item(memo)๊ฐ ์๋ ๋ฉ๋ชจ์ฅ์ผ๋ก drag & drop
// 1) ์์ดํ
์ฆ, ๋ฉ๋ชจ๊ฐ ์กด์ฌํ๋ ๋ฉ๋ชจ์ฅ๋ผ๋ฆฌ ๋๋๊ทธ ์ค ๋๋
// todoItem.js
this.setAttribute('draggable', 'true');
// ๋๋๊ทธ ์์ ์
this.addEventListener('dragstart', (event) => {
const containerTitle = event.target.dataset.containerTitle;
const $todoApp = document.querySelector('todo-app');
const $targetContainer = $todoApp.shadowRoot.querySelector(
`[data-container-title=${containerTitle}]`,
);
const $targetList = $targetContainer.shadowRoot.querySelector('todo-list');
// โญ already ํด๋์ค๋ ์ ์์๊น? ์๋์์ ๊ทธ ์ด์ ๊ฐ ๋์จ๋ค!!
event.target.classList.add('drag-target', 'already');
$targetList.classList.add('drag-target');
$targetContainer.classList.add('drag-target');
});
๋ฉ๋ชจ์
draggable = true
๋ฅผ ๋ถ์ฌํ๊ณ shadow dom์ผ๋ก ์ธํด ์ถํ drop ์ ํ์ฌ event.target์ ์ ๊ทผํ ์ ์๊ธฐ์ dragstart ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ ์๊ฐ, ํด๋น item ๋ฐ ์์ ์์๋ค(Item, List, Container)์drag-target
์ด๋ผ๋ ํด๋์ค๋ฅผ ๋ํด์ค๋ค.
this.addEventListener('drop', (event) => {
event.preventDefault();
event.stopPropagation();
// dragstart ๋ฐ์ ์, ๋ํด์ค drag-target์ผ๋ก drag์ค์ธ ์์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค.
const $todoApp = document.querySelector('todo-app');
const $targetContainer = $todoApp.shadowRoot.querySelector('.drag-target');
const $targetList = $targetContainer.shadowRoot.querySelector('.drag-target');
const $targetItem = $targetList.shadowRoot.querySelector('.drag-target');
// ๋๋๊ทธ ํ์ผ๋ ์ง์ด๋ค.
$targetItem.remove();
// ์ด ์๋๋ ๋๋ํ๊ฒ ๋ Item(๋ฉ๋ชจ ์์)์ ์ ๋ฐ ์ค์ ์๋ถ๋ถ์ด๋ฉด ์๋ก, ์๋ซ๋ถ๋ถ์ด๋ฉด ์๋๋ก drop๋๋๋ก
// drop๋๋ ๋
ธ๋์ ์ค์์ clientY์ drop ์ด๋ฒคํธ ๋ฐ์ ์ง์ ์ clientY๋ฅผ ๋น๊ตํ์ฌ
// ๊ตฌ๋ถํด์ ๋๋์ ํด์ฃผ์๋ค.
const dropTargetClientTop = event.target.getBoundingClientRect().top;
const dropTargetClientBottom = event.target.getBoundingClientRect().bottom;
const dropTargetClientMiddle = (dropTargetClientTop + dropTargetClientBottom) / 2;
if (event.clientY < dropTargetClientMiddle) {
event.target.parentNode.insertBefore($targetItem, event.target);
} else {
event.target.parentNode.insertBefore($targetItem, event.target.nextElementSibling);
}
$targetItem.dataset.containerTitle = event.target.dataset.containerTitle;
// drag & drop ํ ๊ฐ๊ฐ์ ๋ฉ๋ชจ์ฅ์์ -1, +1 count ํด์ฃผ๊ธฐ
const $targetToolbar = $targetContainer.shadowRoot.querySelector('todo-toolbar');
const $noteCountBefore = $targetToolbar.shadowRoot.querySelector('.count-item');
$noteCountBefore.textContent = +$noteCountBefore.textContent - 1;
const $noteCountAfter = document
.querySelector('todo-app')
.shadowRoot.querySelector(
`[
data-container-title=${event.target.dataset.containerTitle}
]`,
)
.shadowRoot.querySelector('todo-toolbar')
.shadowRoot.querySelector('.count-item');
$noteCountAfter.textContent = +$noteCountAfter.textContent + 1;
// drag & drop ๊ธฐ๋ก ๋จ๊ฒจ์ฃผ๊ธฐ
const $record = document.createElement('div');
$record.className = 'record';
$record.innerHTML = `<span class="record-important">@Jayden</span> moved <span class="record-item">${$targetItem.dataset.itemTitle}</span> to <span class="record-container">${event.target.dataset.containerTitle}</span> from <span class="record-container">${$targetContainer.dataset.containerTitle}</span>`;
const now = Date.now();
$record.dataset.timeMakeNote = now;
activityLog.push($record);
// drop ํ drag target์ ์ถ๊ฐํ๋ ํด๋์ค๋ ์ง์์ค๋ค.
$targetContainer.classList.remove('drag-target');
$targetList.classList.remove('drag-target');
$targetItem.classList.remove('drag-target');
});
์ผ๋จ drag ๋์๊ณผ drop ๋์์ ์ ๊ทผํ๋ ๊ฒ ๋งค์ฐ ์ง์ ๋ถํ์ง๋ง, ๋ฉ๋ชจ์ฅ์ ๋ฉ๋ชจ๋ค์ด ์กด์ฌํ ๋ ๋๋๊ทธ ์ค ๋๋์ ๊ตฌํํ์๋ค.
ํ์ , ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. drag & drop ํ ์ฎ๊ฒจ์ง ๋ฉ๋ชจ๋ฅผ ์ญ์ , ์์ ๊ธฐ๋ฅ์ ์คํํด๋ณด๋ ํด๋น ์ด๋ฒคํธ๊ฐ 2๋ฒ ๋ฐ์ํ๋ ๊ฒ์ด๋ค.
์ฒ์์ ๋ญ๊ฐ ๋ฌธ์ ์ง..? ํ๋ค๊ฐ ์๊ณ ๋ณด๋ drag ํ dropํ๊ฒ ๋๋ฉด ํด๋น todo-item ํ๊ทธ๊ฐ ์ง์์ก๋ค๊ฐ ๋ค์ ์์ฑ๋๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์
์น ์ปดํฌ๋ํธ์์ customElement๊ฐ DOM์ ์ถ๊ฐ๋ ๋ ์คํ๋๋ ๋ฉ์๋์ธ connectedCallback์ด ์คํ๋๊ฒ ๋๊ณ ๊ทธ์ ๋ฐ๋ผ, addEventListener๊ฐ ํ ๋ฒ ๋ ์คํ๋๋ ๊ฒ์ด์๋ค.
์ด๋ฅผ ํด๊ฒฐํ ์ง๊ด์ ์ธ(?) ๋จ์ํ ๋ฐฉ๋ฒ์ disconnectedCallback์์ removeEventListener()๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด์๋ค.
ํ๋, ์ด์ ์ฝ๋๋ฅผ ๋ณด๋ฉด ์๊ฒ ์ง๋ง ์์์ ๋ถ์ธ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ค์ด ๋๋ฌด ์ง์ ๋ถํ๊ณ ๋ณต์กํ๋ค.
๊ทธ๋ฌ๋ค ๋ฒ๋ฉ ๋ ์ค๋ฅธ ์์ด๋์ด! drag & drop์ ํ ์์์ ๋ํด์๋ง ๋ฐ์ํ๋ ํ์์ด๋, ํด๋น ์์๋ค์ ๊ตฌ๋ถํ๊ณ connectedCallback์ early return์ ์ฃผ๋ฉด ์ด๋จ๊น? ๋ฐ๋ก ์ฝ๋๋ฅผ ๋ณด์.
// dragstart ์ด๋ฒคํธ ์ already๋ผ๋ ํด๋์ค๋ฅผ ๊ฐ์ด ์ถ๊ฐํด์ฃผ์๋ค.
event.target.classList.add('drag-target', 'already');
connectedCallback() {
// drag & drop๋ ํ๊ทธ๋ .already๊ฐ ์กด์ฌํ๊ธฐ์ ๋ฐ๋ก return๋๋ค.
if (this.className) return;
}
ํฌ์ผโฆ ์กฐ์กํ์ง๋ง ๋ญ๊ฐ ์ ๋ง ๊น๋ํ๊ฒ ์ฝ๋ ๋ช์ค๋ก ํด๊ฒฐํด์ ๊ธฐ๋ถ์ด ์ข์๋คใ ใ ใ ใ ใ
2. item(memo)๊ฐ ์๋ ๋น ๋ฉ๋ชจ์ฅ์ผ๋ก drag & drop
// todoList.js
connectedCallback() {
this.addEventListener('dragover', (event) => {
event.preventDefault();
});
this.addEventListener('drop', (event) => {
event.preventDefault();
const $todoApp = document.querySelector('todo-app');
const $targetContainer =
$todoApp.shadowRoot.querySelector('.drag-target');
const $targetList =
$targetContainer.shadowRoot.querySelector('todo-list');
const $targetItem = $targetList.shadowRoot.querySelector('.drag-target');
$targetItem.remove();
event.target.shadowRoot.appendChild($targetItem);
$targetItem.dataset.containerTitle = event.target.dataset.containerTitle;
const $targetToolbar =
$targetContainer.shadowRoot.querySelector('todo-toolbar');
const $noteCountBefore =
$targetToolbar.shadowRoot.querySelector('.count-item');
$noteCountBefore.textContent = +$noteCountBefore.textContent - 1;
const $noteCountAfter =
event.target.previousElementSibling.previousElementSibling.shadowRoot.querySelector(
'.count-item'
);
$noteCountAfter.textContent = +$noteCountAfter.textContent + 1;
const $record = document.createElement('div');
$record.className = 'record';
$record.innerHTML = `<span class="record-important">@Jayden</span> moved <span class="record-item">${$targetItem.dataset.itemTitle}</span> to <span class="record-container">${event.target.dataset.containerTitle}</span> from <span class="record-container">${$targetContainer.dataset.containerTitle}</span>`;
const now = Date.now();
$record.dataset.timeMakeNote = now;
activityLog.push($record);
$targetContainer.classList.remove('drag-target');
$targetList.classList.remove('drag-target');
$targetItem.classList.remove('drag-target');
});
}
์ด๋ฒ์ ๋น ๊ณต๊ฐ(Item๋ค์ด ๋ค์ด๊ฐ List)์ drop ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ๋ถ์ด๊ณ ํ๊ฒ์ ์ฐพ๋ ๊ฒฝ๋ก(?)๋ง ์กฐ๊ธ ๋ฌ๋ ค์ก์ ๋ฟ ์์ ๊ฑฐ์ ๋ก์ง์ ๋์ผํ๋ค!
๐ ํ๋ก์ ํธ ํ๊ณ
๊ทธ๋์ html, css, js๋ก๋ง ๊ฐ๋จํ๊ฒ ์ด๋ค์ง ํ๋ก์ ํธ๋ง ์งํํ๋ค๊ฐ ๋ชจ๋, ์ปดํฌ๋ํธ ๋ฑ์ ๊ฐ๋
์ ๋์
ํ๋ฉด์ ์๋ก์ด ๋์ ์ป๊ฒ ๋ ๊ฒ ๊ฐ๋ค.
์ ๋ชจ๋์ ์ฌ์ฉํ๋์ง, ์ปดํฌ๋ํธ๋ ๋ญ๊ณ ์น ์ปดํฌ๋ํธ๋ ์ ์กด์ฌํ๋์ง๋ถํฐ ๋ ๋์๊ฐ์ ์ด๋ ๊ฒ ๊ธฐ๋ฅ ๋ณ ํน์ UI์ ๋ฐ๋ผ ๊ตฌ๋ถ์ง์ด์ ์ ํ์ ๋ง๋๋ ๊ฒ ๋ฌด์จ ์๋ฏธ๊ฐ ์๋์ง์ ๋ํด์ ์๊ฐํด๋ณผ ์ ์์๋ค. ์์ฐ์ค๋ฝ๊ฒ ๊ฐ์ฒด์งํฅ์ ๋ํ ๊ฐ๋
๋ ์ฐพ์๋ณด๊ฒ ๋๊ณ ์์ง์ ์ ๋ฆฌํด์ผํ ๊ฒ ์ฐ๋๋ฏธ์ง๋ง ์ ๋ง ๊ทธ๋์์ ๋ณผ ์ ์์๋ ์ธ์์ ์๊ฒ ๋ ๊ฒ ๊ฐ์์ ๋ฟ๋ฏํ๋ค.
๋, ์ฝ๋ ์์ฒด๋ ๋๋ฌด๋ ์ง์ ๋ถํ์ง๋ง ๋ชฉํํ๋ ๊ธฐ๋ฅ๋ค์ ๋๊น์ง ๊ตฌํํ๋ค๋ ์ ์์ ๋ ์์ ์ ์นญ์ฐฌํด์ฃผ๊ณ ์ถ๋ค.
๋ฐฐ์ธ ๊ฒ ๋ง๋ค๋ ๊ฑด ๋๋ก๋ ์คํธ๋ ์ค์ด๊ณ ๊ณ ํต์ผ ์ ์๊ฒ ์ง๋ง, ๋์๊ฐ ๊ฒ์ด ๋ง๋ค๋ ์๋ฏธ์ด๊ธฐ์ ์ค๋ ๋ ์ผ์ด๊ธฐ๋ ํ ๊ฒ ๊ฐ๋ค.
ํํ ๋์๋ ์ํ, ์นํฐ ๋ฑ์์ ๋ช
์์ ์์ง ์๋ณธ ์ฌ๋๋ค์๊ฒ ์... ๋๋ฌด ๋ถ๋ฝ๋ค. ๊ทธ ๋ช
์ ์๋ณธ ๋ ์ฌ๊ณ ์ถ๋ค.
๋ผ๋ ํํ์ ์ด๋ค.
๋๋ก๋ ์์ง ๋ชจ๋ฅด๋ ๊ฒ ๋๋ฌด๋ ๋ง์ ์กฐ๋ฐ์ฌ์ด ๋์ง๋ง, ์ด๋ ๊ฒ ๋ฐฐ์ธ ๊ฒ ๋ง์ ์ง๊ธ์ด ์ฐธ ์์คํ๊ณ ์ด๋ฐ ์๊ฐ์ ํ๊ณ ์๋ ๋ด ์์ ์ ๋ ์์ํ๊ณ ์ถ๋ค.