ppt 66p
템플릿의 데이터를 바인딩
저번 수업 때는 '단방향' 바인딩
오늘 학습할 내용 V-디렉티브를 이용한 바인딩
p71

SPA에서는 a태그를 사용하면 안된다고 했다. -> RouterLink를 사용
★ 외부파일을 참조하여 가져올 때는 require함수 형태로 작성하여 가져온다.
쉽게 말해 앞에 콜론(:)이 붙는 경우 자바스크립트 코드처럼 사용한다.
실습해보자.
1. Ch03DataBinding 폴더에 Exam05AttrBinding.vue 생성
2. 라우터 폴더의 Ch03DataBinding.js 컴포넌트 추가
3. 컴포넌츠 폴더의 AppMenu.vue 리스트 하나 추가

4. Exam05AttrBinding.vue 코드 작성
<template>
<div class="card">
<div class="card-header">Exam05AttrBinding</div>
<div class="card-body">
<div class="mb-4">
<h5>:속성 = "변수"</h5>
<!-- <a href="https://www.vuejs.org" class="me-3">Vue 홈페이지</a> -->
<a :href="vueHome" class="me-3">Vue 홈페이지</a>
<!-- <RouterLink to="/">홈 페이지</RouterLink> -->
<RouterLink :to="appHome">홈 페이지</RouterLink>
</div>
<hr>
<div class="mb-4">
<h5>:속성 = "연산식" | `매개변수화된 문자열`</h5>
<!-- <img src="@/assets/photos/photo1.jpg" height="150" class="me-2"/> -->
<img :src="require('@/assets/photos/' + imgName)" height="150" class="me-2" />
<img :src="require(`@/assets/photos/${imgName}`)" height="150" class="me-2" />
</div>
<hr>
<div class="mb-4">
<h5>:class="변수 | 객체 | 배열"</h5>
<div class="mb-3 fw-bold">아름다운 풍경</div> <!-- fw = font-weight -->
<div class="mb-3" :class="className1">아름다운 풍경</div> <!-- mr-3과 className이 더해짐 -->
<div class="mb-3" :class="[className1, className2]">아름다운 풍경</div> <!-- 배열식으로 사용 가능 -->
<div class="mb-3" :class="{'fw-bold':true, 'text-info':true}">아름다운 풍경</div> <!-- 자바스크립트의 객체 표시 {키:값} // true, false 값은 우리가 변수를 넣어 제어 가능하다.-->
<div class="mr-3" :class="{'fw-bold':isBold, 'text-danger':isRed}">아름다운 풍경</div>
</div>
<hr>
<div class="mb-4">
<h5>:style="변수 | 객체 | 배열"</h5>
<div style="margin-bottom: 10px; font-weight: bold;">아름다운 풍경</div>
<div :style="[style1, style2]">아름다운 풍경</div>
<div style="margin-bottom: 10px" :style="{'font-weight': fontWeight, 'color':textColor}">아름다운 풍경</div>
</div>
<hr>
<button class="btn btn-info btn-sm" @click="changeData">상태 변수값 변경</button>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
let vueHome = ref("https://vuejs.org");
let appHome = ref("/");
let imgName = ref("photo1.jpg");
let className1 = ref("fw-bold");
let className2 = ref("text-danger");
let isBold = ref(false);
let isRed = ref(true);
let style1 = ref("margin-bottom:10px");
let style2 = ref("font-weight: bold; color:red");
// let style3 = ref("font-weight: bold;");
// let style4 = ref("color: green;");
let fontWeight = ref('bold');
let textColor = ref('green');
let toggle = ref(true);
function changeData() {
if(toggle.value) {
imgName.value = "photo2.jpg";
className1.value = "text-warning";
fontWeight.value = "";
textColor.value = "#0000ff";
} else {
imgName.value = "photo1.jpg";
className1.value = "text-danger";
fontWeight.value = "bold";
textColor.value = "#00ff00";
}
toggle.value = !toggle.value;
}
</script>
<style scoped></style>

AJAX를 사용하여 innerHTML. html의 조각을 얻어 적용을 한다면?
1. view폴더 - Ch03DataBinding폴더 - Exam06InnerHtmlBinding.vue 생성 (기초 코드만 작성)
2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가
3. components폴더 - AppMenu.vue파일에 리스트 코드 추가
cf) src 밑의 자원들은 필요에 따라서 pulbic으로 이동하게 된다. require에 의해 변환되어 public/ 경로로 설정이 바뀌거나 한다.
Exam06InnerHtmlBinding.vue
<template>
<div class="card">
<div class="card-header">Exam06InnerHtmlBinding</div>
<div class="card-body">
<div v-html="innerHtml"></div>
<!-- 테스트 용도 : <img src="/images/photos/photo1.jpg" height="150"/> -->
<hr>
<button @click="changeData" class="btn btn-info btn-sm">innerHTML 변경</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
let innerHtml = ref("");
let toggle = ref(true);
function changeData() {
if (toggle.value) {
// @assets/ 경로를 못잡기 때문에 public/images/photos에서 가져오게끔 바꿈
innerHtml.value = `
<div>
<h5>풍경 1</h5>
<img src="/images/photos/photo1.jpg" height="150"/>
</div>
`;
} else {
innerHtml.value = `
<div>
<h5>풍경 2</h5>
<img src="/images/photos/photo2.jpg" height="150"/>
</div>
`;
}
toggle.value = !toggle.value;
}
</script>
<style scoped></style>

cf) 리액트나 뷰에서는 태그에 id를 거의 사용하지 않는다.
1. view폴더 - Ch03DataBinding폴더 - Exam07IfShowBinding.vue 생성 (기초 코드만 작성)
2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가
3. components폴더 - AppMenu.vue파일에 리스트 코드 추가
Exam07IfShowBinding.vue
<template>
<div class="card">
<div class="card-header">Exam07IfShowBinding</div>
<div class="card-body">
<div id="" v-if="isPhoto" class="mb-2">
<img src="/images/photos/photo3.jpg" height="150">
</div>
<div v-show="!isPhoto">
<img src="/images/photos/photo4.jpg" height="150">
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
let isPhoto = ref(true);
</script>
<style scoped></style>
v-if는 false가 되면 아예 DOM에서 추가가 안됨
v-show는 false가 되어도 DOM에 추가는 되지만 화면에서는 안보인다.

태그를 계속적으로 추가. 반복할 태그에 지정. ( 배열일 경우 / 객체일 경우 )
대부분 배열일 경우로 사용하고 객체일 경우는 별로 없다..
:key는 반복하는 것에 대해 식별할 수 있는 값. ex) 게시물을 식별할 수 있는 값은? - 게시물 번호
v-for를 쓰면 항상 :key가 따라와야 한다.
우리가 이미지 파일을 저장할 때는 assets와 public에서 저장할 수 있다.
cf) assets 밑의 이미지 파일과 public 밑의 이미지 파일을 사용할 때의 차이점은?
assets에서 사용할 경우 빌드하는 과정에서 차이가 있다. @/assests을 사용하면서 경로를 잡아주게 되고
:src="require('@/assets/ ... ')" 로 사용하거나 :src="require(`@/assets/ ... `)" 빽틱을 사용할 수도 있다. (코드로 인식)
assets 디렉토리
- 빌드 처리: assets 디렉토리의 파일은 Vue CLI나 Vite 등의 번들러에 의해 처리됩니다. 파일은 해시값이 포함된 이름으로 변경되어, 캐싱 문제를 방지합니다.
- 경로 접근: assets 디렉토리의 파일에 접근할 때는 주로 import 문이나 require 문을 사용합니다. JavaScript 또는 Vue 파일 내에서 모듈 방식으로 불러와야 합니다.
public 디렉토리
- 빌드 처리: public 디렉토리의 파일은 빌드 과정에서 그대로 복사됩니다. 파일 이름이나 내용은 변경되지 않습니다.
- 경로 접근: public 디렉토리의 파일은 정적 파일로 취급되며, 애플리케이션의 루트 경로(/)를 기준으로 접근할 수 있습니다. 즉, 절대 경로로 접근합니다.
- 사용 예시:
# 정리
- assets 디렉토리: 번들링 및 최적화를 위해 파일을 처리할 필요가 있을 때 사용합니다. 주로 이미지, 스타일 시트, JavaScript 모듈 등을 포함합니다.
- --------------
- public 디렉토리: 빌드 과정에서 파일을 변경하지 않고 그대로 유지하고 싶을 때 사용합니다. 주로 정적 리소스, favicon, 로봇 파일 등을 포함합니다.
1. view폴더 - Ch03DataBinding폴더 - Exam08RepeatBinding.vue 생성 (기초 코드만 작성)
2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가
3. components폴더 - AppMenu.vue파일에 리스트 코드 추가
Exam08RepeatBinding.vue
<template>
<div class="card">
<div class="card-header">Exam08RepeatBinding</div>
<div class="card-body">
<div>
<h6>[범위 반복]</h6>
<span v-for="n in 3" :key="n" class="me-2">
<!-- 빽틱(`)이 들어가게 되면 코드이다. -->
<img :src="`/images/photos/photo${n}.jpg`" width="150" height="150" />
</span>
</div>
<hr>
<div class="mt-5">
<h6>[배열항목 반복]</h6>
<span v-for="(item, index) in photos" :key="index" class="me-2">
<!-- 빽틱(`)이 들어가게 되면 코드이다. -->
<img :src="`/images/photos/${item}`" width="150" height="150" />
</span>
</div>
<hr>
<div class="mt-5">
<h6>[배열항목 반복]</h6>
<table class="table table-bordered">
<thead>
<tr>
<th>No</th>
<th>Title</th>
<th>Writer</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<!-- 태그 안 내용에 값을 나타내려면 표현식 {{ }} 을 사용 -->
<tr v-for="item in boards" :key="item.bno">
<td>{{ item.bno }}</td>
<td>{{ item.btitle }}</td>
<td>{{ item.bwriter }}</td>
<td>{{ item.bdate }}</td>
</tr>
</tbody>
</table>
</div>
<hr>
<div class="mt-5">
<!-- 이렇게 사용하는 경우는 드물다.. -->
<h6>[객체 속성 반복]</h6>
<div v-for="(value, name, index) in board" :key="index">
<p>{{ index }}. {{ name }}: {{ value }}</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
// 배열
const photos = ref(["photo1.jpg", "photo2.jpg", "photo3.jpg"]);
// 객체를 담은 배열
// 실제로 백엔드 쪽에서 이렇게 배열로 받고 컴포넌트에 활용해야 할 것.
const boards = ref([
{ bno: 1, btitle: "제목1", bwriter: "글쓴이1", bdate: "2021-08-07" },
{ bno: 2, btitle: "제목2", bwriter: "글쓴이2", bdate: "2021-08-08" },
{ bno: 3, btitle: "제목3", bwriter: "글쓴이3", bdate: "2021-08-09" }
]);
// 하나의 객체
const board = ref({
bno: 4,
btitle: "제목4",
bcontent: "내용4",
bwriter: "글쓴이4",
bdate: "2021-08-10"
});
</script>
<style scoped></style>

양방향을 사용할 경우 v-model
v- 가 들어가면 뒤에는 '코드'
ex) 사용자가 입력시 바로 product.name 등 변수에 저장된다(프론트에서 백으로 데이터 즉시 전달 후 저장). 그래서 '양방향'이라고 부르는 것이다.
checkbox는 보통 name으로 그룹을 형성하고 서로 구별한다. 위의 그림에서는 v-model의 변수 값이 product.colors로 변수 값으로 name처럼 서로를 구별할 수 있게 형성되어 있다.
이전에 배웠던 v-on:click = @click처럼 위에서도 @submit으로 사용할 수 있다.
유효성 검사기능을 @submit.prevent에서 사용할 수 있다.
1. view폴더 - Ch03DataBinding폴더 - Exam09FormBinding.vue 생성 (기초 코드만 작성)
2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가
3. components폴더 - AppMenu.vue파일에 리스트 코드 추가

양방향 데이터 바인딩
cf) ref는 상태를 만드는 것.
Exam09FormBinding.vue
<template>
<div class="card">
<div class="card-header">Exam09FormBinding</div>
<div class="card-body">
<!-- <form> 프론트엔드에서 form이 눌렸다고 해서 다른 서버로(요청반응) 가면 안된다. -->
<!-- <form v-on:submit.prevent="handleSubmit"> -->
<form @submit.prevent="handleSubmit">
<div class="input-group mb-2">
<span class="input-group-text">상품</span>
<input type="text" class="form-control" v-model="product.name">
</div>
<div class="input-group mb-2">
<span class="input-group-text">회사</span>
<input type="text" class="form-control" v-model="product.company">
</div>
<div class="input-group mb-2">
<span class="input-group-text">가격</span>
<input type="number" class="form-control" v-model="product.price">
</div>
<div class="input-group mb-2">
<span class="input-group-text">설명</span>
<input type="text" class="form-control" v-model="product.info">
</div>
<div class="input-group mb-2">
<span class="input-group-text">국가</span>
<select class="form-control" v-model="product.madein">
<option value="한국">한국</option>
<option value="미국">미국</option>
<option value="독일">독일</option>
</select>
</div>
<div class="input-group mb-2">
<span class="input-group-text">색상</span>
<div class="form-control">
<div class="form-check form-check-inline">
<!-- 체크박스는 하나의 그룹(name속성으로 이용하여)안에서 다중값을 선택할 수 있다.
product.colors의 값은 배열값을 가지게 된
-->
<input id="checkboxBlack" class="form-check-input" type="checkbox" value="black"
v-model="product.colors">
<label class="form-check-label" for="chekboxBlack">블랙</label>
</div>
<div class="form-check form-check-inline">
<input id="checkboxRed" class="form-check-input" type="checkbox" value="red"
v-model="product.colors">
<label class="form-check-label" for="chekboxRed">레드</label>
</div>
<div class="form-check form-check-inline">
<input id="checkboxYellow" class="form-check-input" type="checkbox" value="yellow"
v-model="product.colors">
<label class="form-check-label" for="chekboxYellow">옐로우</label>
</div>
</div>
</div>
<div class="input-group mb-2">
<span class="input-group-text">판매1</span>
<div class="form-control">
<div class="form-check form-check-inline">
<!-- 체크박스가 하나인 경우 product.sale1은 배열이 아니라
하나의 값이다. 하나밖에 없기 때문에 ture / false의 값을 가진다. -->
<input class="form-check-input" type="checkbox" id="chekboxSale1" value="true"
v-model="product.sale1">
<label class="form-check-label" for="chekboxSale1">판매1</label>
</div>
</div>
</div>
<div class="input-group mb-2">
<span class="input-group-text">판매2</span>
<div class="form-control">
<div class="form-check form-check-inline">
<!-- 위의 코드와 다른 점 true-value / false-value
이 경우에 값을 yes로 주어야 체크가 됨-->
<input class="form-check-input" type="checkbox" id="chekboxSale2" v-model="product.sale2"
true-value="yes" false-value="no">
<label class="form-check-label" for="chekboxSale2">판매2</label>
</div>
</div>
</div>
<div class="input-group mb-2">
<span class="input-group-text">성별</span>
<div class="form-control">
<div class="form-check form-check-inline">
<!-- 이번엔 radio 버튼 -->
<input class="form-check-input" type="radio" id="chekboxSex1" value="man"
v-model="product.sex">
<label class="form-check-label" for="chekboxSex1">남성</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" id="chekboxSex2" value="woman"
v-model="product.sex">
<label class="form-check-label" for="chekboxSex2">여성</label>
</div>
</div>
</div>
<div>
<input type="submit" value="등록" class="btn btn-danger btn-sm me-2" />
<!-- type="reset"은 처음 값으로 돌아가게 만듦
Vue에서 리셋 버튼은 양식을 초기화하지 않음 -->
<!-- 체크 박스가 디폴트 값으로 바인딩 되지 않음 -->
<!-- <input type="reset" value="재작성" class="btn btn-info btn-sm" /> -->
<!-- 폼 태그 안의 버튼은 기본적으로 submit효과를 가지기 때문에
type="button"을 명시해주어야 한다. -->
<button type="button" class="btn btn-info btn-sm" @click="handleReset">재작성</button>
</div>
</form>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
// 디폴트
let product = ref({
name: "셔츠",
company: "지오다노",
price: "20000",
info: "통풍이 잘되어 시원해요",
madein: "미국",
colors: ["black", "yellow"],
//sale1: "black" // 체크가 되지 않는다.
sale1: true,
// sale2: true
sale2: "yes",
sex: "woman"
});
function handleSubmit() {
console.log(product.value);
console.log(JSON.stringify(product.value)); // 객체를 문자열로 바꿈
console.log(JSON.parse(JSON.stringify(product.value))); // 문자열을 객로 바꿈
// 여기에 유효성 검사 ...
// 여기에 서버 통신 코드 추가 ...
}
function handleReset() {
console.log("handleReset 실행")
// product = ref({
// name: "셔츠",
// company: "지오다노",
// price: "20000",
// info: "통풍이 잘되어 시원해요",
// madein: "미국"
// });
// 상태를 바꾸면 console에 나오는 값은 변한 값이 나오지만 사용자 UI에서는 재작성 버튼을 눌렀을 때 바뀐 값이 나오지 않는다. 이전 주소를 참조하고 있기 때문이다.
// product의 상태를 바꾸는 것이 아닌 value를 바꾸어보자. (객체 통째로 value를 바꿈)
product.value = {
name: "셔츠",
company: "지오다노",
price: "20000",
info: "통풍이 잘되어 시원해요",
madein: "미국"
};
// 객체 안의 하나의 인자 값을 바꿈
product.value.name = "셔츠";
product.value.company = "지오다노";
}
</script>
<style scoped></style>

*바인딩은 아니지만 비슷한 속성을 가진다.
템플릿의 첫번째 엘리먼트의 속성으로 모두 들어가게 된다. (기본적으로 이런 것을 Fallthrough 속성이라 한다)
1. view폴더 - Ch03DataBinding폴더 - Exam10FallthroughAttr폴더 생성 - index.vue파일 생성(기초 코드만 작성)
2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가
3. components폴더 - AppMenu.vue파일에 리스트 코드 추가
4. view폴더 - Ch03DataBinding폴더 - Exam10FallthroughAttr폴더 - UIComponentA.vue 파일 생성
5. index.vue의 card-body부분에 <UIComponentA /> 추가.
<template>
<div class="card">
<div class="card-header">Exam10FallThroughAttr</div>
<div class="card-body">
<UIComponentA />
</div>
</div>
</template>
<script setup>
// import UIComponentA from '@/components/Ch02ComponentRouting/UIComponentA.vue';
import UIComponentA from './UIComponentA.vue';
</script>
<style scoped></style>



이렇게 Template의 첫번제 엘리멘트 속성에 들어가게 된다.


UIComponentA 카드 부분을 클릭 시 console.log를 이용하여 확인

defineProps를 이용하여 "class"와 "style"의 속성을 다른곳에 사용하려 변수를 선언.

class와 style의 값이 태그에서 빠진 것을 볼 수 있다.

@click="handleClick"도 events 변수로 값을 빼옴..
class와 style이 예약어이기 때문에 적용에 실패... ★ 예약어는 피하자!
다시 시도


.여기서 click도 예약어이기 때문에 콘솔에 출력이 되지 않는다.
--> onClick으로 변경해주자.


※ 여기에서 id만 Fallthrough ???

이 방법을 주로 사용한다.
# UIComponentB.vue 작성하여 다른 방법으로 사용해보기
위 코드의 방식으로 사용하려면 Fallthrough 속성을 꺼야 한다. 때문에 false로 두는 것이다.
<template>
<div class="card" :id="$attrs.id">
<div class="card-header">UIComponentB</div>
<div class="card-body">
<!-- 템플릿에서만 $attrs. 를 붙인다. -->
<div :class="$attrs.myclass" :style="$attrs.mystyle" @click="$attrs.onClick">
Contents
</div>
<hr>
<button class="btn btn-info btn-sm me-3" @click="handleBtn1">버튼 1 (속성값 이용)</button>
<button class="btn btn-info btn-sm" @click="handleBtn2">버튼 2 (리스너 호출)</button>
</div>
</div>
</template>
<script setup>
import { defineOptions, useAttrs } from 'vue';
defineOptions({
// fallthrough 속성을 비활성화
inheritAttrs: false
});
const attrs = useAttrs();
function handleBtn1() {
// '속성'으로 전달된 값 얻기
console.log(attrs.id);
console.log(attrs.myclass);
console.log(attrs.mystyle);
}
function handleBtn2() {
// '속성'으로 전달된 핸들러 함수(리스너)를 호출
attrs.onClick();
}
</script>
<style scoped></style>
# 앞으로 접두사에 'use'가 들어가는 것은 'hook'을 이용한다고 생각하면 된다.
# 앞으로 <UIComponentB/> 만 사용하는 경우가 대부분이겠지만, 경우에 따라서는
<UIComponentB id="uiComponentB" myclass="bg-dark" mystyle="color:white" @click="handleClick" /> 이렇게 사용할줄도 알아야 한다!!
--> 게시판 댓글 / 리뷰 등록에 사용하면 좋을 것 같다!
index.vue
<template>
<div class="card">
<div class="card-header">Exam10FallthroughAttr</div>
<div class="card-body">
<!--id는 UIComponentA.vue에 루트 엘리먼트 속성으로 들어가게 된다.-->
<UIComponentA id="uiComponentA" myclass="bg-warning" mystyle="color:blue" @click="handleClick" />
<UIComponentB id="uiComponentB1" myclass="bg-dark" mystyle="color:white" @click="handleClick" />
<UIComponentB id="uiComponentB2" myclass="bg-danger" mystyle="color:white" @click="handleClick" />
</div>
</div>
</template>
<script setup>
import UIComponentA from "./UIComponentA.vue";
import UIComponentB from "./UIComponentB.vue";
function handleClick() {
console.log("Exam10FallthroughAttr UIComponentA 클릭됨");
}
</script>
<style scoped></style>
이런 식으로 속성을 다르게 주고 UIComponent를 재사용할 수 있다.


탈부착 가능한 슬롯을 의미한다.
cf) 슬롯은 꼭 name이 있어야 한다.
1. view폴더 - Ch03DataBinding폴더 - Exam11Slot폴더 생성 - index.vue, DialogTemplate.vue, DialogA .vue파일 생성
DialogTemplate는 DialogA 를 사용하고 index는 DialogTemplate을 사용한다.
-> 기초적인 코드 작성
index.vue
<template>
<div class="card">
<div class="card-header">Exam11Slot</div>
<div class="card-body">
<button class="btn btn-info btn-sm" @click="showDialogA">기본 다이얼로그</button>
</div>
<DialogA id="dialogA" />
</div>
</template>
<script setup>
import { onMounted } from "vue"; // 라이프사이클과 관련. 나중에 배운다.
import DialogA from "./DialogA.vue";
import { Modal } from "bootstrap";
let modalDialogA = null;
// 컴포넌트가 생성되고, DOM에 부착될 때 자동으로 실행되는 콜백
onMounted(() => {
modalDialogA = new Modal(document.querySelector("#dialogA"));
});
function showDialogA() {
modalDialogA.show();
}
</script>
<style scoped></style>
DialogTemplate.vue (모달 사용)
<template>
<div class="modal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<!-- slot -->
<slot name="header">제목</slot>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- slot -->
<slot name="body">
<p>다이얼로그 내용</p>
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<!-- slot -->
<button type="button" class="btn btn-info btn-sm" data-bs-dismiss="modal">확인</button>
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">닫기</button>
</slot>
</div>
</div>
</div>
</div>
</template>
<script setup>
</script>
<style scoped></style>
DialogA.vue
<template>
<DialogTemplate />
</template>
<script setup>
import DialogTemplate from "./DialogTemplate.vue";
</script>
<style scoped></style>
# 모든 다이알로그는 헤더, 바디, 풋터가 있다. 구조는 똑같지만 각각의 위치는 내용이 다를 수 있다.
다이얼로그템플레이트는 '틀'이고, 코드에서 Slot 부분은 교체될 수 있다.
2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가
3. components폴더 - AppMenu.vue파일에 리스트 코드 추가
4. DialogB.vue 생성
- 여기에선 Slot을 교체할 것이다.
<template>
<DialogTemplate>
// DialogTemplate의 name들을 이용
<template v-slot:header>
알림
</template>
<template v-slot:body>
<P>DialogB의 내용입니다!</P>
</template>
<template v-slot:footer>
<button class="btn btn-secondary btn-sm">확인</button>
</template>
</DialogTemplate>
</template>
<script setup>
import DialogTemplate from "./DialogTemplate.vue";
</script>
<style scoped></style>
5. DialogC.vue 생성
<template>
<DialogTemplate>
<!-- DialogTemplate의 name들을 이용 -->
<template v-slot:header>
로그인
</template>
<template v-slot:body>
<div class="mb-3">
<label for="inputEmail" class="form-label">이메일</label>
<input type="text" class="form-control" id="inputEmail" v-model="inputData.email">
</div>
<div class="mb-3">
<label for="inputPassword" class="form-label">비밀번호</label>
<input type="password" class="form-control" id="inputPassword" v-model="inputData.password">
</div>
</template>
<template v-slot:footer>
<button class="btn btn-outline-warning btn-sm me-2">로그인</button>
<button class="btn btn-outline-warning btn-sm" data-bs-dismiss="modal">닫기</button>
</template>
</DialogTemplate>
</template>
<script setup>
import DialogTemplate from "./DialogTemplate.vue";
import { ref } from "vue";
const inputData = ref({
email: "",
password: ""
});
</script>
<style scoped></style>
# Slot을 사용한다면 중복된 코드를 줄일 수 있다.
# index.vue
<template>
<div class="card">
<div class="card-header">Exam11Slot</div>
<div class="card-body">
<button class="btn btn-info btn-sm me-3" @click="showDialogA">기본 다이얼로그</button>
<button class="btn btn-info btn-sm me-3" @click="showDialogB">알림 다이얼로그</button>
<button class="btn btn-info btn-sm" @click="showDialogC">로그인 다이얼로그</button>
</div>
<DialogA id="dialogA" />
<DialogB id="dialogB" />
<DialogC id="dialogC" />
</div>
</template>
<script setup>
import { onMounted } from "vue"; // 라이프사이클과 관련. 나중에 배운다.
import DialogA from "./DialogA.vue";
import DialogB from "./DialogB.vue";
import DialogC from "./DialogC.vue";
import { Modal } from "bootstrap";
let modalDialogA = null; // ?
let modalDialogB = null;
let modalDialogC = null;
// 컴포넌트가 생성되고, DOM에 부착될 때 자동으로 실행되는 콜백
onMounted(() => {
modalDialogA = new Modal(document.querySelector("#dialogA"));
modalDialogB = new Modal(document.querySelector("#dialogB"));
modalDialogC = new Modal(document.querySelector("#dialogC"));
});
function showDialogA() {
modalDialogA.show();
}
function showDialogB() {
modalDialogB.show();
}
function showDialogC() {
modalDialogC.show();
}
function hideDialogB() {
modalDialogB.hide();
}
</script>
<style scoped></style>