연습장

[Todo list] 03. 리스트 추가하기 본문

클론코딩해보기/TodoList

[Todo list] 03. 리스트 추가하기

js0616 2024. 8. 18. 00:49

필수구현

입력폼 구현

입력에는 제목과 내용을 입력할 수 있다.
둘중 하나라도 값이 없는경우 사용자에게 알려줘야 한다.
alert 또는 입력창 하단에 문구 노출 방식
첫 페이지가 렌더링 되었을때 제목에 포커스가 맞춰져야 한다.
제출은 키보드의 Enter 를 누르거나 입력 버튼을 누르면 제출이 되어야 한다.
제출 후에는 입력값들이 초기화 되어야 한다.
제출 후 할일 목록에 티켓이 생성되고 티켓에는 제목, 내용, 제출 시간이 표시되어야 한다.


ItemAdd.js : 내용 추가기능

 


method 전달 확인

 

// 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',
        },
        {
          seq: 2,
          title: '그럼 혹시..',
          contents: '탕후루도 같이?',
          date: '2024.08.17. 02:45',
        },
      ],
    };
  }

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

  // mounted에서 자식 컴포넌트를 마운트
  mounted() {
    const { addItem } = 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,
    });
    new ItemAdd($itemAdd, {
      items: this.state.items,
      addItem: addItem,
      addItemthis: addItem.bind(this),
    });
  }

  // 함수 정의
  addItem(NewContent) {
    console.log('addItem:', this);
  }
}

 

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

export default class ItemAdd extends Component {
  template() {
    this.props.addItem();
    this.props.addItemthis();
  }

  setEvent() {}
}

 

 

입력받은 내용으로 state 를 수정하는 함수 addItem 을 App.js 에서 생성하고 자식 컴포넌트인 ItemAdd 에 props 로 전달한다. 

 

bind(this) 를 사용하는 이유는 this 메서드가 확실하게 App.js 를 가리키기 위해서 사용하며,

bind(this) 를 사용하지 않는다면 호출 되는 상황에 따라서 다른 환경을 가리키게 된다. 


ItemAdd.js 기능 구현

메소드가 자식 컴포넌트에 전달되는것을 확인했으니.. 실제 기능을 구현해보자.

 

// 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="제목" />
        <input class='contents' name='content' placeholder="내용" />
        <button class='addBtn' type='button'>제출</button>
    `;
  }

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

    this.addEvent('click', '.addBtn', () => {
      console.log('addBtn_click');
      let title = document.querySelector('.title').value;
      let contents = document.querySelector('.contents').value;
      console.log(title, contents);
      addItem({ title: title, contents: contents });
    });
  }
}

 

'addBtn' 클릭시 각 input 에 접근하여 value 를 가져와서 App.js 에서 가져온 addItem 에게 입력받은 값을 인수로 넘겨준다.

 

그러면 App.js 의 addItem에 정의된 setState 기능으로 state 를 설정하게 되고 

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

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

 

core의 Component.js 에서 state 변경시 추가 하고 render 를 진행하게 된다. 

  setState(newState) {
    // 상태 업데이트
    this.state = { ...this.state, ...newState };
    this.render();
  }

 

해당 내용이 ItemList 에서 보여지게 된다. 


currentTime()

시간을 알려줄 함수를 App.js 에서 생성

Date 객체를 이용해서 원하는 format 으로 날짜를 표시 가능

  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;
  }

 

해당 함수를 ItemAdd 에게 props 로 전달하며, 

ItemAdd 에서 날짜를 추가하여 addItem 함수에게 전달

    new ItemAdd($itemAdd, {
      items: this.state.items,
      addItem: addItem.bind(this),
      currentTime: currentTime.bind(this),
    });

 

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

    this.addEvent('click', '.addBtn', () => {
      let title = document.querySelector('.title').value;
      let contents = document.querySelector('.contents').value;
      let date = currentTime();
      addItem({ title: title, contents: contents, date: date });
    });
  }


 

필수구현

입력폼 구현

입력에는 제목과 내용을 입력할 수 있다.
둘중 하나라도 값이 없는경우 사용자에게 알려줘야 한다. -> alert 또는 입력창 하단에 문구 노출 방식
첫 페이지가 렌더링 되었을때 제목에 포커스가 맞춰져야 한다.
제출은 키보드의 Enter 를 누르거나 입력 버튼을 누르면 제출이 되어야 한다.
제출 후에는 입력값들이 초기화 되어야 한다. -> 일단은 비워지긴 하는데
제출 후 할일 목록에 티켓이 생성되고 티켓에는 제목, 내용, 제출 시간이 표시되어야 한다.

 


'제출은 키보드의 Enter 를 누르거나'

-> keyup 이나 keydown 활용

    this.addEvent('keyup', '.title, .contents', e => {
      if (e.key === 'Enter') {
        let title = document.querySelector('.title').value;
        let contents = document.querySelector('.contents').value;
        let date = currentTime();
        addItem({ title: title, contents: contents, date: date });
      }
    });

 

첫 페이지가 렌더링 되었을때 제목에 포커스가 맞춰져야 한다.

-> focus 함수 생성

-> 처음 렌더링시 포커스

-> addItem 이후 제목에 포커스

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

 

 

둘중 하나라도 값이 없는경우 사용자에게 알려줘야 한다.

-> alert 또는 입력창 하단에 문구 노출 방식

-> 값이 없는 경우 div 에 관련 문구가 나오도록 함

 

기타. 

input 의 autocomplete="off"  : 자동완성 끄기

 

전체 코드

// 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',
        },
        {
          seq: 2,
          title: '그럼 혹시..',
          contents: '탕후루도 같이?',
          date: '2024.08.17. 02:45',
        },
      ],
    };
  }

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

  // mounted에서 자식 컴포넌트를 마운트
  mounted() {
    const { addItem, currentTime, inputFocus } = 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,
    });
    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 } = NewContent;

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

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

  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;
  }
}
// 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 });

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

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

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

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

 

 

예시) 제목이나 내용이 비어있을 경우