미니 가계부앱 with vanilla javascript
바닐라 자바스크립트로 첫번째로 선택한 프로젝트는 가계부앱!
노마드코더 투두리스트 강의때 배웠던 내용들을 응용하였고 나머지는 구글링 해가며 코딩하였다.
실행 화면
(흔한 대학생의 가계부..)
기능
- 수입/지출, 종류, 금액, 내용 입력하면 전체 내역란에 리스트가 등록
=> classList를 사용해 +버튼을 누르면 입력창이 나타남. 밑에서 위로 올라오는 css 애니메이션 사용
=> 수입/지출은 라디오 버튼, 종류는 select 태그, 금액과 내용은 input태그로 각각 type은 number, text로 지정
=> 지출 체크 시 금액은 입력 값이 양수든 음수든 모두 음수로 받음
=> 가격이 양수인지 음수인지에 따라 빨간색, 초록색으로 색상 다르게 출력
=> 모든 값들을 하나의 오브젝트에 넣고 history 배열에 삽입 한 후 로컬 스토리지에 저장 - 리스트 삭제하기
=> 리스트에 마우스를 올리면 삭제 버튼 보여짐
=> 삭제 버튼 누르면 목록에서 사라지고 로컬 스토리지에도 해당 오브젝트가 삭제 - 전체, 수입, 지출, 초기화 버튼
=> 수입 버튼을 누르면 수입 값들만 리스트에 보여지기
=> 지출 버튼을 누르면 지출 값들만 리스트에 보여지기
=> 전체 버튼을 누르면 수입, 지출 모든 값들이 리스트에 보여지기
=> 초기화 버튼을 누르면 값들이 모두 사라지고 로컬 스토리지안도 빈배열로 초기화
코드
로컬 스토리지에 저장하기
let historyArr = [];
function getHistoryObj(select, text, price) {
return {
id: String(Date.now()),
select: select,
text: text,
price: price,
};
}
히스토리 내역이 담길 배열과 입력값들로 이루어진 오브젝트들을 생성할 함수를 만들어준다.
이 때 아이디를 지금까지 경과된 밀리초를 반환하는 Date.now()함수로 고유한 번호를 부여해주었다.
function saveHistory(history) { // 오브젝트를 배열에 추가
historyArr.push(history);
}
function saveLS() { // 배열을 로컬 스토리지에 저장
localStorage.setItem("history", JSON.stringify(historyArr));
}
function loadLS() { // 로컬 스토리지에서 가져옴
historyArr = JSON.parse(localStorage.getItem("history")) || [];
}
function restoreState() { // 배열 값들을 갱신해 출력
historyArr.forEach(function (item) {
paintHistory(item);
});
}
입력, 출력, 삭제
function submitBtnClick() {
const selectedValue = select.options[select.selectedIndex].text;
const textValue = inputText.value;
let priceValue = inputPrice.value;
if (priceValue.trim() === "") {
alert("가격을 입력하세요.");
}
if (expenseBtn.checked === true) {
if (priceValue > 0) {
priceValue = priceValue * -1;
}
}
inputText.value = "";
inputPrice.value = "";
const historyObj = getHistoryObj(selectedValue, textValue, priceValue);
paintHistory(historyObj);
saveHistory(historyObj);
saveLS();
inputBox.classList.add("hide");
addBtn.classList.remove("hide");
inputBox.classList.remove("showingInputBox");
updatePrice();
}
입력 후 저장하기 버튼을 클릭 했을 때 실행될 함수.
모든 value들을 가져와 오브젝트에 넣어주고 히스토리에 출력하고 로컬스토리지에 저장할 함수들을 실행한다.
지출 버튼이 클릭 되었을 경우 price value는 항상 음수 값으로 저장된다.
function paintHistory(history) {
const addValueBox = document.createElement("div");
const li = document.createElement("li");
const delBtnSpan = document.createElement("button");
const selectSpan = document.createElement("span");
const textSpan = document.createElement("text");
const priceSpan = document.createElement("price");
addValueBox.id = "addValueBox";
textSpan.id = "text";
priceSpan.id = "price";
selectSpan.id = "select";
delBtnSpan.id = "delBtn";
delBtnSpan.innerHTML = `<i class="far fa-trash-alt"></i>`;
delBtnSpan.addEventListener("click", deleteHistory);
li.id = history.id;
addValueBox.appendChild(selectSpan);
addValueBox.appendChild(textSpan);
addValueBox.appendChild(priceSpan);
addValueBox.appendChild(delBtnSpan);
li.appendChild(addValueBox);
historyList.appendChild(li);
if (history.price < 0) {
priceSpan.classList.add("red");
priceSpan.innerText = `${history.price}원`;
} else {
priceSpan.classList.add("green");
priceSpan.innerText = `+${history.price}원`;
}
selectSpan.innerText = `${history.select}`;
textSpan.innerText = `${history.text}`;
}
createElement를 사용해 HTML요소를 동적으로 생성한다.
id를 각각 부여하고 appendChild로 ul -> li -> div -> span 순으로 자식 노드들을 추가한다.
생성되는 li의 아이디와 historyArr의 아이디를 같게 해준다.
function deleteHistory(e) {
const btn = e.target.parentNode;
const div = btn.parentNode;
const li = div.parentNode;
historyList.removeChild(li);
historyArr = historyArr.filter(function (item) {
return item.id !== li.id;
});
saveLS();
updatePrice();
}
클릭 이벤트가 발생한 요소를 반환하고 부모를 탐색해 li를 삭제 해 준다.
filter 함수로 배열에 있는 아이디와 리스트의 아이디가 같지않는 것들만 모아 새로 배열을 업데이트해준다.
가격 계산하기
function updatePrice() {
// 수입, 지출, 총 금액 계산하고 화면에 출력
const price = historyArr.map((item) => Number(item.price));
const totalPrice = price.reduce((acc, cur) => (acc += cur), 0);
const income = price
.filter((price) => price > 0)
.reduce((acc, cur) => (acc += cur), 0);
const expense = price
.filter((price) => price < 0)
.reduce((acc, cur) => (acc += cur), 0);
incomeText.innerText = `${income}`;
expenseText.innerText = `${expense}`;
totalPriceText.innerText = `${totalPrice}`;
}
수입, 지출 총 금액을 각각 reduce함수를 사용해 모두 더해준 후 출력한다.
function displayExpense() {
//지출만 보여주기
const expenseArr = historyArr.filter((item) => item.price < 0);
historyList.innerHTML = "";
for (let i = 0; i < expenseArr.length; i++) {
const li = document.createElement("li");
const valueBoxHTML = `
<div id="addValueBox">
<span id="select">${expenseArr[i].select}</span>
<span id="text">${expenseArr[i].text}</span>
<span id="price" style="color:red">${expenseArr[i].price}원</span>
</div>
`;
li.innerHTML = valueBoxHTML;
historyList.appendChild(li);
}
}
function displayIncome() {
// 수입만 보여주기
const incomeArr = historyArr.filter((item) => item.price > 0);
historyList.innerHTML = "";
for (let i = 0; i < incomeArr.length; i++) {
const li = document.createElement("li");
const valueBoxHTML = `
<div id="addValueBox">
<span id="select">${incomeArr[i].select}</span>
<span id="text">${incomeArr[i].text}</span>
<span id="price" style="color:green">+${incomeArr[i].price}원</span>
</div>
`;
//<button id="delBtn" onclick="deleteHistory()"><i class="far fa-trash-alt"></i></button>
li.innerHTML = valueBoxHTML;
historyList.appendChild(li);
}
}
function clearHistory() {
// 모두 삭제하기
historyList.innerHTML = "";
localStorage.clear();
incomeText.innerText = `0`;
expenseText.innerText = `0`;
totalPriceText.innerText = `0`;
historyArr = [];
}
function showAllHistory() {
// 전체 보여주기
location.reload(true);
}
수입, 지출만을 보여주는 함수는 paintHistory 함수와 다르게 html 요소를 문자열로 한꺼번에 추가해주었다.
전체 내역을 보여주는건 새로고침 함수를 실행시켰다.
보완할 점
수입과 지출 내역만을 보여주는 함수에서 html 태그에 delBtn을 넣어줘서 버튼을 누르면 전체 내역에서도 삭제가 되는걸 구현해보고 싶었으나 삭제 버튼을 누르면 이런 에러가 떴다.
처음에 span태그로 넣어줬기 때문에 그 문젠가 싶어 버튼 태그로 바꿨지만 여전히 안된다.
일단 수입, 지출 각각의 내역들만 보여주는 기능만으로도 의의가 있긴 해서 이대로 끝내긴 했는데 무엇이 문제인지 조금 더 고민해보고 알아내야겠다.
또 디자인 자체는 모바일 앱이라 생각하고 css를 작업했는데 아직 모바일 화면에서 봤을 때 비율이 맞질 않는다.
모바일일 때도 고려해서 반응형으로 만들어 보도록 수정을 해야겠다.
'FRONT > JAVASCRIPT' 카테고리의 다른 글
[JS] 클릭하면 특정 위치로 화면 스크롤 하기: scrollIntoView (0) | 2021.07.24 |
---|---|
[JS] MouseEvent.clientX, MouseEvent.clientY 로 좌표 실습하기 (0) | 2021.07.24 |
[드림코딩 by 엘리] 바닐라JS로 쇼핑몰 미니게임 만들기 (0) | 2021.07.11 |
[백준/ node.js] 배열 - 1546번 평균 (0) | 2021.07.02 |
[Javascript] 클로저 (0) | 2021.07.01 |