[JS] Udemy 문벅스 카페 메뉴 앱 만들기 - Section2 : 에스프레소 메뉴판 만들기

요들레이후·2022년 12월 25일
0

Javascript

목록 보기
8/11
post-thumbnail

📌 step1. 요구사항 구현을 위한 전략

TODO 메뉴 추가

  • 메뉴의 이름을 입력 받고 엔터키 입력으로 추가한다.
  • 메뉴의 이름을 입력 받고 확인 버튼을 클릭하면 메뉴를 추가한다.
  • 추가되는 메뉴의 마크업은 <ul id=”espresso-menu-list” class=”mt-3 pl-0”></ul> 안에 삽입해야 한다.
  • 총 메뉴 갯수를 count하여 상단에 보여준다.
  • 메뉴가 추가되고 나면, input은 빈 값으로 초기화한다.
  • 사용자 입력값이 빈 값이라면 추가되지 않는다.

TODO 메뉴 수정

  • 메뉴의 수정 버튼클릭 이벤트를 받고, 메뉴수정하는 모달창이 뜬다.
  • 모달창에서 신규메뉴명을 입력받고, 확인버튼을 누르면 메뉴가 수정된다.

TODO 메뉴 삭제

  • 메뉴 삭제 버튼 클릭 이벤트를 받고, 메뉴 삭제 컨펌 모달창이 뜬다.
  • 확인 버튼을 클릭하면 메뉴가 삭제된다.
  • 총 메뉴 갯수를 count하여 상단에 보여준다.

📌 메뉴의 이름을 입력받는 코드

js를 불러왔을 때 실행이 되어야 하기 때문에 app함수 먼저 만들기.

에스프레소 메뉴 이름에서 입력을 받고 있기 때문에 html태그에서 속성값에 id값을 이용해야한다.

html에서 id값은 element를 정확히 찾는 용도로 활용하고 이벤트를 붙일 수 있다.

사용자가 입력학 키보드의 값을 받기 위해 keypress라는 이벤트로 작성한다.

사용자가 keypress라는 키를 누르는 이벤트를 했을 때, 함수에서 받아온 값을 e.key라고 받아올 수 있다.

function App() {
  document.querySelector("#espresso-menu-name")
  .addEventListener("keypress", (e) => {
    console.log(e.key);
  });
}

App();

이렇게 하면 콘솔창에 한 글자씩 입력값이 받아와지는 것을 볼 수 있는데, 사용자가 입력한 값을 한꺼번에 받고 싶을 때 엔터키 입력으로 받아오게끔

e.key가 enter키 일 때 사용자가 입력한 값을 받아오도록 하면 된다.

function App() {
  document
    .querySelector("#espresso-menu-name")
    .addEventListener("keypress", (e) => {
      if (e.key === "Enter") {
        console.log(document.querySelector("#espresso-menu-name").value);
      }
    });
}

App();

엔터를 치면 화면이 새로고침되는 이유는 form태그 때문,

form

form 태그는 웹서버에 무언가를 전송하기 위한 것이다.

enter키를 쳤을 때 자동으로 전송하는 동작을 브라우저에서 제공을 해서 enter키를 눌렀을 때 새로고침이 되는 것이다.

따라서 form태그가 자동으로 전송되는 것을 막아주는 것을 구현해야한다.

이벤트리스너를 이용해서 submit이벤트가 있을 때 전송 이벤트를 막는 메소드는 e.preventDefult() 이다.

function App() {
  document
    .querySelector("#espresso-menu-form")
    .addEventListener("submit", (e) => {
      e.preventDefault();
    });
  document
    .querySelector("#espresso-menu-name")
    .addEventListener("keypress", (e) => {
      if (e.key === "Enter") {
        console.log(document.querySelector("#espresso-menu-name").value);
      }
    });
}

App();


📌 추가되는 입력값을 ul태그 안 삽입

이전에 util함수를 만들어서 document.queryselector가 길어지는 부분들을 단순하게 작성.

$ ⇒ 자바스크립트에서의 HTML DOM element를 가져올 때 관용적으로 많이 사용하여 queryselector에 들어오는 id를 받아서 document.queryselector를 리턴하는 형태로 만들어서 재사용

const $ = (selector) => document.querySelector(selector);

function App() {
  $("#espresso-menu-form").addEventListener("submit", (e) => {
    e.preventDefault();
  });
  $("#espresso-menu-name").addEventListener("keypress", (e) => {
    if (e.key === "Enter") {
      console.log(document.querySelector("#espresso-menu-name").value);
    }
  });
}

App();

사용자가 입력한 espressoMenuName의 값을 받와와주고 li태그 안에 name을 넣어서 새로운 메뉴네임이 들어간 것을 만들어줘야하기 때문에 함수로 선언

const $ = (selector) => document.querySelector(selector);

function App() {
  $("#espresso-menu-form").addEventListener("submit", (e) => {
    e.preventDefault();
  });
  $("#espresso-menu-name").addEventListener("keypress", (e) => {
    if (e.key === "Enter") {
      const espressoMenuName = $("#espresso-menu-name").value;
      const menuItemTemplate = (espressoMenuName) => {
        return `<li class="menu-list-item d-flex items-center py-2">
      <span class="w-100 pl-2 menu-name">${espressoMenuName}</span>
      <button
        type="button"
        class="bg-gray-50 text-gray-500 text-sm mr-1 menu-edit-button"
      >
        수정
      </button>
      <button
        type="button"
        class="bg-gray-50 text-gray-500 text-sm menu-remove-button"
      >
        삭제
      </button>
    </li>`;
      };
      console.log(menuItemTemplate(espressoMenuName));
    }
  });
}

App();

요구사항 체크, 이 만들어진 템플릿을 ul태그에 넣어야함.

html코드에 넣을 때는 innerHTML이라는 속성을 이용해서 넣어줄 수 있다.

console.log로 탬플렛 출력하는 코드를 지우고 아래의 코드를 넣어보자.

$("#espresso-menu-list").innerHTML = menuItemTemplate(espressoMenuName);

새로운 메뉴를 입력하면 이전 값이 사라지고 갱신된다.

그렇기 때문에 그냥 넣기 보단 추가되는 형식으로 만들어야하는데,

이때 사용하는 메서드는 .insertAdjacentHTML()이다.

내가 특정한 어떤 태그에서 원하는 위치에 넣을 수 있다.

  • ✨참고✨

Element.insertAdjacentHTML() - Web API | MDN

💡 Element.insertAdjacentHTML() - element.insertAdjacentHTML(position, text);

'beforebegin' : element 앞에

'afterbegin' : element 안에 가장 첫번째 child

'beforeend' : element 안에 가장 마지막 child

'afterend' : element 뒤에

$("#espresso-menu-list").insertAdjacentHTML(
        "beforeend",
        menuItemTemplate(espressoMenuName)
      );

📌 총 메뉴 갯수 카운트해서 업데이트

메뉴가 추가되고 나서, 메뉴의 총 갯수를 구하고 보여주면 된다.

총 메뉴 카운트 갯수를 업데이트할 것인데, 메뉴 카운트가 어디서 되고 있는지 체크해보면 총 0개라고 되어있는 부분이 있다.

그 클래스 명을 이용해서 바꾼다.

클래스는 id와 다르게 . 을 붙여서 element값을 가져올 수 있다. 안에 있는 문자값을 바꾸기 위해서는 innerText()라는 메서드를 이용해서 바꿔줄 수 있다.

갯수를 구해야하는데, 갯수를 구하려면 li갯수를 카운팅한다.

const변수 = li 갯수를 카운팅

  • li갯수를 카운팅할 때 querySelector라는 메서드를 이용해서 li태그를 가져온다. querySelector는 li태그 요소의 첫번째 요소만 가져오기에, querySelectorAll이라는 메서드를 이용해서 가져온다.
  • 그 다음 length 속성으로 길이를 가져온다.
const menuCount = $("#espresso-menu-list").querySelectorAll("li").length;
$(".menu-count").innerText = `${menuCount}`;

📌 메뉴가 추가되고 나면, input 빈 값으로 초기화

$("#espresso-menu-name").value=""

📌 사용자 입력값이 빈 값이라면 추가되지 않는다.

사용자 편의성을 위해서 모달창을 추가한다.

if ($("#espresso-menu-name").value === "") {
      alert("값을 입력해주세요.");
			return;
}

하지만 이렇게 했을 때 엔터키가 아닌 아무값을 입력했을 때도 알림창이 뜬다. 사용자가 입력한 값이 엔터키가 아닐 때 먼저 return을 해주면 이 문제가 해결된다.

if (e.key !== "Enter") {
      return;
    }
    if ($("#espresso-menu-name").value === "") {
      alert("값을 입력해주세요.");
      return;
    }

📌 확인 버튼을 눌렀을 때에도 메뉴 추가

확인 버튼에 대한 이벤트를 체크해줘야한다. 확인 버튼의 id값을 활용해서 이벤트를 바인딩한다.

버튼 클릭 이벤트를 받아서 똑같이 쓰면 되는데 코드의 중복이 될 것 같기에 재사용 함수를 만들어본다.

const $ = (selector) => document.querySelector(selector);

function App() {
  $("#espresso-menu-form").addEventListener("submit", (e) => {
    e.preventDefault();
  });

  const addMenuName = () => {
    if ($("#espresso-menu-name").value === "") {
      alert("값을 입력해주세요.");
      return;
    }
    const espressoMenuName = $("#espresso-menu-name").value;
    const menuItemTemplate = (espressoMenuName) => {
      return `<li class="menu-list-item d-flex items-center py-2">
      <span class="w-100 pl-2 menu-name">${espressoMenuName}</span>
      <button
        type="button"
        class="bg-gray-50 text-gray-500 text-sm mr-1 menu-edit-button"
      >
        수정
      </button>
      <button
        type="button"
        class="bg-gray-50 text-gray-500 text-sm menu-remove-button"
      >
        삭제
      </button>
    </li>`;
    };
    $("#espresso-menu-list").insertAdjacentHTML(
      "beforeend",
      menuItemTemplate(espressoMenuName)
    );
    const menuCount = $("#espresso-menu-list").querySelectorAll("li").length;
    $(".menu-count").innerText = `${menuCount}`;
    $("#espresso-menu-name").value = "";
  };

  $("#espresso-menu-submit-button").addEventListener("click", () => {
    addMenuName();
  });

  $("#espresso-menu-name").addEventListener("keypress", (e) => {
    if (e.key !== "Enter") {
      return;
    }
    addMenuName();
  });
}

App();

📌 수정 버튼을 눌러 “prompt”창에서 메뉴 수정

메뉴 수정 버튼을 이벤트를 바인딩, 문제는 수정 버튼이 없음.

이벤트 위임이라는 특징을 활용할 것, 아직 요소가 없는 것들 대신에 다른 애들에게 이벤트를 먼저 받고 있으라고 위임을 하는 것이다.

동적으로 element li태그가 추가되는 경우에는 코드를 작성하는 시점에서 상위 태그에게 event를 위임하는 것을 말한다. 부모 요소에게 위임되었기 때문에 새로운 요소에 이벤트 핸들러를 다시 지정할 필요가 없다.

  • 이벤트 위임, 동적으로 li태그 추가

✨ 이벤트 위임이란?

    <ul>
    	<li id="item1">Item 1</li>
    	<li id="item2">Item 2</li>
    	<li id="item3">Item 3</li>
    </ul>
    // 상위 노드에 이벤트 설정
    document.getElementById("parent-list").addEventListener("click", function (e) {            
       if (e.target && e.target.nodeName == "LI") {                
          console.log(`List item  ${e.target.id} was clicked!`);
        }
     });

클릭이벤트를 먼저 받고 있을 것인데, 수정버튼을 클릭했을 때만 수정되게끔 하고 싶은데, 클릭한 element가 수정 버튼인 것을 확인해줘야 하는데 클릭한 해당 대상 이벤트를 e.target을 이용해서 해당 element가 어떤 것인지 확인이 가능하다.

수정 버튼인 것을 확인해주는 if문이 필요한데, class가 다르므로 class를 이용해서 수정 버튼과 삭제 버튼을 구분해준다.

classList라는 메서드를 이용하여 값들을 배열처럼 가져올 수 있는데, 배열에서 contains라는 메서드를 이용해서 해당 클래스 네임이 있는 지 없는 지 체크할 수 있다.

$("espresso-menu-list").addEventListener("click", (e) => {
    if (e.target.classList.contains("menu-edit-button")) {
      console.log(e.target);
    }
  });

  • 이제 prompt를 띄어 값을 받아오자.

prompt(”윈도우창”, “입력창 default값”)

default값을 기존의 menuname을 입력하면 좋을 것 같은데, 기존의 menuname을 가져오는 방법은 수정 버튼을 클릭한 li태그의 span의 값을 가져와야한다.

보통 이럴 때 값을 가져올 때 가장 손쉽게 가져올 수 있는 방법은 수정 버튼을 클릭한 element기준으로 상위에 있는 li element로 올라간 다음 li element 하위에 있는 메뉴 네임을 가져온다.

현재 target에서 가장 가까운 li태그로 찾아서 올라가야하는데, closest메서드를 이용해서 찾을 수 있다.

$("espresso-menu-list").addEventListener("click", (e) => {
    if (e.target.classList.contains("menu-edit-button")) {
      const menuName = e.target.closest("li").querySelector(".menu-name");
      prompt("메뉴명을 수정하세요", menuName);
    }
  });

이렇게 하면 queryselector로 element 자체가 담겨서 object로 들어온다.

그때 text값을 가져오는 메서드로는 innerText가 있어서 그것을 사용한다.

  • prompt 에서 수정된 값을 받아서 업데이트

업데이트된 메뉴 이름을 e.target에 있는 메뉴 네임 텍스트에 대입해준다.

$("#espresso-menu-list").addEventListener("click", (e) => {
    if (e.target.classList.contains("menu-edit-button")) {
      const menuName = e.target
        .closest("li")
        .querySelector(".menu-name").innerText;
      const updateMenuName = prompt("메뉴명을 수정하세요", menuName);
      e.target.closest("li").querySelector(".menu-name").innerText =
        updateMenuName;
    }
  });
  • 리펙터링 - 중복 코드 제거
$("#espresso-menu-list").addEventListener("click", (e) => {
    if (e.target.classList.contains("menu-edit-button")) {
      const $menuName = e.target.closest("li").querySelector(".menu-name");
      const updateMenuName = prompt("메뉴명을 수정하세요", $menuName.innerText);
      $menuName.innerText = updateMenuName;
    }
  });

📌 삭제 버튼을 눌러 confirm창에서 메뉴 삭제

삭제 버튼을 클릭했을 때 삭제 → 수정버튼과 동일하게 접근한다.

수정버튼과 다른 점은 li태그를 통으로 삭제를 해야 한다는 점이다. queryselector을 하지 않고 closest만 하면 li태그가 통으로 삭제된다.

삭제하는 메서드로는 remove()를 사용하면 된다.

const updateMenuCount = () => {
    const menuCount = $("#espresso-menu-list").querySelectorAll("li").length;
    $(".menu-count").innerText = `${menuCount}`;
  };
  $("#espresso-menu-list").addEventListener("click", (e) => {
    if (e.target.classList.contains("menu-edit-button")) {
      const $menuName = e.target.closest("li").querySelector(".menu-name");
      const updateMenuName = prompt("메뉴명을 수정하세요", $menuName.innerText);
      $menuName.innerText = updateMenuName;
    }

    if (e.target.classList.contains("menu-remove-button")) {
      if (confirm("정말 삭제하시겠습니까?")) {
        alert("메뉴가 삭제되었습니다.");
        e.target.closest("li").remove();
        updateMenuCount();
      }
    }
  });

📌 리펙터링

  • 중복을 줄이고, 가독성을 높임

1. 메뉴 이름 수정하는 코드 부분을 함수로 따로 빼려고 함

에러 발생 : 이벤트 객체 에러

이벤트 객체를 함수에 넘겨주지 않아서 생기는 에러

다음과 같이 이벤트 객체를 넘겨주면 됨.

메뉴 삭제 시에도 동일하게 적용

2. espress-menu-submit-button 이벤트 객체를 따로 사용하지 않음

이 코드를

이렇게 변경

3. 구현된 함수들은 일단 접어두기

내가 구현하는 부분말고는 접어놓은 상태로 어떤 함수를 선언했는 지를 한눈에 보면 숲을 잃지 않고 구현을 할 수 있다.

INSIGHT

  • 이벤트 위임
  • 요구사항을 전략적으로 어떻게 단계별로 세세하게 나누는 게 중요하다는 걸 알게 됨
  • dom 요소를 가져올 때는 $표시를 써서 변수처럼 사용할 수 있다.
  • 새롭게 알게 된 메서드 : innerText, innerHTML, insertAdjacentHTML, closest, e.target,
profile
성공 = 무한도전 + 무한실패

0개의 댓글