diff --git a/MISSION.md b/MISSION.md new file mode 100644 index 000000000..e97a1d649 --- /dev/null +++ b/MISSION.md @@ -0,0 +1,114 @@ +# πŸš‡ μ§€ν•˜μ²  노선도 λ―Έμ…˜ + +## πŸš€ κΈ°λŠ₯ μš”κ΅¬μ‚¬ν•­ + +### μ§€ν•˜μ²  μ—­ κ΄€λ ¨ κΈ°λŠ₯ +- μ§€ν•˜μ²  역을 λ“±λ‘ν•˜κ³  μ‚­μ œν•  수 μžˆλ‹€. (단, 노선에 λ“±λ‘λœ 역은 μ‚­μ œν•  수 μ—†λ‹€) +- μ€‘λ³΅λœ μ§€ν•˜μ²  μ—­ 이름이 등둝될 수 μ—†λ‹€. +- μ§€ν•˜μ²  역은 2κΈ€μž 이상이어야 ν•œλ‹€. +- μ§€ν•˜μ²  μ—­μ˜ λͺ©λ‘μ„ μ‘°νšŒν•  수 μžˆλ‹€. + +### μ§€ν•˜μ²  λ…Έμ„  κ΄€λ ¨ κΈ°λŠ₯ +- μ§€ν•˜μ²  노선을 λ“±λ‘ν•˜κ³  μ‚­μ œν•  수 μžˆλ‹€. +- μ€‘λ³΅λœ μ§€ν•˜μ²  λ…Έμ„  이름이 등둝될 수 μ—†λ‹€. +- λ…Έμ„  등둝 μ‹œ 상행 쒅점역과 ν•˜ν–‰ 쒅점역을 μž…λ ₯λ°›λŠ”λ‹€. +- μ§€ν•˜μ²  λ…Έμ„ μ˜ λͺ©λ‘μ„ μ‘°νšŒν•  수 μžˆλ‹€. + +### μ§€ν•˜μ²  ꡬ간 μΆ”κ°€ κΈ°λŠ₯ +- μ§€ν•˜μ²  노선에 ꡬ간을 μΆ”κ°€ν•˜λŠ” κΈ°λŠ₯은 노선에 역을 μΆ”κ°€ν•˜λŠ” κΈ°λŠ₯이라고도 ν•  수 μžˆλ‹€. + - μ—­κ³Ό 역사이λ₯Ό ꡬ간이라 ν•˜κ³  이 κ΅¬κ°„λ“€μ˜ λͺ¨μŒμ΄ 노선이닀. +- ν•˜λ‚˜μ˜ 역은 μ—¬λŸ¬κ°œμ˜ 노선에 좔가될 수 μžˆλ‹€. +- μ—­κ³Ό μ—­ 사이에 μƒˆλ‘œμš΄ 역이 μΆ”κ°€ 될 수 μžˆλ‹€. +- λ…Έμ„ μ—μ„œ κ°ˆλž˜κΈΈμ€ 생길 수 μ—†λ‹€. + + + +### μ§€ν•˜μ²  ꡬ간 μ‚­μ œ κΈ°λŠ₯ +- 노선에 λ“±λ‘λœ 역을 μ œκ±°ν•  수 μžˆλ‹€. +- 쒅점을 μ œκ±°ν•  경우 λ‹€μŒ 역이 쒅점이 λœλ‹€. +- 노선에 ν¬ν•¨λœ 역이 λ‘κ°œ μ΄ν•˜μΌ λ•ŒλŠ” 역을 μ œκ±°ν•  수 μ—†λ‹€. + + + +### μ§€ν•˜μ²  노선에 λ“±λ‘λœ μ—­ 쑰회 κΈ°λŠ₯ +- λ…Έμ„ μ˜ 상행 쒅점뢀터 ν•˜ν–‰ μ’…μ κΉŒμ§€ μ—°κ²°λœ μˆœμ„œλŒ€λ‘œ μ—­ λͺ©λ‘μ„ μ‘°νšŒν•  수 μžˆλ‹€. + +
+ +## πŸ’» ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ κ²°κ³Ό + +### 역관리 + + +### 노선관리 + + +### ꡬ간관리 + + +### 노선도 좜λ ₯ + + + +## βœ… ν”„λ‘œκ·Έλž˜λ° μš”κ΅¬μ‚¬ν•­ + +### 메뉴 λ²„νŠΌ +- μ—­ 관리 button νƒœκ·ΈλŠ” `#station-manager-button` id값을 κ°€μ§„λ‹€. +- λ…Έμ„  관리 button νƒœκ·ΈλŠ” `#line-manager-button` id값을 κ°€μ§„λ‹€. +- ꡬ간 관리 button νƒœκ·ΈλŠ” `#section-manager-button` id값을 κ°€μ§„λ‹€. +- μ§€ν•˜μ²  노선도 좜λ ₯ 관리 button νƒœκ·ΈλŠ” `#map-print-manager-button` id값을 κ°€μ§„λ‹€. + +### μ§€ν•˜μ²  μ—­ κ΄€λ ¨ κΈ°λŠ₯ +- μ§€ν•˜μ²  역을 μž…λ ₯ν•˜λŠ” input νƒœκ·ΈλŠ” `#station-name-input` id값을 κ°€μ§„λ‹€. +- μ§€ν•˜μ²  역을 μΆ”κ°€ν•˜λŠ” button νƒœκ·ΈλŠ” `#station-add-button` id값을 κ°€μ§„λ‹€. +- μ§€ν•˜μ²  역을 μ‚­μ œν•˜λŠ” button νƒœκ·ΈλŠ” `.station-delete-button` class값을 κ°€μ§„λ‹€. + +### μ§€ν•˜μ²  λ…Έμ„  κ΄€λ ¨ κΈ°λŠ₯ +- μ§€ν•˜μ²  λ…Έμ„ μ˜ 이름을 μž…λ ₯ν•˜λŠ” input νƒœκ·ΈλŠ” `#line-name-input` id값을 κ°€μ§„λ‹€. +- μ§€ν•˜μ²  λ…Έμ„ μ˜ 상행 쒅점을 μ„ νƒν•˜λŠ” select νƒœκ·ΈλŠ” `#line-start-station-selector` id값을 κ°€μ§„λ‹€. +- μ§€ν•˜μ²  λ…Έμ„ μ˜ ν•˜ν–‰ 쒅점을 μ„ νƒν•˜λŠ” select νƒœκ·ΈλŠ” `#line-end-station-selector` id값을 κ°€μ§„λ‹€. +- μ§€ν•˜μ²  노선을 μΆ”κ°€ν•˜λŠ” button νƒœκ·ΈλŠ” `#line-add-button` id값을 κ°€μ§„λ‹€. +- μ§€ν•˜μ²  노선을 μ‚­μ œν•˜λŠ” button νƒœκ·ΈλŠ” `.line-delete-button` class값을 κ°€μ§„λ‹€. + +### μ§€ν•˜μ²  ꡬ간 μΆ”κ°€ κΈ°λŠ₯ +- μ§€ν•˜μ²  노선을 μ„ νƒν•˜λŠ” button νƒœκ·ΈλŠ” `.section-line-menu-button` class값을 κ°€μ§„λ‹€. +- μ§€ν•˜μ²  ꡬ간을 μ„€μ •ν•  μ—­ select νƒœκ·ΈλŠ” `#section-station-selector` id값을 κ°€μ§„λ‹€. +- μ§€ν•˜μ²  κ΅¬κ°„μ˜ μˆœμ„œλ₯Ό μž…λ ₯ν•˜λŠ” input νƒœκ·ΈλŠ” `#section-order-input` id값을 κ°€μ§„λ‹€. +- μ§€ν•˜μ²  ꡬ간을 λ“±λ‘ν•˜λŠ” button νƒœκ·ΈλŠ” `#section-add-button` id값을 κ°€μ§„λ‹€. +- μ§€ν•˜μ²  ꡬ간을 μ œκ±°ν•˜λŠ” button νƒœκ·ΈλŠ” `.section-delete-button` class값을 κ°€μ§„λ‹€. + +### μ§€ν•˜μ²  노선도 좜λ ₯ κΈ°λŠ₯ +- μ§€ν•˜μ²  노선도 좜λ ₯ λ²„νŠΌμ„ λˆ„λ₯΄λ©΄ `
` νƒœκ·Έλ₯Ό λ§Œλ“€κ³  ν•΄λ‹Ή νƒœκ·Έ 내뢀에 노선도λ₯Ό 좜λ ₯ν•œλ‹€. + +### κΈ°μ‘΄ μš”κ΅¬μ‚¬ν•­ + +- μ‚¬μš©μžκ°€ 잘λͺ»λœ μž…λ ₯ 값을 μž‘μ„±ν•œ 경우 `alert`을 μ΄μš©ν•΄ λ©”μ‹œμ§€λ₯Ό 보여주고, μž¬μž…λ ₯ν•  수 있게 ν•œλ‹€. +- μ™ΈλΆ€ 라이브러리(jQuery, Lodash λ“±)λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³ , 순수 Vanilla JS둜만 κ΅¬ν˜„ν•œλ‹€. +- **μžλ°”μŠ€ν¬λ¦½νŠΈ μ½”λ“œ μ»¨λ²€μ…˜μ„ μ§€ν‚€λ©΄μ„œ ν”„λ‘œκ·Έλž˜λ°** ν•œλ‹€ + - [https://google.github.io/styleguide/jsguide.html](https://google.github.io/styleguide/jsguide.html) + - [https://ui.toast.com/fe-guide/ko_CODING-CONVENSION/](https://ui.toast.com/fe-guide/ko_CODING-CONVENTION) +- **indent(인덴트, λ“€μ—¬μ“°κΈ°) depthλ₯Ό 3이 λ„˜μ§€ μ•Šλ„λ‘ κ΅¬ν˜„ν•œλ‹€. 2κΉŒμ§€λ§Œ ν—ˆμš©**ν•œλ‹€. + - 예λ₯Ό λ“€μ–΄ whileλ¬Έ μ•ˆμ— if문이 있으면 λ“€μ—¬μ“°κΈ°λŠ” 2이닀. + - 힌트: indent(인덴트, λ“€μ—¬μ“°κΈ°) depthλ₯Ό μ€„μ΄λŠ” 쒋은 방법은 ν•¨μˆ˜(λ˜λŠ” λ©”μ†Œλ“œ)λ₯Ό λΆ„λ¦¬ν•˜λ©΄ λœλ‹€. +- **ν•¨μˆ˜(λ˜λŠ” λ©”μ†Œλ“œ)의 길이가 15라인을 λ„˜μ–΄κ°€μ§€ μ•Šλ„λ‘ κ΅¬ν˜„ν•œλ‹€.** + - ν•¨μˆ˜(λ˜λŠ” λ©”μ†Œλ“œ)κ°€ ν•œ κ°€μ§€ 일만 잘 ν•˜λ„λ‘ κ΅¬ν˜„ν•œλ‹€. +- λ³€μˆ˜ μ„ μ–Έμ‹œ `var` λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€. `const` 와 `let` 을 μ‚¬μš©ν•œλ‹€. + - [const](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/const) + - [let](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/let) +- `import` 문을 μ΄μš©ν•΄ 슀크립트λ₯Ό λͺ¨λ“ˆν™”ν•˜κ³  뢈러올 수 있게 λ§Œλ“ λ‹€. + - [https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/import](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/import) +- `template literal`을 μ΄μš©ν•΄ 데이터와 html string을 가독성 μ’‹κ²Œ ν‘œν˜„ν•œλ‹€. + - [https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals) + +### μΆ”κ°€λœ μš”κ΅¬μ‚¬ν•­ +- [data](https://developer.mozilla.org/ko/docs/Learn/HTML/Howto/%EB%8D%B0%EC%9D%B4%ED%84%B0_%EC%86%8D%EC%84%B1_%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0)속성을 ν™œμš©ν•˜μ—¬ html νƒœκ·Έμ— μ—­, λ…Έμ„ , κ΅¬κ°„μ˜ μœ μΌν•œ 데이터 값듀을 κ΄€λ¦¬ν•œλ‹€. +- [localStorage](https://developer.mozilla.org/ko/docs/Web/API/Window/localStorage)λ₯Ό μ΄μš©ν•˜μ—¬, μƒˆλ‘œκ³ μΉ¨ν•˜λ”λΌλ„ κ°€μž₯ μ΅œκ·Όμ— μž‘μ—…ν•œ 정보듀을 뢈러올 수 μžˆλ„λ‘ ν•œλ‹€. + +
+ +## πŸ“ λ―Έμ…˜ μ €μž₯μ†Œ 및 μ§„ν–‰ μš”κ΅¬μ‚¬ν•­ + +- λ―Έμ…˜μ€ [https://github.com/woowacourse/javascript-subway-map-precours](https://github.com/woowacourse/javascript-subway-map-precourse) μ €μž₯μ†Œλ₯Ό fork/cloneν•΄ μ‹œμž‘ν•œλ‹€. +- **κΈ°λŠ₯을 κ΅¬ν˜„ν•˜κΈ° 전에 javascript-subway-precourse/docs/README.md νŒŒμΌμ— κ΅¬ν˜„ν•  κΈ°λŠ₯ λͺ©λ‘**을 정리해 μΆ”κ°€ν•œλ‹€. +- **git의 commit λ‹¨μœ„λŠ” μ•ž λ‹¨κ³„μ—μ„œ README.md νŒŒμΌμ— μ •λ¦¬ν•œ κΈ°λŠ₯ λͺ©λ‘ λ‹¨μœ„λ‘œ μΆ”κ°€**ν•œλ‹€. +- [ν”„λ¦¬μ½”μŠ€ 과제 제좜](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) λ¬Έμ„œ 절차λ₯Ό 따라 λ―Έμ…˜μ„ μ œμΆœν•œλ‹€. diff --git a/README.md b/README.md index e97a1d649..33942b111 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,49 @@ # πŸš‡ μ§€ν•˜μ²  노선도 λ―Έμ…˜ -## πŸš€ κΈ°λŠ₯ μš”κ΅¬μ‚¬ν•­ +[μš°μ•„ν•œ ν…Œν¬ μ½”μŠ€ - 3κΈ°] ν”„λ¦¬μ½”μŠ€ 3μ£Όμ°¨ λ―Έμ…˜μ„ μ§„ν–‰ν•œ μ €μž₯μ†Œμž…λ‹ˆλ‹€. -### μ§€ν•˜μ²  μ—­ κ΄€λ ¨ κΈ°λŠ₯ -- μ§€ν•˜μ²  역을 λ“±λ‘ν•˜κ³  μ‚­μ œν•  수 μžˆλ‹€. (단, 노선에 λ“±λ‘λœ 역은 μ‚­μ œν•  수 μ—†λ‹€) -- μ€‘λ³΅λœ μ§€ν•˜μ²  μ—­ 이름이 등둝될 수 μ—†λ‹€. -- μ§€ν•˜μ²  역은 2κΈ€μž 이상이어야 ν•œλ‹€. -- μ§€ν•˜μ²  μ—­μ˜ λͺ©λ‘μ„ μ‘°νšŒν•  수 μžˆλ‹€. +[λ―Έμ…˜λ‚΄μš© ν™•μΈν•˜κΈ°](./MISSION.md) -### μ§€ν•˜μ²  λ…Έμ„  κ΄€λ ¨ κΈ°λŠ₯ -- μ§€ν•˜μ²  노선을 λ“±λ‘ν•˜κ³  μ‚­μ œν•  수 μžˆλ‹€. -- μ€‘λ³΅λœ μ§€ν•˜μ²  λ…Έμ„  이름이 등둝될 수 μ—†λ‹€. -- λ…Έμ„  등둝 μ‹œ 상행 쒅점역과 ν•˜ν–‰ 쒅점역을 μž…λ ₯λ°›λŠ”λ‹€. -- μ§€ν•˜μ²  λ…Έμ„ μ˜ λͺ©λ‘μ„ μ‘°νšŒν•  수 μžˆλ‹€. +## πŸš€ κ΅¬ν˜„ κΈ°λŠ₯ -### μ§€ν•˜μ²  ꡬ간 μΆ”κ°€ κΈ°λŠ₯ -- μ§€ν•˜μ²  노선에 ꡬ간을 μΆ”κ°€ν•˜λŠ” κΈ°λŠ₯은 노선에 역을 μΆ”κ°€ν•˜λŠ” κΈ°λŠ₯이라고도 ν•  수 μžˆλ‹€. - - μ—­κ³Ό 역사이λ₯Ό ꡬ간이라 ν•˜κ³  이 κ΅¬κ°„λ“€μ˜ λͺ¨μŒμ΄ 노선이닀. -- ν•˜λ‚˜μ˜ 역은 μ—¬λŸ¬κ°œμ˜ 노선에 좔가될 수 μžˆλ‹€. -- μ—­κ³Ό μ—­ 사이에 μƒˆλ‘œμš΄ 역이 μΆ”κ°€ 될 수 μžˆλ‹€. -- λ…Έμ„ μ—μ„œ κ°ˆλž˜κΈΈμ€ 생길 수 μ—†λ‹€. +각 κΈ°λŠ₯으둜 μ΄λ™ν•˜λŠ” navigator 화면을 κ΅¬μ„±ν•œλ‹€. - +### μ—­ 관리 -### μ§€ν•˜μ²  ꡬ간 μ‚­μ œ κΈ°λŠ₯ -- 노선에 λ“±λ‘λœ 역을 μ œκ±°ν•  수 μžˆλ‹€. -- 쒅점을 μ œκ±°ν•  경우 λ‹€μŒ 역이 쒅점이 λœλ‹€. -- 노선에 ν¬ν•¨λœ 역이 λ‘κ°œ μ΄ν•˜μΌ λ•ŒλŠ” 역을 μ œκ±°ν•  수 μ—†λ‹€. +- μ—­ 관리 화면을 κ΅¬μ„±ν•œλ‹€. +- μ €μž₯된 λͺ¨λ“  μ§€ν•˜μ²  μ—­ 이름을 μ‘°νšŒν•œλ‹€. +- μž…λ ₯받은 μ—­ 이름이 2κΈ€μž 이상인지 κ²€μ‚¬ν•œλ‹€. +- μž…λ ₯받은 μ—­ 이름이 기쑴에 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”μ§€ κ²€μ‚¬ν•œλ‹€. +- μž…λ ₯받은 μ—­ μ΄λ¦„μœΌλ‘œ μƒˆλ‘œμš΄ μ§€ν•˜μ²  역을 λ“±λ‘ν•œλ‹€. +- μ„ νƒλœ μ§€ν•˜μ²  역이 노선에 λ“±λ‘λ˜μ–΄μžˆλŠ”μ§€ κ²€μ‚¬ν•œλ‹€. +- μ§€ν•˜μ²  역을 μ‚­μ œν•œλ‹€. - +### λ…Έμ„  관리 -### μ§€ν•˜μ²  노선에 λ“±λ‘λœ μ—­ 쑰회 κΈ°λŠ₯ -- λ…Έμ„ μ˜ 상행 쒅점뢀터 ν•˜ν–‰ μ’…μ κΉŒμ§€ μ—°κ²°λœ μˆœμ„œλŒ€λ‘œ μ—­ λͺ©λ‘μ„ μ‘°νšŒν•  수 μžˆλ‹€. +- λ…Έμ„  관리 화면을 κ΅¬μ„±ν•œλ‹€. +- μ €μž₯된 λͺ¨λ“  μ§€ν•˜μ²  노선을 μ‘°νšŒν•œλ‹€. +- μž…λ ₯받은 λ…Έμ„  이름이 1κΈ€μž 이상인지 κ²€μ‚¬ν•œλ‹€. +- μž…λ ₯받은 λ…Έμ„  이름이 기쑴에 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”μ§€ κ²€μ‚¬ν•œλ‹€. +- μž…λ ₯받은 상행 쒅점역과 ν•˜ν–‰ 쒅점역이 λ™μΌν•˜μ§€ μ•Šμ€μ§€ κ²€μ‚¬ν•œλ‹€. +- μƒˆλ‘œμš΄ μ§€ν•˜μ²  노선을 λ“±λ‘ν•œλ‹€. +- μ‚¬μš©μž 확인 ν›„ μ§€ν•˜μ²  노선을 μ‚­μ œν•œλ‹€. -
+### ꡬ간 관리 -## πŸ’» ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ κ²°κ³Ό +- ꡬ간 관리 화면을 κ΅¬μ„±ν•œλ‹€. +- μ„ νƒλœ 노선에 λ“±λ‘λœ μ§€ν•˜μ²  역을 μ‘°νšŒν•œλ‹€. +- ꡬ간 등둝 μ‹œ 이미 λ“±λ‘λœ 역은 μ˜΅μ…˜μ— ν¬ν•¨ν•˜μ§€ μ•ŠλŠ”λ‹€. +- μž…λ ₯받은 ꡬ간 μˆœμ„œκ°€ μžμ—°μˆ˜μΈμ§€ κ²€μ‚¬ν•œλ‹€. +- μž…λ ₯받은 ꡬ간 μˆœμ„œ μˆ«μžκ°€ 기쑴에 λ“±λ‘λœ μ§€ν•˜μ²  수λ₯Ό μ΄ˆκ³Όν•˜μ§€ μ•ŠλŠ”μ§€ κ²€μ‚¬ν•œλ‹€. +- 노선에 μƒˆλ‘œμš΄ ꡬ간을 λ“±λ‘ν•œλ‹€. +- 노선에 λ“±λ‘λœ 역이 2개 μ΄ν•˜μΈμ§€ κ²€μ‚¬ν•œλ‹€. +- μ„ νƒλœ μ§€ν•˜μ²  역을 λ…Έμ„ μ—μ„œ μ‚­μ œν•œλ‹€. -### 역관리 - +### μ§€ν•˜μ²  노선도 좜λ ₯ -### 노선관리 - +- μ €μž₯λ˜μ–΄μžˆλŠ” μ§€ν•˜μ²  노선도λ₯Ό ν˜Έμ„ λ³„λ‘œ 화면에 좜λ ₯ν•œλ‹€. -### ꡬ간관리 - +## πŸ“ μ°Έκ³  μ‚¬μ΄νŠΈ -### 노선도 좜λ ₯ - - - -## βœ… ν”„λ‘œκ·Έλž˜λ° μš”κ΅¬μ‚¬ν•­ - -### 메뉴 λ²„νŠΌ -- μ—­ 관리 button νƒœκ·ΈλŠ” `#station-manager-button` id값을 κ°€μ§„λ‹€. -- λ…Έμ„  관리 button νƒœκ·ΈλŠ” `#line-manager-button` id값을 κ°€μ§„λ‹€. -- ꡬ간 관리 button νƒœκ·ΈλŠ” `#section-manager-button` id값을 κ°€μ§„λ‹€. -- μ§€ν•˜μ²  노선도 좜λ ₯ 관리 button νƒœκ·ΈλŠ” `#map-print-manager-button` id값을 κ°€μ§„λ‹€. - -### μ§€ν•˜μ²  μ—­ κ΄€λ ¨ κΈ°λŠ₯ -- μ§€ν•˜μ²  역을 μž…λ ₯ν•˜λŠ” input νƒœκ·ΈλŠ” `#station-name-input` id값을 κ°€μ§„λ‹€. -- μ§€ν•˜μ²  역을 μΆ”κ°€ν•˜λŠ” button νƒœκ·ΈλŠ” `#station-add-button` id값을 κ°€μ§„λ‹€. -- μ§€ν•˜μ²  역을 μ‚­μ œν•˜λŠ” button νƒœκ·ΈλŠ” `.station-delete-button` class값을 κ°€μ§„λ‹€. - -### μ§€ν•˜μ²  λ…Έμ„  κ΄€λ ¨ κΈ°λŠ₯ -- μ§€ν•˜μ²  λ…Έμ„ μ˜ 이름을 μž…λ ₯ν•˜λŠ” input νƒœκ·ΈλŠ” `#line-name-input` id값을 κ°€μ§„λ‹€. -- μ§€ν•˜μ²  λ…Έμ„ μ˜ 상행 쒅점을 μ„ νƒν•˜λŠ” select νƒœκ·ΈλŠ” `#line-start-station-selector` id값을 κ°€μ§„λ‹€. -- μ§€ν•˜μ²  λ…Έμ„ μ˜ ν•˜ν–‰ 쒅점을 μ„ νƒν•˜λŠ” select νƒœκ·ΈλŠ” `#line-end-station-selector` id값을 κ°€μ§„λ‹€. -- μ§€ν•˜μ²  노선을 μΆ”κ°€ν•˜λŠ” button νƒœκ·ΈλŠ” `#line-add-button` id값을 κ°€μ§„λ‹€. -- μ§€ν•˜μ²  노선을 μ‚­μ œν•˜λŠ” button νƒœκ·ΈλŠ” `.line-delete-button` class값을 κ°€μ§„λ‹€. - -### μ§€ν•˜μ²  ꡬ간 μΆ”κ°€ κΈ°λŠ₯ -- μ§€ν•˜μ²  노선을 μ„ νƒν•˜λŠ” button νƒœκ·ΈλŠ” `.section-line-menu-button` class값을 κ°€μ§„λ‹€. -- μ§€ν•˜μ²  ꡬ간을 μ„€μ •ν•  μ—­ select νƒœκ·ΈλŠ” `#section-station-selector` id값을 κ°€μ§„λ‹€. -- μ§€ν•˜μ²  κ΅¬κ°„μ˜ μˆœμ„œλ₯Ό μž…λ ₯ν•˜λŠ” input νƒœκ·ΈλŠ” `#section-order-input` id값을 κ°€μ§„λ‹€. -- μ§€ν•˜μ²  ꡬ간을 λ“±λ‘ν•˜λŠ” button νƒœκ·ΈλŠ” `#section-add-button` id값을 κ°€μ§„λ‹€. -- μ§€ν•˜μ²  ꡬ간을 μ œκ±°ν•˜λŠ” button νƒœκ·ΈλŠ” `.section-delete-button` class값을 κ°€μ§„λ‹€. - -### μ§€ν•˜μ²  노선도 좜λ ₯ κΈ°λŠ₯ -- μ§€ν•˜μ²  노선도 좜λ ₯ λ²„νŠΌμ„ λˆ„λ₯΄λ©΄ `
` νƒœκ·Έλ₯Ό λ§Œλ“€κ³  ν•΄λ‹Ή νƒœκ·Έ 내뢀에 노선도λ₯Ό 좜λ ₯ν•œλ‹€. - -### κΈ°μ‘΄ μš”κ΅¬μ‚¬ν•­ - -- μ‚¬μš©μžκ°€ 잘λͺ»λœ μž…λ ₯ 값을 μž‘μ„±ν•œ 경우 `alert`을 μ΄μš©ν•΄ λ©”μ‹œμ§€λ₯Ό 보여주고, μž¬μž…λ ₯ν•  수 있게 ν•œλ‹€. -- μ™ΈλΆ€ 라이브러리(jQuery, Lodash λ“±)λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³ , 순수 Vanilla JS둜만 κ΅¬ν˜„ν•œλ‹€. -- **μžλ°”μŠ€ν¬λ¦½νŠΈ μ½”λ“œ μ»¨λ²€μ…˜μ„ μ§€ν‚€λ©΄μ„œ ν”„λ‘œκ·Έλž˜λ°** ν•œλ‹€ - - [https://google.github.io/styleguide/jsguide.html](https://google.github.io/styleguide/jsguide.html) - - [https://ui.toast.com/fe-guide/ko_CODING-CONVENSION/](https://ui.toast.com/fe-guide/ko_CODING-CONVENTION) -- **indent(인덴트, λ“€μ—¬μ“°κΈ°) depthλ₯Ό 3이 λ„˜μ§€ μ•Šλ„λ‘ κ΅¬ν˜„ν•œλ‹€. 2κΉŒμ§€λ§Œ ν—ˆμš©**ν•œλ‹€. - - 예λ₯Ό λ“€μ–΄ whileλ¬Έ μ•ˆμ— if문이 있으면 λ“€μ—¬μ“°κΈ°λŠ” 2이닀. - - 힌트: indent(인덴트, λ“€μ—¬μ“°κΈ°) depthλ₯Ό μ€„μ΄λŠ” 쒋은 방법은 ν•¨μˆ˜(λ˜λŠ” λ©”μ†Œλ“œ)λ₯Ό λΆ„λ¦¬ν•˜λ©΄ λœλ‹€. -- **ν•¨μˆ˜(λ˜λŠ” λ©”μ†Œλ“œ)의 길이가 15라인을 λ„˜μ–΄κ°€μ§€ μ•Šλ„λ‘ κ΅¬ν˜„ν•œλ‹€.** - - ν•¨μˆ˜(λ˜λŠ” λ©”μ†Œλ“œ)κ°€ ν•œ κ°€μ§€ 일만 잘 ν•˜λ„λ‘ κ΅¬ν˜„ν•œλ‹€. -- λ³€μˆ˜ μ„ μ–Έμ‹œ `var` λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€. `const` 와 `let` 을 μ‚¬μš©ν•œλ‹€. - - [const](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/const) - - [let](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/let) -- `import` 문을 μ΄μš©ν•΄ 슀크립트λ₯Ό λͺ¨λ“ˆν™”ν•˜κ³  뢈러올 수 있게 λ§Œλ“ λ‹€. - - [https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/import](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/import) -- `template literal`을 μ΄μš©ν•΄ 데이터와 html string을 가독성 μ’‹κ²Œ ν‘œν˜„ν•œλ‹€. - - [https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals) - -### μΆ”κ°€λœ μš”κ΅¬μ‚¬ν•­ -- [data](https://developer.mozilla.org/ko/docs/Learn/HTML/Howto/%EB%8D%B0%EC%9D%B4%ED%84%B0_%EC%86%8D%EC%84%B1_%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0)속성을 ν™œμš©ν•˜μ—¬ html νƒœκ·Έμ— μ—­, λ…Έμ„ , κ΅¬κ°„μ˜ μœ μΌν•œ 데이터 값듀을 κ΄€λ¦¬ν•œλ‹€. -- [localStorage](https://developer.mozilla.org/ko/docs/Web/API/Window/localStorage)λ₯Ό μ΄μš©ν•˜μ—¬, μƒˆλ‘œκ³ μΉ¨ν•˜λ”λΌλ„ κ°€μž₯ μ΅œκ·Όμ— μž‘μ—…ν•œ 정보듀을 뢈러올 수 μžˆλ„λ‘ ν•œλ‹€. - -
- -## πŸ“ λ―Έμ…˜ μ €μž₯μ†Œ 및 μ§„ν–‰ μš”κ΅¬μ‚¬ν•­ - -- λ―Έμ…˜μ€ [https://github.com/woowacourse/javascript-subway-map-precours](https://github.com/woowacourse/javascript-subway-map-precourse) μ €μž₯μ†Œλ₯Ό fork/cloneν•΄ μ‹œμž‘ν•œλ‹€. -- **κΈ°λŠ₯을 κ΅¬ν˜„ν•˜κΈ° 전에 javascript-subway-precourse/docs/README.md νŒŒμΌμ— κ΅¬ν˜„ν•  κΈ°λŠ₯ λͺ©λ‘**을 정리해 μΆ”κ°€ν•œλ‹€. -- **git의 commit λ‹¨μœ„λŠ” μ•ž λ‹¨κ³„μ—μ„œ README.md νŒŒμΌμ— μ •λ¦¬ν•œ κΈ°λŠ₯ λͺ©λ‘ λ‹¨μœ„λ‘œ μΆ”κ°€**ν•œλ‹€. -- [ν”„λ¦¬μ½”μŠ€ 과제 제좜](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) λ¬Έμ„œ 절차λ₯Ό 따라 λ―Έμ…˜μ„ μ œμΆœν•œλ‹€. +- [[HTML λ°”λ‘œ μ•ŒκΈ°] Semantic Web을 μœ„ν•œ Semantic Elements - bono](https://blueshw.github.io/2020/05/09/know-html-semantic-elements/) +- [비ꡬ쑰화 ν• λ‹Ή(ꡬ쑰뢄해) 문법 - velopert](https://learnjs.vlpt.us/useful/06-destructuring.html) diff --git a/index.html b/index.html index fc99deac2..6f3862daa 100644 --- a/index.html +++ b/index.html @@ -3,11 +3,11 @@ μ§€ν•˜μ²  노선도 관리 + -
-

πŸš‡ μ§€ν•˜μ²  노선도 관리

-
+
+ diff --git a/src/components/App.js b/src/components/App.js new file mode 100644 index 000000000..03044cf06 --- /dev/null +++ b/src/components/App.js @@ -0,0 +1,159 @@ +import Header from "./Header.js"; +import Navigator from "./Navigator.js"; +import StationManager from "./StationManager.js"; +import LineManager from "./LineManager.js"; +import SectionManager from "./SectionManager.js"; +import localStorageManager from "../util/localStorage.js"; +import { ELEMENT_INFO, ERROR_MESSAGE, STORAGE_KEY } from "../util/constants.js"; +import SubwayMap from "./SubwayMap.js"; + +export default function App($app) { + this.$app = $app; + this.header = new Header({ $target: this.$app }); + + this.positionId = ""; + + this.onTogglePosition = (nextPositionId) => { + this.stationManager.setState({ nextIsShow: nextPositionId === this.stationManager.id }); + this.lineManager.setState({ + nextIsShow: nextPositionId === this.lineManager.id, + nextStations: this.stations, + }); + this.sectionManager.setState({ + nextIsShow: nextPositionId === this.sectionManager.id, + nextStations: this.stations, + }); + this.subwayMap.setState({ + nextIsShow: nextPositionId === this.subwayMap.id, + nextLines: this.lines, + }); + }; + + this.navigator = new Navigator({ + $target: this.$app, + onTogglePosition: this.onTogglePosition, + }); + + this.$main = document.createElement("main"); + this.$app.append(this.$main); + + this.stations = localStorageManager.getItem({ + key: STORAGE_KEY.station, + defaultValue: [], + }); + this.lines = localStorageManager.getItem({ + key: STORAGE_KEY.line, + defaultValue: [], + }); + + this.onAddStation = (stationName) => { + const nextStations = [...this.stations, stationName]; + this.setState({ nextStations }); + }; + + this.onDeleteStation = (stationIndex) => { + if (!this.isContaineLines(this.stations[stationIndex])) { + const nextStations = [...this.stations]; + nextStations.splice(stationIndex, 1); + this.setState({ nextStations }); + } + }; + + this.isContaineLines = (stationName) => { + const result = this.lines.find((line) => { + return line.stations.find((station) => station === stationName) ? true : false; + }); + + if (result) { + alert(ERROR_MESSAGE.notPossibleToDeleteStation); + return true; + } + + return false; + }; + + this.stationManager = new StationManager({ + id: ELEMENT_INFO.navigator[0].id, + $target: this.$main, + stations: this.stations, + isShow: false, + onAddStation: this.onAddStation, + onDeleteStation: this.onDeleteStation, + }); + + this.onAddLine = (line) => { + const nextLines = [...this.lines, line]; + this.setState({ nextLines }); + }; + + this.onDeleteLine = (lineIndex) => { + const nextLines = [...this.lines]; + nextLines.splice(lineIndex, 1); + + this.setState({ nextLines }); + }; + + this.lineManager = new LineManager({ + id: ELEMENT_INFO.navigator[1].id, + $target: this.$main, + isShow: false, + stations: this.stations, + lines: this.lines, + onAddLine: this.onAddLine, + onDeleteLine: this.onDeleteLine, + }); + + this.updateSection = (lineIndex, nextStations) => { + const nextLines = [...this.lines]; + nextLines.splice(lineIndex, 1, { ...nextLines[lineIndex], stations: nextStations }); + + this.setState({ nextLines }); + }; + + this.sectionManager = new SectionManager({ + id: ELEMENT_INFO.navigator[2].id, + $target: this.$main, + isShow: false, + stations: this.stations, + lines: this.lines, + updateSection: this.updateSection, + }); + + this.subwayMap = new SubwayMap({ + id: ELEMENT_INFO.navigator[3].id, + $target: this.$main, + isShow: false, + lines: this.lines, + }); + + this.setNextStations = (nextStations) => { + this.stations = nextStations; + + localStorageManager.setItem({ + key: STORAGE_KEY.station, + item: nextStations, + }); + }; + + this.setNextLines = (nextLines) => { + this.lines = nextLines; + + localStorageManager.setItem({ + key: STORAGE_KEY.line, + item: nextLines, + }); + }; + + this.setState = ({ nextStations, nextLines }) => { + if (nextStations) { + this.setNextStations(nextStations); + this.stationManager.setState({ nextStations: this.stations }); + } + + if (nextLines) { + this.setNextLines(nextLines); + this.lineManager.setState({ nextLines: this.lines }); + this.sectionManager.setState({ nextLines: this.lines }); + } + }; +} diff --git a/src/components/Header.js b/src/components/Header.js new file mode 100644 index 000000000..bcb12f86f --- /dev/null +++ b/src/components/Header.js @@ -0,0 +1,15 @@ +export default function Header({ $target }) { + this.$target = $target; + + this.render = () => { + const HTMLString = ` +
+

πŸš‡ μ§€ν•˜μ²  노선도 관리

+
+ `; + + this.$target.insertAdjacentHTML("afterbegin", HTMLString); + }; + + this.render(); +} diff --git a/src/components/LineInput.js b/src/components/LineInput.js new file mode 100644 index 000000000..24b36ebb9 --- /dev/null +++ b/src/components/LineInput.js @@ -0,0 +1,97 @@ +import { isTextOverMinLength } from "../util/validation.js"; +import { ELEMENT_INFO, LINE_NAME_MIN_LENGTH, ERROR_MESSAGE } from "../util/constants.js"; + +export default function LineInput({ $target, stations, isExistLineName, onAddLine }) { + const { lineNameInput, lineStartStationSelector, lineEndStationSelector, lineAddButton } = ELEMENT_INFO; + + this.$container = document.createElement("form"); + $target.append(this.$container); + + this.stations = stations; + this.onAddLine = onAddLine; + + this.$container.addEventListener("submit", (e) => { + const $lineNameInput = document.querySelector(`#${lineNameInput.id}`); + const startStationName = document.querySelector(`#${lineStartStationSelector.id}`).value; + const endStationName = document.querySelector(`#${lineEndStationSelector.id}`).value; + + if (this.isValidInput({ $lineNameInput, startStationName, endStationName })) { + this.onAddLine({ + name: $lineNameInput.value, + stations: [startStationName, endStationName], + }); + + e.target.reset(); + } + + e.preventDefault(); + }); + + this.isValidInput = ({ $lineNameInput, startStationName, endStationName }) => { + return ( + this.isLineNameOverMinLength($lineNameInput) && + !this.isExistLineName($lineNameInput) && + !this.isSameStation(startStationName, endStationName) + ); + }; + + this.isLineNameOverMinLength = ($lineNameInput) => { + const result = isTextOverMinLength($lineNameInput.value, LINE_NAME_MIN_LENGTH); + + if (!result) { + alert(ERROR_MESSAGE.shortLineName); + $lineNameInput.value = ""; + } + + return result; + }; + + this.isExistLineName = ($lineNameInput) => { + const result = isExistLineName($lineNameInput.value); + + if (result) { + alert(ERROR_MESSAGE.duplicatedLineName); + $lineNameInput.value = ""; + } + + return result; + }; + + this.isSameStation = (startStationName, endStationName) => { + if (startStationName === endStationName) { + alert(ERROR_MESSAGE.sameStartAndEndStation); + + return true; + } + + return false; + }; + + this.setState = ({ nextStations }) => { + this.stations = nextStations; + + this.render(); + }; + + this.createSelectOptionHTMLString = () => { + return this.stations.map((station) => ``).join(""); + }; + + this.render = () => { + this.$container.innerHTML = ` + + + + + + `; + }; + + this.render(); +} diff --git a/src/components/LineList.js b/src/components/LineList.js new file mode 100644 index 000000000..3e1746458 --- /dev/null +++ b/src/components/LineList.js @@ -0,0 +1,59 @@ +import { ELEMENT_INFO } from "../util/constants.js"; + +export default function LineList({ $target, lines, onDeleteLine }) { + const { lineDeleteButton } = ELEMENT_INFO; + + this.$container = document.createElement("section"); + $target.append(this.$container); + + this.lines = lines; + this.onDeleteLine = onDeleteLine; + + this.bindOnDelete = () => { + this.$container.addEventListener("click", (e) => { + if (e.target.className === lineDeleteButton.className) { + this.onDeleteLine(e.target.dataset.lineIndex); + } + }); + }; + + this.setState = ({ nextLines }) => { + this.lines = nextLines; + this.render(); + }; + + this.createTableRowHTMLString = (line, index) => { + return ` + + ${line.name} + ${line.stations[0]} + ${line.stations[line.stations.length - 1]} + + + + + `; + }; + + this.render = () => { + this.$container.innerHTML = ` +

πŸš‰ μ§€ν•˜μ²  λ…Έμ„  λͺ©λ‘

+ + + + + + + + + + + ${this.lines.map((line, index) => this.createTableRowHTMLString(line, index)).join("")} + +
λ…Έμ„  이름상행 μ’…μ μ—­ν•˜ν–‰ 쒅점역섀정
+ `; + }; + + this.render(); + this.bindOnDelete(); +} diff --git a/src/components/LineManager.js b/src/components/LineManager.js new file mode 100644 index 000000000..573bbd798 --- /dev/null +++ b/src/components/LineManager.js @@ -0,0 +1,55 @@ +import LineInput from "./LineInput.js"; +import LineList from "./LineList.js"; + +export default function LineManager({ id, $target, isShow, stations, lines, onAddLine, onDeleteLine }) { + this.$container = document.createElement("div"); + this.$container.className = "line-manager"; + $target.append(this.$container); + + this.id = id; + this.isShow = isShow; + this.stations = stations; + this.lines = lines; + + this.isExistLineName = (lineName) => { + if (this.lines.find((line) => line.name === lineName)) { + return true; + } + + return false; + }; + + this.lineInput = new LineInput({ + $target: this.$container, + stations: this.stations, + isExistLineName: this.isExistLineName, + onAddLine, + }); + + this.lineList = new LineList({ + $target: this.$container, + lines: this.lines, + onDeleteLine, + }); + + this.setState = ({ nextIsShow, nextStations, nextLines }) => { + if (nextIsShow !== undefined) { + this.isShow = nextIsShow; + this.stations = nextStations; + this.lineInput.setState({ nextStations }); + + this.render(); + } + + if (nextLines) { + this.lines = nextLines; + this.lineList.setState({ nextLines: this.lines }); + } + }; + + this.render = () => { + this.$container.style.display = this.isShow ? "block" : "none"; + }; + + this.render(); +} diff --git a/src/components/LineSelector.js b/src/components/LineSelector.js new file mode 100644 index 000000000..6a7aec221 --- /dev/null +++ b/src/components/LineSelector.js @@ -0,0 +1,36 @@ +import { ELEMENT_INFO } from "../util/constants.js"; + +export default function LineSelector({ $target, lines, onChangeLine }) { + const { sectionLineMenuButton } = ELEMENT_INFO; + + this.$container = document.createElement("section"); + this.$container.className = "line-selector"; + $target.append(this.$container); + + this.lines = lines; + this.onChangeLine = onChangeLine; + + this.bindOnClick = () => { + this.$container.addEventListener("click", (e) => { + if (e.target.className === sectionLineMenuButton.className) { + this.onChangeLine(e.target.dataset.lineIndex); + } + }); + }; + + this.createLineList = (line, index) => { + return `
  • `; + }; + + this.render = () => { + this.$container.innerHTML = ` +

    ꡬ간을 μˆ˜μ •ν•  노선을 μ„ νƒν•΄μ£Όμ„Έμš”

    + + `; + }; + + this.render(); + this.bindOnClick(); +} diff --git a/src/components/Navigator.js b/src/components/Navigator.js new file mode 100644 index 000000000..c3e6b0766 --- /dev/null +++ b/src/components/Navigator.js @@ -0,0 +1,28 @@ +import { ELEMENT_INFO } from "../util/constants.js"; + +export default function Navigator({ $target, onTogglePosition }) { + const { navigator: navInfo } = ELEMENT_INFO; + + this.onTogglePosition = onTogglePosition; + this.$container = document.createElement("nav"); + $target.append(this.$container); + + this.bindOnClick = () => { + this.$container.addEventListener("click", (e) => { + if (e.target.tagName === "BUTTON") { + this.onTogglePosition(e.target.id); + } + }); + }; + + this.render = () => { + this.$container.innerHTML = ` +
      + ${navInfo.map((nav, index) => `
    1. `).join("")} +
    + `; + }; + + this.render(); + this.bindOnClick(); +} diff --git a/src/components/SectionInput.js b/src/components/SectionInput.js new file mode 100644 index 000000000..7ab538459 --- /dev/null +++ b/src/components/SectionInput.js @@ -0,0 +1,87 @@ +import { isNatureNumber } from "../util/validation.js"; +import { ELEMENT_INFO, ERROR_MESSAGE } from "../util/constants.js"; + +export default function SectionInput({ $target, stations, selectedLineName, stationsInSelectedLine, onAddSection }) { + const { sectionStationSelector, sectionOrderInput, sectionAddButton } = ELEMENT_INFO; + + this.$container = document.createElement("section"); + this.$container.className = "section-input"; + $target.append(this.$container); + + this.stations = stations; + this.selectedLineName = selectedLineName; + this.stationsInSelectedLine = stationsInSelectedLine; + + this.bindOnSubmit = () => { + this.$container.addEventListener("click", (e) => { + if (e.target.id === sectionAddButton.id) { + const selectedStation = document.querySelector(`#${sectionStationSelector.id}`).value; + let sectionOrder = document.querySelector(`#${sectionOrderInput.id}`).value - 0; + + if (this.isNatureNumber(sectionOrder)) { + sectionOrder = this.checkOverStationsLength(sectionOrder); + onAddSection(selectedStation, sectionOrder); + } + } + }); + }; + + this.isNatureNumber = (sectionOrder) => { + const result = isNatureNumber(sectionOrder); + + if (!result) { + alert(ERROR_MESSAGE.notNatureNumberSectionOrder); + } + + return result; + }; + + this.checkOverStationsLength = (sectionOrder) => { + if (sectionOrder > this.stationsInSelectedLine.length) { + alert(ERROR_MESSAGE.overStationsLength); + + return this.stationsInSelectedLine.length; + } + + return sectionOrder; + }; + + this.setState = ({ nextStations, nextSelectedLineName, nextStationsInSelectedLine }) => { + if (nextStations) { + this.stations = nextStations; + } else { + this.selectedLineName = nextSelectedLineName; + this.stationsInSelectedLine = nextStationsInSelectedLine; + + this.render(); + } + }; + + this.createSelectOptionHTMLString = () => { + return this.stations + .map((station) => + this.stationsInSelectedLine.includes(station) ? "" : `` + ) + .join(""); + }; + + this.render = () => { + this.$container.innerHTML = + this.selectedLineName !== "" + ? ` +

    ${this.selectedLineName} 관리

    +
    +

    ꡬ간 등둝

    + + + +
    + ` + : ""; + }; + + this.render(); + this.bindOnSubmit(); +} diff --git a/src/components/SectionList.js b/src/components/SectionList.js new file mode 100644 index 000000000..84a3334fa --- /dev/null +++ b/src/components/SectionList.js @@ -0,0 +1,68 @@ +import { ELEMENT_INFO, ERROR_MESSAGE } from "../util/constants.js"; + +export default function SectionList({ $target, stationsInSelectedLine, onDeleteSection }) { + const { sectionDeleteButton } = ELEMENT_INFO; + + this.$container = document.createElement("section"); + $target.append(this.$container); + + this.stationsInSelectedLine = stationsInSelectedLine; + this.onDeleteSection = onDeleteSection; + + this.bindOnDelete = () => { + this.$container.addEventListener("click", (e) => { + if (e.target.className === sectionDeleteButton.className && this.isPossibleToDelete()) { + this.onDeleteSection(e.target.dataset.stationIndex); + } + }); + }; + + this.isPossibleToDelete = () => { + const result = this.stationsInSelectedLine.length > 2 ? true : false; + + if (!result) { + alert(ERROR_MESSAGE.notPossibleToDeleteSection); + } + + return result; + }; + + this.setState = (nextStationsInSelectedLine) => { + this.stationsInSelectedLine = nextStationsInSelectedLine; + + this.render(); + }; + + this.createTableRowHTMLString = (stationName, index) => { + return ` + + ${index} + ${stationName} + + + `; + }; + + this.render = () => { + this.$container.innerHTML = + this.stationsInSelectedLine.length > 0 + ? ` + + + + + + + + + ${this.stationsInSelectedLine + .map((stationName, index) => this.createTableRowHTMLString(stationName, index)) + .join("")} + +
    μˆœμ„œμ΄λ¦„μ„€μ •
    ` + : ""; + }; + + this.render(); + this.bindOnDelete(); +} diff --git a/src/components/SectionManager.js b/src/components/SectionManager.js new file mode 100644 index 000000000..eb643f5dc --- /dev/null +++ b/src/components/SectionManager.js @@ -0,0 +1,94 @@ +import LineSelector from "./LineSelector.js"; +import SectionInput from "./SectionInput.js"; +import SectionList from "./SectionList.js"; + +export default function SectionManager({ id, $target, isShow, stations, lines, updateSection }) { + this.$container = document.createElement("div"); + this.$container.className = "section-manager"; + $target.append(this.$container); + + this.id = id; + this.isShow = isShow; + this.stations = stations; + this.lines = lines; + this.selectedLineIndex = -1; + + this.onChangeLine = (nextSelectedLineIndex) => { + this.setState({ nextSelectedLineIndex }); + }; + + this.lineSelector = new LineSelector({ + $target: this.$container, + lines: this.lines, + onChangeLine: this.onChangeLine, + }); + + this.onAddSection = (selectedStation, sectionOrder) => { + const nextStations = [...this.lines[this.selectedLineIndex].stations]; + nextStations.splice(sectionOrder, 0, selectedStation); + + updateSection(this.selectedLineIndex, nextStations); + }; + + this.sectionInput = new SectionInput({ + $target: this.$container, + stations: this.stations, + selectedLineName: "", + stationsInSelectedLine: [], + onAddSection: this.onAddSection, + }); + + this.onDeleteSection = (selectedStationIndex) => { + const nextStations = [...this.lines[this.selectedLineIndex].stations]; + nextStations.splice(selectedStationIndex, 1); + + updateSection(this.selectedLineIndex, nextStations); + }; + + this.sectionList = new SectionList({ + $target: this.$container, + stationsInSelectedLine: [], + onDeleteSection: this.onDeleteSection, + }); + + this.getSelectedLineName = () => { + return this.lines[this.selectedLineIndex].name; + }; + + this.getStationsInSelectedLine = () => { + return this.lines[this.selectedLineIndex].stations; + }; + + this.setStateComponents = () => { + this.sectionInput.setState({ + nextSelectedLineName: this.getSelectedLineName(), + nextStationsInSelectedLine: this.getStationsInSelectedLine(), + }); + this.sectionList.setState(this.getStationsInSelectedLine()); + }; + + this.setState = ({ nextIsShow, nextStations, nextLines, nextSelectedLineIndex }) => { + if (nextIsShow !== undefined) { + this.isShow = nextIsShow; + this.stations = nextStations; + this.sectionInput.setState({ nextStations: this.stations }); + this.render(); + } + + if (nextLines) { + this.lines = nextLines; + this.setStateComponents(); + } + + if (nextSelectedLineIndex) { + this.selectedLineIndex = nextSelectedLineIndex; + this.setStateComponents(); + } + }; + + this.render = () => { + this.$container.style.display = this.isShow ? "block" : "none"; + }; + + this.render(); +} diff --git a/src/components/StationList.js b/src/components/StationList.js new file mode 100644 index 000000000..c7968f1db --- /dev/null +++ b/src/components/StationList.js @@ -0,0 +1,53 @@ +import { ELEMENT_INFO } from "../util/constants.js"; + +export default function StationList({ $target, stations, onDeleteStation }) { + const { stationDeleteButton } = ELEMENT_INFO; + + this.$container = document.createElement("section"); + $target.append(this.$container); + + this.stations = stations; + this.onDeleteStation = onDeleteStation; + + this.bindOnDelete = () => { + this.$container.addEventListener("click", (e) => { + if (e.target.className === stationDeleteButton.className) { + this.onDeleteStation(e.target.dataset.stationIndex); + } + }); + }; + + this.setState = ({ nextStations }) => { + this.stations = nextStations; + this.render(); + }; + + this.createTableRowHTMLString = (stationName, index) => { + return ` + + ${stationName} + + + `; + }; + + this.render = () => { + this.$container.innerHTML = ` +

    πŸš‰ μ§€ν•˜μ²  μ—­ λͺ©λ‘

    + + + + + + + + + ${this.stations.map((station, index) => this.createTableRowHTMLString(station, index)).join("")} + +
    μ—­ 이름섀정
    + `; + }; + + this.render(); + this.bindOnDelete(); +} diff --git a/src/components/StationManager.js b/src/components/StationManager.js new file mode 100644 index 000000000..f2db841d9 --- /dev/null +++ b/src/components/StationManager.js @@ -0,0 +1,46 @@ +import StationNameInput from "./StationNameInput.js"; +import StationList from "./StationList.js"; + +export default function StationManager({ id, $target, stations, isShow, onAddStation, onDeleteStation }) { + this.$container = document.createElement("div"); + this.$container.className = "station-manager"; + $target.append(this.$container); + + this.id = id; + this.isShow = isShow; + this.stations = stations; + + this.isExistStationName = (stationName) => { + return this.stations.includes(stationName); + }; + + this.stationNameInput = new StationNameInput({ + $target: this.$container, + isExistStationName: this.isExistStationName, + onAddStation: onAddStation, + }); + + this.stationList = new StationList({ + $target: this.$container, + stations: this.stations, + onDeleteStation: onDeleteStation, + }); + + this.setState = ({ nextIsShow, nextStations }) => { + if (nextStations) { + this.stations = nextStations; + this.stationList.setState({ nextStations: this.stations }); + } + + if (nextIsShow !== undefined) { + this.isShow = nextIsShow; + this.render(); + } + }; + + this.render = () => { + this.$container.style.display = this.isShow ? "block" : "none"; + }; + + this.render(); +} diff --git a/src/components/StationNameInput.js b/src/components/StationNameInput.js new file mode 100644 index 000000000..ab9b9a925 --- /dev/null +++ b/src/components/StationNameInput.js @@ -0,0 +1,55 @@ +import { isTextOverMinLength } from "../util/validation.js"; +import { ELEMENT_INFO, STATION_NAME_MIN_LENGTH, ERROR_MESSAGE } from "../util/constants.js"; + +export default function StationNameInput({ $target, isExistStationName, onAddStation }) { + const { stationNameInput, stationNameSubmit } = ELEMENT_INFO; + + this.$container = document.createElement("form"); + $target.append(this.$container); + + this.isExistStationName = isExistStationName; + this.onAddStation = onAddStation; + + this.bindOnSubmit = () => { + this.$container.addEventListener("submit", (e) => { + const $stationNameInput = document.querySelector(`#${stationNameInput.id}`); + + if (this.isValidStationName($stationNameInput.value)) { + this.onAddStation($stationNameInput.value); + } + + $stationNameInput.value = ""; + + e.preventDefault(); + }); + }; + + this.isValidStationName = (stationName) => { + if (!isTextOverMinLength(stationName, STATION_NAME_MIN_LENGTH)) { + alert(ERROR_MESSAGE.shortStationName); + + return false; + } + + if (this.isExistStationName(stationName)) { + alert(ERROR_MESSAGE.duplicatedStationName); + + return false; + } + + return true; + }; + + this.render = () => { + this.$container.innerHTML = ` + + + `; + }; + + this.render(); + this.bindOnSubmit(); +} diff --git a/src/components/SubwayMap.js b/src/components/SubwayMap.js new file mode 100644 index 000000000..9d84762a3 --- /dev/null +++ b/src/components/SubwayMap.js @@ -0,0 +1,30 @@ +export default function SubwayMap({ id, $target, isShow, lines }) { + this.$container = document.createElement("div"); + this.$container.className = "map"; + $target.append(this.$container); + + this.id = id; + this.isShow = isShow; + this.lines = lines; + + this.setState = ({ nextIsShow, nextLines }) => { + this.isShow = nextIsShow; + this.lines = nextLines; + + this.render(); + }; + + this.createStationListInLine = (stations) => { + return `
      ${stations.map((station) => `
    1. ${station}
    2. `).join("")}
    `; + }; + + this.render = () => { + this.$container.innerHTML = this.isShow + ? `` + : ""; + }; +} diff --git a/src/index.js b/src/index.js index e69de29bb..a8a730c71 100644 --- a/src/index.js +++ b/src/index.js @@ -0,0 +1,3 @@ +import App from "./components/App.js"; + +new App(document.querySelector("#app")); diff --git a/src/util/constants.js b/src/util/constants.js new file mode 100644 index 000000000..a29302d45 --- /dev/null +++ b/src/util/constants.js @@ -0,0 +1,92 @@ +export const ELEMENT_INFO = { + navigator: [ + { + text: "μ—­ 관리", + id: "station-manager-button", + }, + { + text: "λ…Έμ„  관리", + id: "line-manager-button", + }, + { + text: "ꡬ간 관리", + id: "section-manager-button", + }, + { + text: "μ§€ν•˜μ²  노선도 좜λ ₯", + id: "map-print-manager-button", + }, + ], + stationNameInput: { + text: "μ—­ 이름", + id: "station-name-input", + }, + stationNameSubmit: { + text: "μ—­ μΆ”κ°€", + id: "station-add-button", + }, + stationDeleteButton: { + text: "μ‚­μ œ", + className: "station-delete-button", + }, + lineNameInput: { + text: "λ…Έμ„  이름", + id: "line-name-input", + }, + lineStartStationSelector: { + text: "상행 쒅점", + id: "line-start-station-selector", + }, + lineEndStationSelector: { + text: "ν•˜ν–‰ 쒅점", + id: "line-end-station-selector", + }, + lineAddButton: { + text: "λ…Έμ„  μΆ”κ°€", + id: "line-add-button", + }, + lineDeleteButton: { + text: "μ‚­μ œ", + className: "line-delete-button", + }, + sectionLineMenuButton: { + className: "section-line-menu-button", + }, + sectionStationSelector: { + id: "section-station-selector", + }, + sectionOrderInput: { + text: "μˆœμ„œ", + id: "section-order-input", + }, + sectionAddButton: { + text: "등둝", + id: "section-add-button", + }, + sectionDeleteButton: { + text: "λ…Έμ„ μ—μ„œ 제거", + className: "section-delete-button", + }, +}; + +export const STATION_NAME_MIN_LENGTH = 2; +export const LINE_NAME_MIN_LENGTH = 1; + +export const ERROR_MESSAGE = { + getItem: "⚠ 데이터λ₯Ό κ°€μ Έμ˜¬ 수 μ—†μŠ΅λ‹ˆλ‹€.", + setItem: "⚠ 데이터λ₯Ό μ €μž₯ν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.", + shortStationName: `⚠ ${STATION_NAME_MIN_LENGTH}자 μ΄μƒμ˜ μ—­ 이름을 μž…λ ₯ν•΄μ£Όμ„Έμš”.`, + duplicatedStationName: "⚠ 이미 μ‘΄μž¬ν•˜λŠ” μ—­ μ΄λ¦„μž…λ‹ˆλ‹€.", + notPossibleToDeleteStation: "⚠ 노선에 λ“±λ‘λœ 역은 μ‚­μ œν•  수 μ—†μŠ΅λ‹ˆλ‹€.", + shortLineName: `⚠ ${LINE_NAME_MIN_LENGTH}자 μ΄μƒμ˜ λ…Έμ„  이름을 μž…λ ₯ν•΄μ£Όμ„Έμš”.`, + duplicatedLineName: "⚠ 이미 μ‘΄μž¬ν•˜λŠ” λ…Έμ„  μ΄λ¦„μž…λ‹ˆλ‹€.", + sameStartAndEndStation: "⚠ 상행 쒅점역과 ν•˜ν–‰ 쒅점역은 같을 수 μ—†μŠ΅λ‹ˆλ‹€.", + notNatureNumberSectionOrder: "⚠ ꡬ간 μˆœμ„œλŠ” μ†Œμˆ˜λ‚˜ μŒμˆ˜κ°€ 될 수 μ—†μŠ΅λ‹ˆλ‹€. μžμ—°μˆ˜λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.", + overStationsLength: "⚠ μž…λ ₯된 μˆ«μžκ°€ κΈ°μ‘΄ κ΅¬κ°„μ˜ 개수λ₯Ό λ„˜μŠ΅λ‹ˆλ‹€. 이 λ…Έμ„ μ˜ λ§ˆμ§€λ§‰ μˆœμ„œλ‘œ ꡬ간을 λ“±λ‘ν•©λ‹ˆλ‹€.", + notPossibleToDeleteSection: "⚠ λ…Έμ„  내에 μ—­μ˜ κ°œμˆ˜κ°€ 2개 μ΄ν•˜μ΄λ©΄ λ…Έμ„ μ—μ„œ ꡬ간을 μ œκ±°ν•  수 μ—†μŠ΅λ‹ˆλ‹€.", +}; + +export const STORAGE_KEY = { + station: "station", + line: "line", +}; diff --git a/src/util/localStorage.js b/src/util/localStorage.js new file mode 100644 index 000000000..6db253a1c --- /dev/null +++ b/src/util/localStorage.js @@ -0,0 +1,25 @@ +import { ERROR_MESSAGE } from "./constants.js"; + +const localStorageManager = { + getItem({ key, defaultValue }) { + try { + const storedData = localStorage.getItem(key); + + return storedData ? JSON.parse(storedData) : defaultValue; + } catch (e) { + alert(ERROR_MESSAGE.getItem); + console.error(e); + return defaultValue; + } + }, + setItem({ key, item }) { + try { + localStorage.setItem(key, JSON.stringify(item)); + } catch (e) { + alert(ERROR_MESSAGE.setItem); + console.error(e); + } + }, +}; + +export default localStorageManager; diff --git a/src/util/validation.js b/src/util/validation.js new file mode 100644 index 000000000..77d6614b2 --- /dev/null +++ b/src/util/validation.js @@ -0,0 +1,7 @@ +export const isTextOverMinLength = (text, minLength) => { + return text.trim().length >= minLength ? true : false; +}; + +export const isNatureNumber = (num) => { + return Number.isInteger(num) && num > 0 ? true : false; +}; diff --git a/style.css b/style.css new file mode 100644 index 000000000..0e9232bfa --- /dev/null +++ b/style.css @@ -0,0 +1,75 @@ +ul, +ol { + padding: 0; +} + +nav li { + list-style: none; + display: inline; + margin-right: 10px; +} + +section { + margin-bottom: 20px; +} + +label { + display: block; +} + +input { + margin-bottom: 10px; +} + +button[type="submit"] { + margin-top: 15px; +} + +table { + border-spacing: 0; + border: 2px solid black; +} + +th { + padding: 6px; + border-bottom: 2px solid black; +} + +td { + padding: 6px; + border-bottom: 1px solid black; +} + +tr:last-child > td { + border-bottom: none; +} + +.sr-only { + position: fixed; + top: -100px; + width: 1px; + height: 1px; + overflow: hidden; +} + +.line-selector li { + display: inline; + margin-right: 10px; +} + +.section-input form > * { + margin-top: 0; + margin-bottom: 10px; +} + +.map ul { + list-style: none; +} + +.map ol { + list-style: disc; +} + +.map ol { + margin-left: 50px; +}