diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..3aa66ddd4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,83 @@ +## ✅ 구현 기능 목록 + +### 기본 세팅 + +- 클릭되는 버튼마다 하단 result 변경하기 + +### 지하철 역 관련 + +- 지하철 역 목록 조회하기 +- 지하철 역 등록하기 +- 지하철 역 생성 객체(name, 속하는 line 등 포함) +- 지하철 역 삭제하기(노선에 등록 안 된거만) + +### 지하철 노선 관련 + +- 노선 등록하기(상행 종점역, 하행 종점역) +- 노선 삭제하기 +- 중복 노선 불가 +- 지하철 노선 목록 조회하기 + +### 지하철 구간 + +- 관리할 노선 선택하기 +- 선택된 노선의 전체 구간 보여주기 +- 구간 추가하기( === 노선에 역 추가하기) + - 입력 받은 순서로, line 내 해당 번호 station 사이에 추가하기 + - 추가 사항 적용된 구간 보여주기 +- 상행 종점과 하행 종점 삭제시, 다음 역 종점으로 설정 +- + +### 지하철 노선도 출력 기능 + +- 노선도 출력 버튼 누르면 노선도 출력 + - Line 객체 배열 순회하면서, 차례로 출력 + +## :star: 예외처리 항목 + +#### 역 관리 + +- 지하철 역 이름 예외처리 +- 역이 한 line에 포함된 상태에서 삭제 될 때 + - 노선에 등록된 역은 삭제할 수 없다는 조건 존재 +- 역 삭제 시 + 1. 역 관리에서 삭제 + - 삭제하는 대상의 station 객체의 isIncluded 속성의 길이가 === 1 일 시 삭제 불가 + => 역이 이미 노선에 등록되어 있음 + - isIncluded 속성의 길이 === 0 일 시 삭제 가능 + => 역이 등록된 노선 없음 + 2. 구간 관리에서 삭제 + - station 객체의 isInCluded 속성에서 선택된 라인을 삭제 +- 중복 지하철 역 불가 => isOverlappedStationName() + +#### 노선 관리 + +- 노선 이름 입력 값 예외처리 +- 상행과 하행 이름 같을 때 + +#### 구간 관리 + +- 선택 노선 변경 시 지하철 역 계속 나열 안되게 수정 +- 지하철 역 추가하고 나서 초기화 +- 중복 지하철 역 추가 불가해야함 +- 0 < inputIdx <= 마지막 인덱스 && 정수일 때 +- 상행 종점과 하행 종점 삭제시, 다음 역 종점으로 설정 + +#### 그 외 + +- station 객체가 노선에 등록 될 때마다 등록되는 노선을 추가하면 안됌. + - 하나의 역은 여러 노선에 포함될 수 있음 + - 하지만 하나의 역은 하나의 다음 역을 가짐 + - 이상해짐 + => 노선을 추가할 때 새로운 역 객체를 만들기 + => 역 객체가 속하는 노선은 1개 +- station 객체 전체를 보여줄 때 + 1. 역 관리에서 + - isIncluded === null 인거 하나만 있으면 이거 보여주기 + - isIncluded === null 외에 더 있으면 그 중 하나 보여주기 + 2. 노선 관리, 구간 관리의 option에서 + - 중복만 없애서 보여주기 + +## :open_mouth: 의문점 + +- 객체 Line을 다룰 때 addStaionToIdx()라는 메소드를 통해서 station을 원하는 위치에 끼워넣으려 한다. 그런데 prototype을 메소드 명 앞에 붙히면 안되고, 안 붙히면 내가 원하는 대로 작동한다. 그래서 prototype에 대해 공부하였는데 내가 원하는 답을 찾지는 못했다. 더 알아봐야겠다. diff --git a/index.html b/index.html index fc99deac2..7b7744d1d 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,83 @@
-

🚇 지하철 노선도 관리

+

🚇 지하철 노선도 관리

+
+ + + + +
+
+
+
+
역 이름
+
+ + +
+
+
+
지하철 역 목록
+ + + + + + +
역 이름설정
+
+
+ + + +
diff --git a/src/index.js b/src/index.js index e69de29bb..b6711b5c2 100644 --- a/src/index.js +++ b/src/index.js @@ -0,0 +1,93 @@ +import Station from "./station.js"; +import { showAllStationInManager } from "./station-manager.js"; +import Line from "./line.js"; +import { showAllLineInLineManager } from "./line-manager.js"; +import { showLineMenuInSectionManager } from "./section-manager.js"; +import { showMapList } from "./map-print-manager.js"; +import { manager } from "./manager.js"; +import { makeStationOption, makeStationList } from "./make-selector-list.js"; + +const btnStationManager = document.getElementById("station-manager-button"); +const btnLineManager = document.getElementById("line-manager-button"); +const btnSectionManager = document.getElementById("section-manager-button"); +const btnMapPrintManager = document.getElementById("map-print-manager-button"); +const resultStationManager = document.getElementById("station-manager-result"); +const resultSectionManager = document.getElementById("section-manager-result"); +const resultLineManager = document.getElementById("line-manager-result"); +const resultMapPrintManager = document.getElementById( + "map-print-manager-result" +); +const resultList = [ + resultStationManager, + resultLineManager, + resultSectionManager, + resultMapPrintManager, +]; + +// storage.clear(); +export const initializeStationList = () => { + const stationList = JSON.parse(storage.getItem("stationList")); + if (stationList === null) { + manager.stationList = []; + } else { + for (let i in stationList) { + const station = new Station(stationList[i].name); + station.next = stationList[i].next; + station.isIncluded = stationList[i].isIncluded; + manager.setStationInManager(station); + } + } + showAllStationInManager(makeStationList()); +}; +export const initializeLineList = () => { + const lineList = JSON.parse(storage.getItem("lineList")); + if (lineList === null) { + manager.lineList = []; + } else { + for (let i in lineList) { + const line = new Line(lineList[i].name); + line.length = lineList[i].length; + line.head = lineList[i].head; + manager.setLineInManager(line); + } + } +}; +const storage = window.localStorage; +window.onload = () => { + // 페이지 새로 열릴 때 + initializeStationList(); + initializeLineList(); +}; +window.onbeforeunload = () => { + // 페이지 닫히기 전 + storage.setItem("stationList", JSON.stringify(manager.stationList)); + storage.setItem("lineList", JSON.stringify(manager.lineList)); +}; +const makeResultBlock = (idx) => { + for (let i in resultList) { + if (i === String(idx)) { + resultList[i].style.display = "Block"; + } else { + resultList[i].style.display = "None"; + } + } +}; +showAllStationInManager(manager.stationList); +btnStationManager.onclick = () => { + makeResultBlock(0); + showAllStationInManager(makeStationList()); +}; +btnLineManager.onclick = () => { + makeResultBlock(1); + makeStationOption("line-start-station-selector"); + makeStationOption("line-end-station-selector"); + showAllLineInLineManager(manager.lineList); +}; +btnSectionManager.onclick = () => { + makeResultBlock(2); + showLineMenuInSectionManager(manager.lineList); +}; +btnMapPrintManager.onclick = () => { + makeResultBlock(3); + showMapList(); +}; diff --git a/src/line-manager.js b/src/line-manager.js new file mode 100644 index 000000000..2bf969c7e --- /dev/null +++ b/src/line-manager.js @@ -0,0 +1,113 @@ +import { makeStationOption } from "./make-selector-list.js"; +import { manager } from "./manager.js"; +import Line from "./line.js"; +import Station from "./station.js"; + +export const deleteLineInList = (lineName) => { + const parent = document.querySelector("tbody#line-list"); + const deleteIdx = manager.lineList.findIndex((line) => { + return line.name === lineName; + }); + parent.removeChild(document.getElementById(`${lineName}-line`)); + manager.lineList[deleteIdx].deleteAllStationInLine(); + manager.lineList.splice(deleteIdx, 1); +}; +export const makeLineChildUI = (line) => { + const lineName = document.createElement("td"); + const startStation = document.createElement("td"); + const endStation = document.createElement("td"); + const deleteButton = document.createElement("td"); + lineName.innerHTML = line.name; + startStation.innerHTML = line.getStartStation(); + endStation.innerHTML = line.getEndStation(); + deleteButton.innerHTML = "삭제"; + deleteButton.setAttribute("class", "line-delete-button"); + deleteButton.onclick = () => { + deleteLineInList(`${line.name}`); + }; + deleteButton.innerHTML = "삭제"; + + return [lineName, startStation, endStation, deleteButton]; +}; +export const makeLineUI = (line) => { + const [lineName, startStation, endStation, deleteButton] = makeLineChildUI( + line + ); + const newLine = document.createElement("tr"); + newLine.setAttribute("id", `${line.name}-line`); + newLine.append(lineName, startStation, endStation, deleteButton); + + return newLine; +}; +export const showAllLineInLineManager = (lineList) => { + const table = document.getElementById("line-list"); + table.innerHTML = ""; + lineList.forEach((line) => { + const newLine = makeLineUI(line); + table.appendChild(newLine); + }); + document.getElementById("line-name-input").value = ""; +}; +export const isPossibleLineName = (name) => { + const rHangel = /^[0-9A-Za-z가-힣]*$/; + if (name.length > 0 && rHangel.exec(name) !== null) { + return true; + } + alert("올바른 노선 이름을 입력 해주세요"); + + return false; +}; +export const resetLineInput = () => { + document.getElementById("line-name-input").value = ""; + makeStationOption("line-start-station-selector"); + makeStationOption("line-end-station-selector"); +}; +export const isPossibleLine = (startName, endName) => { + if (startName === endName) { + alert("상행 종점과 다른 하행 종점을 선택 해주세요."); + resetLineInput(); + + return false; + } + return true; +}; +export const isOverlappedLineName = (newLineName) => { + const overlappedName = manager.lineList.find( + (line) => line.name === newLineName + ); + if (overlappedName) { + alert("이미 등록된 노선입니다."); + resetLineInput(); + + return true; + } + return false; +}; +export const addLineToList = () => { + const newLineName = document.getElementById("line-name-input").value; + const startStationName = document.getElementById( + "line-start-station-selector" + ).value; + const endStationName = document.getElementById("line-end-station-selector") + .value; + if ( + isPossibleLineName(newLineName) && + isPossibleLine(startStationName, endStationName) && + !isOverlappedLineName(newLineName) + ) { + const line = new Line(newLineName); + const startStation = new Station(startStationName); + const endStation = new Station(endStationName); + manager.setStationInManager(startStation); + manager.setStationInManager(endStation); + line.addLine(startStation, endStation); + manager.setLineInManager(line); + showAllLineInLineManager(manager.lineList); + resetLineInput(); + } +}; + +const btnAddLine = document.getElementById("line-add-button"); +btnAddLine.onclick = () => { + addLineToList(); +}; diff --git a/src/line.js b/src/line.js new file mode 100644 index 000000000..a2f5424a1 --- /dev/null +++ b/src/line.js @@ -0,0 +1,84 @@ +import { manager } from "./manager.js"; + +export default function Line(name) { + this.name = name; + this.length = 0; + this.head = null; + this.addLine = (startStation, endStation) => { + this.head = startStation; + this.head.next = endStation; + this.length += 2; + + startStation.addIncludedLine(this.name); + endStation.addIncludedLine(this.name); + }; + this.getStartStation = () => { + return this.head.name; + }; + this.getEndStation = () => { + let current = this.head; + while (current.next) { + current = current.next; + } + + return current.name; + }; + this.getAllStationName = () => { + let stationList = []; + let current = this.head; + while (current.next) { + stationList.push(current.name); + current = current.next; + } + stationList.push(current.name); + + return stationList; + }; + this.addStationInIdx = (station, idx) => { + const addStation = station; + let currentIdx = 0; + let currentStation = this.head; + while (currentIdx < idx - 1) { + currentStation = currentStation.next; + currentIdx++; + } + addStation.next = currentStation.next; + currentStation.next = addStation; + this.length++; + manager.setChangedLine(this); + + station.addIncludedLine(this.name); + }; + this.deleteStationInIdx = (idx) => { + let currentIdx = 0; + let currentStation = this.head; + if (idx === "0") { + this.deleteOneStationInLine(currentStation); + this.head = currentStation.next; + } else { + while (currentIdx < idx - 1) { + currentStation = currentStation.next; + currentIdx++; + } + this.deleteOneStationInLine(currentStation.next); + currentStation.next = currentStation.next.next; + } + this.length--; + manager.setChangedLine(this); + }; + this.deleteOneStationInLine = (current) => { + const deleteIdx = manager.stationList.findIndex( + (station) => + station.name === current.name && station.isIncluded === this.name + ); + manager.stationList.splice(deleteIdx, 1); + }; + this.deleteAllStationInLine = () => { + let current = this.head; + while (current.next !== null) { + this.deleteOneStationInLine(current); + current = current.next; // 마지막 전까지 지우기 + } + this.deleteOneStationInLine(current); // 마지막 역 지우기 + }; +} diff --git a/src/make-selector-list.js b/src/make-selector-list.js new file mode 100644 index 000000000..3b09a8cfa --- /dev/null +++ b/src/make-selector-list.js @@ -0,0 +1,37 @@ +import { manager } from "./manager.js"; + +export const pushStation = (sameName, name, stationList) => { + if (sameName.length === 0) { + return stationList.filter( + (station) => station.name === name && station.isIncluded === null + )[0]; + } else { + return sameName[0]; + } +}; +export const makeStationList = () => { + const finalStationList = []; + const stationList = manager.stationList; + const stationName = stationList.map((station) => { + return station.name; + }); + const stationNameSet = Array.from(new Set(stationName)); + stationNameSet.forEach((name) => { + const sameName = stationList.filter( + (station) => station.name === name && station.isIncluded !== null + ); + finalStationList.push(pushStation(sameName, name, stationList)); + }); + + return finalStationList; +}; +export const makeStationOption = (optionName) => { + const StationListInOption = makeStationList(); + const optionList = document.getElementById(optionName); + optionList.innerHTML = ""; // 선택 노선 변경 시 지하철 역 새로 load + for (let idx in StationListInOption) { + const newOption = document.createElement("option"); + newOption.innerHTML = StationListInOption[idx].name; + optionList.appendChild(newOption); + } +}; diff --git a/src/manager.js b/src/manager.js new file mode 100644 index 000000000..0a1d35280 --- /dev/null +++ b/src/manager.js @@ -0,0 +1,30 @@ +export function Manager() { + this.lineList = []; + this.stationList = []; + this.selectedLine = null; + this.setStationInManager = (station) => { + this.stationList.push(station); + }; + this.setLineInManager = (line) => { + this.lineList.push(line); + }; + this.getAllLineName = () => { + return this.lineList.map((line) => { + return line.name; + }); + }; + this.setSelectedLine = (selectedLine) => { + this.selectedLine = selectedLine; + }; + this.getSelectedLine = () => { + return this.selectedLine; + }; + this.setChangedLine = (changedLine) => { + this.lineList.forEach((line) => { + if (line.name === changedLine.name) { + line = changedLine; + } + }); + }; +} +export const manager = new Manager(); diff --git a/src/map-print-manager.js b/src/map-print-manager.js new file mode 100644 index 000000000..e2146f75b --- /dev/null +++ b/src/map-print-manager.js @@ -0,0 +1,39 @@ +import { manager } from "./manager.js"; + +export const makeLineNameUI = (lineName) => { + const lineNameBox = document.createElement("div"); + lineNameBox.innerHTML = lineName; + + return lineNameBox; +}; +export const makeStationInLineUI = (line) => { + const stationBox = document.createElement("ul"); + const stationList = line.getAllStationName(); + stationList.forEach((station) => { + const oneStation = document.createElement("li"); + oneStation.innerHTML = station; + stationBox.appendChild(oneStation); + }); + + return stationBox; +}; +export const makeMapUI = (line) => { + const oneMap = document.createElement("div"); + const lineNameBox = makeLineNameUI(line.name); + const stationBox = makeStationInLineUI(line); + oneMap.append(lineNameBox, stationBox); + + return oneMap; +}; +export const showMapList = () => { + const lineList = manager.lineList; + const mapPrintResult = document.getElementById("map-print-manager-result"); + const mapList = document.createElement("div"); + mapPrintResult.innerHTML = ""; // 버튼 누를때 마다 map 추가되지 않게 초기화 + mapList.setAttribute("class", "map"); + lineList.forEach((line) => { + const map = makeMapUI(line); // 라인 하나의 map그리기 + mapList.appendChild(map); + }); + mapPrintResult.appendChild(mapList); +}; diff --git a/src/section-manager.js b/src/section-manager.js new file mode 100644 index 000000000..61a403185 --- /dev/null +++ b/src/section-manager.js @@ -0,0 +1,121 @@ +import { makeStationOption } from "./make-selector-list.js"; +import { manager } from "./manager.js"; +import Station from "./station.js"; + +export const isCorrectDeleteIdx = () => { + if (manager.getSelectedLine().length <= 2) { + alert("더 이상 지하철 역을 제거 할 수 없습니다."); + + return false; + } + return true; +}; +export const deleteSectionInList = (idx) => { + if (isCorrectDeleteIdx()) { + const stationInSelecteLine = document.getElementById( + "station-in-selected-line" + ); + stationInSelecteLine.innerHTML = ""; + manager.getSelectedLine().deleteStationInIdx(idx); + manager.setSelectedLine(manager.selectedLine); + showSectionList(manager.getSelectedLine().getAllStationName()); + } +}; +export const makeSectionUI = (station, idx) => { + const oneStation = document.createElement("tr"); + oneStation.setAttribute("id", `${idx}`); + const stationIdx = document.createElement("td"); + const stationName = document.createElement("td"); + const deleteButton = document.createElement("td"); + stationIdx.innerHTML = idx; + stationName.innerHTML = station; + deleteButton.innerHTML = "삭제"; + deleteButton.setAttribute("class", "section-delete-button"); + deleteButton.onclick = () => { + deleteSectionInList(`${idx}`); + }; + + return [stationIdx, stationName, deleteButton]; +}; +export const showSectionList = (allStationName) => { + const table = document.getElementById("station-in-selected-line-list-table"); + allStationName.forEach((station, idx) => { + const [stationIdx, stationName, deleteButton] = makeSectionUI(station, idx); + const oneStation = document.createElement("tr"); + oneStation.setAttribute("id", `${idx}`); + oneStation.append(stationIdx, stationName, deleteButton); + + table.children[1].appendChild(oneStation); + }); +}; +export const onClickedLine = (lineName) => { + const selectedLineName = lineName; + const selectedLineBox = document.getElementById("selected-section-line"); + selectedLineBox.style.display = "Block"; + const sectionManagerTitle = document.getElementById("section-line-name"); + sectionManagerTitle.innerHTML = `${selectedLineName} 관리`; + resetSectionInput(); + resetSectionList(); + const selectedLine = manager.lineList.find( + (line) => line.name === selectedLineName + ); + manager.setSelectedLine(selectedLine); + showSectionList(manager.getSelectedLine().getAllStationName()); +}; +export const makeLineButton = (line) => { + const lineMenuButton = document.createElement("button"); + lineMenuButton.setAttribute("class", "section-line-menu-button"); + lineMenuButton.setAttribute("id", `${line.name}`); + lineMenuButton.onclick = () => { + onClickedLine(`${line.name}`); + }; + lineMenuButton.innerHTML = line.name; + + return lineMenuButton; +}; +export const showLineMenuInSectionManager = (lineList) => { + const lineMenu = document.getElementById("section-line-list"); + lineMenu.innerHTML = ""; + lineList.forEach((line) => { + const lineMenuButton = makeLineButton(line); + lineMenu.appendChild(lineMenuButton); + }); +}; +export const resetSectionInput = () => { + document.getElementById("section-order-input").value = ""; + makeStationOption("section-station-selector"); +}; +export const resetSectionList = () => { + document.getElementById("station-in-selected-line").innerHTML = ""; +}; +export const isCorrectAddIdx = (idx) => { + if ( + idx > 0 && + idx < manager.getSelectedLine().length && + !isNaN(idx) && + Number.isInteger(Number(idx)) + ) { + return true; + } else { + alert("올바른 구간 번호를 입력하세요"); + resetSectionInput(); + + return false; + } +}; +const btnAddSection = document.getElementById("section-add-button"); +btnAddSection.onclick = () => { + const addSectionIdx = document.getElementById("section-order-input").value; + if (isCorrectAddIdx(addSectionIdx)) { + const addStationName = document.getElementById("section-station-selector") + .value; + const selectedLine = manager.getSelectedLine(); + const inputStation = new Station(addStationName); + inputStation.addIncludedLine(selectedLine.name); + manager.setStationInManager(inputStation); + selectedLine.addStationInIdx(inputStation, addSectionIdx); + resetSectionList(); + showSectionList(manager.getSelectedLine().getAllStationName()); + resetSectionInput(); + } +}; diff --git a/src/station-manager.js b/src/station-manager.js new file mode 100644 index 000000000..be7c37c32 --- /dev/null +++ b/src/station-manager.js @@ -0,0 +1,93 @@ +import { manager } from "./manager.js"; +import Station from "./station.js"; +import { makeStationList } from "./make-selector-list.js"; + +export const isCorrectStationName = (newStationName) => { + const rHangel = /^[0-9A-Za-z가-힣]*$/; + if (newStationName.length >= 2 && rHangel.exec(newStationName) !== null) { + return true; + } + alert("지하철 역 이름을 두 글자 이상 입력하세요."); + document.getElementById("station-name-input").value = ""; + + return false; +}; +export const isOverlappedStationName = (newStationName) => { + if (manager.stationList.length > 0) { + const overlappedName = manager.stationList.find( + (station) => station.name === newStationName + ); + if (overlappedName) { + alert("중복된 역 이름 입니다."); + document.getElementById("station-name-input").value = ""; + + return true; + } + return false; + } +}; +export const isPossibleToDeleteStation = (stationIsIncluded) => { + if (stationIsIncluded === "null") { + return true; + } + alert("이미 노선에 등록되어 있는 역입니다."); + + return false; +}; +export const deleteStationInList = (stationName, stationIsIncluded) => { + const parent = document.querySelector("table#staion-list-table tbody"); + if (isPossibleToDeleteStation(stationIsIncluded)) { + const deleteIdx = manager.stationList.findIndex((station) => { + return station.name === stationName && station.isIncluded === null; + }); + parent.removeChild( + document.querySelector( + `table#staion-list-table tbody tr#${stationName}-null` + ) + ); + manager.stationList.splice(deleteIdx, 1); + showAllStationInManager(makeStationList()); + } +}; +export const makeStationBox = (newStationName, newStationIsIncluded) => { + const newStation = document.createElement("tr"); + const stationName = document.createElement("td"); + const deleteButton = document.createElement("td"); + newStation.setAttribute("id", `${newStationName}-null`); + stationName.innerHTML = newStationName; + deleteButton.setAttribute("class", "station-delete-class"); + deleteButton.onclick = () => { + deleteStationInList(`${newStationName}`, `${newStationIsIncluded}`); + }; + deleteButton.innerHTML = "삭제"; + newStation.appendChild(stationName); + newStation.appendChild(deleteButton); + + return newStation; +}; +export const showAllStationInManager = (stationList) => { + const table = document.getElementById("station-list"); + table.innerHTML = ""; + stationList.forEach((station) => { + const newStation = makeStationBox(station.name, station.isIncluded); + table.appendChild(newStation); + }); +}; +export const addStationToList = (newStationName) => { + const station = new Station(newStationName); + manager.setStationInManager(station); + const stationList = makeStationList(); // 중복 제거 된 지하철 역 리스트 + showAllStationInManager(stationList); + document.getElementById("station-name-input").value = ""; +}; + +const btnAddStation = document.getElementById("station-add-button"); +btnAddStation.onclick = () => { + const newStationName = document.getElementById("station-name-input").value; + if ( + isCorrectStationName(newStationName) && + !isOverlappedStationName(newStationName) + ) { + addStationToList(newStationName); + } +}; diff --git a/src/station.js b/src/station.js new file mode 100644 index 000000000..fc03f01d1 --- /dev/null +++ b/src/station.js @@ -0,0 +1,8 @@ +export default function Station(name) { + this.name = name; + this.next = null; + this.isIncluded = null; + this.addIncludedLine = (lineName) => { + this.isIncluded = lineName; + }; +}