๐Ÿ”จ ํ”„๋กœ์ ํŠธ ์ผ์ง€

์ •๋ณด ์ „๋‹ฌ๋ณด๋‹จ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ ๊ฒช์€ ์ ๋“ค, ๋Š๋‚€ ์ ๋“ค์„ ๊ธฐ๋กํ•œ ์ผ์ง€

Project: Get Shit Done

๋‚˜๋งŒ์˜ ToDo๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์‹œ์ž‘ํ•œ ํ”„๋กœ์ ํŠธ
Get Shit Done์€ ๊ทธ๋ƒฅ ๋‹ฅ์น˜๊ณ  ํ•ด๋ผ๋Š” ์˜๋ฏธ๋กœ ํ‰์ƒ์‹œ ์Šค์Šค๋กœ ๋‹ค์งํ•˜๋Š” ๋ฌธ์žฅ์ธ๋ฐ, ํˆฌ๋‘๋ฆฌ์ŠคํŠธ์™€ ์–ด์šธ๋ฆฐ๋‹ค ์ƒ๊ฐํ•˜์—ฌ ํ”„๋กœ์ ํŠธ๋ช…์œผ๋กœ ์ •ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์˜ ๊ฐ€์žฅ ํฐ ๋ชฉ์ ๊ณผ ์˜์˜๋Š” ์›นํŒฉ ๋ฐ ๋ฐ”๋ฒจ์„ ํ†ตํ•ด ๊ฐ ๋ชจ๋“ˆ์„ ์›น ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌ์„ฑํ•˜์—ฌ ๋™์ž‘ํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค!!!

๐Ÿคช ์ปดํฌ๋„ŒํŠธ ๋กœ์ง

๋จผ์ € ๋“ค์–ด๊ฐ€๊ธฐ์— ์•ž์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‚˜๋ˆˆ ๋ถ€๋ถ„์„ ๋‹ค์‹œ ๋ณด์ž๋ฉด, ํฌ๊ฒŒ ์•„๋ž˜์™€ ๊ฐ™์ด ๋‚˜๋ˆ„์—ˆ๋‹ค.

  • todoApp.js: ์ „์ฒด ์•ฑ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • todoContainer.js: ์•ฑ ๋‚ด์— ๋ฉ”๋ชจ์žฅ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.(์—ฌ๋Ÿฌ๊ฐœ์˜ ๋ฉ”๋ชจ์žฅ์„ ์ƒ์„ฑํ•˜๊ฒŒ ํ•  ์˜ˆ์ •)
  • todoToolbar.js: ๋ฉ”๋ชจ์— ๋Œ€ํ•œ ์กฐ์ž‘์„ ํ•˜๋Š” ํˆด๋ฐ” ๋ถ€๋ถ„
  • todoNote.js: ํˆด๋ฐ” ์กฐ์ž‘์— ๋”ฐ๋ผ ๋ฉ”๋ชจ๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” input ๋ถ€๋ถ„
  • todoList.js: ๋ฉ”๋ชจ ์ „์ฒด๋ฅผ ๊ฐ์‹ธ๊ณ  ์žˆ๋Š” ๋ถ€๋ถ„
  • todoItem.js: ๋ฉ”๋ชจ ์ปดํฌ๋„ŒํŠธ

์ง€๊ธˆ ๋‹ค์‹œ ์ƒ๊ฐํ•ด๋ณด๋ฉด todoItem์„ ๋‹ด์•„๋‘” todoList๋Š” ๋”ฐ๋กœ ์ปดํฌ๋„ŒํŠธํ™” ์•ˆํ•ด๋„ ๋˜์ง€ ์•Š์•˜์„๊นŒ ์‹ถ๋‹ค.(์ด๋Ÿฐ ๊ฒŒ ๋‹ค ๋ฐฐ์šฐ๋Š” ๊ฑฐ๋‹ˆ๊นŒ ๐Ÿคช)

๐Ÿงญ todoApp

์—ฌ๊ธฐ์„  ์ฒ˜์Œ์— ํฌ๊ฒŒ ์ปจํ…Œ์ด๋„ˆ ์ƒ์„ฑ ๋ฒ„ํŠผ์„ ๋‘๊ณ  ํด๋ฆญ์‹œ ๋ฉ”๋ชจ์žฅ์„ ๋งŒ๋“ค์–ด์ง€๋Š” ์‹์œผ๋กœ ๊ตฌ์„ฑํ•˜์˜€๋‹ค.
์Šคํƒ€์ผ๋ง๊นŒ์ง€ ๋‹ค๋ฃจ๊ธฐ์—” ์–‘์ด ๋„ˆ๋ฌด ๋งŽ์œผ๋ฏ€๋กœ ํŒจ์Šคํ•˜๊ณ  ์ „๋ฐ˜์ ์ธ ์ฝ”๋“œ ๊ตฌ์กฐ๋งŒ ๋ณด์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

<header class="header">
  <span>Just Do it!</span>
  <span class="header__menu">Menu</span>
</header>
<main class="main">
  <div class="create-container-button">+ Add Container</div>
</main>
connectedCallback() {
    // 1. create-container-button ํด๋ฆญ ์‹œ ์ปจํ…Œ์ด๋„ˆ ์ƒ์„ฑ
    const $createContainerButton = this.shadowRoot.querySelector(
      '.create-container-button'
    );
    const $appMain = this.shadowRoot.querySelector('.main');
    $createContainerButton.addEventListener('click', (event) => {
      const containerTitleInput = prompt(
        '๐Ÿ’ฅ (๋„์–ด์“ฐ๊ธฐ ์—†์ด)๋ฉ”๋ชจ์žฅ ์ด๋ฆ„์„ ์ ์–ด์ฃผ์„ธ์š”.'
      )
        .split(' ')
        .join('');
      if (!containerTitleInput) return;
      const $newContainer = document.createElement('todo-container');
      $newContainer.dataset.containerTitle = containerTitleInput;

      // ์ถ”ํ›„ ๋งŒ๋“ค todo-container ํƒœ๊ทธ๋ฅผ ๋„ฃ์–ด์ค€๋‹ค
      $appMain.insertBefore($newContainer, $createContainerButton);

      // 1-1. ๋ฉ”๋ชจ ์ปจํ…Œ์ด๋„ˆ 5๊ฐœ ์ด์ƒ ์‹œ, Add column ์‚ญ์ œ
      if ($appMain.children.length >= 6) {
        $createContainerButton.style.display = 'none';
      }
    });
  }

todoApp

์œ„์™€ ๊ฐ™์ด + Add container๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋ฉ”๋ชจ์žฅ ์ด๋ฆ„์„ ๋ฌผ์–ด๋ณด๋Š” ๋ชจ๋‹ฌ ์ฐฝ์ด ๋‚˜์˜ค๊ณ  ๋ฉ”๋ชจ์žฅ์ด ๋งŒ๋“ค์–ด์ง„๋‹ค.

๐Ÿงญ todoContainer

์—ฌ๊ธฐ์„œ๋Š” ํฌ๊ฒŒ ๋ณต์žกํ•œ ๋กœ์ง์€ ์—†๊ณ  App์—์„œ ๋ฐ›์€ ๋ฐ์ดํ„ฐ ํƒ€์ดํ‹€์„ ๊ฐ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€ ์š”์†Œ๋“ค์— dataset์œผ๋กœ ์ „๋‹ฌํ•ด์ฃผ์—ˆ๋‹ค.(์ถ”ํ›„ ๊ฐ ๋ฉ”๋ชจ์žฅ ๋ณ„ ๊ฐ’์„ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•จ)

connectedCallback() {
  const containerTitle = this.dataset.containerTitle;
  const $toolBar = this.shadowRoot.querySelector('.todo-toolbar');
  $toolBar.dataset.containerTitle = containerTitle;
  const $note = this.shadowRoot.querySelector('.todo-note');
  $note.dataset.containerTitle = containerTitle;
  }
๐Ÿงญ todoToolbar

๋ฉ”๋ชจ์žฅ์„ ์กฐ์ž‘ํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ„๋‹จํ•œ ํˆด๋ฐ”

connectedCallback() {
    // 1. ํˆด๋ฐ” + ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ, note ์—ด๊ธฐ/๋‹ซ๊ธฐ
    const $openNoteButton = this.shadowRoot.querySelector('.open-note-button');
    const $todoNote = this.nextElementSibling;

    $openNoteButton.addEventListener('click', (event) => {
      if ($todoNote.style.display === 'none') {
        $todoNote.style.display = 'flex';
      } else {
        $todoNote.style.display = 'none';
      }
    });

    // 2. X ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ, container ์‚ญ์ œ
    const $deleteContainerButton = this.shadowRoot.querySelector(
      '.delete-container-button'
    );
    $deleteContainerButton.addEventListener('click', (event) => {
      if (!confirm('์ •๋ง ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?')) return;
      const containerTitle = this.dataset.containerTitle;
      const $todoApp = document.querySelector('todo-app');
      const $containerSelected = $todoApp.shadowRoot.querySelector(
        `[
            data-container-title=${containerTitle}
          ]`
      );
      console.log($containerSelected);
      $containerSelected.remove();

      // 2-1. ์ปจํ…Œ์ด๋„ˆ 5๊ฐœ ๋ฏธ๋งŒ์ผ ๋•Œ, add column ์žฌ์ƒ์„ฑ
      const $appMain = $todoApp.shadowRoot.querySelector('.main');
      const $createContainerButton = $appMain.querySelector(
        '.create-container-button'
      );
      if ($createContainerButton.style.display === 'none') {
        $createContainerButton.style.display = 'block';
      }
    });

    // 3. add column ํด๋ฆญ ์‹œ ์ž…๋ ฅ๊ฐ’์„ name์œผ๋กœ ๊ฐ–๋Š” container ์ƒ์„ฑ
    const $containerName = this.shadowRoot.querySelector('.container-name');
    $containerName.textContent = this.dataset.containerTitle;
  }

2๋ฒˆ container๋ฅผ ์ง€์šฐ๋Š” ๋ถ€๋ถ„์„ ์ถ”ํ›„์— ๋ฆฌํŒฉํ† ๋งํ•  ์˜ˆ์ •์ด๋‹ค. shadow DOM์ด ๊ฐ๊ฐ์˜ DOM์„ ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ๋กœ ์กด์žฌํ•˜๊ฒŒ ํ•ด์ฃผ์–ด ๋…๋ฆฝ์ ์œผ๋กœ ์Šคํƒ€์ผ ์ ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์€ ์ข‹์•˜๋‹ค. ํ—Œ๋ฐ, ๋ง ๊ทธ๋Œ€๋กœ ์ปดํฌ๋„ŒํŠธ ํ•˜๋‚˜ํ•˜๋‚˜๊ฐ€ ๋…๋ฆฝ์ ์ธ ๊ฐ์ฒด(OOP ๊ด€์ ์—์„œ)๋กœ ์กด์žฌํ•˜๋‹ค๋ณด๋‹ˆ ํ•˜๋‚˜์˜ html ๊ตฌ์กฐ๋กœ ๋˜์–ด์žˆ์„ ๋•Œ์ฒ˜๋Ÿผ Node api๋ฅผ ํ†ตํ•œ ์ ‘๊ทผ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์ ์ด ๋ถˆํŽธํ–ˆ๋‹ค. ๊ฐ๊ฐ์˜ Shadow DOM์˜ root node๊ฐ€ ๊ณง ๊ทธ ๋ชจ๋“ˆ์˜ ํƒœ๊ทธ ์ฆ‰, custom element์ด๋‹ค๋ณด๋‹ˆ ๊ทธ ์œ„๋กœ ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด dataset์„ ์ด์šฉํ•˜์—ฌ ์œ„์—์„œ๋ถ€ํ„ฐ document๋กœ ์ ‘๊ทผํ•ด์•ผํ–ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์ ‘๊ทผํ•˜๋Š” ๊ฒŒ ๋ญ”๊ฐ€ ๊น”๋”ํ•œ ๋ฐฉ๋ฒ•์€ ์•„๋‹ˆ๋ผ, ์ถ”ํ›„์— ์ข€๋” ์ฐพ์•„๋ด์•ผํ•  ๊ฒƒ ๊ฐ™๋‹ค.

3ํŽธ์—์„œ ๊ณ„์†โ€ฆ