bootstrap-vue 모달 본문과 바닥글에 콘텐츠를 프로그래밍 방식으로 주입하려면 어떻게 해야 합니까?
부트스트랩 vue 모드 컴포넌트를 사용하여 vuejs 앱에서 다음 기능을 구현합니다.
사용자가 페이지 UI의 [Delete]버튼을 클릭하면 다음과 같이 됩니다.
"고객을 삭제하시겠습니까: customer_name_here"라는 동적 콘텐츠가 포함된 모달입니다.
사용자가 '취소' 버튼을 클릭하는 경우:모달은 사라진다.
사용자가 '확인' 버튼을 클릭하는 경우:
모달 본문의 내용을 다음과 같이 변경합니다.고객 'customer_name_here'를 삭제하면 Cancel 버튼과 OK 버튼이 비활성화되고 API가 호출되어 고객을 삭제합니다.
API에서 응답이 정상적으로 수신된 경우:
- 모달 본문의 내용이 다음과 같이 변경됩니다. '고객 'customer_name_here'를 성공적으로 삭제했습니다.
- 모달 바닥글에 OK 버튼만 표시합니다.모달 클릭 시 해당 버튼은 사라집니다.
지금까지의 코드는 다음과 같습니다.
<b-button v-b-modal.modal1 variant="danger">Delete</b-button>
<b-modal id="modal1" title="Delete Customer"
@ok="deleteCustomer" centered no-close-on-backdrop -close-on-esc ref="modal">
<p class="my-4">Are you sure, you want to delete customer:</p>
<p>{{customer.name}}</p>
</b-modal>
Vue JS 코드:
deleteCustomer(evt) {
evt.preventDefault()
this.$refs.modal.hide()
CustomerApi.deleteCustomer(this.customer.id).then(response => {
// successful response
})
제가 이해한 것이 맞다면, 당신은 다른 상태의 조합에 따라 모달 콘텐츠를 표시하고자 합니다.
설명대로 다음 두 가지 상태가 있습니다.
deleting State: 삭제를 시작할지 여부를 나타냅니다.
loading State: 가 서버로부터의 응답을 대기하고 있음을 나타냅니다.
Bootstrap Vue Modal Guide를 확인하고 검색 키워드= 기본 제공 버튼을 비활성화하면 사용할 수 있습니다.cancel-disabled
그리고.ok-disabled
기본 Cancel 버튼 및 OK 버튼의 비활성화 상태를 제어하는 소품(또는 slot=modal-filename 또는 modal-ok, modal-filename 사용 가능)
기타 사용할 수 있는 소품:ok-only
,cancel-only
,busy
.
마지막으로 바인드v-if
소품이나 스테이트 콤비네이션이 들어가 있습니다.
아래와 같은 데모:
Vue.config.productionTip = false
new Vue({
el: '#app',
data() {
return {
customer: {name: 'demo'},
deletingState: false, // init=false, if pop up modal, change it to true
loadingState: false // when waiting for server respond, it will be true, otherwise, false
}
},
methods: {
deleteCustomer: function() {
this.deletingState = false
this.loadingState = false
this.$refs.myModalRef.show()
},
proceedReq: function (bvEvt) {
if(!this.deletingState) {
bvEvt.preventDefault() //if deletingState is false, doesn't close the modal
this.deletingState = true
this.loadingState = true
setTimeout(()=>{
console.log('simulate to wait for server respond...')
this.loadingState = false
this.deletingState = true
}, 1500)
} else {
console.log('confirm to delete...')
}
},
cancelReq: function () {
console.log('cancelled')
}
}
})
.customer-name {
background-color:green;
font-weight:bold;
}
<!-- Add this to <head> -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<!-- Add this after vue.js -->
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-button v-b-modal.modal1 variant="danger" @click="deleteCustomer()">Delete</b-button>
<b-modal title="Delete Customer" centered no-close-on-backdrop no-close-on-esc ref="myModalRef"
@ok="proceedReq($event)" @cancel="cancelReq()" :cancel-disabled="deletingState" :ok-disabled="loadingState" :ok-only="deletingState && !loadingState">
<div v-if="!deletingState">
<p class="my-4">Are you sure, you want to delete customer:<span class="customer-name">{{customer.name}}</span></p>
</div>
<div v-else>
<p v-if="loadingState">
Deleting customer <span class="customer-name">{{customer.name}}</span>
</p>
<p v-else>
Successfully deleted customer <span class="customer-name">{{customer.name}}</span>
</p>
</div>
</b-modal>
</div>
별도의 모달 사용을 선호할 수 있습니다. 그러면 논리가 좀 더 명확해지고 API 오류 시 재시도 등의 경로를 쉽게 추가할 수 있습니다.
console.clear()
const CustomerApi = {
deleteCustomer: (id) => {
return new Promise((resolve,reject) => {
setTimeout(() => {
if (id !== 1) {
reject(new Error('Delete has failed'))
} else {
resolve('Deleted')
}
}, 3000);
});
}
}
new Vue({
el: '#app',
data() {
return {
customer: {id: 1, name: 'myCustomer'},
id: 1,
error: null
}
},
methods: {
deleteCustomer(e) {
e.preventDefault()
this.$refs.modalDeleting.show()
this.$refs.modalDelete.hide()
CustomerApi.deleteCustomer(this.id)
.then(response => {
this.$refs.modalDeleting.hide()
this.$refs.modalDeleted.show()
})
.catch(error => {
this.error = error.message
this.id = 1 // For demo, api success 2nd try
this.$refs.modalError.show()
})
}
}
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-button v-b-modal.modal-delete variant="danger">Delete</b-button>
<input type="test" id="custId" v-model="id">
<label for="custId">Enter 2 to make it fail</label>
<b-modal
id="modal-delete"
ref="modalDelete"
title="Delete Customer"
@ok="deleteCustomer"
centered no-close-on-backdrop close-on-esc>
<p class="my-4">Are you sure, you want to delete customer: {{customer.name}}</p>
</b-modal>
<b-modal
ref="modalDeleting"
title="Deleting Customer"
centered no-close-on-backdrop no-close-on-esc
no-fade
:busy="true">
<p class="my-4">Deleting customer: {{customer.name}}</p>
</b-modal>
<b-modal
ref="modalDeleted"
title="Customer Deleted"
centered no-close-on-backdrop close-on-esc
no-fade
:ok-only="true">
<p class="my-4">Customer '{{customer.name}}' has been deleted</p>
</b-modal>
<b-modal
ref="modalError"
title="Error Deleting Customer"
centered no-close-on-backdrop close-on-esc
no-fade
:ok-title="'Retry'"
@ok="deleteCustomer">
<p class="my-4">An error occured deleting customer: {{customer.name}}</p>
<p>Error message: {{error}}</p>
</b-modal>
</div>
코멘트에서도 설명했듯이 Quasar Stepper와 같은 솔루션이 있습니다.
하나의 컴포넌트를 스텝으로 설계합니다(이름:
b-step-modal
아래 데모에서)다음으로 1개의 모달스테퍼를 사용합니다(이름은
b-stepper-modal
아래 데모에서)를 부모로 합니다.그럼 넌 네 모든 단계를 나열하기만 하면 돼
modal-stepper
버튼을 비활성화하거나 스텝을 건너뛰고 싶은 경우 스텝 훅을 사용할 수 있습니다(아래 데모에서는step-begin
★★★★★★★★★★★★★★★★★」step-end
를 사용하여 목표를 구현합니다.
다음과 같은 대략적인 데모:
Vue.config.productionTip = false
let bModal = Vue.component('BModal')
Vue.component('b-stepper-modal', {
provide () {
return {
_stepper: this
}
},
extends: bModal,
render(h) {
let _self = this
return h(bModal, {props: _self.$props, ref: '_innerModal', on: {
ok: function (bvEvt) {
_self.currentStep++
if(_self.currentStep < _self.steps.length) {
bvEvt.preventDefault()
}
}
}}, _self.$slots.default)
},
data() {
return {
steps: [],
currentStep: 0
}
},
methods: {
_registerStep(step) {
this.steps.push(step)
},
show () {
this.$refs._innerModal.show()
}
}
})
Vue.component('b-step-modal', {
inject: {
_stepper: {
default () {
console.error('step must be child of stepper')
}
}
},
props: ['stepBegin', 'stepEnd'],
data () {
return {
isActive: false,
stepSeq: 0
}
},
render(h) {
return this.isActive ? h('p', {}, this.$slots.default) : null
},
created () {
this.$watch('_stepper.currentStep', function (newVal, oldVal) {
if(oldVal) {
if(typeof this.stepEnd === 'function') this.stepEnd()
} else {
if(typeof this.stepBegin === 'function') this.stepBegin()
}
this.isActive = (newVal === this.stepSeq)
})
},
mounted () {
this.stepSeq = this._stepper.steps.length
this._stepper._registerStep(this)
this.isActive = this._stepper.currentStep === this.stepSeq
}
})
new Vue({
el: '#app',
data() {
return {
customer: {
name: 'demo'
},
deletingState: false, // init=false, if pop up modal, change it to true
loadingState: false // when waiting for server respond, it will be true, otherwise, false
}
},
methods: {
deleteCustomer: function() {
this.deletingState = false
this.loadingState = false
this.$refs.myModalRef.show()
},
proceedReq: function(bvEvt) {
if (!this.deletingState) {
bvEvt.preventDefault() //if deletingState is false, doesn't close the modal
this.deletingState = true
this.loadingState = true
setTimeout(() => {
console.log('simulate to wait for server respond...')
this.loadingState = false
this.deletingState = true
}, 1500)
} else {
console.log('confirm to delete...')
}
},
cancelReq: function() {
console.log('cancelled')
},
testStepBeginHandler: function () {
this.deletingState = true
this.loadingState = true
setTimeout(() => {
console.log('simulate to wait for server respond...')
this.loadingState = false
this.deletingState = true
}, 1500)
},
testStepEndHandler: function () {
console.log('step from show to hide')
}
}
})
<!-- Add this to <head> -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<!-- Add this after vue.js -->
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-button v-b-modal.modal1 variant="danger" @click="deleteCustomer()">Delete</b-button>
<b-stepper-modal title="Delete Customer" centered no-close-on-backdrop no-close-on-esc ref="myModalRef" @ok="proceedReq($event)" @cancel="cancelReq()" :cancel-disabled="deletingState" :ok-disabled="loadingState" :ok-only="deletingState && !loadingState">
<b-step-modal>
<div>
<p class="my-4">Are you sure, you want to delete customer:<span class="customer-name">{{customer.name}}</span></p>
</div>
</b-step-modal>
<b-step-modal :step-begin="testStepBeginHandler" :step-end="testStepEndHandler">
<div>
<p v-if="loadingState">
Deleting customer <span class="customer-name">{{customer.name}}</span>
</p>
<p v-else>
Successfully deleted customer <span class="customer-name">{{customer.name}}</span>
</p>
</div>
</b-step-modal>
</b-stepper-modal>
</div>
컴포넌트는 일련의 하여 Bootstrap-vue에 .이러한 컴포넌트는 일련의 상태를 취득하고,nextState
소유물.계산된 속성을 사용하여 상태 변경에 응답합니다.
부모에서는 고객(또는 사진) 속성을 메시지에 추가할 수 있도록 상태 배열도 계산된 속성에 정의됩니다.
편집
모달 콘텐츠 내에서 부모 컴포넌트가 정확한 마크업을 정의할 수 있는 콘텐츠슬롯이 추가되었습니다.
console.clear()
// Mock CustomerApi
const CustomerApi = {
deleteCustomer: (id) => {
console.log('id', id)
return new Promise((resolve,reject) => {
setTimeout(() => {
if (id !== 1) {
reject(new Error('Delete has failed'))
} else {
resolve('Deleted')
}
}, 3000);
});
}
}
// Wrapper component to handle state changes
Vue.component('state-based-modal', {
template: `
<b-modal
ref="innerModal"
:title="title"
:ok-disabled="okDisabled"
:cancel-disabled="cancelDisabled"
:busy="busy"
@ok="handleOk"
:ok-title="okTitle"
@hidden="hidden"
v-bind="otherAttributes"
>
<div class="content flex-grow" :style="{height: height}">
<!-- named slot applies to current state -->
<slot :name="currentState.id + 'State'" v-bind="currentState">
<!-- default content if no slot provided on parent -->
<p>{{message}}</p>
</slot>
</div>
</b-modal>`,
props: ['states', 'open'],
data: function () {
return {
current: 0,
error: null
}
},
methods: {
handleOk(evt) {
evt.preventDefault();
// save currentState so we can switch display immediately
const state = {...this.currentState};
this.displayNextState(true);
if (state.okButtonHandler) {
state.okButtonHandler()
.then(response => {
this.error = null;
this.displayNextState(true);
})
.catch(error => {
this.error = error.message;
this.displayNextState(false);
})
}
},
displayNextState(success) {
const nextState = this.getNextState(success);
if (nextState == -1) {
this.$refs.innerModal.hide();
this.hidden();
} else {
this.current = nextState;
}
},
getNextState(success) {
// nextState can be
// - a string = always go to this state
// - an object with success or fail pathways
const nextState = typeof this.currentState.nextState === 'string'
? this.currentState.nextState
: success && this.currentState.nextState.onSuccess
? this.currentState.nextState.onSuccess
: !success && this.currentState.nextState.onError
? this.currentState.nextState.onError
: undefined;
return this.states.findIndex(state => state.id === nextState);
},
hidden() {
this.current = 0; // Reset to initial state
this.$emit('hidden'); // Inform parent component
}
},
computed: {
currentState() {
const currentState = this.current;
return this.states[currentState];
},
title() {
return this.currentState.title;
},
message() {
return this.currentState.message;
},
okDisabled() {
return !!this.currentState.okDisabled;
},
cancelDisabled() {
return !!this.currentState.cancelDisabled;
},
busy() {
return !!this.currentState.busy;
},
okTitle() {
return this.currentState.okTitle;
},
otherAttributes() {
const otherAttributes = this.currentState.otherAttributes || [];
return otherAttributes
.reduce((obj, v) => { obj[v] = null; return obj; }, {})
},
},
watch: {
open: function(value) {
if (value) {
this.$refs.innerModal.show();
}
}
}
})
// Parent component
new Vue({
el: '#app',
data() {
return {
customer: {id: 1, name: 'myCustomer'},
idToDelete: 1,
openModal: false
}
},
methods: {
deleteCustomer(id) {
// Return the Promise and let wrapper component handle result/error
return CustomerApi.deleteCustomer(id)
},
modalIsHidden(event) {
this.openModal = false; // Reset to start condition
}
},
computed: {
avatar() {
return `https://robohash.org/${this.customer.name}?set=set4`
},
modalStates() {
return [
{
id: 'delete',
title: 'Delete Customer',
message: `delete customer: ${this.customer.name}`,
okButtonHandler: () => this.deleteCustomer(this.idToDelete),
nextState: 'deleting',
otherAttributes: ['centered no-close-on-backdrop close-on-esc']
},
{
id: 'deleting',
title: 'Deleting Customer',
message: `Deleting customer: ${this.customer.name}`,
okDisabled: true,
cancelDisabled: true,
nextState: { onSuccess: 'deleted', onError: 'error' },
otherAttributes: ['no-close-on-esc'],
contentHeight: '250px'
},
{
id: 'deleted',
title: 'Customer Deleted',
message: `Deleting customer: ${this.customer.name}`,
cancelDisabled: true,
nextState: '',
otherAttributes: ['close-on-esc']
},
{
id: 'error',
title: 'Error Deleting Customer',
message: `Error deleting customer: ${this.customer.name}`,
okTitle: 'Retry',
okButtonHandler: () => this.deleteCustomer(1),
nextState: 'deleting',
otherAttributes: ['close-on-esc']
},
];
}
}
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-button @click="openModal = true" variant="danger">Delete</b-button>
<input type="test" id="custId" v-model="idToDelete">
<label for="custId">Enter 2 to make it fail</label>
<state-based-modal
:states="modalStates"
:open="openModal"
@hidden="modalIsHidden"
>
<template slot="deleteState" scope="state">
<img alt="Mindy" :src="avatar" style="width: 150px">
<p>DO YOU REALLY WANT TO {{state.message}}</p>
</template>
<template slot="errorState" scope="state">
<p>Error message: {{state.error}}</p>
</template>
</state-based-modal>
</div>
언급URL : https://stackoverflow.com/questions/52005443/how-to-programmatically-inject-content-in-bootstrap-vue-modal-body-and-footer
'itsource' 카테고리의 다른 글
mariadb 사용자에게 권한을 부여하려고 할 때 테이블 오류에서 일치하는 행을 찾을 수 없습니다. (0) | 2022.09.29 |
---|---|
즉시 인라인 SQL 테이블 생성(왼쪽 조인 제외용) (0) | 2022.09.29 |
케이스 스테이트먼트가 연속적으로 필요한 이유는 무엇입니까? (0) | 2022.09.29 |
SELECT 속도를 높이는 방법..MySQL의 LIKE 쿼리가 여러 열에 있습니까? (0) | 2022.09.29 |
두 변수의 값을 교환하기 위한 PHP 함수가 있습니까? (0) | 2022.09.28 |