1) 코드 실습
1-1) 게시판 구현(CRUD)
1-2) 배열 메서드
1) 코드 실습
1-1) 게시판 구현(CRUD)
게시판 흐름
(1) list page
- localStorage(로컬 스토리지)에 데이터가 이미 존재하는 경우와 없는 경우를 구분해야 함!!
(즉, 처음 접속한 사람과 두번째 접속한 사람을 구분하여 데이터 case를 나눈다!!)
// 처음 접속한 사람인지 아닌지를 구분하기 위한 코드
let item = localStorage.getItem("boards");
if (item === null) {
const initialState = [];
const state = JSON.stringify(initialState);
localStorage.setItem("boards", state);
item = state;
}
- 로컬 스토리지에 데이터가 쌓여 있어야만 게시판 리스트에 해당 데이터가 출력된다!
- 로컬 스토리지에서 데이터를 가져올 때는 "getItem()"을 사용하고, 로컬 스토리지에 데이터를 저장할 때는 "setItem()"을 사용함!!
(2) write page
- CRUD 중 가장 먼저 구현해야 하는 것은 "C(Create)"이므로 우리는 글쓰기(write)부터 만든다!!
- 글쓰기의 목적은 로컬 스토리지에 데이터를 넣는 것(데이터를 추가하는 것)이다!!
- 로컬 스토리지는 기본적으로 데이터를 "string" 타입으로 밖에 저장을 못한다!!
- 로컬 스토리지에서 string 데이터를 가져와서 객체로 형변환한 뒤 push 메서드를 써서 새로운 데이터를 추가한 뒤 이를 다시 string으로 형변환한 후 해당 string 데이터를 로컬 스토리지에 다시 새로 저장(로컬 스토리지에 값을 재할당한다고 볼 수 있음)한다!!
>> 아래 코드가 해당 내용을 수행하기 위한 코드
const boards = JSON.parse(localStorage.getItem("boards"));
boards.push(instance);
const index = boards.length - 1;
const item = JSON.stringify(boards);
localStorage.setItem("boards", item);
(3) view page
로컬 스토리지에는 배열(단, 데이터 타입은 "String"이다!!)이 들어가 있다!
우선 로컬 스토리지에서 string 데이터를 가져온 뒤 이를 객체로 형변환함!
const item = window.localStorage.getItem("boards"); // 여기서 item의 데이터 타입: string
// console.log(item); >> 출력 결과가 string이기에 object로 형변환 후 해당 객체 안의 특정 값을 가져올 수 있음!!
const boards = JSON.parse(item); // string 데이터(item)를 객체로 형변환한 뒤 boards에 담음!!
write page의 url에 들어있던 query string(ex. index=3)에서 index 값('3')만 추출하여 해당 인덱스로 새로운 데이터를 추가함!!
const idx = location.search.split("="); // 데이터 타입이 string이다!! >> '?index=3' >> ['?index', '3']
const index = idx[1];
const board = boards[index];
(4) modify page
글수정에서는 idx(index)를 그대로 가져옴!!
(5) delete page
view page에 삭제 버튼을 element로 추가한 뒤 해당 element에 click event를 걸어 삭제 버튼을 클릭하면 형변환을 거쳐 배열 내부의 값을 splice 메서드로 삭제함!!
화면 구성도
index.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>
</head>
<body>
<h1>
<a href="/index.html">로고</a>
</h1>
<a href="./board/list.html">게시판 리스트로 가기</a>
</body>
</html>
list.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>
</head>
<body>
<h1>
<a href="/index.html">로고</a>
</h1>
<h2>게시판 리스트</h2>
<table border="1">
<thead>
<tr>
<td>번호</td>
<td>제목</td>
<td>작성자</td>
<td>등록일</td>
<td>조회수</td>
</tr>
</thead>
<tbody></tbody>
<!-- <tr>
<td>1</td>
<td><a href="/board/view.html?index=0">글 제목1</a></td>
<td>황상범</td>
<td>2022-11-17</td>
<td>0</td>
</tr> -->
</table>
<a href="/board/write.html">글쓰기</a>
<script src="../public/js/list.js" type="text/javascript"></script>
</body>
</html>
write.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>
</head>
<body>
<h1>
<a href="/index.html">로고</a>
</h1>
<h2>게시글 쓰기</h2>
<form id="writeFrm">
<div>제목 : <input type="text" name="subject" /></div>
<div>작성자 : <input type="text" name="writer" /></div>
<div>내용 : <textarea name="content"></textarea></div>
<input type="submit" value="글작성" />
</form>
<script src="../public/js/write.js" type="text/javascript"></script>
</body>
</html>
view.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>
</head>
<body>
<h1>
<a href="/index.html">로고</a>
</h1>
<h2>게시글 보기</h2>
<form id="viewFrm">
<div id="subject">제목 :<span></span></div>
<div id="writer">작성자 :<span></span></div>
<div id="date">작성일 :<span></span></div>
<div id="content">내용 :<span></span></div>
<a href="/board/write.html">뒤로 가기</a>
<a href="/board/list.html">리스트로 가기</a>
</form>
<button id="modifyBtn">글수정</button>
<button id="deleteBtn">글삭제</button>
<script src="../public/js/view.js" type="text/javascript"></script>
</body>
</html>
modify.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>
</head>
<body>
<h1>
<a href="/index.html">로고</a>
</h1>
<h2>게시글 수정</h2>
<form id="modifyFrm">
<div>제목 : <input type="text" name="subject" /></div>
<div>작성자 : <input type="text" name="writer" /></div>
<div>내용 : <textarea name="content"></textarea></div>
<input type="submit" value="글수정" />
</form>
<script src="../public/js/modify.js" type="text/javascript"></script>
</body>
</html>
list.js
/*
let a = "sangbeom";
console.log(a);
const person = {
name: "sangbeom",
age: 27,
};
console.log(document.cookie); // 원래대로라면 해당 코드는 출력이 안되어야 하지만 "name=sangbeom"을 출력함
// >> 이는 cookie를 통해 브라우저가 가진 directory에서 특정 파일을 생성하기에 cookie에 값으로 넣은 데이터를 넘기는 것이 가능해짐!!
*/
// boards 있니
let item = localStorage.getItem("boards");
if (item === null) {
const initialState = [];
const state = JSON.stringify(initialState);
localStorage.setItem("boards", state);
item = state; // 바로 위의 코드까지만 수행하면 item이 "null"이 되므로 string 데이터로 형변환한 state 변수의 값을 넣어줌!!
}
const tbody = document.querySelector("tbody");
function template(item, index) {
return `
<tr>
<td>${index + 1}</td>
<td><a href='/board/view.html?index=${index}'>${item.subject}</a></td>
<td>${item.content}</td>
<td>${item.date}</td>
<td>${item.hit}</td>
</tr>
`;
}
// const obj = { index: 0, subject: "11", content: "11", date: "11", hit: 0 };
// tbody.innerHTML = template(obj);
// console.log(item)
const boards = JSON.parse(item);
// [{}, {}, {}]
/*
// 첫번째 게시글이 list 맨 상단에 마지막 게시글(최신 게시글)이 list 맨 하단에 오도록 순서를 정렬한 경우
for (let i = 0; i < boards.length; i++) {
tbody.innerHTML += template(boards[i], i);
}
*/
// 마지막 게시글(최신 게시글)이 list 맨 상단에 첫번째 게시글이 list 맨 하단에 오도록 순서를 정렬한 경우
for (let i = boards.length - 1; i >= 0; i--) {
tbody.innerHTML += template(boards[i], i);
}
// item = localStorage.getItem("boards");
write.js
const writeFrm = document.querySelector("#writeFrm");
class Board {
constructor(subject, content, writer) {
let writeYear = new Date().getFullYear();
let writeMonth = new Date().getMonth() + 1;
let writeDay = new Date().getDate();
this.index = "";
this.subject = subject;
this.content = content;
this.writer = writer;
function datePrinter() {
if (writeMonth < 10 && writeDay < 10) {
return `${writeYear}-0${writeMonth}-0${writeDay}`;
} else if (writeMonth < 10 && writeDay >= 10) {
return `${writeYear}-0${writeMonth}-${writeDay}`;
} else if (writeMonth >= 10 && writeDay < 10) {
return `${writeYear}-${writeMonth}-0${writeDay}`;
} else {
return `${writeYear}-${writeMonth}-${writeDay}`;
}
}
this.date = datePrinter();
this.hit = 0;
}
}
function submitHandler(e) {
e.preventDefault();
const subject = e.target.subject.value;
const content = e.target.content.value;
const writer = e.target.writer.value;
const instance = new Board(subject, content, writer);
// '[]' -> []
// boards -> []
// boards.push(instance)
const boards = JSON.parse(localStorage.getItem("boards"));
boards.push(instance);
// console.log(boards);
const index = boards.length - 1;
const item = JSON.stringify(boards);
// boards: []
localStorage.setItem("boards", item);
// [] -> {}
e.target.reset();
// location : html의 a 태그 없이 자바스크립트만으로 링크 이동이 가능하게 하는 객체(location: url과 관련된 내용을 담고 있는 객체)
location.href = "/board/view.html?index=" + index;
}
writeFrm.addEventListener("submit", submitHandler);
/*
{
index: 0,
subject: '',
content: '',
writer: '',
date: '',
hit: ''
}
*/
// search : window.location 객체 안에 있으며, url에서 ?와 그 이후의 내용, 즉 query string을 가져오는(담고 있는) 객체
view.js
const modifyBtn = document.querySelector("#modifyBtn");
const deleteBtn = document.querySelector("#deleteBtn");
const item = window.localStorage.getItem("boards");
// console.log(item); // 출력 결과가 string이기에 object로 형변환 후 해당 객체 안의 특정 값을 가져올 수 있음!!
const boards = JSON.parse(item);
// console.log(boards); // object
// console.log(boards.index);
// console.log(boards.subject);
// console.log(boards.content);
// console.log(boards.writer);
// console.log(boards.date);
// console.log(boards.hit);
const idx = location.search.split("="); // 데이터 타입이 string이다!! >> '?index=3' >> ['?index', '3']
const index = idx[1];
const board = boards[index];
const viewFrm = document.querySelectorAll("#viewFrm > div");
for (let i = 0; i < viewFrm.length; i++) {
const id = viewFrm[i].id; // element의 id 값('subject')
// board[i] -> board['subject']
// console.log("id", id, board[id]);
const span = viewFrm[i].querySelector("span");
span.innerHTML = board[id];
}
modifyBtn.addEventListener("click", function () {
location.href = `/board/modify.html?index=${index}`;
});
function deleteHandler(e) {
if (confirm("정말 삭제하시겠습니까?") === true) {
boards.splice(index, 1);
const data = JSON.stringify(boards);
localStorage.setItem("boards", data);
location.href = "/board/list.html";
}
}
deleteBtn.addEventListener("click", deleteHandler);
// 각 게시글에 대한 조회수를 카운팅하기 위해서는 list page에서 view page로 넘어온 경우(view.js 파일 load된 경우)에
// 해당하는 게시글만 조회수를 1씩 증가시켜야 함!!(처음에 게시글을 작성힌 뒤 업로드하게 되었을 시 view page로 이동하는 경우는
// 해당 게시글의 조회수 카운팅이 되면 안되기 때문에 위와 같이 로직을 설계함)
// >> 즉, view.page가 load되었을 시에 이전 페이지 url 주소가 list page에 해당되는 경우(url에 "list.html"이 포함되어 있는 경우)에
// 한해서만 해당 게시글의 조회수 카운팅을 1씩 증가시면 됨!!(이를 위해 "document.referrer"을 사용함!)
// document.referrer : 링크를 통해 현재 페이지로 이동하기 전 페이지, 즉 이전 페이지의 URI(url을 포함하는 상위의 개념) 정보를 반환!!
let prevPage = document.referrer;
if (prevPage.indexOf("list.html") !== -1) {
board.hit += 1;
const hitCount = JSON.stringify(boards);
localStorage.setItem("boards", hitCount);
}
modify.js
const modifyFrm = document.querySelector("#modifyFrm");
const subject = document.querySelector("input[name=subject]");
const writer = document.querySelector("input[name=writer]");
const content = document.querySelector("textarea[name=content]");
const idx = location.search.split("="); // ?index=0 => ['?index', '0']
const index = idx[1];
console.log(index);
const item = localStorage.getItem("boards"); // [{}, {}, {}] : string
const boards = JSON.parse(item); // [{}, {}, {}] : object
console.log(boards);
console.log(boards[index]); // {index: 0, subject: 'dsdsd', content: 'dss', writer: 'ddd', date: '2022-11-17', …}
subject.value = boards[index].subject;
writer.value = boards[index].writer;
content.innerHTML = boards[index].content;
modifyFrm.addEventListener("submit", function (e) {
e.preventDefault();
console.log(subject.value);
console.log(writer.value);
console.log(content.value);
boards[index].subject = subject.value;
boards[index].writer = writer.value;
boards[index].content = content.value;
const data = JSON.stringify(boards);
localStorage.setItem("boards", data);
location.href = `/board/view.html?index=${index}`;
});
1-2) 배열 메서드
배열은 대부분 for 문이랑 함께 많이 쓰인다!!
배열 method는 반복을 돌려주는 함수들이 굉장히 많다!!
- forEach
- filter
- some
- map
- reduce ...
(1) Array.prototype.forEach()
forEach 메서드의 인자값은 함수이다!!
forEach의 단점은 반복을 멈출 수 없다는 점이다!!
아래 1번과 2번은 방법만 다르지 결국 동일한 출력 결과를 가짐!!
1. for문을 통해 배열의 인덱스를 활용하여 배열 값을 모두 뽑아내는 방법
const arr3 = [2, 7, 5, 4, 5];
for (let i = 0; i < arr2.length; i++) {
console.log(arr3[i]);
}
2. forEach 메서드를 활용하여 해당 배열의 값을 전부 뽑아내는 방법
const arr3 = [2, 7, 5, 4, 5];
arr3.forEach(function(value) {
console.log(value);
})
const arr3 = [2, 7, 5, 4, 5];
arr3.forEach(function(value) {
console.log(value);
})
let obj = {
length : 5,
forEach: function(callback) {
console.log(obj.length)
},
}
function loop() {
}
obj.forEach(loop);
const arr3 = [2, 7, 5, 4, 5];
arr3.forEach(function(value) {
console.log(value);
})
let obj = {
length : 5,
forEach: function(callback) {
callback();
},
}
function loop() {
console.log(obj.length);
}
obj.forEach(loop);
const arr3 = [2, 7, 5, 4, 5];
arr3.forEach(function (value, index) {
console.log(value, index);
});
let obj = {
length: 5,
forEach: function (callback) {
for (let i = 0; i < obj.length; i++) {
callback(i);
}
},
};
function loop(index) {
console.log(obj.length, index);
}
obj.forEach(loop);
const arr3 = [2, 7, 5, 4, 5];
arr3.forEach(function (value, index) {
console.log(value, index);
});
let obj = {
arr: [2, 7, 5, 4, 5],
length: 5,
forEach: function (callback) {
for (let i = 0; i < obj.length; i++) {
callback(obj.arr[i], i);
}
},
};
function loop(value, index) {
console.log(obj.length, value, index);
}
obj.forEach(loop);
// forEach의 단점은 반복을 멈출 수 없다는 점이다!!
const arr4 = [2, 7, 5, 4, 5];
arr4.forEach(function (value, index) {
console.log(value, index);
});
즉, 자신의 내부에서 반복문을 실행(중간에 멈출 수 없음!!)하는 것이 "forEach" 메서드이다!!
const numbers = [1, 2, 3];
let pows = [];
// for 문으로 배열 순회
for (let i = 0; i < numbers.length; i++) {
pows.push(numbers[i] ** 2);
}
console.log(pows); // [1, 4, 9]
const numbers = [1, 2, 3];
let pows = [];
// forEach 메서드는 numbers 배열의 모든 요소를 순회하면서
// 콜백 함수를 반복 호출함
numbers.forEach(item => pows.push(item ** 2));
console.log(pows); // [1, 4, 9]
(2) Array.prototype.filter()
// filter 메서드: 기존 배열에서 특정 해당값들만 빼서 새로운 배열을 만들어 주는 함수
const arr = [2, 7, 5, 4, 5];
const arr2 = arr.filter((v) => v === 5);
console.log(arr2); // [5, 5]
- 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출함
- 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환함
const numbers = [1, 2, 3, 4, 5];
const odds = numbers.filter((item) => item % 2 === 0);
console.log(odds); // [2, 4]
(3) Array.prototype.map()
- 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출함
- 콜백 함수의 반환값들로 구성된 새로운 배열을 반환함
const numbers = [1, 4, 9];
const roots = numbers.map((item) => item * 9);
console.log(roots); // [9, 36, 81]
console.log(numbers); // [1, 4, 9]
(4) Array.prototype.reduce()
- 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출함
- 콜백 함수의 반환값을 다음 순회 시에 콜백 함수 첫번째 인수로 전달하면서 콜백 함수를 호출하여 하나의 결과값을 만들어 반환함
// 평균 구하기
const values = [1, 2, 3, 4, 5, 6];
const average = values.reduce((acc, cur, i, { length }) => {
// 마지막 순회가 아니면 누적값을 반환하고 마지막 순회이면 누적값으로 평균을 구해 반환함
return i === length - 1 ? (acc + cur) / length : acc + cur;
}, 0);
console.log(average); // 3.5
// 요소의 중복 횟수 구하기
const fruits = ['banana', 'apple', 'orange', 'orange', 'apple'];
const count = fruits.reduce((acc, cur) => {
// 첫 번째 순회 시 acc는 초기값인 {}이고 cur은 첫 번째 요소인 'banana'임
// 초기값으로 전달받은 빈 객체에 요소값인 cur을 프로퍼티 키로 요소의 개수를 프로퍼티 값으로
// 할당함. 만약 프로퍼티 값이 undefined(처음 등장하는 요소)이면 프로퍼티 값을 1로 초기화함
acc[cur] = (acc[cur] || 0) + 1;
return acc;
}, {});
console.log(count); // { banana: 1, apple: 2, orange: 2 }
const products = [
{ id: 1, price: 100 },
{ id: 2, price: 200 },
{ id: 3, price: 300 },
];
/*
1번째 순회: acc => 0, cur => { id: 1, price: 100 }
2번째 순회: acc => 100, cur => { id: 2, price: 200 }
3번째 순회: acc => 300, cur => { id: 3, price: 300 }
*/
const priceSum = products.reduce((acc, cur) => acc + cur.price, 0);
console.log(priceSum); // 600
'DOM' 카테고리의 다른 글
DOM 기초(9) - 댓글 구현 기능 보강(CRUD), 게시판 기초 (0) | 2022.11.17 |
---|---|
DOM 기초(8) - 댓글 구현(CRUD) (0) | 2022.11.16 |
DOM 기초(7) - 숫자 증감기, 댓글(Create / Read) 구현 (0) | 2022.11.15 |
DOM 기초(6) - 메뉴 / 서브메뉴, 이미지 슬라이드, 로또 (0) | 2022.11.14 |
DOM 기초(5) - 이미지 슬라이드, preview / next 버튼 구현 (0) | 2022.11.11 |