본문 바로가기

DOM

DOM 기초(10) - 게시판 구현(CRUD) 정리 및 배열 메서드

728x90
반응형

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