본문 바로가기

DOM

DOM 기초(8) - 댓글 구현(CRUD)

728x90
반응형

1) 댓글 구현(CRUD)

    1-1) update
    1-2) delete

    1-3) try / catch

    1-4) 코드 실습

 

 

 

 

 

1) 댓글 구현(CRUD)

 

// 함수 선언문
function comment1() {

}


// 생성자 함수와 class는 객체 리터럴 문법과 더불어 객체를 찍어낼 때 사용하는 방법임!!

// 생성자 함수
// 생성자 함수의 경우, new 키워드를 써서 새로 객체를 생성할 시 아래와 같이 "this = {}", "return this"가 생략되어 있다고 보면 됨!!
function Comment2() {
    // this = {};
    // return this;
}

// class
class Comment() {
    constructor() {

    }
}

new Comment()

// 생성자 함수와 class의 차이(우리가 class를 사용하는 이유)
// class는 생성 시 new 키워드를 사용하지 않으면 에러를 표시함(객체 생성이라는 용도가 확실함), 또한 class는 호이스팅을 발생시키지 않음!!
// 생성자 함수는 생성 시 new 키워드를 사용하지 않아도 에러를 표시하지 않고 함수 실행이 정상적으로 됨(function 키워드는 용도가 모호함), 또한 생성자 함수는 호이스팅을 발생시킴!!
// 따라서 객체 생성을 위해서 생성자 함수를 사용할 일이 있다면 가급적 class를 사용하는 것이 코드 가독성(class는 용도가 확실함!!) 측면을 고려했을 때 더 좋다!!

 

 

※ 댓글 구현 목적 - "CRUD"를 연습하기 위함!!

1. 댓글을 입력할 수 있다. (Create)

  • 댓글 입력 폼에 내용을 입력한 뒤 'submit' 버튼을 누르면 리스트에 추가되도록 함
  • 만일 입력 폼이 비어있는 상태에서 'submit' 버튼을 누르면 경고 팝업을 띄움(alert or modal)
  • 댓글이 성공적으로 처리되면 입력 폼을 'reset'
 

2. 댓글을 리스트로 볼 수 있다. (Read)

  • 댓글 내용은 '아이디', '댓글내용', '날짜'로 표현함
  • 댓글 리스트는 최신순으로 나타냄(가장 최근의 댓글이 맨 상단으로 올라오도록 한다는 의미)
  • 댓글 총 갯수를 나타냄
  • 댓글 삭제를 위한 삭제 버튼을 생성함


3. 댓글을 수정할 수 있다. (Update)

  • 댓글 리스트에서 내용을 '클릭'하면 input box로 변경됨
  • input value 값의 경우, '클릭한 내용'을 유지함(내용 수정 시 기존의 내용을 일단 띄워주고 거기서 수정을 하도록 한다는 의미)
  • input 내용의 경우, 'enter'를 누르면 수정사항을 저장함


4. 댓글을 삭제할 수 있다. (Delete)

  • 해당 리스트의 삭제 버튼을 '클릭'하면 안내창을 띄우도록 함
  • 안내창에서 확인 버튼을 누르면 삭제를 진행함
  • 안내창에서 취소 버튼을 누르면 아무런 수정을 하지 않음

 


설명

기본적으로 CRUD는 작업 진행 시 'C(Create)'를 가장 먼저 작업함!
Create 작업 시 Read와 연관성이 높음!

데이터를 어떻게 저장하는지까지가 Create의 범위이다!!
즉, 리스트에 어떻게 뿌려질지 생각하면서 변수를 어떻게 넣을지 고려하는 것이 Create의 범위이다!!

 

const commentFrm = document.querySelector("#commentFrm");
const commentList = document.querySelector("#comment-list");
const state = [];

function submitHandler(e) {
e.preventDefault();
console.log("hello world!");
}

commentFrm.addEventListener("submit", submitHandler);

 

form element 안의 input element의 value를 가져오는 것을 목표로 함!!

console.log(e.target.content.value);

 

e.target까지의 내용
e.target.content까지의 내용
e.target.content.value까지의 내용

 

Create
고유한 번호(index 혹은 key 값)를 만들기 위해서 Create을 사용함

 

Read
고유한 번호(index 혹은 key 값)를 리스트에 남기기 위해서 Read를 사용함

 

 

 

1-1) update
Update

Read에서 남겨 놓은 고유한 번호 혹은 흔적(index)을 찾아 수정 작업을 진행하는 것이 Update이다!!

Update, Delete는 해당 index 혹은 고유번호(key 값)만 잘 가져올 수 있으면 됨!!
Update의 경우, 가장 중요한 것은 내가 어떤 '댓글(element)'을 수정할지 알아야 한다는 것이다!!

우리는 'create', 'read'를 구현 시,
'array(배열)'에다가 데이터를 보관하고 있음!!

const students = [];

const person1 = {
	name : 'ingoo',
	age : 32
}

const person2 = {
	name : 'sangbeom',
	age : 30
}

const person3 = {
	name : 'kyeongcheol',
	age : 28
}

students.push(person1);
students.push(person2);
students.push(person3);

students.splice(0, 1); // [person2(sangbeom), person3(kyeongcheol)]

students[0].age = 27;



dataset

<div id='header' data-index='5'></div>

 

const header = document.querySelector('#header');

console.log(header.dataset.index); // ex) 5


댓글 리스트에서
댓글 내용에 클릭 이벤트를 넣고 싶음!!

1. hello world!
2. 댓글 내용에 해당하는 index를 출력

 

 

 

 

1-2) delete

삭제가 제일 쉬움!

( >> CRUD를 순서대로 구현한다는 전제 하에 앞서 Create, Read, Update를 구현하는 과정에서 이미 Delete 구현에 필요한 요소들을 전부 준비했기 때문에 코드를 살짝만 수정하면 금방 완성되기 때문!!)

버튼을 클릭하면 해당 인덱스를 찾아온 뒤
해당하는 element만 splice를 진행한 후
다시 웹 페이지에 그려주면(drawing 함수를 실행하라는 뜻) 끝!!

 


join 메서드(date 부분 날짜 형식 변환 시 사용함!!)

const phone = [010, 7234, 7722];
console.log(phone.join('')); // 72347722

const arr = ["hello","world"];
console.log(arr.join()); // 'hello, world'
console.log(arr.join('')); // 'helloworld'

const arr2 = ["1991", "04", "27"];
console.log(arr2.join('')); // '19910427'
console.log(arr2.join('-')); // '1991-04-27'
console.log(arr2.join('/')); // '1991/04/27'

 

 

 

 

1-3) try / catch

/*
function a() {
  throw "에러 났음";
  // throw "에러 났음"; // throw는 에러를 던져주는 역할을 함(throw가 실행되면 catch에서 throw의 값을 받아서 매개변수 "e(error의 약자)"에 넣어줌)!!
}

try {
  // 코드를 실행할 영역
  a();
  console.log("나 실행됨!"); // 해당 console.log('나 실행됨!');의 코드는 위에서 throw가 실행되어 결과적으로 이것이 try 문을 중단시키고 catch 문을 실행시켜 해당 라인의 코드는 실행되지 않음!!(순서 기억할 것!!)
} catch (e) {
  // 에러가 날 경우, 실행!
  console.log(e);
}
*/

function checkName(value) {
  if (value !== "sangbeom") {
    throw new Error("나 에러났음"); // >> 위의 throw 구문과 달리 에러 객체를 만들어주기에 몇 번째 코드 줄에서 throw 구문을 통해 에러를 던졌는지 확인 가능함!!
  }
  return true;
}

try {
  // 코드를 실행할 영역
  let name = "sangbeom7";
  checkName(name);
  // 아래에 중요한 로직...
} catch (e) {
  // 에러가 날 경우, 실행!
  console.log(e);
}

 

 

 

 

1-4) 코드 실습

 

comment.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="./public/css/comment.css" />
  </head>
  <body>
    <div>
      <ul class="comment">
        <li class="comment-form">
          <form id="commentFrm">
            <h4>
              댓글쓰기
              <span></span>
            </h4>
            <span class="ps_box">
              <input
                type="text"
                placeholder="댓글 내용을 입력해주세요."
                class="int"
                name="content"
              />
            </span>
            <input type="submit" class="btn" value="등록" />
          </form>
        </li>
        <li id="comment-list"></li>
      </ul>
    </div>
    <script src="./public/js/comment.js" type="text/javascript"></script>
  </body>
</html>

 

 

comment.css

* {
  margin: 0;
  padding: 0;
}

ul,
li {
  list-style: none;
}

.comment {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  padding: 30px;
  width: 600px;
  margin: 0 auto;
}

.comment > li {
  margin-top: 20px;
}

.comment > li:nth-child(1) {
  margin: 0px;
}

.comment-row {
  display: flex;
  justify-content: space-between;
  flex-direction: row;
}

.comment-row {
  margin-top: 20px;
  width: 100%;
}

.comment-row > li:nth-child(2) {
  flex-shrink: 0;
  flex-grow: 1;
  padding-left: 25px;
  z-index: 1;
  width: 100%;
}

.comment-row > li:nth-child(2) {
  width: 85px;
}

.comment-form > form {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-between;
}

.comment-form > form > h4 {
  width: 100%;
  margin: 14px 0 14px 0;
}

.comment-content {
  cursor: pointer;
  word-break: break-all;
  padding-right: 25px;
}

.ps_box {
  display: block;
  position: relative;
  width: 80%;
  height: 51px;
  border: solid 1px #dadada;
  padding: 10px 14px 10px 14px;
  background: #fff;
  box-sizing: border-box;
}

.ps_box > input {
  outline: none;
}

.int {
  display: block;
  position: relative;
  width: 100%;
  height: 29px;
  padding-right: 25px;
  line-height: 29px;
  border: none;
  background: #fff;
  font-size: 15px;
  box-sizing: border-box;
  z-index: 10;
}

.btn {
  width: 18%;
  padding: 18px 0 16px;
  text-align: center;
  box-sizing: border-box;
  text-decoration: none;
  border: none;
  background: #333;
  color: #fff;
  font-size: 14px;
}

.comment-delete-btn {
  display: inline-block;
  margin-left: 7px;
  cursor: pointer;
}

.comment-update-input {
  border: none;
  border-bottom: 1px solid #333;
  font-size: 16px;
  color: #666;
  outline: none;
}

 

 

comment.js

const commentFrm = document.querySelector("#commentFrm");
const commentList = document.querySelector("#comment-list");
const state = [];

class Comment {
  constructor(content) {
    this.userid = "web7722";
    this.Content = content;
    // this.date = "2022-11-16";
    this.now = new Date();
    this.updated = false;
  }

  // 대문자와 소문자로 content를 구분한 이유는 이름을 동일하게 설정 시 무한루프를 돌게 되기 때문!!
  set Content(value) {
    if (value.length === 0) {
      // alert("content의 내용을 채워주세요!");
      throw new Error("content의 내용을 채워주세요!");
    }
    this.content = value;
  }

  getToday(separator = "") {
    const date = this.now;
    let mm = date.getMonth() + 1; // "getMonth()"는 0 ~ 11로 각 월을 표시하기에 1 ~ 12월을 표현하기 위해 1을 더해준다!!
    let dd = date.getDate();
    let yyyy = date.getFullYear();
    mm = (mm > 9 ? "" : "0") + mm; // ex) 6 >> "06", 12 >> 12
    dd = (dd > 9 ? "" : "0") + dd;
    const arr = [yyyy, mm, dd];
    return arr.join(separator);
  }
}

function addComment(instance) {
  state.push(instance);
}

function createRow(index) {
  const ul = document.createElement("ul");
  const li1 = document.createElement("li");
  const li2 = document.createElement("li");
  const li3 = document.createElement("li");

  const deleteBtn = document.createElement("span");

  ul.append(li1);
  ul.append(li2);
  ul.append(li3);

  ul.setAttribute("class", "comment-row");
  ul.setAttribute("data-index", index);
  // ul.dataset.index = index; // 바로 위의 "ul.setAttribute("data-index", index);" 코드와 동일함!!
  li1.setAttribute("class", "comment-id");
  li2.setAttribute("class", "comment-content");
  // li2.addEventListener("click", function () {
  //   console.log(index, "hello world!");
  // });
  li3.setAttribute("class", "comment-date");
  deleteBtn.setAttribute("class", "comment-delete-btn");
  deleteBtn.innerHTML = "❌"; // Tip: mac 사용자의 경우, fn 키를 누르면 해당 코드 라인에 있는 "X"와 같은 이모지를 골라서 사용 가능함!!

  /*
  // 여기까지 return 값
  <ul class='comment-row' data-index="1">
    <li class='comment-id'></li>
    <li class='comment-content'></li>
    <li class='comment-date'></li>
  </ul>
  */

  li1.innerHTML = state[index].userid;
  // li2.innerHTML = state[index].content;
  if (state[index].updated) {
    const input = document.createElement("input");
    input.addEventListener("keyup", function (e) {
      // e.keycode는 각 키보드의 키마다 해당하는 키 코드(번호)를 확인할 수 있는데 enter 키는 키 코드가 13이다!!
      if (e.keyCode !== 13) return;
      state[index].content = e.target.value;
      state[index].updated = false;
      drawing();
    });
    input.setAttribute("class", "comment-update-input");
    // <input type="text" class="comment-update-input" />
    input.value = state[index].content;
    li2.append(input);
  } else {
    li2.innerHTML = state[index].content;
    li2.append(deleteBtn);
  }
  li3.innerHTML = state[index].getToday("/");

  /*
  // 여기까지 return 값
  <ul class='comment-row' data-index="1">
    <li class='comment-id'>web7722</li>
    <li class='comment-content'>asdf</li>
    <li class='comment-date'>2022-11-16</li>
  </ul>
  */

  return ul;
}

function drawing() {
  commentList.innerHTML = "";
  for (let i = state.length - 1; i >= 0; i--) {
    const row = createRow(i);
    commentList.append(row);
  }
}

function submitHandler(e) {
  e.preventDefault();

  const form = e.target; // e.target은 form element이기에 이 사실을 보다 직관적으로 볼 수 있도록 form 변수에 담아줌!!
  const value = form.content.value;
  // e.target.content // Element
  // console.log(e.target.content.value); // 출력 확인용

  // const instance = new Comment(value);
  // state.push(instance);
  // console.log(state); // 출력 확인용

  // try 구문은 throw를 통해 에러를 던져주는지 체크하는 역할을 수행하며, 만일 throw를 통해 에러를 던질 시에는 try 문을 거기서 종료하고 catch 문을 실행시킴!!
  try {
    const instance = new Comment(value);
    addComment(instance);
    drawing();
  } catch (error) {
    // (중요!!) catch 문의 "error" 매개변수는 객체이다!!
    alert(error.message);
    console.log(error);
  }
  form.content.focus();
  e.target.reset();
}

function clickHandler(e) {
  /*
  //   if (e.target.className === "comment-content") {
  //     // console.log("hello world!", e.target);
  //     console.log(e.target.innerHTML);
  //     console.dir(e.target.parentNode.dataset.index);
  //   }

  // 바로 위의 if 문과 동일하게 동작하지만 반대로 조건을 설정하여 코드블록을 벗겨내고 return으로 받았기에 코드 형태가 더 좋음!!
  if (e.target.className !== "comment-content") return;
  console.log(e.target.innerHTML);
  console.dir(e.target.parentNode.dataset.index);

  const value = e.target.innerHTML;
  e.target.innerHTML = "";
  const input = document.createElement("input");
  input.value = value;
  e.target.append(input);
  */

  console.log(e.target);
  if (e.target.className === "comment-content") {
    const index = parseInt(e.target.parentNode.dataset.index);
    state[index].updated = true;
    // state[index].updated = !state[index].updated; // 이렇게 하면 content 내용을 클릭할 때마다 true, false가 번갈아가면서 바뀜!!
    drawing();
  } else if (e.target.className === "comment-delete-btn") {
    const index = parseInt(e.target.parentNode.parentNode.dataset.index);
    state.splice(index, 1);
    drawing();
  }

  if (e.target.className !== "comment-content") return;
}

commentList.addEventListener("click", clickHandler);
commentFrm.addEventListener("submit", submitHandler);