[JS] 바닐라 자바스크립트로 미니 가계부앱 만들기
미니 가계부앱 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를 작업했는데 아직 모바일 화면에서 봤을 때 비율이 맞질 않는다.
모바일일 때도 고려해서 반응형으로 만들어 보도록 수정을 해야겠다.