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);
'DOM' 카테고리의 다른 글
DOM 기초(10) - 게시판 구현(CRUD) 정리 및 배열 메서드 (0) | 2022.11.18 |
---|---|
DOM 기초(9) - 댓글 구현 기능 보강(CRUD), 게시판 기초 (0) | 2022.11.17 |
DOM 기초(7) - 숫자 증감기, 댓글(Create / Read) 구현 (0) | 2022.11.15 |
DOM 기초(6) - 메뉴 / 서브메뉴, 이미지 슬라이드, 로또 (0) | 2022.11.14 |
DOM 기초(5) - 이미지 슬라이드, preview / next 버튼 구현 (0) | 2022.11.11 |