✨ DB를 이용해 게시판을 만들기 전에, 간단히 연습할 수 있는 방법입니다.
Chrome Browser - F12(or Inspect) - Application - Storage - Local Storage
위의 경로를 찾아 들어가면, 내 브라우저에만 저장되는 공간이 있다. 이걸 이용해 간단하게 게시판을 구현할 수 있다.
📢 Local Storage 사용법
1. 데이터 저장하기
localStorage.setItem( 명칭, 저장할 내용 );
2. 데이터 불러오기
localStorage.getItem( 명칭 )
글은 여러 개이며 하나의 글에 필요한 데이터는 번호, 제목, 내용, 작성자, 작성일, 조회이기 때문에
하나의 글을 저장할 때는 배열로 저장하고, 하나의 글에 들어간 데이터는 Object로 저장해주면 간단히 저장할 수 있다.
예를 들어, objArr = [ { num: 1, title: 'title1'... }, { num: 2, title: 'title2'... }, ... ] 형식으로 저장하면 된다.
📢 게시판을 만들어가는 작업 순서
1. input과 form tag를 이용해 글을 작성하는 부분을 만들어주고, 이 내용을 받아 localStorage에 저장해준다.
2. localStorage에서 데이터를 받아와, DOM을 이용해 목록을 출력해준다.
3. 글 제목을 누르면, 글 내용이 출력되도록 해준다.
4. 한 화면에는 최대 5개의 글만 보여주며, 그 이상의 글은 인덱스 처리를 해주고 눌렀을 때 해당 목록을 보여준다.
😃 보기 쉽게 하기 위해서, 싱글 페이지에 글 목록, 글 내용, 작성 부분을 한 번에 보여주도록 구성하였다.
이 과정에서 글 번호가 곧 데이터 index이므로 꼬이지 않도록 Sort해주는 작업이 필요하며,
각 글 목록마다 id를 다르게 부여하여, event를 받아줄 수 있도록 해주는 작업이 필요하다.
아래는 예제가 실제로 실행되는 모습이다.
이렇게 연습을 하고, DB와 함께 진행할 때는 어떻게 적용하고 개발을 진행해 나가야 할지 고민하면 될 듯 하다.
궁금한 점이 있으시면 언제든 댓글 달아주세요!
Full Code (https://github.com/DasolPark/Play-Ground_JavaScript) board.html
<!DOCTYPE html> |
<html lang="en"> |
<head> |
<meta charset="UTF-8" /> |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
<meta http-equiv="X-UA-Compatible" content="ie=edge" /> |
<title>Board with Local Storage</title> |
<link |
href="https://fonts.googleapis.com/css?family=Jua&display=swap" |
rel="stylesheet" |
/> |
<style> |
* { |
box-sizing: border-box; |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, |
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; |
font-family: 'Jua', sans-serif; |
} |
body { |
padding: 0px; |
margin: 0px; |
display: flex; |
justify-content: center; |
align-items: center; |
flex-direction: column; |
width: 100%; |
height: 95vh; |
background-color: #f6f9fc; |
} |
.main__container { |
height: 700px; |
padding-top: 10px; |
padding-bottom: 10px; |
} |
.board__container, |
.contents__container, |
.editor__container { |
width: 700px; |
display: flex; |
justify-content: flex-start; |
align-items: center; |
flex-direction: column; |
background-color: #ffffff; |
} |
.board__container { |
margin: 10px 0px 0px 0px; |
height: 220px; |
padding-top: 10px; |
padding-bottom: 10px; |
border-radius: 15px; |
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.1); |
position: relative; |
} |
.contents__container { |
margin: 10px 0px; |
height: 220px; |
padding-top: 10px; |
padding-bottom: 10px; |
border-radius: 15px; |
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.1); |
} |
.editor__container { |
margin: 10px 0px; |
height: 220px; |
padding-top: 10px; |
padding-bottom: 10px; |
border-radius: 15px; |
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.1); |
} |
/* Board Lists */ |
.board { |
width: 600px; |
text-align: center; |
margin-bottom: 15px; |
} |
.board__index-container { |
position: absolute; |
bottom: 10px; |
} |
.board__index-link { |
all: unset; |
margin-right: 5px; |
} |
.board__index-link:hover { |
cursor: pointer; |
color: blue; |
} |
/* Contents */ |
.contents__titles { |
width: 600px; |
display: flex; |
justify-content: space-between; |
align-items: center; |
padding-bottom: 10px; |
border-bottom: 2px solid rgba(0, 0, 0, 0.08); |
margin-bottom: 20px; |
} |
.contents__column { |
width: 200px; |
display: flex; |
justify-content: space-around; |
align-items: center; |
} |
.contents__content { |
width: 600px; |
display: flex; |
justify-content: start; |
align-items: center; |
} |
/* Editor */ |
.editor__form { |
width: 600px; |
display: flex; |
justify-content: center; |
align-items: center; |
flex-direction: column; |
} |
input { |
all: unset; |
border-radius: 5px; |
border: 1px solid rgba(0, 0, 0, 0.5); |
} |
.editor__title-input, |
.editor__content-input { |
width: 100%; |
padding: 5px 10px; |
margin-bottom: 10px; |
} |
.editor__content-input { |
height: 100px; |
} |
.editor__btn { |
all: unset; |
border: 1px solid rgba(0, 0, 0, 0.5); |
border-radius: 5px; |
padding: 5px 10px; |
} |
button:active { |
transform: scale(0.93); |
} |
</style> |
</head> |
<body> |
<main class="main__container"> |
<div class="board__container"> |
<table class="board"> |
<thead class="board__table-head"> |
<tr class="board__titles"> |
<th class="board__column">번호</th> |
<th class="board__column">제목</th> |
<th class="board__column">작성자</th> |
<th class="board__column">작성일</th> |
<th class="board__column">조회</th> |
</tr> |
</thead> |
<tbody class="board__contents" id="board-body"></tbody> |
</table> |
<div class="board__index-container" id="index-container"></div> |
</div> |
<div class="contents__container"></div> |
<div class="editor__container"> |
<form action="#" class="editor__form" id="editor-form"> |
<input |
type="text" |
class="editor__title-input" |
id="editor-title-input" |
placeholder="제목" |
/> |
<input |
type="text" |
class="editor__content-input" |
id="editor-content-input" |
placeholder="내용" |
/> |
<button class="editor__btn" id="editor-submit-btn">작성 완료</button> |
</form> |
</div> |
</main> |
<script> |
const boardTableBody = document.querySelector('#board-body'); |
const contentsContainer = document.querySelector('.contents__container'); |
const editorForm = document.querySelector('#editor-form'); |
const titleInput = document.querySelector('#editor-title-input'); |
const contentInput = document.querySelector('#editor-content-input'); |
const BOARDLIST_LS = 'boardLists'; |
const boardListsObj = []; |
let nums = 0; |
let author = 'David'; |
let date = new Date(); |
let views = Math.floor(Math.random() * 99) + 1; |
function onTitleClick(e) { |
contentsContainer.textContent = ''; |
const lists = JSON.parse(localStorage.getItem(BOARDLIST_LS)); |
const index = e.target.parentNode.id.replace(/[a-z|-]/gi, ''); |
const contentsTitles = document.createElement('div'); |
contentsTitles.classList.add('contents__titles'); |
const contentsColumnFirst = document.createElement('div'); |
contentsColumnFirst.classList.add('contents__column'); |
const contentsTitle = document.createElement('div'); |
contentsTitle.classList.add('contents__title'); |
contentsTitle.textContent = lists[index].title; |
// contents__titles > column >author, date, views |
const contentsColumnSecond = document.createElement('div'); |
contentsColumnSecond.classList.add('contents__column'); |
const contentsAuthor = document.createElement('div'); |
contentsAuthor.classList.add('contents__author'); |
contentsAuthor.textContent = lists[index].author; |
const contentsDate = document.createElement('div'); |
contentsDate.classList.add('contents__date'); |
contentsDate.textContent = lists[index].date; |
const contentsViews = document.createElement('div'); |
contentsViews.classList.add('contents__views'); |
contentsViews.textContent = lists[index].views; |
const contentsContent = document.createElement('div'); |
contentsContent.classList.add('contents__content'); |
contentsContent.textContent = lists[index].content; |
contentsColumnFirst.appendChild(contentsTitle); |
contentsColumnSecond.appendChild(contentsAuthor); |
contentsColumnSecond.appendChild(contentsDate); |
contentsColumnSecond.appendChild(contentsViews); |
contentsTitles.appendChild(contentsColumnFirst); |
contentsTitles.appendChild(contentsColumnSecond); |
contentsContainer.appendChild(contentsTitles); |
contentsContainer.appendChild(contentsContent); |
} |
function assignIndex() { |
const lists = JSON.parse(localStorage.getItem(BOARDLIST_LS)); |
if (!lists) { |
nums = 0; |
} else { |
nums = parseInt(lists[lists.length - 1].num) + 1; |
} |
} |
function onEditorFormSubmit(e) { |
e.preventDefault(); |
const title = titleInput.value; |
const content = contentInput.value; |
const lists = JSON.parse(localStorage.getItem(BOARDLIST_LS)); |
if (!lists) { |
const objArr = []; |
objArr.push({ |
num: `${nums++}`, |
title: `${title}`, |
author: `${author}`, |
date: `${date.getFullYear()}.${date.getMonth() + |
1}.${date.getDate()}.`, |
views: `${views++}`, |
content: `${content}` |
}); |
localStorage.setItem(BOARDLIST_LS, JSON.stringify(objArr)); |
} else { |
lists.push({ |
num: `${nums++}`, |
title: `${title}`, |
author: `${author}`, |
date: `${date.getFullYear()}.${date.getMonth() + |
1}.${date.getDate()}.`, |
views: `${views++}`, |
content: `${content}` |
}); |
localStorage.setItem(BOARDLIST_LS, JSON.stringify(lists)); |
} |
titleInput.value = ''; |
contentInput.value = ''; |
titleInput.focus(); |
window.location.reload(); |
} |
function showBoardLists() { |
const lists = JSON.parse(localStorage.getItem(BOARDLIST_LS)); |
lists.forEach((list, index) => { |
if (index < 5) { |
const tr = document.createElement('tr'); |
tr.classList.add('board__content'); |
const tdNum = document.createElement('td'); |
tdNum.classList.add('board__content-column'); |
tdNum.textContent = list.num; |
const aTitle = document.createElement('a'); |
aTitle.href = '#'; |
aTitle.id = `link-to-content${index}`; |
const tdTitle = document.createElement('td'); |
tdTitle.classList.add('board__content-column'); |
tdTitle.textContent = list.title; |
aTitle.appendChild(tdTitle); |
const tdAuthor = document.createElement('td'); |
tdAuthor.classList.add('board__content-column'); |
tdAuthor.textContent = list.author; |
const tdDate = document.createElement('td'); |
tdDate.classList.add('board__content-column'); |
tdDate.textContent = list.date; |
const tdViews = document.createElement('td'); |
tdViews.classList.add('board__content-column'); |
tdViews.textContent = list.views; |
tr.appendChild(tdNum); |
tr.appendChild(aTitle); |
tr.appendChild(tdAuthor); |
tr.appendChild(tdDate); |
tr.appendChild(tdViews); |
boardTableBody.appendChild(tr); |
const linkToContent = document.querySelector( |
`#link-to-content${index}` |
); |
linkToContent.addEventListener('click', onTitleClick); |
} else if (index === 5) { |
const boardIndexMax = Math.ceil(lists.length / 5); |
for (let i = 0; i < boardIndexMax; i++) { |
const indexContainer = document.querySelector('#index-container'); |
const aIndex = document.createElement('a'); |
aIndex.classList.add('board__index-link'); |
const spanIndexText = document.createElement('span'); |
spanIndexText.classList.add('board__index'); |
spanIndexText.textContent = i + 1; |
aIndex.appendChild(spanIndexText); |
indexContainer.appendChild(aIndex); |
aIndex.addEventListener('click', () => { |
showBoardListsNewPage(i); |
}); |
} |
} |
}); |
} |
function showBoardListsNewPage(pageIndex) { |
boardTableBody.textContent = ''; |
const lists = JSON.parse(localStorage.getItem(BOARDLIST_LS)); |
const limitPage = pageIndex * 5; |
lists.forEach((list, index) => { |
if (index >= limitPage && index < limitPage + 5) { |
const tr = document.createElement('tr'); |
tr.classList.add('board__content'); |
const tdNum = document.createElement('td'); |
tdNum.classList.add('board__content-column'); |
tdNum.textContent = list.num; |
const aTitle = document.createElement('a'); |
aTitle.href = '#'; |
aTitle.id = `link-to-content${index}`; |
const tdTitle = document.createElement('td'); |
tdTitle.classList.add('board__content-column'); |
tdTitle.textContent = list.title; |
aTitle.appendChild(tdTitle); |
const tdAuthor = document.createElement('td'); |
tdAuthor.classList.add('board__content-column'); |
tdAuthor.textContent = list.author; |
const tdDate = document.createElement('td'); |
tdDate.classList.add('board__content-column'); |
tdDate.textContent = list.date; |
const tdViews = document.createElement('td'); |
tdViews.classList.add('board__content-column'); |
tdViews.textContent = list.views; |
tr.appendChild(tdNum); |
tr.appendChild(aTitle); |
tr.appendChild(tdAuthor); |
tr.appendChild(tdDate); |
tr.appendChild(tdViews); |
boardTableBody.appendChild(tr); |
const linkToContent = document.querySelector( |
`#link-to-content${index}` |
); |
linkToContent.addEventListener('click', onTitleClick); |
} |
}); |
} |
editorForm.addEventListener('submit', onEditorFormSubmit); |
if (boardTableBody) { |
assignIndex(); |
showBoardLists(); |
} |
</script> |
</body> |
</html> |
'Project > Side-Project' 카테고리의 다른 글
[CSS] 웹&모바일 앱 디자인(Web and Mobile Application Design) (0) | 2020.02.19 |
---|---|
[ReactJS] Navigator와 OpenWeatherMapAPI를 이용한 날씨 정보 페이지 (0) | 2020.02.15 |
[JS] 나만의 모멘텀 만들기(Momentum Clone) Side-Project (2) | 2020.01.23 |
[CSS] 왓츠앱 디자인 패턴 복제하기(WhatsApp Design Clone) Side Project (0) | 2020.01.20 |
[JavaScript] 운동 관리 타이머(Workout Timer) Side Project (2) | 2020.01.16 |