Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[14기 이하은] step2 상태 관리로 메뉴 관리하기 #287

Open
wants to merge 6 commits into
base: Leehaeun0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@

## 🎯 step2 요구사항 - 상태 관리로 메뉴 관리하기

- [ ] [localStorage](https://developer.mozilla.org/ko/docs/Web/API/Window/localStorage)에 데이터를 저장하여 새로고침해도 데이터가 남아있게 한다.
- [ ] 에스프레소, 프라푸치노, 블렌디드, 티바나, 디저트 각각의 종류별로 메뉴판을 관리할 수 있게 만든다.
- [ ] 페이지에 최초로 접근할 때는 에스프레소 메뉴가 먼저 보이게 한다.
- [ ] 품절 상태인 경우를 보여줄 수 있게, 품절 버튼을 추가하고 `sold-out` class를 추가하여 상태를 변경한다.
- [x] [localStorage](https://developer.mozilla.org/ko/docs/Web/API/Window/localStorage)에 데이터를 저장하여 새로고침해도 데이터가 남아있게 한다.
- [x] 에스프레소, 프라푸치노, 블렌디드, 티바나, 디저트 각각의 종류별로 메뉴판을 관리할 수 있게 만든다.
- [x] 페이지에 최초로 접근할 때는 에스프레소 메뉴가 먼저 보이게 한다.
- [x] 품절 상태인 경우를 보여줄 수 있게, 품절 버튼을 추가하고 `sold-out` class를 추가하여 상태를 변경한다.
- 품절 상태 메뉴의 마크업

```js
Expand Down
18 changes: 9 additions & 9 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<a href="/" class="text-black">
<h1 class="text-center font-bold">🌝 문벅스 메뉴 관리</h1>
</a>
<nav class="d-flex justify-center flex-wrap">
<nav id="cafe-category-name-list" class="d-flex justify-center flex-wrap">
<button
data-category-name="espresso"
class="cafe-category-name btn bg-white shadow mx-1"
Expand Down Expand Up @@ -52,33 +52,33 @@ <h1 class="text-center font-bold">🌝 문벅스 메뉴 관리</h1>
<main class="mt-10 d-flex justify-center">
<div class="wrapper bg-white p-10">
<div class="heading d-flex justify-between">
<h2 class="mt-1">☕ 에스프레소 메뉴 관리</h2>
<h2 id="category-title" class="mt-1">☕ 에스프레소 메뉴 관리</h2>
<span class="mr-2 mt-4 menu-count">총 0개</span>
</div>
<form id="espresso-menu-form">
<form id="menu-form">
<div class="d-flex w-100">
<label for="espresso-menu-name" class="input-label" hidden>
에스프레소 메뉴 이름
<label for="menu-name" class="input-label" hidden>
메뉴 이름
</label>
<input
type="text"
id="espresso-menu-name"
id="menu-name"
name="espressoMenuName"
class="input-field"
placeholder="에스프레소 메뉴 이름"
placeholder="메뉴 이름"
autocomplete="off"
/>
<button
type="submit"
name="submit"
id="espresso-menu-submit-button"
id="menu-submit-button"
class="input-submit bg-green-600 ml-2"
>
확인
</button>
</div>
</form>
<ul id="espresso-menu-list" class="mt-3 pl-0"></ul>
<ul id="menu-list" class="mt-3 pl-0"></ul>
</div>
</main>
</div>
Expand Down
214 changes: 135 additions & 79 deletions src/js/index.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,137 @@
const $input = document.getElementById('espresso-menu-name');
const $form = document.getElementById('espresso-menu-form');

// menuList = [{name: string, category: string}]
let menuList = [];
let category = 'espresso';

// events
const editMenu = ({target}) => {
if (!target.classList.contains('menu-edit-button')) return;
const name = window.prompt('메뉴명을 수정하세요');
if (!name) return;

const menuId = Number(target.parentElement.dataset.menuId);
menuList = menuList.map((menu, index) => index === menuId ? {...menu, name} : menu);
renderAboutMenus();
};

const removeMenu = ({target}) => {
if (!target.classList.contains('menu-remove-button')) return;
if (!window.confirm('정말 삭제하시겠습니까?')) return;

const menuId = Number(target.parentElement.dataset.menuId);
menuList = menuList.filter((_, index) => index !== menuId);
renderAboutMenus();
};

const addMenuList = (name) => {
if (!name) return;
menuList = [...menuList, {name, category}];
renderAboutMenus();
$input.value = '';
};

// addEventListeners
$form.addEventListener('submit', (e) => {
e.preventDefault();
addMenuList($input.value);
});

const addEventListenersToMenuList = () => {
const $menu = document.getElementById('espresso-menu-list');
$menu.addEventListener('click', editMenu);
$menu.addEventListener('click', removeMenu);
import { $, $id } from './utils/dom.js';
import LocalStorage from './utils/localStorage.js'

function App () {
const MenuListStorage = new LocalStorage('menuList');

// states
// [{name: string; category: string; isSoldOut: boolean;}]
let menuList = MenuListStorage.get() ?? [];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?? 라는게 있었네요! 저는 || 를 쓰는데 앞으로는 ??를 써야겠어요! 감사합니다~!😀

let category = 'espresso';

window.onload = () => {
initEventListenes();
renderAboutMenus();
};

// utils
const getMenuId = (target) => {
return Number(target.parentElement.dataset.menuId);
};

// events
const addMenu = () => {
let name = $id('menu-name').value?.trim();
if (!name) return;

menuList = [...menuList, {name, category}];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서
{name, category} 값만을 추가해주는 건
처음 생성한 메뉴의 default 상태가 품절이지 않고,
undefined 값일 경우 falsy 처리되기 때문일까요?

MenuListStorage.set(menuList);
renderAboutMenus();
$id('menu-name').value = '';
};

const editMenu = ({target}) => {
const name = window.prompt('메뉴명을 수정하세요').trim();
if (!name) return;

const menuId = getMenuId(target);
menuList = menuList.map((menu, index) => index === menuId ? {...menu, name} : menu);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

menuList를 관리하는 객체를 만들어 보는건 어떨까요~? 😀

MenuListStorage.set(menuList);
renderAboutMenus();
};

const removeMenu = ({target}) => {
if (!window.confirm('정말 삭제하시겠습니까?')) return;

const menuId = getMenuId(target);
menuList = menuList.filter((_, index) => index !== menuId);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

menuList를 변경하고, 로컬스토리지에 저장하는 작업이 반복적으로 사용이 되고있는데요, 함수로 따로 관리한다면 더 좋을것 같아요!

MenuListStorage.set(menuList);
renderAboutMenus();
};

const toggleIsSoldOut = ({target}) => {
const menuId = getMenuId(target);
menuList = menuList.map((menu, index) =>
index === menuId ? {...menu, isSoldOut: !menu.isSoldOut} : menu,
Copy link

@bingwer bingwer Jul 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

editMenu(), removeMemu(), toggleIsSoldOut() 이 함수들에서
menuList를 변경하는부분에서 category를 확인하는 부분이 빠져있는데요.
이렇게되면 각 카테고리별로 중복된 이름의 메뉴가 존재한다면 해당 함수의 작업들이 사용자가 원하는 카테고리의 메뉴가 아니라
menuList에서 index가 가장 빠른 메뉴가 수정, 삭제, 품절이되는 버그가 발생할수 있을것 같아요!

);
MenuListStorage.set(menuList);
renderAboutMenus();
};

const setCategory = ({target}) => {
const selectedCategory = target.dataset.categoryName;
category = selectedCategory;
Comment on lines +62 to +63

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const selectedCategory = target.dataset.categoryName;
category = selectedCategory;
category = target.dataset.categoryName;

이렇게 선언하지 않은건 selectedCategory 라는 변수 이름을 강조하고 싶으셨던건지 궁금해요~!

$id('category-title').textContent = `${target.textContent} 메뉴 관리`;
renderAboutMenus();
};

// addEventListeners
const initEventListenes = () => {
const $form = $id('menu-form');
$form.addEventListener('submit', (e) => {
e.preventDefault();
addMenu();
});

const $menu = $id('menu-list');
$menu.addEventListener('click', (e) => {
switch (e.target.dataset.action) {
case 'edit':
editMenu(e);
return;
case 'remove':
removeMenu(e);
return;
case 'sold-out':
toggleIsSoldOut(e);
return;
}
});

const $categoryList = $id('cafe-category-name-list');
$categoryList.addEventListener('click', setCategory);
};

// renders
const renderAboutMenus = () => {
const menuListTemplate = menuList
.filter(menu => menu.category === category)
.map((menu, index) => {
return `
<li data-menu-id="${index}" class="menu-list-item d-flex items-center py-2">
<span class="w-100 pl-2 menu-name${menu.isSoldOut ? ' sold-out' : ''}">${menu.name}</span>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"sold-out" class 앞의 공백을 삼항연산자 안이 아니라
템플릿 리터럴 안에 넣으면 조금더 가독성에 좋을 것 같아요!

Suggested change
<span class="w-100 pl-2 menu-name${menu.isSoldOut ? ' sold-out' : ''}">${menu.name}</span>
<span class="w-100 pl-2 menu-name ${menu.isSoldOut ? 'sold-out' : ''}">${menu.name}</span>

<button
type="button"
data-action="sold-out"
class="bg-gray-50 text-gray-500 text-sm mr-1 menu-sold-out-button"
>
품절
</button>
<button
type="button"
data-action="edit"
class="bg-gray-50 text-gray-500 text-sm mr-1 menu-edit-button"
>
수정
</button>
<button
type="button"
data-action="remove"
class="bg-gray-50 text-gray-500 text-sm menu-remove-button"
>
삭제
</button>
</li>
`
});
$id('menu-list').innerHTML = menuListTemplate.join('');

renderMenuCount(menuListTemplate.length);
};

const renderMenuCount = (count) => {
$('.menu-count').textContent = `총 ${count}개`;
};
}

// renders
const renderMenuList = () => {
const $menuList = document.getElementById('espresso-menu-list');
const menuListItemElements = menuList.map((menu, index) => {
return `
<li data-menu-id="${index}" class="menu-list-item d-flex items-center py-2">
<span class="w-100 pl-2 menu-name">${menu.name}</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>
`
});
$menuList.innerHTML = menuListItemElements.join('');
addEventListenersToMenuList();
};

const renderMenuCount = () => {
const $menuCount = document.querySelector('.menu-count');
$menuCount.textContent = `총 ${menuList.length}개`;
};

const renderAboutMenus = () => {
renderMenuList();
renderMenuCount();
};
App();
3 changes: 3 additions & 0 deletions src/js/utils/dom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const $ = (selector) => document.querySelector(selector);

export const $id = (id) => document.getElementById(id);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$(#id)나, $(.class) 로 위의 선언하신 두가지 유틸함수를 사용할 수 있을것 같습니다!
혹시 두가지를 나누신 이유는 성능상의 차이를 구분하기 위해서 나누신 걸까요?

18 changes: 18 additions & 0 deletions src/js/utils/localStorage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export default class LocalStorage {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스토리지가 깔끔하게 분리되어 있네요! 👍

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 단순히 util함수로 만들어서 로컬스토리지를 관리했는데 이 방법도 좋은것 같아요!👍


constructor(key) {
this.key = key;
}

get () {
return JSON.parse(localStorage.getItem(this.key));
}

set (value) {
return localStorage.setItem(this.key, JSON.stringify(value));
}

remove (){
localStorage.removeItem(this.key);
}
}