FRONT/JAVASCRIPT

[JS] 바닐라 자바스크립트로 미니 가계부앱 만들기

연듀 2021. 7. 13. 15:56

 

 

미니 가계부앱 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를 작업했는데 아직 모바일 화면에서 봤을 때 비율이 맞질 않는다.

모바일일 때도 고려해서 반응형으로 만들어 보도록 수정을 해야겠다.