이번 시간에는 Alpine.js를 사용하여 3개의 Select Box를 만들고, 첫 번째 Select Box의 값에 따라 두 번째 Select Box의 옵션이 바뀌고, 두 번째 Select Box의 값에 따라 세 번째 Select Box의 옵션이 바뀌는 기능을 구현해 보겠습니다. 또한, 첫 번째 Select Box의 값이 변경되면 두 번째와 세 번째 Select Box는 초기화되도록 만들어 보겠습니다.
핵심 개념:
HTML 구조:
먼저 기본적인 HTML 구조를 만들어 보겠습니다. 3개의 <select> 태그를 감싸는 <div>를 만들고, Alpine.js를 사용할 것이므로 x-data 속성을 추가합니다.
실행 결과:
코드를 HTML 파일에 저장하고 웹 브라우저로 열면, 다음과 같은 동작을 확인할 수 있습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alpine.js 종속형 Select Box</title>
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body>
<div x-data="{
categories: [
{ value: 'fruit', label: '과일' },
{ value: 'vegetable', label: '채소' },
{ value: 'drink', label: '음료' }
],
subCategories: {
'fruit': [
{ value: 'apple', label: '사과' },
{ value: 'banana', label: '바나나' },
{ value: 'orange', label: '오렌지' }
],
'vegetable': [
{ value: 'carrot', label: '당근' },
{ value: 'broccoli', label: '브로콜리' },
{ value: 'cucumber', label: '오이' }
],
'drink': [
{ value: 'water', label: '물' },
{ value: 'juice', label: '주스' },
{ value: 'soda', label: '탄산음료' }
]
},
items: {
'apple': ['빨간 사과', '푸른 사과'],
'banana': ['달콤한 바나나', '작은 바나나'],
'orange': ['신선한 오렌지', '제주 오렌지'],
'carrot': ['단단한 당근', '미니 당근'],
'broccoli': ['유기농 브로콜리', '냉동 브로콜리'],
'cucumber': ['아삭한 오이', '백오이'],
'water': ['생수', '탄산수'],
'juice': ['오렌지 주스', '사과 주스'],
'soda': ['콜라', '사이다']
},
selectedCategory: '',
selectedSubCategory: '',
selectedItem: '',
filteredSubCategories: [],
filteredItems: []
}">
<div>
<label for="category">카테고리:</label>
<select id="category" x-model="selectedCategory" @change="selectedSubCategory = ''; selectedItem = ''; filteredSubCategories = subCategories[selectedCategory] || []; filteredItems = []">
<option value="">선택하세요</option>
<template x-for="category in categories" :key="category.value">
<option :value="category.value" x-text="category.label"></option>
</template>
</select>
</div>
<div style="margin-top: 10px;">
<label for="subCategory">세부 카테고리:</label>
<select id="subCategory" x-model="selectedSubCategory" @change="selectedItem = ''; filteredItems = items[selectedSubCategory] || []">
<option value="">선택하세요</option>
<template x-for="subCategory in filteredSubCategories" :key="subCategory.value">
<option :value="subCategory.value" x-text="subCategory.label"></option>
</template>
</select>
</div>
<div style="margin-top: 10px;">
<label for="item">품목:</label>
<select id="item" x-model="selectedItem">
<option value="">선택하세요</option>
<template x-for="item in filteredItems" :key="item">
<option :value="item" x-text="item"></option>
</template>
</select>
</div>
<div style="margin-top: 20px;">
선택된 카테고리: <span x-text="selectedCategory"></span><br>
선택된 세부 카테고리: <span x-text="selectedSubCategory"></span><br>
선택된 품목: <span x-text="selectedItem"></span>
</div>
</div>
</body>
</html>
웹 서비스를 호출하는 방식으로 변경해 보겠습니다. 이번에는 서버에서 데이터를 비동기적으로 가져와서 Select Box의 옵션을 채우게 됩니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alpine.js 웹 서비스 호출 종속형 Select Box</title>
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body>
<div x-data="{
categories: [],
subCategories: [],
items: [],
selectedCategory: '',
selectedSubCategory: '',
selectedItem: '',
categoryLoading: false,
subCategoryLoading: false,
itemLoading: false,
categoryError: null,
subCategoryError: null,
itemError: null,
// 첫 번째 Select Box 데이터 가져오기
loadCategories() {
this.categoryLoading = true;
this.categoryError = null;
fetch('/api/categories') // 실제 API 엔드포인트로 변경해야 합니다.
.then(response => {
if (!response.ok) {
throw new Error('카테고리 데이터를 불러오는 데 실패했습니다.');
}
return response.json();
})
.then(data => {
this.categories = data;
this.categoryLoading = false;
})
.catch(error => {
this.categoryError = error.message;
this.categoryLoading = false;
});
},
// 두 번째 Select Box 데이터 가져오기
loadSubCategories() {
if (!this.selectedCategory) {
this.subCategories = [];
return;
}
this.subCategoryLoading = true;
this.subCategoryError = null;
this.subCategories = []; // 이전 데이터 초기화
this.selectedSubCategory = ''; // 두 번째 Select Box 초기화
this.selectedItem = ''; // 세 번째 Select Box 초기화
this.items = []; // 세 번째 Select Box 데이터 초기화
this.itemError = null;
fetch(`/api/subcategories?category=${this.selectedCategory}`) // 실제 API 엔드포인트로 변경해야 합니다.
.then(response => {
if (!response.ok) {
throw new Error('세부 카테고리 데이터를 불러오는 데 실패했습니다.');
}
return response.json();
})
.then(data => {
this.subCategories = data;
this.subCategoryLoading = false;
})
.catch(error => {
this.subCategoryError = error.message;
this.subCategoryLoading = false;
});
},
// 세 번째 Select Box 데이터 가져오기
loadItems() {
if (!this.selectedSubCategory) {
this.items = [];
return;
}
this.itemLoading = true;
this.itemError = null;
this.items = []; // 이전 데이터 초기화
this.selectedItem = ''; // 세 번째 Select Box 초기화
fetch(`/api/items?subcategory=${this.selectedSubCategory}`) // 실제 API 엔드포인트로 변경해야 합니다.
.then(response => {
if (!response.ok) {
throw new Error('품목 데이터를 불러오는 데 실패했습니다.');
}
return response.json();
})
.then(data => {
this.items = data;
this.itemLoading = false;
})
.catch(error => {
this.itemError = error.message;
this.itemLoading = false;
});
},
// 컴포넌트 초기화 시 첫 번째 Select Box 데이터 로드
init() {
this.loadCategories();
}
}">
<div>
<label for="category">카테고리:</label>
<select id="category" x-model="selectedCategory" @change="loadSubCategories()" :disabled="categoryLoading">
<option value="">선택하세요</option>
<template x-for="category in categories" :key="category.value">
<option :value="category.value" x-text="category.label"></option>
</template>
</select>
<template x-if="categoryLoading">
<span>로딩 중...</span>
</template>
<template x-if="categoryError">
<span style="color: red;" x-text="categoryError"></span>
</template>
</div>
<div style="margin-top: 10px;">
<label for="subCategory">세부 카테고리:</label>
<select id="subCategory" x-model="selectedSubCategory" @change="loadItems()" :disabled="subCategoryLoading || !selectedCategory">
<option value="">선택하세요</option>
<template x-for="subCategory in subCategories" :key="subCategory.value">
<option :value="subCategory.value" x-text="subCategory.label"></option>
</template>
</select>
<template x-if="subCategoryLoading">
<span>로딩 중...</span>
</template>
<template x-if="subCategoryError">
<span style="color: red;" x-text="subCategoryError"></span>
</template>
</div>
<div style="margin-top: 10px;">
<label for="item">품목:</label>
<select id="item" x-model="selectedItem" :disabled="itemLoading || !selectedSubCategory">
<option value="">선택하세요</option>
<template x-for="item in items" :key="item">
<option :value="item" x-text="item"></option>
</template>
</select>
<template x-if="itemLoading">
<span>로딩 중...</span>
</template>
<template x-if="itemError">
<span style="color: red;" x-text="itemError"></span>
</template>
</div>
<div style="margin-top: 20px;">
선택된 카테고리: <span x-text="selectedCategory"></span><br>
선택된 세부 카테고리: <span x-text="selectedSubCategory"></span><br>
선택된 품목: <span x-text="selectedItem"></span>
</div>
</div>
</body>
</html>
코드 변경 상세 설명:
// /api/categories 응답 예시
[
{ "value": "fruit", "label": "과일" },
{ "value": "vegetable", "label": "채소" },
{ "value": "drink", "label": "음료" }
]
// /api/subcategories?category=fruit 응답 예시
[
{ "value": "apple", "label": "사과" },
{ "value": "banana", "label": "바나나" },
{ "value": "orange", "label": "오렌지" }
]
// /api/items?subcategory=apple 응답 예시
[
"빨간 사과",
"푸른 사과"
]
이제 이 HTML 파일을 웹 브라우저로 열면, 페이지가 로드될 때 /api/categories를 호출하여 첫 번째 Select Box의 옵션을 채웁니다. 첫 번째 Select Box의 값을 변경하면 /api/subcategories를 호출하여 두 번째 Select Box의 옵션을 채우고, 두 번째 Select Box의 값을 변경하면 /api/items를 호출하여 세 번째 Select Box의 옵션을 채우는 방식으로 동작하게 됩니다.
Bootstrap v5.1.0 : AJAX 에서 modal 창 사용 방법 (0) | 2025.03.11 |
---|---|
JavaScript : 부동소수점 연산 오차 문제와 해결 방법 (1) | 2025.03.07 |
JavaScript : Hoisting(호이스팅) 최상단으로 끌어올려지는 현상 알아보자 (0) | 2025.03.05 |
사용자 이미지 업로드(JavaScript) -> Google Cloud Vision API 로 이미지 분석 (1) | 2024.11.20 |
JS : 오늘, 내일 날씨 및 온도 구현하기 (openweathermap 활용) (1) | 2024.10.31 |