diff --git a/README.md b/README.md deleted file mode 100644 index 51abd25..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -# javascript-subway-final \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..dba286c --- /dev/null +++ b/docs/README.md @@ -0,0 +1,218 @@ +# 구현할 기능 목록 + +## 초기 설정 + +- [x] 데이터 초기 설정 + +## 경로 조회 기능 + +### 입력 + +- [x] 출발역을 입력 받아야 한다 +- [x] 도착역을 입력 받아야 한다 +- [x] ! 입력 값이 역 데이터에 있어야 한다 +- [x] ! 두 글자 이상 +- [x] 잘못된 입력시 input 태그 reset +- [x] 선택된 radio value 구하기 + +### 경로 조회 + +- [x] ! 출발역, 도착역 같을 수 없다 +- [x] ! 출발역과 도착역이 연결 되어 있어야 한다 + +### 에러 출력 + +- [x] alert 에러 출력 + +## 프로젝트 구조 + +```sh +. +├── docs +│ └── README.md +├── images +│ ├── dijkstra_example.png +│ ├── path_result.gif +│ └── path_result.jpg +├── index.html +└── src + ├── app.js + ├── controllers + │ └── MainController.js + ├── models + │ └── data.js + ├── styles + │ └── global.css + ├── utils + │ ├── Dijkstra.js + │ └── constants.js + └── views + ├── ArrivalStationView.js + ├── DepartureStationView.js + ├── FormView.js + ├── ResultTable.js + ├── View.js + ├── minimumRadioView.js + └── shortestRadioView.js +``` + +# 🚇 지하철 노선도 경로 조회 미션 + +- 등록된 지하철 노선도에서 경로를 조회하는 기능을 구현한다. + +## 🚀 기능 요구사항 + +> 프리코스 3주차 미션에서 사용한 코드를 참고해도 무관하다. + +### 초기 설정 + +- 프로그램 시작 시 역, 노선, 구간 데이터를 초기 설정 해야 한다. +- 거리와 소요 시간은 양의 정수이며 단위는 km와 분을 의미한다. +- 아래의 사전 등록 정보로 반드시 초기 설정을 한다. + +``` +1. 지하철역으로 교대, 강남, 역삼, 남부터미널, 양재, 양재시민의숲, 매봉 역 정보가 등록되어 있다. +2. 지하철 노선으로 2호선, 3호선, 신분당선이 등록되어 있다. +3. 노선에 역이 아래와 같이 등록되어 있다.(왼쪽 끝이 상행 종점) + - 2호선: 교대 - ( 2km / 3분 ) - 강남 - ( 2km / 3분 ) - 역삼 + - 3호선: 교대 - ( 3km / 2분 ) - 남부터미널 - ( 6km / 5분 ) - 양재 - ( 1km / 1분 ) - 매봉 + - 신분당선: 강남 - ( 2km / 8분 ) - 양재 - ( 10km / 3분 ) - 양재시민의숲 +``` + +### 경로 조회 기능 + + + +- 출발역과 도착역을 입력받아 경로를 조회한다. +- 경로 조회 시 총 거리, 총 소요 시간을 함께 출력한다. +- 경로 조회 시 `최단 거리` 또는 `최소 시간` 옵션을 선택할 수 있다. + +### 예외 처리 + +- 출발역과 도착역은 2글자 이상이어야 한다. +- 존재하지 않는 역을 출발역 또는 도착역으로 입력할 수 없다. +- 경로 조회 시 출발역과 도착역이 같을 수 없다. +- 경로 조회 시 출발역과 도착역이 연결되지 않으면 경로를 조회할 수 없다. +- 그 외 정상적으로 프로그램이 수행되지 않은 경우 `alert`으로 에러를 출력한다. + +
+ +## 💻 프로그래밍 실행 결과 + +### 경로 조회 + + + +## ✅ 프로그래밍 요구사항 + +### 길찾기 관련 기능 + +- 출발역을 입력하는 input 태그는 `departure-station-name-input` id 속성값을 가진다. +- 도착역을 입력하는 input 태그는 `arrival-station-name-input` id 속성값을 가진다. +- 최단거리, 최소시간을 선택하는 radio는 `search-type` name 속성값을 가진다. + - **radio option의 default 값은 최단거리이다.** +- 길찾기 버튼은 `search-button` id 속성값을 가진다. +- 📝 결과는 `table`을 이용하여 보여준다. + +## ❗️힌트 + +## 데이터 초기화 + +자바스크립트에서 데이터를 초기화하는 방법 중에 하나는 아래와 같이 data를 `export`하고, `import`하는 것이다. + +```javascript +export const users = [ + { + name: "Alt", + }, + { + name: "Jamie", + }, + { + name: "Sony", + }, +]; + +export const courses = [ + { + name: "frontend", + }, + { + name: "backend", + }, + { + name: "iOS", + }, + { + name: "Android", + }, +]; +``` + +위와 같이 데이터를 `export`하면 아래와 같이 데이터를 `import` 하여 사용할 수 있다. + +```javascript +import { users, courses } from "./data.js"; + +function App() { + this.users = users; + this.courses = courses; +} +``` + +## 최단 경로 라이브러리 + +- `utils/Dijkstra.js` 라이브러리를 활용하면 간편하게 최단거리를 조회할 수 있다. +- 정점(Vertex)과 간선(Edge), 그리고 가중치 개념을 이용 + - 정점: 지하철역 + - 간선: 지하철역 연결정보 + - 가중치: 거리 or 소요 시간 +- 최단 거리 기준 조회 시 가중치를 거리로 설정 +- 최소 시간 기준 조회 시 가중치를 시간으로 설정 + +```javascript +import Dijkstra from "./utils/Dijkstra.js"; +const dijkstra = new Dijkstra(); + +//dijkstra.addEdge("출발역", "도착역", 거리 또는 시간); +dijkstra.addEdge("V1", "V2", 2); +dijkstra.addEdge("V2", "V3", 2); +dijkstra.addEdge("V1", "V3", 100); + +const result = dijkstra.findShortestPath("V1", "V3"); +// result = ["V1", "V2", "V3"] +``` + +#### 테스트설명 + + + +- 역 사이의 거리를 고려하지 않는 경우 V1->V3 경로가 최단 경로 +- 역 사이의 거리를 고려할 경우 V1->V3 경로의 거리는 100km, V1->V2->V3 경로의 거리는 4km이므로 최단 경로는 V1->V2->V3 + +
+ +### 요구사항 + +- 사용자가 잘못된 입력 값을 작성한 경우 `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](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/var) 를 사용하지 않는다. [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) 문을 이용해 스크립트를 모듈화하고 불러올 수 있게 만든다. +- [template literal](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals)을 이용해 데이터와 html string을 가독성 좋게 표현한다. + +
+ +## 📝 미션 저장소 및 진행 요구사항 + +- 미션은 [https://github.com/woowacourse/javascript-subway-path-precourse](https://github.com/woowacourse/javascript-subway-path-precourse) 저장소를 fork/clone해 시작한다. +- **기능을 구현하기 전에 javascript-subway-path-precourse/docs/README.md 파일에 구현할 기능 목록**을 정리해 추가한다. +- **git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가**한다. +- [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서 절차를 따라 미션을 제출한다. diff --git a/images/dijkstra_example.png b/images/dijkstra_example.png new file mode 100644 index 0000000..7c75197 Binary files /dev/null and b/images/dijkstra_example.png differ diff --git a/images/path_result.gif b/images/path_result.gif new file mode 100644 index 0000000..ee394bd Binary files /dev/null and b/images/path_result.gif differ diff --git a/images/path_result.jpg b/images/path_result.jpg new file mode 100644 index 0000000..40a4bed Binary files /dev/null and b/images/path_result.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..b469ff6 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + 지하철 길찾기 + + + +
+ + + diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..4290431 --- /dev/null +++ b/src/app.js @@ -0,0 +1,17 @@ +import MainController from "./controllers/MainController.js"; + +class App { + constructor() { + this.tag = "[App]"; + this.MainController = new MainController(); + this.init(); + } + + init() { + document.addEventListener("DOMContentLoaded", () => { + this.MainController.init(); + }); + } +} + +new App(); diff --git a/src/controllers/MainController.js b/src/controllers/MainController.js new file mode 100644 index 0000000..5ec5b4f --- /dev/null +++ b/src/controllers/MainController.js @@ -0,0 +1,233 @@ +import { Stations, Lines } from "../models/data.js"; +import { DOM, INITIAL_RADIO_OPTION } from "../utils/constants.js"; +import Dijkstra from "../utils/Dijkstra.js"; +import FormView from "../views/FormView.js"; +import DepartureStationView from "../views/DepartureStationView.js"; +import ArrivalStationView from "../views/ArrivalStationView.js"; +import shortestRadioView from "../views/shortestRadioView.js"; +import minimumRadioView from "../views/minimumRadioView.js"; +import ResultTable from "../views/ResultTable.js"; + +export default class MainController { + constructor() { + this.tag = "[MainController]"; + this._app = document.querySelector("#app"); + this.FormView = new FormView(); + this.DepartureStationView = new DepartureStationView(); + this.ArrivalStationView = new ArrivalStationView(); + this.shortestRadioView = new shortestRadioView(); + this.minimumRadioView = new minimumRadioView(); + this.Dijkstra = new Dijkstra(); + this.ResultTable = new ResultTable(); + + this.radioOption = INITIAL_RADIO_OPTION; + } + + init() { + this.FormView.setup(document.querySelector("#app")).on("@submit", (e) => + this.onSubmit(e.detail.stations) + ); + + this.DepartureStationView.setup( + document.querySelector("#" + DOM.DEPARTURE_STATION_NAME_INPUT_ID) + ); + this.ArrivalStationView.setup( + document.querySelector("#" + DOM.ARRIVAL_STAION_NAME_INPUT_ID) + ); + + this.shortestRadioView.setup( + document.querySelector("#" + DOM.SHORTEST_DISTANCE_RADIO_ID) + ); + this.minimumRadioView.setup( + document.querySelector("#" + DOM.MINIMUM_DISTANCE_RADIO_ID) + ); + this.selectedOptionRadio(); + + this.ResultTable.setup(document.querySelector("#app")); + } + + onSubmit(e) { + console.log(this.tag, "onSubmit", e); + + const [departureStation, arrivalStation] = e; + + if (!this.isValidStationName(departureStation, arrivalStation)) { + this.DepartureStationView.reset(); + this.ArrivalStationView.reset(); + return; + } + + this.selectedOptionRadio(); + const result = this.dijkstraResult( + departureStation, + arrivalStation, + this.radioOption + ); + + this.clear(); + this.ResultTable.render( + [...result], + this.radioOptionText(this.radioOption), + this.totalDistanceTime(result) + ); + } + + totalDistanceTime(result) { + console.log(this.tag, "totalDistance()", result); + let distance = 0; + let time = 0; + + while (result.length) { + const nowResult = result[0]; + Lines.forEach((lineInfo) => { + const stations = lineInfo.stations; + const stationsLength = stations.length; + const stationIndex = stations.indexOf(nowResult); + + if ( + stationIndex !== -1 && + stationIndex + 1 < stationsLength && + result[1] === stations[stationIndex + 1] + ) { + result.shift(); + distance += lineInfo.distance[stationIndex]; + time += lineInfo.time[stationIndex]; + } + }); + + if (result.length === 1) break; + } + console.log(distance, time); + return { distance, time }; + } + + radioOptionText(option) { + if (option === "distance") return "최단거리"; + if (option === "time") return "최소거리"; + } + + dijkstraResult(start, end, option) { + console.log(this.tag, "dijkstraResult()", start, end, option); + + Lines.forEach((lineInfo) => { + const lineStations = lineInfo.stations; + const lineLength = lineStations.length; + + for (let i = 0; i < lineLength - 1; i++) { + this.Dijkstra.addEdge( + lineStations[i], + lineStations[i + 1], + lineInfo[option][i] + ); + } + }); + + return this.Dijkstra.findShortestPath(start, end); + } + + selectedOptionRadio() { + console.log(this.tag, "selectedOptionRadio", this.radioOption); + if (this.shortestRadioView.radioInfo()) { + this.radioOption = "distance"; + console.log("option----", this.radioOption); + return; + } + + if (this.minimumRadioView.radioInfo()) { + this.radioOption = "time"; + console.log("option----", this.radioOption); + return; + } + + return new Error("잘못된 radio value 값"); + } + + isValidStationName(departureStation, arrivalStation) { + if ( + this.isDepartureStationName(departureStation) && + this.isArrivalStationName(arrivalStation) && + this.isSameStation(departureStation, arrivalStation) && + this.isConnection(departureStation, arrivalStation) + ) { + return true; + } + + return false; + } + + isDepartureStationName(station) { + const locationText = "출발역"; + if (this.isValid(station, locationText)) { + return true; + } + + return false; + } + isArrivalStationName(station) { + const locationText = "도착역"; + if (this.isValid(station, locationText)) { + return true; + } + + return false; + } + + isValid(station, locationText) { + if ( + this.is2Digit(station, locationText) && + this.isHasStation(station, locationText) + ) { + return true; + } + + return false; + } + + is2Digit(station, locationText) { + console.log(this.tag, "is2Digit()", station); + if (station.length >= 2) { + return true; + } + window.alert(`${locationText}은 두 글자 이상 입력 하셔야 합니다. `); + return false; + } + + isHasStation(station, locationText) { + console.log(this.tag, "isHasStation()", station); + if (Stations.indexOf(station) !== -1) { + return true; + } + window.alert( + `${locationText} 입력값 ${station}역은 등록된 역 이름이 아닙니다.` + ); + return false; + } + + isSameStation(start, end) { + console.log(this.tag, "isSameStation()", start, end); + if (start !== end) { + return true; + } + window.alert(`출발역과 도착역은 서로 달라야 합니다`); + return false; + } + + isConnection(start, end) { + console.log(this.tag, "isConnection()", start, end); + for (const line of Lines) { + if (line.stations.includes(start) && line.stations.includes(end)) { + return true; + } + } + window.alert( + `출발역 ${start}역은 도착역 ${end}역과 연결되어 있지 않습니다.` + ); + return false; + } + + clear() { + while (this._app.children.length > 2) { + this._app.children[this._app.children.length - 1].remove(); + } + } +} diff --git a/src/models/data.js b/src/models/data.js new file mode 100644 index 0000000..89fa4dd --- /dev/null +++ b/src/models/data.js @@ -0,0 +1,30 @@ +export const Stations = [ + "교대", + "강남", + "역삼", + "남부터미널", + "양재", + "양재시민의숲", + "매봉", +]; + +export const Lines = [ + { + line: "2호선", + stations: ["교대", "강남", "역삼"], + distance: [2, 2], + time: [3, 3], + }, + { + line: "3호선", + stations: ["교대", "남부터미널", "양재", "매봉"], + distance: [3, 6, 1], + time: [2, 5, 1], + }, + { + line: "신분당선", + stations: ["강남", "양재", "양재시민의숲"], + distance: [2, 10], + time: [8, 3], + }, +]; diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 0000000..9545b1c --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,6 @@ +table, +th, +td, +tr { + border: 1px solid #444444; +} diff --git a/src/utils/Dijkstra.js b/src/utils/Dijkstra.js new file mode 100644 index 0000000..7c6cfd5 --- /dev/null +++ b/src/utils/Dijkstra.js @@ -0,0 +1,220 @@ +export default function Dijkstra() { + const Node = { + init: function (val, priority) { + this.val = val; + this.priority = priority; + }, + }; + + const PriorityQueue = { + init: function () { + this.values = []; + }, + enqueue: function (val, priority) { + const newNode = Object.create(Node); + newNode.init(val, priority); + + this.values.push(newNode); + + let idxOfNewNode = this.values.length - 1; + + while (idxOfNewNode > 0) { + const idxOfParentNode = Math.floor((idxOfNewNode - 1) / 2); + + const parentNode = this.values[idxOfParentNode]; + + if (priority < parentNode.priority) { + this.values[idxOfParentNode] = newNode; + this.values[idxOfNewNode] = parentNode; + idxOfNewNode = idxOfParentNode; + continue; + } + break; + } + return this.values; + }, + dequeue: function () { + if (this.values.length == 0) { + return; + } + const dequeued = this.values.shift(); + const lastItem = this.values.pop(); + if (!lastItem) { + return dequeued; + } + this.values.unshift(lastItem); + + let idxOfTarget = 0; + + while (true) { + let idxOfLeftChild = idxOfTarget * 2 + 1; + let idxOfRightChild = idxOfTarget * 2 + 2; + let leftChild = this.values[idxOfLeftChild]; + let rightChild = this.values[idxOfRightChild]; + + function swap(direction) { + const idxOfChild = + direction == "left" ? idxOfLeftChild : idxOfRightChild; + const child = direction == "left" ? leftChild : rightChild; + this.values[idxOfChild] = this.values[idxOfTarget]; + this.values[idxOfTarget] = child; + idxOfTarget = idxOfChild; + } + + if (!leftChild) { + return dequeued; + } + + if (!rightChild) { + if (leftChild.priority < lastItem.priority) { + swap.call(this, "left"); + continue; + } + return dequeued; + } + + if (leftChild.priority == rightChild.priority) { + swap.call(this, "left"); + continue; + } + + if ( + leftChild.priority < rightChild.priority && + leftChild.priority < lastItem.priority + ) { + swap.call(this, "left"); + continue; + } + + if ( + rightChild.priority < leftChild.priority && + rightChild.priority < lastItem.priority + ) { + swap.call(this, "right"); + continue; + } + } + }, + }; + + const WeightedGraph = { + init: function () { + this.adjacencyList = {}; + this.length = 0; + }, + addVertex: function (vertex) { + if (!this.adjacencyList.hasOwnProperty(vertex)) { + this.adjacencyList[vertex] = {}; + this.length++; + } + }, + addEdge: function (vertex1, vertex2, weight) { + this.addVertex(vertex1); + this.addVertex(vertex2); + this.adjacencyList[vertex1][vertex2] = weight; + this.adjacencyList[vertex2][vertex1] = weight; + return this.adjacencyList; + }, + removeEdge: function (vertex1, vertex2) { + if (!this.adjacencyList.hasOwnProperty(vertex1)) { + return `There's no ${vertex1}`; + } + if (!this.adjacencyList.hasOwnProperty(vertex2)) { + return `There's no ${vertex2}`; + } + + function removeHelper(v1, v2) { + if (!this.adjacencyList.hasOwnProperty(v1)) { + return `There's no edge between ${v1} and ${v2}`; + } + delete this.adjacencyList[v1][v2]; + if (Object.keys(this.adjacencyList[v1]).length == 0) { + delete this.adjacencyList[v1]; + } + } + + removeHelper.call(this, vertex1, vertex2); + removeHelper.call(this, vertex2, vertex1); + + return this.adjacencyList; + }, + removeVertex: function (vertex) { + if (!this.adjacencyList.hasOwnProperty(vertex)) { + return `There's no ${vertex}`; + } + const edges = this.adjacencyList[vertex]; + for (const key in edges) { + this.removeEdge(key, vertex); + } + return this.adjacencyList; + }, + findShortestRoute: function (start, end) { + if (!start || !end) { + throw Error("출발지와 도착지를 모두 입력해야 합니다."); + } + const distance = {}; + const previous = {}; + const pq = Object.create(PriorityQueue); + pq.init(); + pq.enqueue(start, 0); + const visited = {}; + + const hashOfVertex = this.adjacencyList; + for (const vertexName in hashOfVertex) { + const priority = vertexName == start ? 0 : Infinity; + distance[vertexName] = priority; + previous[vertexName] = null; + } + + while (true) { + let current = pq.dequeue(); + if (!current?.val) { + return; + } + current = current.val; + if (current == end) { + break; + } + const neighbors = hashOfVertex[current]; + + for (const vertexName in neighbors) { + if (visited.hasOwnProperty(vertexName)) { + continue; + } + const distFromStart = distance[current] + neighbors[vertexName]; + + if (distFromStart < distance[vertexName]) { + pq.enqueue(vertexName, distFromStart); + distance[vertexName] = distFromStart; + previous[vertexName] = current; + } + } + visited[current] = true; + } + + let node = end; + + const route = []; + while (node) { + route.unshift(node); + node = previous[node]; + } + + return route; + }, + }; + + this.addEdge = (source, target, weight) => { + WeightedGraph.addEdge(source, target, weight); + }; + + this.findShortestPath = (source, target) => { + return WeightedGraph.findShortestRoute(source, target); + }; + + this.addVertex = (vertex) => { + WeightedGraph.addVertex(vertex); + }; + + WeightedGraph.init(); +} diff --git a/src/utils/constants.js b/src/utils/constants.js new file mode 100644 index 0000000..b7ec5a8 --- /dev/null +++ b/src/utils/constants.js @@ -0,0 +1,9 @@ +export const DOM = { + DEPARTURE_STATION_NAME_INPUT_ID: "departure-station-name-input", + ARRIVAL_STAION_NAME_INPUT_ID: "arrival-station-name-input", + SEARCH_BUTTON_ID: "search-button", + SHORTEST_DISTANCE_RADIO_ID: "shortest-distance-radio", + MINIMUM_DISTANCE_RADIO_ID: "minimum-distance-radio", +}; + +export const INITIAL_RADIO_OPTION = "distance"; diff --git a/src/views/ArrivalStationView.js b/src/views/ArrivalStationView.js new file mode 100644 index 0000000..2a9bde7 --- /dev/null +++ b/src/views/ArrivalStationView.js @@ -0,0 +1,18 @@ +//도착 +import View from "./View.js"; + +export default class ArrivalStationView extends View { + constructor() { + super(); + this.tag = "[ArrivalStationView]"; + } + + setup(el) { + this.init(el); + return this; + } + + reset() { + this.el.value = ""; + } +} diff --git a/src/views/DepartureStationView.js b/src/views/DepartureStationView.js new file mode 100644 index 0000000..4b6bb45 --- /dev/null +++ b/src/views/DepartureStationView.js @@ -0,0 +1,18 @@ +//출발 +import View from "./View.js"; + +export default class DepartureStationView extends View { + constructor() { + super(); + this.tag = "[DepartureStationView]"; + } + + setup(el) { + this.init(el); + return this; + } + + reset() { + this.el.value = ""; + } +} diff --git a/src/views/FormView.js b/src/views/FormView.js new file mode 100644 index 0000000..8c53234 --- /dev/null +++ b/src/views/FormView.js @@ -0,0 +1,74 @@ +import View from "./View.js"; +import { DOM } from "../utils/constants.js"; + +export default class FormView extends View { + constructor() { + super(); + this.tag = "[FormView]"; + } + + setup(el) { + this.init(el); + this.el.innerHTML = this.render(); + this.bindEvents(); + return this; + } + + render() { + return ` +

🚇 지하철 길찾기

+
+
+ + +
+ +
+ + +
+ +
+ + + + + +
+ + +
+ `; + } + + bindEvents() { + this.on("submit", (e) => this.onSubmit(e)); + } + + onSubmit(e) { + e.preventDefault(); + // console.log(e.target[0].value); //DepartureStationView + // console.log(e.target[1].value); //ArrivalStationView + // console.log(e.target[2].checked); + // console.log(e.target[2].value); + // console.log(e.target[3]); + // console.log(e.target[4]); + + this.emit("@submit", { stations: [e.target[0].value, e.target[1].value] }); + } +} diff --git a/src/views/ResultTable.js b/src/views/ResultTable.js new file mode 100644 index 0000000..48080b9 --- /dev/null +++ b/src/views/ResultTable.js @@ -0,0 +1,41 @@ +import View from "./View.js"; + +export default class ResultTable extends View { + constructor() { + super(); + } + + setup(el) { + this.init(el); + return this; + } + + render(result, header, totalDistanceTime) { + const tableContainer = document.createElement("div"); + tableContainer.innerHTML = ` +

📃 결과

+

${header}

+ + + + + + + + + + + + + + +
총 거리총 소요시간
${totalDistanceTime.distance}km${totalDistanceTime.time}분
${this.resultListHTML(result)}
+ `; + + this.el.append(tableContainer); + } + + resultListHTML(result) { + return `${result.join("▶︎")}`; + } +} diff --git a/src/views/View.js b/src/views/View.js new file mode 100644 index 0000000..ef5ab1f --- /dev/null +++ b/src/views/View.js @@ -0,0 +1,32 @@ +export default class View { + constructor() { + this.tag = "[View]"; + } + + init(el) { + if (!el) throw el; + this.el = el; + return this; + } + + on(event, handler) { + this.el.addEventListener(event, handler); + return this; + } + + emit(event, data) { + const evt = new CustomEvent(event, { detail: data }); + this.el.dispatchEvent(evt); + return this; + } + + hide() { + this.el.style.display = "none"; + return this; + } + + show() { + this.el.style.display = ""; + return this; + } +} diff --git a/src/views/minimumRadioView.js b/src/views/minimumRadioView.js new file mode 100644 index 0000000..4689820 --- /dev/null +++ b/src/views/minimumRadioView.js @@ -0,0 +1,22 @@ +//최소거리 +import View from "./View.js"; + +export default class minimumRadioView extends View { + constructor() { + super(); + this.tag = "[minimumRadioView]"; + } + + setup(el) { + this.init(el); + this.radioInfo(); + return this; + } + + radioInfo() { + console.log(this.tag, "radioInfo()"); + console.log(this.el.value); + console.log(this.el.checked); + return this.el.checked; + } +} diff --git a/src/views/shortestRadioVIew.js b/src/views/shortestRadioVIew.js new file mode 100644 index 0000000..1af8ee6 --- /dev/null +++ b/src/views/shortestRadioVIew.js @@ -0,0 +1,22 @@ +//최단거리 +import View from "./View.js"; + +export default class shortestRadioView extends View { + constructor() { + super(); + this.tag = "[shortestRadioVIew]"; + } + + setup(el) { + this.init(el); + this.radioInfo(); + return this; + } + + radioInfo() { + console.log(this.tag, "radioInfo()"); + console.log(this.el.value); + console.log(this.el.checked); + return this.el.checked; + } +}