⛳️ 과제 목표
- 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에 저장)
- 어떻게
- 폼 제출 시 값 검증(빈 값/음수 방지).
- addMenu({ name, spicy, price })가 기존 목록을 읽고(id/createdAt 부여) 목록에 push → writeMenus로 "menus" 키에 저장.
- 저장 후 폼 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())
- 어떻게
- readMenus()로 목록을 읽음 → renderList에 전달.
- 데이터가 없으면 “등록된 메뉴가 없습니다.” 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 }) 호출
- 어떻게
- [수정] 클릭 → 수정 대상 id를 기억하고 등록 화면으로 이동.
- 등록 화면 진입 시 해당 아이템을 찾아 input/select에 값 주입.
- 제출 시 updateMenu로 기존 객체에 patch 적용 → 저장.
4. 메뉴 삭제 기능
더보기
- 목록에서 개별 메뉴 옆에 삭제 버튼을 만드세요.
- 삭제 버튼 클릭 시 해당 메뉴를 제거합니다.
- 어디
- 삭제 버튼 출력: view.js에서 각 행에 <button data-action="del" data-id="..."> 출력
- 클릭 처리: controller.js 의 #list-view 클릭 위임 핸들러에서 action === "del" 분기 → removeMenu(id) 호출 후 다시 렌더
- 실제 삭제: model.js 의 removeMenu(id) (필터로 제외 후 저장)
- 어떻게
- 이벤트 위임으로 어떤 행의 삭제 버튼인지 식별(data-id).
- 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 토글
- 어떻게
- 처음 진입하면 해시가 없을 때 **기본 #register**로 강제.
- 해시가 바뀔 때마다 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 (필터 로직 전담)
- 상태 보관: filterState = { spicy: "", priceBand: "" }
- 가격대 판단: inBand(price, band)
- 적용 함수: 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의 규칙이 엇갈리면서 코드가 정상적으로 실행되지 않는 현상이 있었음
- CSS에서 규칙과 html의 규칙이 엇갈리면서 코드가 정상적으로 실행되지 않는 현상이 있었음
- 해결방법
- 💡 찾아본 내용
- SPA의 개념
- Single Page Application(단일 페이지 애플리케이션)
: 하나의 HTML 페이지에서 동작하는 웹 애플리케이션을 의미
- Single Page Application(단일 페이지 애플리케이션)
- SPA 방식
- 최초에 index.html 1개와 필요한 리소스(JS, CSS)를 불러옴
- 그 이후부터는 JavaScript가 동적으로 DOM을 바꾸면서 화면 전환
- 페이지 이동은 실제 새로고침이 아니라, 클라이언트 라우팅으로 처리
- SPA 핵심 특징
- 한 번만 로드 : 초기 로딩 후에는 새로고침 없이 동작
- 빠른 화면 전환 : 서버에서 HTML 전체를 다시 안 받아오기 때문에 빠름
- 클라이언트 라우팅 : /#list, /#register 같은 해시 라우트 or history.pushState 사용
- 데이터 중심 통신 : 서버와는 JSON 같은 데이터만 주고받고, 화면은 JS가 직접 그려줌
- UX 개선 : 앱처럼 부드럽고 즉각적인 사용자 경험
- SPA 형태의 화면 구성을 통해 한번의 접속으로 다양한 화면을 한 화면으로 보는 듯한 효과를 줄 수 있음
- 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 |