2022. 11. 20. 21:21ㆍJavaScript
| 목차 |
| 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를 사용하는 이유
- 생성자 함수는 함수 선언식과 헷갈릴 수도 있고 이를 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문
- try블록 안에서 코드가 진행된다.
- 함수를 호출하면 함수가 실행된다.
- 함수 내부에서 if문을 이용해 조건에 맞는 값을 찾는다.
- throw를 만나는 조건은 에러객체를 생성해 catch블록으로 넘긴다.
- catch(e)에서 인자값으로 에러객체를 받아 출력한다.
- 어디서 에러나 났는지 표시 해준다.
- 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라는 변수의 배열을 기준으로 넣는다.
- drawing 함수를 선언한다.
- drawing에서 for문으로 state변수의 배열의 인덱스를 찍는다.
- for문의 조건으로 최신화를 시켜준다.
- 함수 createRow를 선언한다.
- createRow는 HTML을 생성해주는 함수(DOM)
- createRow의 매개변수는 index로 받는다.
- createRow는 return값으로 생성한 ul을 받는다.
- ul과 li123을 생성하고, ul에 li를 넣는다.
- u과 li123에 클래스를 각각 부여해준다.
- 각각의 li에 innerHTML을 부여한다.
- innerHTML에 대입할 state[index]는 객체데이터이며, 인덱스 번호는 객체가 생성된 순서이다.
- create(i)로 생성하여 변수 row에 담는다.
- row에는 HTML을 생성해주는 객체 ul이 있다.
- 생성된 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 번호를 출력
- 이벤트를 어디에 걸어야하는가?
- 리스트(전체)에 클릭 이벤트를 준다.
- click 이벤트를 준다.
- click 이벤트에 if문으로 comment-content와 같은 클래스네임일 경우에 클릭이 되도록한다.
.parentNode
- 부모요소를 선택할 수 있다.
- if문으로 클릭한 것이comment-content가 아닐경우에는 리턴하여 종료하고, 맞을 경우에는 parseInt로 클래스의 값을 가져온뒤 정수로 변환하여 index에 담는다.
- if문 다음의 e.target은 li2 즉 comment-content를 가리킨다.
- 클래스 Comment의 this.updated값을 false로 준다.(디폴트값)
- state[index].updated에 true 값으로 변경한다.
- drawing함수를 호출한다. 그럼 그를 바탕으로 다시 그린다.
- "X" 버튼을 생성하고, 정상출력시 나오도록 코드를 넣는다.
- 생성
- 클래스 부여
- createRow 함수안의 li2의 innerhtml 부분에 if문을 주어 버튼을 눌렀을 때 실행되도록 한다.(drawing으로 다시그림)
- 키업 이벤트
- 엔터를 눌렀을 때 실행되도록 해야한다.
- e.keyCode로 엔터임을 알아낸다. // "enter = 13"
2.3.4 Delete 파트
Update 파트까지 구상이 되었다면, Delete파트는 어렵지 않게 진행된다.
- Delete에 필요한 것들이 이미 다 있다.
- 버튼을 클릭하면 해당 인덱스를 찾아오고
- splice만 진행하고 다시 그린다.
'JavaScript' 카테고리의 다른 글
| 17. Javascript 심화학습 (0) | 2022.11.27 |
|---|---|
| 16. Javascript 배열과 메서드 (0) | 2022.11.27 |
| 13. Javascript.실습 Counter와 C.R.U.D (0) | 2022.11.18 |
| 12. Javascript. 실습 이벤트를 이용한 Menu 구현 (0) | 2022.11.15 |
| Javascript 슬라이드 구현 (0) | 2022.11.14 |