점메추 SPA 에디션

2025. 9. 10. 20:37·JAVA/Java 실습

⛳️ 과제 목표

  • JavaScript로 JSON 기반 데이터 처리 및 화면 렌더링 이해를 할 수 있어요.
  • 기본적인 DOM 조작 및 이벤트 처리를 구현할 수 있어요.
  • 화면 전환을 통한 CSR 느낌 익히기 (window.location.href 활용)

📚 필수 과제

작성한 Github 과제의 파일을 기준으로 각 문제를 진행하며 어떻게 구성하였는지 풀이를 진행

 

1. 메뉴 등록 기능

더보기
  • 사용자가 HTML form을 통해 이름, 맵기, 가격 등을 입력하고 메뉴를 등록합니다.

 

 

  • 어디
    • 폼 마크업: index.html 의 #register-view 섹션과 <form id="form" class="menu-form"> 구조 
    • 저장 로직: controller.js의 form submit 핸들러 → addMenu(...) 호출 
    • 실제 저장: model.js의 addMenu(id/createdAt 부여) + writeMenus(localStorage에 저장) 
  • 어떻게
    1. 폼 제출 시 값 검증(빈 값/음수 방지).
    2. addMenu({ name, spicy, price })가 기존 목록을 읽고(id/createdAt 부여) 목록에 push → writeMenus로 "menus" 키에 저장. 
    3. 저장 후 폼 reset() → #list로 이동해 결과 확인. 

2. 메뉴 목록 조회 기능

더보기
  • 등록된 메뉴를 JS에서 리스트 형태로 화면에 렌더링합니다.

 

// view.js - 화면에 메뉴 목록 그리기

// 목록 렌더링
export function renderList(menus) {
    const tbody = document.querySelector("#rows");
    if (!tbody) return;

    // 데이터가 없을 때
    if (menus.length === 0) {
        tbody.innerHTML = `<tr><td colspan="4">등록된 메뉴가 없습니다.</td></tr>`;
        return;
    }

    // 데이터가 있을 때
    tbody.innerHTML = menus.map(m => `
    <tr>
      <td>${m.name}</td>
      <td>${m.spicy}</td>
      <td style="text-align:right;">${m.price.toLocaleString()}원</td>
      <td style="text-align:center;">
        <button data-action="edit" data-id="${m.id}">수정</button>
        <button data-action="del" data-id="${m.id}">삭제</button>
      </td>
    </tr>
  `).join("");
}

 

 

  • 어디
    • 목록 테이블 뼈대: index.html 의 #list-view 안 <tbody id="rows"> 
    • 그리기 함수: view.js 의 renderList(menus) 
    • 라우팅에서 호출: controller.js 의 onRoute() 목록 분기에서 renderList(readMenus()) 
  • 어떻게
    1. readMenus()로 목록을 읽음 → renderList에 전달. 
    2. 데이터가 없으면 “등록된 메뉴가 없습니다.” 1행 출력, 있으면 <tr> 여러 개를 만들어 #rows에 한번에 넣음. 

3. 메뉴 수정 기능

더보기
  • 목록에서 개별 메뉴 옆에 수정 버튼을 만드세요.
  • 수정 버튼 클릭 시 해당 메뉴를 수정합니다.

 

 

  • 어디
    • 수정 버튼 출력: view.js에서 <button data-action="edit" data-id="..."> 
    • 수정 진입: controller.js 클릭 핸들러에서 action === "edit" → editingId = id; location.hash = "#register" 
    • 폼 채우기: onRoute()에서 해시가 #register일 때 editingId가 있으면 해당 아이템을 찾아 form 값 채움 
    • 실제 업데이트: submit 시 updateMenu(editingId, { name, spicy, price }) 호출   
  • 어떻게
    1. [수정] 클릭 → 수정 대상 id를 기억하고 등록 화면으로 이동.
    2. 등록 화면 진입 시 해당 아이템을 찾아 input/select에 값 주입.
    3. 제출 시 updateMenu로 기존 객체에 patch 적용 → 저장. 

4. 메뉴 삭제 기능

더보기
  • 목록에서 개별 메뉴 옆에 삭제 버튼을 만드세요.
  • 삭제 버튼 클릭 시 해당 메뉴를 제거합니다.

 

  • 어디
    • 삭제 버튼 출력: view.js에서 각 행에 <button data-action="del" data-id="..."> 출력 
    • 클릭 처리: controller.js 의 #list-view 클릭 위임 핸들러에서 action === "del" 분기 → removeMenu(id) 호출 후 다시 렌더 
    • 실제 삭제: model.js 의 removeMenu(id) (필터로 제외 후 저장) 
  • 어떻게
    1. 이벤트 위임으로 어떤 행의 삭제 버튼인지 식별(data-id).
    2. removeMenu(id)가 해당 id를 제외한 새 배열을 저장 → 목록 재렌더. 

5. 데이터 저장 기능

더보기
  • 목록에서 개별 메뉴 옆에 삭제 버튼을 만드세요.
  • 삭제 버튼 클릭 시 해당 메뉴를 제거합니다.
<script>
localStorage.setItem('menus', JSON.stringify(menuList));
</script>

 

// model.js - 메뉴 데이터 관리 (localStorage)

const KEY = "menus";

// 메뉴 목록 불러오기
export function readMenus() {
    return JSON.parse(localStorage.getItem(KEY) || "[]");
}

// 메뉴 목록 저장하기
export function writeMenus(list) {
    localStorage.setItem(KEY, JSON.stringify(list));
}

// 메뉴 추가
export function addMenu(menu) {
    const list = readMenus();
    // id, createdAt 같은 값은 여기서 생성
    menu.id = Date.now().toString();
    menu.createdAt = Date.now();
    list.push(menu);
    writeMenus(list);
}

// 메뉴 삭제
export function removeMenu(id) {
    const list = readMenus().filter(m => m.id !== id);
    writeMenus(list);
}

// 메뉴 수정 (선택)
export function updateMenu(id, patch) {
    const list = readMenus();
    const i = list.findIndex(m => m.id === id);
    if (i >= 0) {
        list[i] = { ...list[i], ...patch };
        writeMenus(list);
    }
}

 

6. 화면 전환 기능

더보기
  • 각 기능은 독립된 HTML 파일로 구성하며 JS에서 window.location.href로 전환합니다.
  • 예시 : form.jsp, list.jsp ….

 

//controller.js 중 라우팅 관련 내용

// 라우팅 (해시값에 따라 화면 전환)
function onRoute() {
    const hash = (location.hash || "#register").toLowerCase();  // ← 기본: 등록 화면
    if (hash === "#register") {
        show("#register-view");
        // 수정 모드면 값 채우기, 아니면 폼 초기화
        const form = document.querySelector("#form");
        if (editingId) {
            const item = readMenus().find(m => m.id === editingId);
            if (item) {
                form.name.value  = item.name;
                form.spicy.value = item.spicy;
                form.price.value = item.price;
            }
        } else {
            form?.reset();
        }
    } else {
        show("#list-view");
        renderList(applyFilter(readMenus()));    }
}

 

 

  • 어디
    • 네비게이션 앵커: index.html 의 #list, #register 링크 
    • 라우팅 로직: controller.js 의 onRoute() + hashchange 리스너, show("#...")로 display 토글 
  • 어떻게
    1. 처음 진입하면 해시가 없을 때 **기본 #register**로 강제.
    2. 해시가 바뀔 때마다 onRoute()가 섹션을 바꿔 보여줌(등록/목록).  

❤️‍🔥 도전 과제

1. SPA 흉내내기

더보기
  • SPA는 html을 이동하지 않고, 한 페이지 내에서 이동하는 것이 원칙입니다.
  • 따라서 모든 페이지를 이렇게 구성할 필요는 없지만 하나의 페이지를 SPA를 흉내내봅시다.
  • 등록/조회 화면을 같은 페이지 내에서 버튼으로 전환 (display 또는 innerHTML 활용)

 

 

  • 어디
    • 해시 링크로 전환: index.html 의 #list, #register 
    • 해시 기반 전환: controller.js 의 onRoute()/hashchange + show()(display 토글) 
  • 어떻게
    • location.hash만 바꿔도 hashchange가 발생 → JS가 섹션 show/hide 처리 → 새로고침 없이 전환.  

2. 상태 기반 필터링 기능

더보기
  • 맵기, 가격대 기준으로 필터 버튼을 만들고 클릭 시 해당 메뉴만 리스트에 출력
  • UI 형태는 자유롭게 구성하세요.
  • 예시
    • 조회화면에 맵기, 가격대 버튼을 만들고 버튼 클릭시 해당 메뉴 출력 가능하게 처리 가능
    • 혹은 <select> 의 option으로 맵기, 가격대 를 넣어도 됨

 

코드 구성

  • index.html (UI 훅)
    • 목록 화면 상단에 <select id="filter-spicy">, <select id="filter-priceband"> 두 개를 둬서 선택값을 받음
  • controller.js (필터 로직 전담)
    1. 상태 보관: filterState = { spicy: "", priceBand: "" }
    2. 가격대 판단: inBand(price, band)
    3. 적용 함수: applyFilter(list) → AND 조건으로 필터 

필터 기준

  • 맵기(Spicy): 선택이 비어있지 않으면 m.spicy === filterState.spicy 인 항목만 남김. (예: “매움”만 보기) 
  • 가격대(PriceBand): inBand(price, band) 로 구간 판정. 현재 구간은 다음처럼 설정됨. 
    • low : price <= 10,000
    • mid : 10,000 <= price <= 30,000
    • high: 30,000 <= price <= 50,000
    • very high: price > 50,000
  • 논리: 맵기 AND 가격대 (둘 다 만족하는 항목만 표시). applyFilter()에서 한 번에 처리. 

☠️ 트러블 슈팅

  • 발생한 Issue 
    • 첫 화면이 비어있거나, 목록, 등록 화면이나 하위 등록 폼 등이 나타나지 않음
      • 등록 화면을 초기화면으로 구성해야 하나 목록화면이 구성되는 등의 상황이 발생

 

  • 해결방법 / 찾아본 내용
    • 해결방법
      • CSS에서 규칙과 html의 규칙이 엇갈리면서 코드가 정상적으로 실행되지 않는 현상이 있었음
        •  
  • 💡 찾아본 내용 
    • SPA의 개념 
      • Single Page Application(단일 페이지 애플리케이션)
        : 하나의 HTML 페이지에서 동작하는 웹 애플리케이션을 의미
    • SPA 방식
      • 최초에 index.html 1개와 필요한 리소스(JS, CSS)를 불러옴
      • 그 이후부터는 JavaScript가 동적으로 DOM을 바꾸면서 화면 전환
      • 페이지 이동은 실제 새로고침이 아니라, 클라이언트 라우팅으로 처리
    • SPA 핵심 특징
      • 한 번만 로드 : 초기 로딩 후에는 새로고침 없이 동작
      • 빠른 화면 전환 : 서버에서 HTML 전체를 다시 안 받아오기 때문에 빠름
      • 클라이언트 라우팅 : /#list, /#register 같은 해시 라우트 or history.pushState 사용
      • 데이터 중심 통신 : 서버와는 JSON 같은 데이터만 주고받고, 화면은 JS가 직접 그려줌
      • UX 개선 : 앱처럼 부드럽고 즉각적인 사용자 경험
    • SPA 형태의 화면 구성을 통해 한번의 접속으로 다양한 화면을 한 화면으로 보는 듯한 효과를 줄 수 있음

'JAVA > Java 실습' 카테고리의 다른 글

Lv3. Enum, 제네릭, 람다 & 스트림을 이해한 계산기 만들기  (0) 2025.09.18
[JAVA] 계산기 과제 개념 추가 정리  (0) 2025.09.16
MVC 기반 점심 메뉴 추천기  (0) 2025.09.10
JSP 자기소개 버전 만들기  (0) 2025.09.09
나를 소개합니다  (0) 2025.09.08
'JAVA/Java 실습' 카테고리의 다른 글
  • Lv3. Enum, 제네릭, 람다 & 스트림을 이해한 계산기 만들기
  • [JAVA] 계산기 과제 개념 추가 정리
  • MVC 기반 점심 메뉴 추천기
  • JSP 자기소개 버전 만들기
stark77
stark77
하마의 IT 자기개발 이모저모, 백엔드 개발자로 거듭나기
  • stark77
    하마의 개발자 성장일기
    stark77
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 컴퓨터구조와 운영체제
        • 컴퓨터구조
        • 운영체제
      • SQL 기초
      • Spring
        • 백엔드 기초
        • Spring 실습
      • JAVA
        • Java 실습
      • HTML&CSS
        • HTML&CSS 실습
      • Git&GitHub
        • Git&GitHub 실습
      • 내배캠 끄적끄적
        • Today I Learned
      • 유용한 툴 및 사이트 정리
      • 취미
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    네트워크 기초
    algorithm
    프로세스와 쓰레드
    for문
    Github
    Java 문법기초
    Spring
    객체지향프로그래밍
    웹소켓
    java
    RestTemplate
    백엔드 기초다지기
    백엔드 기초
    다형성
    JPA
    jsp
    Til
    객체지향
    Stomp
    Spriingboot
    WebSocket
    git
    경합조건과 교착상태
    MVC
    HTML&CSS
    thymleaf
    SpringSecurity
    BEAN
    실시간 데이터 처리
    String.format
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
stark77
점메추 SPA 에디션
상단으로

티스토리툴바