클론코딩해보기/TodoList

[Todo list] 04. 수정 기능

js0616 2024. 8. 18. 06:23

목록 구현

목록은 총 세가지필드가 존재한다. 할일 / 진행중 / 종료
할일 / 진행중 에 등록된 티켓은 수정 및 삭제가 가능하다.
할일 / 진행중 / 종료 로 서로간에 이동이 가능하다.
각 티켓에는 제목, 내용, 제출 또는 수정날짜가 나타나야 한다.
수정버튼을 눌렀을때 수정모달이 띄워져야 한다.


보기 불편해서 임시로 html 에서 간단하게 css 좀 주고 

    <style>
        .itemList >div {
            margin: 10px;
            padding: 10px;
            border: 1px solid;
            width : 400px
        }
    </style>

 

각 Item 은 진행 상태 - 할일 /진행중/종료 를 가지며 

-> 이는 변경이 가능해야됨 wait / going / end

 

state.item 에 workState 항목을 넣어서 해당 일의 상황을 표시한다.

wait 을 기본값으로 설정 

        {
          seq: 1,
          title: '선배!!',
          contents: '마라탕 사주세요',
          date: '2024.08.17. 02:43',
         workState: 'wait',
        },

 

// src/components/Item.js
import Component from '../core/Component.js';

export default class Item extends Component {
  template() {
    const { item } = this.props;

    console.log('Item', this.props);
    return `
      <div class='title'>제목 : ${item.title} </div>
      <div class='contents'>내용 : ${item.contents} </div>
      <div class='date'>작성시간 : ${item.date} </div>
      <select name="workState" class="workState">
        <option value="wait">할일</option>
        <option value="going">진행중</option>
        <option value="end">종료</option>
      </select>
      <button>수정</button>
      <button>삭제</button>
    `;
  }
  setEvent() {}
}

 

select option은 일단 나중에 진행하기로 하고, 


기존 내용을 update 할 수 있는 ItemUpdate.js 구현

 

ItemList 에서 map 진행시 해당 Item 이 '기본' 상태인지, '수정' 상태 인지에 따라서 <Item.js>  or <ItemUpdate.js> 를 생성

 

'수정' 버튼을 클릭  

  •  <ItemUpdate.js> 컴포넌트를 렌더링
  •  Input 창을 제공하여 수정 내용을 입력

 

'수정완료' 버튼을 클릭

  • 수정된 내용을 저장
  • <Item.js> 컴포넌트를 렌더링

write : false -> 읽기 (기본)

write : true  -> 수정

 


 

전체 코드

 

 

<App.js>

- state.items 에 속성 추가

- updateItem 추가

 
// src/app.js
// state , method 관리

import Component from './core/Component.js';
import ItemList from './components/ItemList.js';
import ItemAdd from './components/ItemAdd.js';

export default class App extends Component {
  // 초기 state
  setup() {
    this.state = {
      items: [
        {
          seq: 1,
          title: '선배!!',
          contents: '마라탕 사주세요',
          date: '2024.08.17. 02:43',
          workState: 'wait',
          write: false,
        },
        {
          seq: 2,
          title: '그럼 혹시..',
          contents: '탕후루도 같이?',
          date: '2024.08.17. 02:45',
          workState: 'wait',
          write: false,
        },
      ],
    };
  }

  // 자식 컴포넌트가 위치할 dom 요소
  template() {
    return `
    <div data-component="itemAdd"> </div>
    <div data-component="itemList" class="itemList"> </div>
    `;
  }

  // mounted에서 자식 컴포넌트를 마운트
  mounted() {
    const { addItem, currentTime, inputFocus, updateItem } = this;

    const $itemList = this.$target.querySelector('[data-component="itemList"]');
    const $itemAdd = this.$target.querySelector('[data-component="itemAdd"]');

    // new 컴포넌트명('DOM'위치 , 전달할 props)
    new ItemList($itemList, {
      items: this.state.items,
      updateItem: updateItem.bind(this),
      currentTime: currentTime.bind(this),
    });
    new ItemAdd($itemAdd, {
      items: this.state.items,
      addItem: addItem.bind(this),
      currentTime: currentTime.bind(this),
      inputFocus: inputFocus.bind(this),
    });
  }

  // 함수 정의

  // 추가하기
  addItem(NewContent) {
    const items = [...this.state.items];
    const seq = Math.max(0, ...items.map(v => v.seq)) + 1;
    const { title, contents, date, workState, write } = NewContent;

    this.setState({
      items: [...items, { seq, title, contents, date, workState, write }],
    });
  }

  // focus 하기
  inputFocus(focusSelector) {
    let newFocus = document.querySelector(focusSelector);
    newFocus.focus();
  }

  // 수정하기
  updateItem(NewContent) {
    const items = [...this.state.items];
    const { seq, title, contents, date, workState, write } = NewContent;

    items.map(item => {
      return item.seq == seq
        ? {
            seq: seq,
            title: title,
            contents: contents,
            date: date,
            workState: workState,
            write: write,
          }
        : item;
    });

    this.setState(items);
    console.log(items);
  }

  currentTime() {
    const now = new Date();

    // 사용자 정의 형식 (예: YYYY-MM-DD HH:MM:SS)
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, '0'); // 월은 0부터 시작하므로 +1
    const day = String(now.getDate()).padStart(2, '0');
    const hours = String(now.getHours()).padStart(2, '0');
    const minutes = String(now.getMinutes()).padStart(2, '0');
    const seconds = String(now.getSeconds()).padStart(2, '0');

    const formattedDate = `${year}.${month}.${day} ${hours}:${minutes}`;

    return formattedDate;
  }
}

 

<ItemList.js>

- write 값에 따라서 <ItemUpdate> or <Item> 컴포넌트 생성

 

// src/components/ItemList.js
import Component from '../core/Component.js';
import Item from './Item.js';
import ItemUpdate from './ItemUpdate.js';

export default class ItemList extends Component {
  template() {
    let ItemDiv = this.props.items.map(item => {
      return `<div data-Item${item.seq} ></div>`;
    });
    return `
      ${ItemDiv.join('')}
    `;
  }
  mounted() {
    const { items, updateItem, currentTime } = this.props;

    // write 상태에 따라 '읽기' or '수정'
    items.map(item => {
      item.write
        ? new ItemUpdate(this.$target.querySelector(`[data-Item${item.seq}]`), {
            item: item,
            updateItem: updateItem,
            currentTime: currentTime,
          })
        : new Item(this.$target.querySelector(`[data-Item${item.seq}]`), {
            item: item,
            updateItem: updateItem,
          });
    });
  }
}

 

<Item.js>

- 수정 / 삭제 버튼 추가 

- workState UI 추가 

- addEvent 추가 : write 상태 변경

// src/components/Item.js
import Component from '../core/Component.js';

export default class Item extends Component {
  template() {
    const { item } = this.props;

    return `
      <div class='title'>제목 : ${item.title} </div>
      <div class='contents'>내용 : ${item.contents} </div>
      <div class='date'>작성시간 : ${item.date} </div>
      <select name="workState" class="workState">
        <option value="wait">할일</option>
        <option value="going">진행중</option>
        <option value="end">종료</option>
      </select>
      <button class='updateBtn'>수정</button>
      <button>삭제</button>
    `;
  }
  setEvent() {
    const { item, updateItem } = this.props;

    this.addEvent('click', '.updateBtn', () => {
      item.write = true;
      updateItem(item);
    });
  }
}

 

 

<ItemUpdate.js> 

- class 명을 title 로 했더니 반영이 안되서 upTitle 로 변경 

- 2개 이상 '수정'을 누르고 아래 컴포넌트를 먼저 '수정완료' 시 맨 위의 컴포넌트 내용으로 저장됨 # 오류1

--> 선택자를 고유하게 만들어서 해결.. 

--> item 의 상위 div 가 data-Item{item.seq} 이므로 이를 선택자에 활용

// src/components/ItemUpdate.js
import Component from '../core/Component.js';

export default class ItemUpdate extends Component {
  template() {
    const { item } = this.props;
    return `
        <div>제목 : <input class='upTitle' value='${item.title}' placeholder="제목" autocomplete="off" /> </div>
        <div>내용 : <input class='upContents' value='${item.contents}' placeholder="내용" autocomplete="off" /> </div>
        <div class='error'></div>
        <button class='upBtn'>수정완료</button>
    `;
  }

  setEvent() {
    const { item, updateItem, currentTime } = this.props;
    this.addEvent('click', '.upBtn', () => {
      let title = document.querySelector(
        `[data-Item${item.seq}] .upTitle`,
      ).value;
      let contents = document.querySelector(
        `[data-Item${item.seq}] .upContents`,
      ).value;
      let error = document.querySelector(`[data-Item${item.seq}] .error`);

      if (title == '' || contents == '') {
        error.innerHTML = '입력 값을 확인해 주세요';
      } else {
        item.title = title;
        item.contents = contents;
        item.write = false;
        item.date = currentTime();
        updateItem(item);
      }
    });
  }
}

 

// src/components/ItemAdd.js
// 입력UI 와 입력기능
import Component from '../core/Component.js';

export default class ItemAdd extends Component {
  template() {
    return `
      <input class='title' name='title' placeholder="제목" autocomplete="off" />
      <input class='contents' name='content' placeholder="내용" autocomplete="off" />
      <button class='addBtn' type='button'>제출</button>
      <div class='error'></div>
    `;
  }

  setEvent() {
    const { addItem, currentTime, inputFocus } = this.props;

    let inputCheck = () => {
      let title = document.querySelector('.title').value;
      let contents = document.querySelector('.contents').value;
      let error = document.querySelector('.error');
      let date = currentTime();

      if (title == '' || contents == '') {
        // error 처리
        error.innerHTML = '입력 값을 확인해 주세요';
      } else {
        // state 추가
        addItem({
          title: title,
          contents: contents,
          date: date,
          workState: 'wait',
          write: false,
        });

        // 추가 후 focus
        inputFocus('.title');
      }
    };

    // 초기 focus
    inputFocus('.title');

    // 클릭 이벤트
    this.addEvent('click', '.addBtn', () => {
      inputCheck();
    });

    // enter키 이벤트
    this.addEvent('keyup', '.title, .contents', e => {
      if (e.key === 'Enter') {
        inputCheck();
      }
    });
  }
}

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .itemList >div {
            margin: 10px;
            padding: 10px;
            border: 1px solid;
            width : 400px
        }
    </style>
</head>
<body>
    <div id="app"></div>
   
    <!-- build 전에 확인 -->
    <script src="./js/main.js" type="module"></script>
</body>
</html>

오류1

- 2개 이상 '수정'을 누르고 아래 컴포넌트를 먼저 '수정완료' 시 맨 위의 컴포넌트 내용으로 저장됨 

--> 선택자를 고유하게 만들어서 해결.. 

 


목록 구현

목록은 총 세가지필드가 존재한다. 할일 / 진행중 / 종료
할일 / 진행중 에 등록된 티켓은 수정 및 삭제가 가능하다.
할일 / 진행중 / 종료 로 서로간에 이동이 가능하다.
각 티켓에는 제목, 내용, 제출 또는 수정날짜가 나타나야 한다.
수정버튼을 눌렀을때 수정모달이 띄워져야 한다.

 

 

만들고 보니까... ' 수정버튼을 눌렀을때 수정모달이 띄워져야 한다. ' 는 내용을 이제 봤는데.. 

 

선택구현

'수정을 클릭하면 모달이 아닌 현재 티켓내에서 입력폼이 나타나고 수정 가능하다.' 

는 기능을 만든셈 치고 

 

모달 기능은 후에 '수정모달' 버튼으로 다시 구현해보겠다. 

 

 

다음으로는 할일/진행중/종료 기능을 완성해야겠다.