14. Javascript.실습 C.R.U.D

2022. 11. 20. 21:21JavaScript

목차
1. C.R.U.D

  1.1 각 파트의 작업내용 요약
    1.1.1. Create
    1.1.2. Read
    1.1.3. Update
    1.1.4. Delete
2. C.R.U.D 작업과 설명

  2.1. HTML
  2.2. CSS
  2.3. Javascript
    2.3.1. Create 파트
    2.3.2. Read 파트
    2.3.3. Update 파트
    2.3.4. Delete 파트

 


1. C.R.U.D 

 

1.1 각 파트의 작업내용 요약

 

1.1.1 Create

Create 부분에서 해결해야할 점은 내용의 저장이다.

  • 댓글 입력폼에 내용을 입력 한 뒤 `submit`을 누르면 내용을 저장한다.
  • 만약 입력폼이 비어있는 상태에서 `submit`을 누르면 경고 팝업을 띄운다.(alert)
  • 댓글을 성공적으로 처리하면, 입력폼을 초기화 시킨다.

 

1.1.2 Read

Read 부분의 주 목적은 화면에 표시 해주는 것이다.

  • 댓글 내용은 한 줄에 각각 `아이디`, `댓글내용`, `날짜`로 표현한다.
  • 댓글 리스트는 최신순으로 나타내어, 최근에 쓴 글이 가장 위로 올라오게한다.
  • 댓글의 총 갯수를 세어 나타내주도록 한다.
  • 삭제버튼이 존재한다.

 

1.1.3 Update

Update는 Read부분에서 보여준 화면을 토대로 작업이 이루어진다.

작업을 마친 후에는 Read부분에 다시 표시해주어야한다.

  • 댓글 리스트에서 해당 줄의 내용 부분을 'click`하면 입력폼으로 변경된다.
  • 변경된 입력폼의 내용은 클릭전의 내용으로 유지된 상태여야한다.
  • 수정 중은 내용은 'enter'키를 누르면 수정이 완료된다.

 

1.1.4 Delete

Delete도 Update와 마찬가지로 Read부분의 화면을 토대로 작업을 진행한 후,

작업을 마치고 Read부분에 다시 표시 해준다.

  • 해당 리스트 중 한 줄의 삭제버튼을 'click'하면 댓글이 삭제된다.

2. C.R.U.D 작업과 설명

 

2.1 HTML

 

지난번의 HTML과 거의 유사하며, 엘리먼트에 데이터를 저장할 수 있는 date set을 이용한다. (Js와 함께 설명)

<!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="./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">
                    <ul class="comment-row" date-index="1">
                        <li class="comment-id"></li>
                        <li class="comment-content"></li>
                        <li class="comment-date"></li>
                    </ul>
                </li>
            </ul>
        </div>
        <script src="./Comment.js"></script>
    </body>
</html>

 

2.2 CSS

 

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

 

2.3 Javascript

자바스크립트 부분은 지난번 진행했던 코드와는 조금 다르게 진행된다. 코드를 보며한번 살펴보자.

// 변수 선언
const commentFrm = document.querySelector("#commentFrm")
const commentList = document.querySelector("#comment-list")
const state = []

// 클래스, 생성자
class Comment{
    constructor(content){
        this.userid = "char1ey"
        this.Content = content
        this.updated = false // 수정 시 true, 수정이 아닐시 false
        this.now = new Date()
    }

    set Content(value) {
        if(value.length === 0) {
            throw new Error("Content가 비어있음")
        }
        this.content = value
    }

    getToday(separator = "") {
        const date = this.now
        let mm = date.getMonth() + 1
        let dd = date.getDate()
        let yyyy = date.getFullYear()
        mm = ((mm > 9) ? "" : "0") + mm // 소괄호 없을시 + mm먼저처리됌
        dd = ((dd > 9) ? "" : "0") + dd // 소괄호 없을시 + dd먼저처리됌
        const arr = [yyyy, mm, dd]
        return arr.join(separator)
    }
}

// Comment로 생성한 인스턴스를 배열에 넣는 함수
// 코드 가독성을 위해 함수로 빼내었다.
function addComment(instance){
    state.push(instance)
}

// 댓글 한줄에 해당하는 ul을 생성하고,
// 클래스와 내용을 넣어 화면에 나타내는 DOM 부분을 함수로 묶었다.
function createRow(index){
    // 한 줄에 필요한 엘리먼트생성 DOM을 변수에 담는다.
    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에 담는다.
    // 순서대로 아이디, 내용, 날짜에 해당한다. 
    ul.append(li1)
    ul.append(li2)
    ul.append(li3)

    // 각 엘리먼트에 속성을 부여한다.
    // 부여된 속성으로 각 엘리먼트를 구분할 수 있기 때문이다.
    ul.setAttribute("class", "comment-row")
    ul.setAttribute("data-index", index) // "ul.dataset.index = index" 같은 방법이다.
    li1.setAttribute("class", "comment-id")
    li2.setAttribute("class", "comment-content")
    li3.setAttribute("class", "comment-date")

    // 삭제버튼을 추가하는 부분이다.
    deleteBtn.setAttribute("class", "comment-delete-btn")
    deleteBtn.innerHTML = "❌"

    // 아이디 부분 li
    li1.innerHTML = state[index].userid

    // 댓글 내용 부분 li
    // 내용부분은 Update 부분에서 수정할 수 있도록 구성한다.
    // 댓글이 수정되면 .updated 속성을 디폴트 값으로 되돌린다.
    // 디폴트는 "false"
    if(state[index].updated){
        const input = document.createElement("input")
        input.addEventListener("keyup", function(e){
            if(e.keyCode !== 13) return
            state[index].content = e.target.value
            state[index].updated = false
            drawing()
        })
        input.setAttribute("class", "comment-update-input")
        li2.append(input)
        input.value = state[index].content
    } else {
        li2.innerHTML = state[index].content
        li2.append(deleteBtn)
    }

    // 날짜 부분
    li3.innerHTML = state[index].getToday("-")

    // 완성된 ul을 리턴한다.
    return ul
}

// createRow를 호출해 리스트를 구성하고, 최신화를 진행하는 함수
// for문을 이용하여 댓글에 변화가 있을 때마다, 처음부터 다시 그린다.
function drawing(){
    commentList.innerHTML = ""
    for(let i = state.length - 1; i >= 0; i--){
        const row = createRow(i)
        
        // 완성된 ul을 배열에 담는다.
        commentList.append(row)
    }
}

// "submit" 이벤트 발생시 호출되는 핸들러 함수
function submitHandler(e) {
    e.preventDefault()
    const form = e.target
    const value = form.content.value
    
    // try_catch문을 통해서 빈값 입력시 에러 메시지를 보낸다.
    try {
        // 값을 체크하는 부분은 setter에 있다.
        const instance = new Comment(value)
        addComment(instance)
        drawing()
    } catch(error) {
        alert(error.message)
    }

    // 값 입력 후 댓글칸으로 커서를 이동시킨다.
    form.content.focus()

    // 값 입력 후 댓글칸을 초기화 시킨다.
    form.reset()
}

// "click" 이벤트 발생시 호출되는 핸들러 함수
// update와 delete라는 두 가지 기능을 담는다.
// e.target.className으로 해당 클래스일 경우에만 작동하도록 한다.
function clickHandler(e){
    if(e.target.className === "comment-content") {
        const index = parseInt(e.target.parentNode.dataset.index) //ul의 index
        const value = e.target.innerHTML // comment-content
        state[index].updated = !state[index].updated//=true
        drawing()
    } else if(e.target.className === "comment-delete-btn") {
        console.log(e.target.parentNode)
        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)

 

 

코드를 구성했던 순서대로 각 파트별 세부사항을 살펴보자.

 

 

2.3.1. Create 파트

 

  • 1. 필요한 선택자들을 선언(commentFrm, comment-list)한다.
  • 2. 객체형태의 배열을 담아둘 stat 배열 선언
  • 3. commentFrm에 `submit`이벤트를 달아주고, 함수는 submitHandler를 준다.
  • 4. submitHandler함수를 선언한다.
  • 5. `submit`버튼의 기능을 멈춰놓는다.(e.preventDefault)

여기서 부터는 입력폼의 내용을 가져오는 방법을 구성하는 부분이다.

  • 6. e.target을 form태그와 함께 쓰면 name 혹은 id의 값을 가져올 수 있다. 이를 이용해 e.target.content.value로 input의 valute 값을 가리킨다.
  • 7. if문을 이용해 선언한 input의 값이 없다면, 경고창(alert)을 띄우고 return을 이용해 종료 시킨다.
  • 8. 정상적으로 코드 진행시 e.target.reset()을 이용하여 초기화 시켜준다.

입력폼의 내용을 가져온뒤 그를 바탕으로 데이터를 저장해야한다.

  •  9. class Comment 선언

입력받은 데이터를 바탕으로 객체를 생성해야한다. 여기에는 2가지 방법이 있는데, 

1. 생성자 함수

2. class 문법

 
이 중에서 저번과는 다르게 class를 사용하기로 한다.
 

class를 사용하는 이유

  • 생성자 함수는 함수 선언식과 헷갈릴 수도 있고 이를 JS에서 오류로 잡히지도 않는다. 물론 원하는 값으로도 출력되지 않는다.
  • class는 new 연산자가 빠져버린다면 바로 오류를 터뜨린다.
  • class는 호이스팅이 일어나지 않아 예상치 못한 일이 일어나지 않는다.
  • function은 가독성면에서도 조금 불편하다.

class를 사용하면서 생기는 다양한 방법론

  • value 값을 넣지 않을 때 오류가 생긴다.
    • Comment를 실행할 때 오류를 체크해준다.
    • submitHandler에는 함수가 실행될때 내가 실행하는 항목들을 나열하자.
      • 값을 만들기전에 오류를 체크해주는것이 좋아보인다.

class 내의 setter

  • 대입연산자를 통해서 값을 넣을 때, 실행이 되는 것을 setter라고 한다.
  • this.Content에 content가 대입연산자를 통해서 들어갈 때, 아래의 set 함수가 실행된다. 대입연산자 오른쪽에 들어갈 값을 set의 인자 value 자리에 넣어 실행을 한다. 여기서는 if문을 거쳐서 진행된 value 값을 content에 다시 넣고, 이 값이 this.Content의 값이 되어 Comment를 생성한다.(그림으로 설명)
  • 인스턴스를 생성할 때 한번 set이 실행된다. 이 때, if를 통해서 값이 비어있는 경우를 한 차례 검증 후 인스턴스를 생성해준다. 그러므로 submitHandler 함수내에서의 if문을 지워줄 수 있다.

이슈 발생

  • 경고창으로 바꾸고 실행하면 이슈가 발생한다.
  • 아무것도 없는채로 실행했을 때 경고창이 뜬다. (== setter가 잘 작동한다.)
  • 하지만 내용이 없는채로 this.content = value가 실행이 되어 버려 인스턴스가 빈값으로 생성이 되어버렸다. 즉, 코드를 멈추게하고 싶은데 코드를 못 멈췄다. 해결하겠다고 if에 return을 쓰게되면 setter가 종료되는 것이지, Comment 자체가 멈추지는 않는다.

이슈 해결 방안

  • try-catch : 어디서 에러가 나서 진행된건지 try, throw와 catch를 통해 알 수 있다.
function a() {
    // throw "에러"
    throw new Error("에러") 
    // 에러 객체를 던진다.
}
try {
    // 코드를 실행할 영역
    a()
    // 호출 >> a함수 실행되면서 throw를 읽으면, catch로 던진다.
    // catch가 이를 잡는데 "에러"를 매개변수 자리 e로
    // 들어간다.
    console.log("나 실행") // 실행안됨
} catch (e) { // e는 error의 약자
    // 에러가 날경우 실행!
    console.log(e) //"에러"
}

 

try_catch문

  1. try블록 안에서 코드가 진행된다.
  2. 함수를 호출하면 함수가 실행된다.
  3. 함수 내부에서 if문을 이용해 조건에 맞는 값을 찾는다.
  4. throw를 만나는 조건은 에러객체를 생성해 catch블록으로 넘긴다.
  5. catch(e)에서 인자값으로 에러객체를 받아 출력한다.
  6. 어디서 에러나 났는지 표시 해준다.
  • try catch문에서 첫번째 시선은 try쪽을 향해야 한다.
  • 코드를 읽을 때, 성공할 떄의 부분만 읽어내려갈 수 있다.
  • submitHandler의 예외처리 코드를 줄이고, 실행하는 중요한 함수만 담아두는게 좋다.(직관적으로 쓸 수 있다.)
  • new Error()를 던지는데, 이것은 객체다.
  • new Error의 인자는 error.message라는 곳에 들어가있다.
  • error라는 같은 인자로 받으면 자동으로 Error를 띄워 처리해준다.

try_catch문을 마지막으로 Create 부분이 마무리가 된다.

 

 

2.3.2 Read 파트

Read 파트는 Create에서 받은 정보를 화면에 렌더링하는 파트이다.

Update와 Delete파트도 Read파트를 보면서 진행되기 때문에 이 후의 작업을 생각하면서,

Update와 Delete를 어떻게 구성해야될지, 생각하면서 진행하는 것이 좋다.

 

  • comment-list에 내용을 채워넣는다.
  • state라는 변수의 배열을 기준으로 넣는다.
  1. drawing 함수를 선언한다.
  2. drawing에서 for문으로 state변수의 배열의 인덱스를 찍는다.
  3. for문의 조건으로 최신화를 시켜준다.
  4. 함수 createRow를 선언한다.
  5. createRow는 HTML을 생성해주는 함수(DOM)
  6. createRow의 매개변수는 index로 받는다.
  7. createRow는 return값으로 생성한 ul을 받는다.
  8. ul과 li123을 생성하고, ul에 li를 넣는다.
  9. u과 li123에 클래스를 각각 부여해준다.
  10. 각각의 li에 innerHTML을 부여한다.
  11. innerHTML에 대입할 state[index]는 객체데이터이며, 인덱스 번호는 객체가 생성된 순서이다.
  12. create(i)로 생성하여 변수 row에 담는다.
  13. row에는 HTML을 생성해주는 객체 ul이 있다.
  14. 생성된 ul을 commentList.append를 통해 넣는다.

이슈 발생

  • drawing을 호출하여 생성된 엘리먼트가 중첩되어 출력된다.
  • 이를 해결하기 위해서는 함수가 호출될 떄 입력창을 초기화 시켜주어야 한다.

Read 파트는 index를 남겨 다음 작업에게 전달해줄 방법을 생각해놓아야한다.

 

 

2.3.3 Update 파트

  • 객체가 배열에 담기기 때문에 인덱스 번호가 리스트에 매겨져 있다.
  • 내가 수정하려는 아이의 인덱스를 알아야한다.
  • 몇 번 인덱스인지 흔적을 남겨야한다.
  • 흔적을 어떻게 남길지 전략을 세워야한다.
  • 가장 중요한것은 내가 어떤 댓글을 수정할지 알아야한다.
  • update와 delete는 해당 `index`, `고유번호`, `key값`을 Read 작업을 할 때 생각해주어야한다.

여기서 index를 넘기는 방법이 하나있다. Dateset을 사용해 인덱스 값을 넘겨보자.

 

Dateset 

date-index 속성은 엘리먼트에 값을 저장할 수 있다.

  • dateset은 data 까지만 본다.
  • .index는 date-index까지 본다.
  • 고유 키값을 주는 용도로 사용하도록하자.
<div id="header" data-index="5">

<script>
	const header = document.querySelector("#header")
	console.log(header.dataset.index) // 5
</script>

Update 세부 설명

  • 어려운 이유: 화면에 표현하기가 어렵다.
  • 클릭했을때 해당 부분을 input박스로 바뀌어야한다.

댓글 리스트에서 댓글 내용을 클릭이벤트를 넣고싶다면

  • 클릭시 hello world! 출력
  • 댓글 내용에 해당하는 index 번호를 출력 

 

  • 이벤트를 어디에 걸어야하는가?
  • 리스트(전체)에 클릭 이벤트를 준다.
  1. click 이벤트를 준다.
  2. click 이벤트에 if문으로 comment-content와 같은 클래스네임일 경우에 클릭이 되도록한다.

 

.parentNode

  • 부모요소를 선택할 수 있다.
  1. if문으로 클릭한 것이comment-content가 아닐경우에는 리턴하여 종료하고, 맞을 경우에는 parseInt로 클래스의 값을 가져온뒤 정수로 변환하여 index에 담는다.
  2. if문 다음의 e.target은 li2 즉 comment-content를 가리킨다.
  3. 클래스 Comment의 this.updated값을 false로 준다.(디폴트값)
  4. state[index].updated에 true 값으로 변경한다.
  5. drawing함수를 호출한다. 그럼 그를 바탕으로 다시 그린다.
  6. "X" 버튼을 생성하고, 정상출력시 나오도록 코드를 넣는다.
    • 생성
    • 클래스 부여
  7. createRow 함수안의 li2의 innerhtml 부분에 if문을 주어 버튼을 눌렀을 때 실행되도록 한다.(drawing으로 다시그림)
  8. 키업 이벤트
    • 엔터를 눌렀을 때 실행되도록 해야한다.
    • e.keyCode로 엔터임을 알아낸다. // "enter = 13"

 

2.3.4 Delete 파트

 

Update 파트까지 구상이 되었다면, Delete파트는 어렵지 않게 진행된다.

  • Delete에 필요한 것들이 이미 다 있다.
  • 버튼을 클릭하면 해당 인덱스를 찾아오고
  • splice만 진행하고 다시 그린다.