data life

[JavaScript] CRUD 기능 구현 본문

Front-end/JavaScript

[JavaScript] CRUD 기능 구현

주술회전목마 2023. 2. 24. 00:05

 json-server 란?

사실 우리가 무엇인가 개발하기 위해서는 백엔드의 구현이 필요합니다. 프론트엔드 개발자들에게 있어서 직접 백엔드 기능을 구현하는 것은 상당한 시간이 걸리기 때문에 짧은 시간에 가짜 API 서버를 구축해줄 도구인 json-sever를 이용해보도록 하겠습니다.

npm을 통해 설치 가능하며 이름과 같이 json 파일 하나로 연습용 서버를 쉽게 구성 할 수 있으나 실제 프로덕션에서 사용하지는 않기 때문에 이 서버를 이용해서 실제 프로젝트를 개발 하면 안된다는 점!!! 유의바랍니다. 

실제 프로젝트 개발 시에는 백엔드 공부를 통해 서버를 직접 준비하거나 Firebase 를 사용해서 구현을 하도록 합시다!

 

json-server 설치

npm install -g json-server

db.json 생성

{
  "todos": [
    {
      "content": "테스트 1",
      "completed": false,
      "id": 1
    },
    {
      "content": "테스트 2",
      "completed": true,
      "id": 2
    },
    {
      "content": "테스트 3",
      "completed": false,
      "id": 3
    },
  ]
}

http://localhost:3000/todos

해당 서버로 들어가면 위의 db.json과 같은 데이터를 조회할 수 있습니다!

 

 

데이터 가져오기

DOMContentLoaded

>> 초기 HTML 문서를 완전히 불러오고 분석했을 때 발생

window.addEventListener("DOMContentLoaded", (event) => {
  console.log("DOM fully loaded and parsed");
});

 

초기 HTML 문서를 완전히 불러온 뒤 위의 할일 목록들의 데이터들을 가져올 getTodos함수를 실행하도록 다음과 같이 작성하였습니다.

const init = () => {
   window.addEventListener("DOMContentLoad", ()=>{
     getTodos()
   })
}
init()

 

Fetch API

- fetch api의 response는 실제 json이 아님 ❌
- 따라서, fetch api에서는 추가 메서드를 호출해 응답 본문을 받을 필요가 있다. (.json())
- axios는 이 과정을 자동으로 해줘서 바로 response를 받을 수 있다.
- body 데이터 타입 = 헤더의 content-type 헤더
var url = 'https://example.com/profile';
var data = {username: 'example'};

fetch(url, {
  method: 'POST', // or 'PUT'
  body: JSON.stringify(data), // data can be `string` or {object}!
  headers:{
    'Content-Type': 'application/json'
  }
}).then(res => res.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));

할일 목록들의 데이터들을 가져올 getTodos함수는 fetch api를 이용하여 작성하였습니다.

const API_URL = 'http://localhost:3000/todos';

const getTodos = () => {
    fetch(API_URL)
      .then((response) => response.json())
      .then((todos) => renderAllTodos(todos))
      .catch((error) => console.error(error))
 }

이때, todos의 목록을 하나하나 랜더링해줄 함수인 renderAllTodos에 대해 알아보도록하겠습니다.

const get = (target) => {
    return document.querySelector(target)
}
const $todos = get('.todos')
const renderAllTodos = (todos) => {
    $todos.innerHTML = '' //초기화
    todos.forEach((item) => {
      const todoElement = createTodoElement(item)
      $todos.appendChild(todoElement)
    })
}

forEach문을 통해 각 데이터들을 class가 item인 div를 생성해주는 createTodoElement함수에 넣어주고,

todos 클래스 div에 각각 연결해주도록 합니다.

다음과 같이 데이터들이 모두 화면에 나타나는 것을 확인할 수 있다!

 

 

데이터(할일) 입력하기

eventListener를 이용하여 입력 데이터를 추가해보도록 구현해보자!

const init = () => {
   window.addEventListener("DOMContentLoad", ()=>{
     getTodos()
   })
   $form.addEventListener("submit", addTodo)
}
init()
const addTodo = (e) => {
    e.preventDefault()
    console.log($todoInput.value)
    const todo = {
      content: $todoInput.value,
      completed: false,
    }
    fetch(API_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(todo),
    })
      .then(getTodos)
      .then(() => {
        $todoInput.value = ''
        $todoInput.focus()
      })
      .error((error) => console.error(error))
}

 

 

체크박스 변경

const init = () => {
   window.addEventListener("DOMContentLoad", ()=>{
     getTodos()
   })
   $form.addEventListener("submit", addTodo)
   $todos.addEventListener('click', toggleTodo)
}
init()

해당 데이터들의 id값을 불러오기

const toggleTodo = (e) => {
    if (e.target.className !== 'todo_checkbox') return
    const $item = e.target.closest('.item')
    const id = $item.dataset.id
    console.log(id)
}

checkbox를 누를때마다 해당 id를 조회할 수 있다. 이를 바탕으로 db.json 데이터에 각 해당하는 completed부분의 false/true를 변경해주어 checkbox의 상태를 변경해줄 수 있다.

const completed = e.target.checked

    fetch(`${API_URL}/${id}`, {
      method: 'PATCH',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ completed }),
    })
      .then(getTodos)
      .catch((error) => console.error(error))
}

💡 이때, 메소드를 PUT이 아닌 PATCH를 적용한 이유

PUT : 자원의 전체 교체, 자원교체 시 모든 필드 필요

       (만약 전체가 아닌 일부만 전달할 경우, 전달한 필드외 모두 null or 초기값 처리됨!!)

PATCH : 자원의 부분 교체, 자원교체시 일부 필드 필요

>> 즉, 우리는 completed 부분만 교체해줄 것이기 때문에 PATCH 메소드를 사용해주도록 하자

 

추가로, createTodoElement 함수에 다음과 같이 삼항연산자 함수를 작성해주어 completed가 true일 경우 checkbox가 체크되도록 아닐 경우 그대로(false) 두도록 함

const isChecked = completed ? 'checked' : ''
<input
  type="checkbox"
  class='todo_checkbox' 
  ${isChecked}
/>

결과는 아래에서 확인!

 

 

데이터(할일) 수정하기

1. 수정버튼 클릭 시, 버튼바뀌기 설정

<div class="content">
              <input
                type="checkbox"
                class='todo_checkbox' 
                ${isChecked}
              />
              <label>${content}</label>
              <input type="text" value="${content}" />
            </div>
            <div class="item_buttons content_buttons">
              <button class="todo_edit_button">
                <i class="far fa-edit"></i>
              </button>
              <button class="todo_remove_button">
                <i class="far fa-trash-alt"></i>
              </button>
            </div>
            <div class="item_buttons edit_buttons">
              <button class="todo_edit_confirm_button">
                <i class="fas fa-check"></i>
              </button>
              <button class="todo_edit_cancel_button">
                <i class="fas fa-times"></i>
              </button>
 </div>

이벤트함수 이용

$todos.addEventListener('click', changeEditMode)

위의 html을 참고하여 (클래스명이 item인 div의 태그들) querySelector로 가져와 변수에 저장해둡니다.

const changeEditMode = (e) => {
    const $item = e.target.closest('.item')
    const $label = $item.querySelector('label')
    const $editInput = $item.querySelector('input[type="text"]')
    const $contentButtons = $item.querySelector('.content_buttons')
    const $editButtons = $item.querySelector('.edit_buttons')
}

그리고 이어서 if문으로 클릭한 클래스명이 해당 클래스와 같을 경우로 나눠 style효과를 적용해줍니다.

- 수정버튼

- 수정취소 버튼

 

display

  • none : 안보임
  • block : 보임
    if (e.target.className === 'todo_edit_button') {
      $label.style.display = 'none'
      $editInput.style.display = 'block'
      $contentButtons.style.display = 'none'
      $editButtons.style.display = 'block'
    }
    if (e.target.className === 'todo_edit_cancel_button') {
      $label.style.display = 'block'
      $editInput.style.display = 'none'
      $contentButtons.style.display = 'block'
      $editButtons.style.display = 'none'
      $editInput.value = $label.innerText
    }

2. 수정내용 저장

앞서 체크박스와 마찬가지로 id를 가져온 후, fetch를 이용하여 editTodo 함수를 만들어줍니다.

+ 추가로 변경된 input값도 동일하게 가져와주면 됩니다

const editTodo = (e) => {
    if (e.target.className !== 'todo_edit_confirm_button') return
    const $item = e.target.closest('.item')
    const id = $item.dataset.id
    const $editInput = $item.querySelector('input[type="text"]')
    const content = $editInput.value

    fetch(`${API_URL}/${id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ content }),
    })
      .then(getTodos)
      .catch((error) => console.error(error))
}

 

📌 수정버튼 클릭 시, 해당 input에 focus 구현 기능을 추가해보자!

$editInput.focus()

>> 해당 input값에 마지막이 아닌 앞쪽에 포커스가 되어버림,,

잠깐의 꼼수를 써보자면,,복붙을 이용해보자!

이를 코드로 구현해보면 다음과 같이 간단함!

이때, value = $editInput.value로 복붙해주기!

$editInput.focus()
$editInput.value = ''
$editInput.value = value

 

데이터(할일) 삭제하기

const removeTodo = (e) => {
    if (e.target.className !== 'todo_remove_button') return
    const $item = e.target.closest('.item')
    const id = $item.dataset.id

    fetch(`${API_URL}/${id}`, {
      method: 'DELETE',
    })
      .then(getTodos)
      .catch((error) => console.error(error))
}

마찬가지로 id를 받아와 fetch 시, DELETE 메소드를 이용하여 데이터들을 삭제시키는 것을 구현해준다.

'Front-end > JavaScript' 카테고리의 다른 글

[JavaScript] 함수  (0) 2023.03.07
[JavaScript] Pagination 구현  (0) 2023.02.24
[JavaScript] 객체 복사  (0) 2023.02.16
[JavaScript] 배열  (0) 2023.02.10
[JavaScript] 객체  (0) 2023.02.10