이전글 보기
JavaScript 법정동 데이터 관리와 동적 선택기 모듈 구현: 최신 데이터 가져오기 및 데이터 가공하기
저번글에서는 최신 데이터를 가져와 가공하는 법에 대해서 글을 썼다. 오늘은 저장한 JSON 데이터를 기반으로 동적 셀렉트 박스를 구현해보려고 한다.
모듈 외부 코드 살펴보기
우선 모듈을 테스트할 HTML 코드이다.
<select id="siDo">
<option value="">시/도 선택</option>
</select>
<select id="siGunGu">
<option value="">시/군/구 선택</option>
</select>
<select id="eupMyeonDong">
<option value="">읍/면/동 선택</option>
</select>
<select id="dongRi">
<option value="">동/리 선택</option>
</select>
구조는 단순히 select와 id 값만 넣어줬다. 내부의 기본 선택 옵션은 넣어주지 않아도 되지만 최초 렌더링 후 확인할 수 있게 넣어줬다. 이후에 선택기가 동작하면 모듈 내부에서 option을 갈아끼울 때 새로 넣어준다.
다음은 JavaScript 코드
let regionModule;
// DOM load 후 지역 모듈 초기화
document.addEventListener("DOMContentLoaded", function () {
initializeRegionModule();
});
// 모듈 초기화 함수
const initializeRegionModule = async () => {
regionModule = new RegionModule();
await regionModule.initialize();
// 가져온 데이터를 기반으로 만든 옵션 요소들을 넣을 셀렉터 바인딩
regionModule.siDoSelector = document.getElementById("siDo");
regionModule.siGunGuSelector = document.getElementById("siGunGu");
regionModule.eupMyeonDongSelector = document.getElementById("eupMyeonDong");
regionModule.dongRiSelector = document.getElementById("dongRi");
// 시/도 셀렉트 박스 로드
regionModule.loadSiDo();
}
// 선택된 법정동 주소를 가져오는 함수
const getSelectedAddress = () => {
console.log(regionModule.getFullAddressName());
}
셀렉터를 바인딩하는 부분의 코드가 너무 중복인 것 같아서 바꾸려다가 작업이 좀 커지는 것 같아서 일단 뒀다.
나중에 리팩토링 해야할 듯..ㅜㅜ
region.module.js 내부 살펴보기
모듈 내부에 대한 설명을 간단하게 하자면, 우선 RegionModule 클래스를 정의하여 행정 구역(시/도, 시/군/구, 읍/면/동, 동/리)을 선택할 수 있는 셀렉트 박스를 구현했다. 또한 선택한 지역에 따라 하위 지역이 업데이트되도록 설계했으며, 비동기적으로 JSON 파일에서 지역 데이터를 로드해서 사용한다.
차근차근 코드를 살펴 보자.
클래스 변수와 상수
constructor() {
this._regionData = [];
this._siDoSelector = null;
this._siGunGuSelector = null;
this._eupMyeonDongSelector = null;
this._dongRiSelector = null;
this._TYPE_KOR = {
SIDO: "시/도",
SIGUNGU: "시/군/구",
EUPMYEONDONG: "읍/면/동",
DONGRI: "동/리"
}
}
_regionData
- 지역 데이터를 저장하는 배열이다. JSON 파일에서 로드한 데이터를 저장하며, getter와 setter로 접근 가능하다.
_siDoSelecotor, _siGunGuSelecotor, _eupMyeonDongSelector, _dongRiSelector:
- select 요소를 참조할 변수들. 각각 시/도, 시/군/구, 읍/면/동, 동/리를 선택하는 드롭다운 메뉴를 가리킨다.
_TYPE_KOR
- 한글로 된 행정구역 명칭을 정의하는 상수 객체다.
Getter 그리고 Setter
get regionData() {
return this._regionData;
}
set regionData(value) {
this._regionData = value;
}
get TYPE_KOR() {
return this._TYPE_KOR;
}
get siDoSelector() {
return this._siDoSelector;
}
get siGunGuSelector() {
return this._siGunGuSelector;
}
get eupMyeonDongSelector() {
return this._eupMyeonDongSelector;
}
get dongRiSelector() {
return this._dongRiSelector;
}
set siDoSelector(value) {
this._siDoSelector = value;
this._siDoSelector.addEventListener("change", this.updateSiGunGu);
this._addChangeListener(this._siDoSelector, this.updateSiGunGu.bind(this));
}
set siGunGuSelector(value) {
this._siGunGuSelector = value;
this._siDoSelector.addEventListener("change", this.updateSiGunGu);
this._addChangeListener(this._siGunGuSelector, this.updateEupMyeonDong.bind(this));
}
set eupMyeonDongSelector(value) {
this._eupMyeonDongSelector = value;
this._siDoSelector.addEventListener("change", this.updateEupMyeonDong);
this._addChangeListener(this._eupMyeonDongSelector, this.updateDongRi.bind(this));
}
set dongRiSelector(value) {
this._dongRiSelector = value;
}
_addChangeListener(element, handler) {
if (element) {
element.removeEventListener("change", handler);
element.addEventListener("change", handler);
}
일반적인 getter와 setter다. 살펴볼 점은 지역 셀렉터 setter에 change 이벤트 리스너 기능을 포함했다.
예를 들어 시/도를 선택해서 siDoSelector가 설정될 때 updateSiGunGu 함수가 등록된다.
또 이벤트 리스너 등록 메소드인 _addChangeListener에서 addEventListener와 removeEventListener의 중복 등록을 방지 처리했다.
모듈 초기화 메소드: initialize()
/**
* 모듈 초기화 함수
*/
initialize = async () => {
this.regionData = await this.loadRegionJsonData();
}
/**
* 지역 JSON DATA 가져오는 함수
* @returns {Promise<any|null>}
*/
loadRegionJsonData = async () => {
try {
const response = await fetch("../../../../resources/js/config/region.json");
return await response.json();
} catch (error) {
console.error(error);
return null;
}
}
모듈 초기화 함수이다. JSON 파일에서 지역 데이터를 비동기로 로드해서 regionData 변수에 저장한다.
fetch 할 떄 JSON 파일 경로를 하드코딩 했기 때문에 조금 아쉬운 점이 있다.
지역 데이터를 옵션 요소로 추가하는 메소드: populateSelectOptions()
populateSelectOptions = (targetSelect, items, selectedText = "") => {
if (!targetSelect)
return;
targetSelect.innerHTML = `<option value="">${selectedText} 선택</option>`;
items.forEach(item => {
const option = document.createElement("option");
option.value = item.fullCode;
option.textContent = item.name;
targetSelect.appendChild(option);
});
}
셀렉터에 option 요소를 추가하는 함수이다. items 배열을 반복해서 옵션을 선택한다. selectedText에는 시/도 등의 TYPE_KOR 값이 전달된다.
지역 선택 옵션 업데이트 메소드
loadSiDo = () => {
this.populateSelectOptions(this.siDoSelector, this.regionData, this.TYPE_KOR.SIDO);
}
updateSiGunGu = () => {
const siDoCode = this.siDoSelector.value;
if (!siDoCode)
return;
const siDo = this.regionData.find(region => region.fullCode === siDoCode);
if (siDo && siDo.children) {
this.populateSelectOptions(this.siGunGuSelector, siDo.children, this.TYPE_KOR.SIGUNGU);
this.eupMyeonDongSelector.innerHTML = `<option value="">${this.TYPE_KOR.EUPMYEONDONG} 선택</option>`;
if (this.dongRiSelector)
this.dongRiSelector.innerHTML = `<option value="">${this.TYPE_KOR.DONGRI} 선택</option>`;
}
}
updateEupMyeonDong = () => {
const siGunGuCode = this.siGunGuSelector.value;
if (!siGunGuCode)
return;
const siDo = this.regionData.find(region =>
region.children.some(siGunGu => siGunGu.fullCode === siGunGuCode)
);
const siGunGu = siDo.children.find(item => item.fullCode === siGunGuCode);
if (siGunGu && siGunGu.children) {
this.populateSelectOptions(this.eupMyeonDongSelector, siGunGu.children, this.TYPE_KOR.EUPMYEONDONG);
if (this.dongRiSelector)
this.dongRiSelector.innerHTML = `<option value="">${this.TYPE_KOR.DONGRI} 선택</option>`;
}
}
updateDongRi = () => {
const eupMyeonDongCode = this.eupMyeonDongSelector.value;
if (!eupMyeonDongCode)
return;
const siDo = this.regionData.find(region =>
region.children.some(siGunGu =>
siGunGu.children.some(eupMyeonDong => eupMyeonDong.fullCode === eupMyeonDongCode)
)
);
const siGunGu = siDo.children.find(siGunGu =>
siGunGu.children.some(eupMyeonDong => eupMyeonDong.fullCode === eupMyeonDongCode)
);
const eupMyeonDong = siGunGu.children.find(item => item.fullCode === eupMyeonDongCode);
if (eupMyeonDong && eupMyeonDong.children)
this.populateSelectOptions(this.dongRiSelector, eupMyeonDong.children, this.TYPE_KOR.DONGRI);
}
loadSiDo 메소드는 regionData에서 시/도 데이터를 가져와서 siDoSelector에 추가한다.
보통 모듈 초기화를 하고 바로 해당 함수 호출한다.
아래 update 함수들은 각 셀렉트 값이 변경될 때 하위 지역 목록을 업데이트 하는 함수들이다.
시/도가 선택될 때 선택된 시/도에 따라 시/군/구 데이터를 가져와 초기화하고,
시/군/구일 때는 읍/면/동을, 읍/면/동일 때는 동/리를 업데이트 한다.
선택된 지역 이름 가져오는 메소드
getNameByFullCode(fullCode) {
const findName = (data, code) => {
for (const item of data) {
if (item.fullCode === code)
return item.name;
if (item.children && item.children.length > 0) {
const name = findName(item.children, code);
if (name)
return name;
}
}
return null;
};
return findName(this._regionData, fullCode);
}
getFullAddressName = () => {
const addressNames = [];
const siDoName = this.getNameByFullCode(this.siDoSelector.value);
if (siDoName)
addressNames.push(siDoName);
const siGunGuName = this.getNameByFullCode(this.siGunGuSelector.value);
if (siGunGuName)
addressNames.push(siGunGuName);
const eupMyeonDongName = this.getNameByFullCode(this.eupMyeonDongSelector.value);
if (eupMyeonDongName)
addressNames.push(eupMyeonDongName);
if (this.dongRiSelector) {
const dongRiName = this.getNameByFullCode(this.dongRiSelector.value);
if (dongRiName)
addressNames.push(dongRiName);
}
return {
addressNames: addressNames,
convertedName: addressNames.join(" ")
};
}
내가 필요로 하는 정보는 법정동명이기 때문에 우선은 이름들만 가져오게 구현했다.
getNameByFullCode() 메소드에서는 전달된 fullCode를 기반으로 지역의 이름을 반한하는 재귀함수다.
getFullAddressName() 메소드에서 위 재귀함수를 호출하여 각각의 이름을 가져온다.
동작 예시와 마무리
현재까지 구현된 내용은 이렇다. 라이브러리 없이 생으로 개발하려니 시행착오도 많았고, 부족한 점도 많이 보인다.
구글 검색이랑 gpt 조언도 많이 구하고... 아직은 부족하지만 그래도 이번 모듈을 만들면서 여러 가지 시도해볼 수 있어서 좋았다.
이벤트 리스너 중복 등록 방지도 평소에는 생각하지 않았는데 이번에 시도해 보았고, bind() 함수로 this 컨텍스트 참조 부분도 많이 헤맸는데 결국 방법을 찾아 냈다. 뿌듯 .. ㅜㅜ
아직은 온전히 내 코드라고 말하기는 어렵지만, 조금씩 고쳐나가다 보면 익숙해질 것 같다.
예정이 언제가 될 진 모르겠지만 서버에서 내려온 데이터를 받아서 셀렉트에 바인딩하는 작업도 구현해야겠다 :D
'Frontend > javascript' 카테고리의 다른 글
JavaScript로 파일 다운로드 하기 with 다운로드시 파일 깨짐 방지 (11) | 2024.11.10 |
---|---|
JavaScript로 HTML 코드에서 텍스트만 추출하기 (1) | 2024.11.09 |
JavaScript 법정동 데이터 관리와 동적 선택기 모듈 구현: 최신 데이터 가져오기 및 데이터 가공하기 (1) | 2024.11.07 |
자바스크립트 매개변수 기본값 처리에 대해서 (2) | 2024.10.17 |
jQuery 선택자를 이용해서 DOM 요소를 선택할 때 주의점 (0) | 2024.03.29 |