-
Notifications
You must be signed in to change notification settings - Fork 89
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
[2단계 - 음식점 목록] 가브리엘(윤주현) 미션 제출합니다. #58
Changes from 158 commits
3ed37ec
bdb0d7a
0461b55
8d5315e
82ad0fc
a1003f8
1fb402c
cabae0e
f0ae2a3
656cd0b
7bcf414
e6c2362
aad5bbb
54e14f7
e665a08
df19090
593cad3
e7d3a30
1647117
e4163c2
98cf052
aa590a4
fc9fc20
80d20b3
394586e
2e3721a
b59b412
45a8508
a0c8cc6
3856fdf
e496eeb
39439bc
4eabead
155f2a3
965c108
29e3c2d
022c559
9aa353e
d73eb64
e4b71ee
3b3f57b
022cab8
7ce6c68
d7168de
cdb78fc
23e7fca
bbfb993
44a8312
2cfea7b
c723169
915edf1
3b9bb24
0aa1de1
f4ee89a
c851dd7
0e710cf
ac867a1
c3e4dbf
980834a
5614ae4
900423a
52d4adc
07efc8a
bdf5592
fe17f26
4b55356
09c1c9a
211007d
0054c07
79f8890
052bff0
fdccfc7
5f2662f
c9608c0
9b1887d
6c847b9
f2f40e1
51d9e33
cc932e5
5a8df6d
a1d5e26
9a1c6ca
4b17561
bac4952
6522005
cacf940
ab3367a
5ca0ed3
5d807f0
290b677
87e073f
f6c2666
8d36ebc
2a68f1f
28cc47d
f673de3
e238922
c371414
43e929c
e726a2f
2ebc2ae
1dab34c
1cedb76
f827af7
21b518c
22eeabe
2a938d3
e973d04
a91bff4
ab2cfee
a31f552
a2d1a55
bc9090a
c367915
af1a9d9
f4a8cd2
c647406
c0be794
25fb9d4
5737888
6d9fd25
eacee08
d568fe5
ce3a8a7
2d060b4
f6817cf
dfae17a
70ada35
e6ccb03
0725292
8c0b83c
b92f610
870682e
0f6d8aa
ec13a7e
91d40eb
7e212f5
97e9e02
77ffd5a
f169db8
9089404
411c1db
de52f56
a04c950
2ef7d73
a8a15b7
63eac7d
c28becf
34655f5
c5f7b28
09337d0
bee332f
b279ca1
6593966
ee53ead
61b6d94
59c114f
0883f22
5b8ffaf
9e3f3bf
380eb14
604a2c4
fd1c57c
ea9d5ce
1df668e
63641f0
0944818
b64d2f8
c46a902
0bd3a91
5718c64
b294d8a
12bd126
2e61eb8
e91ff3a
b91ec37
8a1ceaa
92ae053
a6b7aaf
b5c9d80
a4b0cd3
a485a6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,91 +1,53 @@ | ||
## 점심 뭐먹지 기능 요구 사항 | ||
|
||
- [ ] 음식점 목록을 확인할 수 있다. | ||
- [ ] 카테고리별로 필터링해서 확인할 수 있다. | ||
- [ ] 이름순으로 정렬해서 확인할 수 있다. | ||
- [ ] 거리순으로 정렬해서 확인할 수 있다. | ||
- [ ] 음식점 목록에 새로운 음식점을 추가할 수 있다. | ||
- [ ] 음식점의 카테고리, 이름, 거리(도보 이동 시간), 설명, 참고 링크를 입력해서 추가할 수 있다. | ||
- [ ] 카테고리, 거리는 셀렉트 박스, 이름/설명/참고 링크는 텍스트 인풋을 사용한다. | ||
- [ ] 카테고리, 이름, 거리는 입력 필수. | ||
- [ ] 카테고리는 "한식", "중식", "일식", "아시안", "양식", "기타" 중 하나를 선택한다. | ||
- [ ] 거리는 캠퍼스로부터 도보로 걸리는 시간(분). 5, 10, 15, 20, 30 중 하나를 선택한다. | ||
- [ ] 설명, 참고 링크는 옵션. 입력하지 않아도 음식점을 추가할 수 있어야 한다. | ||
- [ ] 입력값이 잘못되었을 때 사용자에게 알려주는 방식은 자유롭게 구현한다. | ||
- [ ] 새로고침해도 추가한 음식점 정보들이 유지되어야 한다. | ||
- [x] 음식점 목록을 확인할 수 있다. | ||
- [x] 카테고리별로 필터링해서 확인할 수 있다. | ||
- [x] 이름순으로 정렬해서 확인할 수 있다. | ||
- [x] 거리순으로 정렬해서 확인할 수 있다. | ||
- [x] 음식점 목록에 새로운 음식점을 추가할 수 있다. | ||
- [x] 음식점의 카테고리, 이름, 거리(도보 이동 시간), 설명, 참고 링크를 입력해서 추가할 수 있다. | ||
- [x] 카테고리, 거리는 셀렉트 박스, 이름/설명/참고 링크는 텍스트 인풋을 사용한다. | ||
- [x] 카테고리, 이름, 거리는 입력 필수. | ||
- [x] 카테고리는 "한식", "중식", "일식", "아시안", "양식", "기타" 중 하나를 선택한다. | ||
- [x] 거리는 캠퍼스로부터 도보로 걸리는 시간(분). 5, 10, 15, 20, 30 중 하나를 선택한다. | ||
- [x] 설명, 참고 링크는 옵션. 입력하지 않아도 음식점을 추가할 수 있어야 한다. | ||
- [x] 입력값이 잘못되었을 때 사용자에게 알려주는 방식은 자유롭게 구현한다. | ||
- [x] 새로고침해도 추가한 음식점 정보들이 유지되어야 한다. | ||
- [x] 음식점 상세 정보를 확인할 수 있다. | ||
- [x] 카테고리, 이름, 거리, 설명, 참고 링크를 확인할 수 있다. | ||
- [x] 음식점을 삭제할 수 있다. | ||
- [x] 자주 가는 음식점을 추가하고 목록으로 확인할 수 있다. | ||
- [x] 음식점 목록에서 자주 가는 음식점을 추가할 수 있다. | ||
- [x] 음식점 상세 정보에서 자주 가는 음식점으로 추가할 수 있다. | ||
- [x] 자주 가는 음식점 탭에서 추가한 음식점 목록을 확인할 수 있다. | ||
- [x] 새로고침해도 추가한 정보들이 유지되어야 한다. | ||
|
||
--- | ||
|
||
## 점심 뭐먹지 src 폴더 구조 및 역할 목록 | ||
|
||
# components | ||
|
||
: 독립적이고 재사용가능한 ui요소들을 분리하고 정의한다. | ||
|
||
1. NavBar | ||
|
||
2. AddRestaurant | ||
|
||
- 레스토랑 추가를 위한 ui와 인풋 태그들을 가진다. | ||
|
||
3. BottomSheet | ||
|
||
- 위로 올라갔다가 내려가는 애니메이션 효과를 가진다. | ||
|
||
4. RestaurantItem | ||
|
||
- 레스토랑 이름, 거리, 설명, 카테고리 정보를 담는다. | ||
|
||
5. RestaurantList | ||
|
||
- RestaurantItem을 리스트로 나열한다. | ||
|
||
6. CategorySelectBox | ||
|
||
- 카테고리를 선택한다. | ||
|
||
7. SortingSelectBox | ||
|
||
- 정렬순서를 선택한다. | ||
|
||
# domain | ||
|
||
## Controller.ts | ||
|
||
- restaurant 객체들을 반환한다. | ||
- 새로운 restaurant 객체를 추가한다. | ||
- restaurant 객체들을 localStorage에 저장한다. | ||
- localSorage에 있는 정보를 불러온다. | ||
- restaurant 객체들을 이름순 및 거리순으로 정렬한다. | ||
- restaurant 객체들을 카테고리별로 필터링한다. | ||
|
||
## model | ||
|
||
1. Restaurant | ||
|
||
- restaurant 객체를 올바르게 생성한다.(inputValidator를 호출하며) | ||
|
||
## inputValidator | ||
|
||
- 카테고리, 이름, 거리(도보 이동 시간), 설명, 참고 링크에 대한 유효성을 검사한다. | ||
|
||
# index.js | ||
|
||
- customElement들을 정의한다. | ||
|
||
## 구현 순서 | ||
|
||
[1] | ||
NavBar => BottomSheet => form => 음식점 추가하는 기능 완성 | ||
|
||
[2] | ||
음식점 리스트를 영구저장 기능 + 복구하는 기능 | ||
|
||
# 리팩토링 리스트 | ||
|
||
// any 없애기 | ||
// 은닉화 | ||
// util함수 | ||
// bottomSheet 열고 닫을 때, 위에 겹겹이 쌓이는 버그 존재 | ||
// map 안의 new 없애기 | ||
// sortRestaurnat 함수 | ||
## 점심 뭐먹지 폴더 구조 및 역할 목록 | ||
|
||
``` | ||
. | ||
├─src | ||
│ ├─assets | ||
│ ├─components | ||
│ │ ├─AddRestaurant // 식당 정보를 추가하는 폼 | ||
│ │ ├─BottomSheet // 모달 | ||
│ │ ├─FavoriteButton // 즐겨찾기 버튼 | ||
│ │ ├─MenuTab // 즐겨찾기 모아보기 버튼 | ||
│ │ ├─RestaurantItem // 식당 리스트에 표기될 카드 | ||
│ │ ├─RestaurantList // 식당 리스트 | ||
│ │ ├─RestaurantView // 식당의 상세 정보를 표시 | ||
│ │ ├─CategoryImage // 카테고리에 맞는 이미지를 태그로 제작해주는 기능 | ||
│ │ ├─CategorySelectBox // 카테고리를 선택하는 박스 | ||
│ │ ├─NavBar // 헤더 | ||
│ │ └─SortingSelectBox // 정렬 기준을 선택하는 박스 | ||
│ ├─constants | ||
│ ├─css | ||
│ ├─domain | ||
│ │ ├─restaurant // 레스토랑 관련 도메인 로직 모음 | ||
│ │ └─restaurants // 레스토랑 데이터를 Proxy로 관리해주는 객체 | ||
│ ├─tools | ||
│ └─type | ||
└─index.html | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,24 @@ | ||
import BottomSheet from "."; | ||
|
||
export const onClickBackdrop = () => { | ||
const modalBackdrop = document.getElementById("modalBackdrop"); | ||
modalBackdrop?.addEventListener("click", () => { | ||
closeBottomSheet(); | ||
}); | ||
}; | ||
|
||
export const openBottomSheet = (children: string) => { | ||
const bottomSheet = document.getElementById("bottomSheet"); | ||
if (bottomSheet instanceof BottomSheet) { | ||
bottomSheet.classList.add("modal--open"); | ||
bottomSheet.render(children); | ||
} | ||
}; | ||
|
||
export const closeBottomSheet = () => { | ||
const bottomSheet = document.getElementById("bottomSheet"); | ||
if (bottomSheet instanceof BottomSheet) { | ||
bottomSheet.close(); | ||
bottomSheet.classList.remove("modal--open"); | ||
bottomSheet.render(""); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import findImage from "../tools/findImage"; | ||
|
||
export const CategoryImage = (category: string) => ` | ||
<img | ||
src="${findImage(category)}" | ||
alt="${category}" | ||
class="category-icon" | ||
> | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { updateFavorite } from "../../domain/restaurant"; | ||
import { restaurants } from "../../domain/restaurants"; | ||
import findImage from "../../tools/findImage"; | ||
import Storage from "../../tools/Storage"; | ||
import { renderRestaurantList } from "../RestaurantList/handleRestaurantList"; | ||
|
||
class FavoriteButton extends HTMLElement { | ||
restaurantId: string | null; | ||
favorite: string | null; | ||
constructor() { | ||
woowahan-cron marked this conversation as resolved.
Show resolved
Hide resolved
|
||
super(); | ||
this.restaurantId = this.getAttribute("restaurant-id"); | ||
this.favorite = JSON.parse(this.getAttribute("favorite") as string); | ||
this.render(); | ||
this.onClickFavoriteButton(this.restaurantId as string); | ||
} | ||
render() { | ||
this.innerHTML = ` | ||
<img | ||
src="${findImage(this.favorite ? "favoriteFilled" : "favoriteLined")}" | ||
alt="즐겨찾기 버튼" | ||
class="category-icon" | ||
> | ||
`; | ||
} | ||
|
||
static get observedAttributes() { | ||
return ["favorite"]; | ||
} | ||
|
||
attributeChangedCallback(name: string, oldValue: string, newValue: string) { | ||
this.favorite = JSON.parse(newValue); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. newValue는 오류가 나지 않을 것이란 보장을 받을 수 있나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 보장 받기 어렵습니다. 반영하겠습니다. |
||
this.render(); | ||
} | ||
|
||
onClickFavoriteButton(id: string) { | ||
this.addEventListener("click", (event) => { | ||
event.stopPropagation(); | ||
updateFavorite(id); | ||
const buttons = document.querySelectorAll( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✨ 깜짝 퀴즈
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙋♂️ 깜짝 대답querySelector은 조건에 따라 탐색한 엘리먼트 중 가장 먼저 등장한 엘리먼트 1개를 반환합니다. |
||
`.favorite-button-${this.restaurantId}` | ||
); | ||
buttons.forEach((button) => { | ||
button.setAttribute("favorite", `${!this.favorite}`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 웹 컴포넌트로 작성되는 부분이기는 하지만 favorite는 버튼이 가지는 기본 속성은 아닐 텐데요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 보내주신 자료를 토대로 버튼의 favorite 속성을 데이터 속성으로 표기해봤는데, 엘리먼트의 dataset을 통해 접근을 할 수 있다는 것이 유용한 것 같습니다. 예를 들면 제가 임의로 어트리뷰트 명을 짓게되면, 해당 네이밍이 표준인지 비표준인지 헷갈릴 수 있게 되는 문제가 있는데, 다만 이번 미션에서는 기존에 적용해놨던 어트리뷰트들이 어디서 쓰이는지 파악하기 어려워진 것 같아서 즐겨찾기 기능에만 적용해보겠습니다. |
||
}); | ||
}); | ||
} | ||
} | ||
export default FavoriteButton; |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,11 @@ | ||||||||
import { restaurants } from "../../domain/restaurants"; | ||||||||
|
||||||||
export const onChangeMenuTabs = () => { | ||||||||
const form = document.getElementById("menuTabForm"); | ||||||||
form?.addEventListener("change", (event) => { | ||||||||
if (event.target instanceof HTMLInputElement) { | ||||||||
const newMenu = event.target.value; | ||||||||
restaurants.state.menuTab = newMenu; | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. newMenu 값은 8번째 줄 이후로 참조되지 않는다면 굳이 선언하여 사용할 필요가 있을까요?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 새로 선택된 탭이라는 것을 명시하기 위해 newMenu를 선언했었는데, menuTab에 들어가는 것이 당연히 새로운 탭이라고 생각해보면 불필요한 조치였던 것 같습니다. |
||||||||
} | ||||||||
}); | ||||||||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { onChangeMenuTabs } from "./handleMenuTab"; | ||
|
||
class MenuTab extends HTMLElement { | ||
constructor() { | ||
super(); | ||
this.render(); | ||
onChangeMenuTabs(); | ||
} | ||
render() { | ||
this.innerHTML = ` | ||
<form id="menuTabForm" class="d-flex justify-content-between mx-2"> | ||
<label class="tab-menu w-100 text-center py-1"> | ||
<input type="radio" name="tab-menu" value="tab-all" checked> | ||
모든 음식점 | ||
</label> | ||
<label class="tab-menu w-100 text-center py-1"> | ||
<input type="radio" name="tab-menu" value="tab-favorites"> | ||
자주 가는 음식점 | ||
</label> | ||
</form> | ||
`; | ||
} | ||
} | ||
export default MenuTab; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여닫는 태그 사이에 아무것도 존재하지 않으면
<tagName />
과 같이 간결하게 사용할 수도 있답니다!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
step1에서
<tagName />
을 사용하는 경우 오류가 발생하는 경우가 있어서<tagName></tagName >
으로 표기했습니다. insertAdjacentHtml으로 모달 내부를 그려주는 과정에서 잘못 된 위치에서 태그가 닫히는 등 여러 문제가 있었습니다 ㅠㅠ 지금은 insertAdjacentHtml을 제거하여 해당 문제가 없긴 하지만 당시에 그 원인이<tagName />
표기법 때문인지를 확신하기 어려워서<tagName></tagName >
로 교체하고 innerHtml을 쓰는 것으로 문제를 해결하였습니다.gabrielyoon7@394586e
gabrielyoon7@d73eb64