How to programmatically inject content in bootstrap-vue modal body and footer?
Solution 1
If I understand correctly, you'd like to display the Modal content based on different state combinations.
As your descriptions, there should be 2 state:
deletingState: it indicates whether begin deleting
loadingState: it indicates whether is waiting the response from the server
Check Bootstrap Vue Modal Guide, then search keyword= Disabling built-in buttons, you will see we can use cancel-disabled
and ok-disabled
props to control the disable state of default Cancel and OK buttons (or you can use the slot=modal-footer, or modal-ok, modal-cancel.).
Other props you may use: ok-only
, cancel-only
, busy
.
Finally bind v-if
and props with the state combinations to show the content.
Like below demo:
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>
Solution 2
You might prefer to use separate modals, the logic becomes a bit clearer and you can easily add more pathways, for example retry on API error.
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>
Related videos on Youtube
ace
Updated on June 04, 2022Comments
-
ace almost 2 years
I want to implement this functionality in vuejs app using bootstrap vue modal component:
When the user clicks on the Delete button on the page UI :
It shows the modal with a dynamic content in its body: "Are you sure you want to delete customer: customer_name_here"
If the user clicks on the 'Cancel' button: The modal goes away.
If the user clicks on the 'OK' button:
It changes the modal body content to: 'Deleting customer 'customer_name_here' ... , it disables the Cancel and OK buttons and it calls the API to delete the customer.
When successful response is received from the API:
- It changes the modal body content to: 'Successfully deleted customer 'customer_name_here'
- Display only the OK button in the modal footer, which if clicked modal goes away.
This the code so far:
<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 code:
deleteCustomer(evt) { evt.preventDefault() this.$refs.modal.hide() CustomerApi.deleteCustomer(this.customer.id).then(response => { // successful response })
-
Sphinx over 5 yearsso what is the problem you met? it looks uses
v-if
/v-show
will reach the goal. like if delete, show the warning message and OK/Cancel buttons, then hide delete button
-
ace over 5 yearsSphinx thank u great answer works very well but I could not figure out once deletingState is set to true who sets it back to false.
-
Sphinx over 5 years@ace many choices. 1. Always set to false when pop up the modal (as above demo does), 2. set to false when click 'Cancel' button or click OK second time. 3. listen hide event, if hide, set the state to false
-
Sphinx over 5 yearsfor generic purpose, i think use slott&scoped-slot will be better like Quasar Stepper implemented.
-
Richard Matsen over 5 yearsA stepper is not a bad idea in principle, but take a look at the examples - the code is as long as an content-injected modal. You would want to loose the step indicator and the buttons should be in a fixed footer position, not changing position with the content height. And would need branching logic not just linear steps.
-
Sphinx over 5 yearsthis is one rough demo, my idea, uses template control the content, uses the
step-begin
andstep-end
etc to control the modal itself or skip step etc. -
Richard Matsen over 5 yearsCheers, thanks for that, works well. A tad longer than the wrapped component, but perhaps I'm just more familiar with that pattern. When you mentioned a stepper I was thinking of dropping in a stepper component into the b-modal in to save coding the state transition logic directly.
-
Richard Matsen over 5 yearsIt is still a big advantage to declaratively define the states and pathway. I think there is more flexibility doing that in an object rather than html (
<b-step-modal>
tags). -
Sphinx over 5 yearsI think that is a choice between left hand and right hand. But I prefer module/component oriented. Then one advantage in your demo, how it supports a Complicated layout? If I'd like to put one data table and some other complex components into modal body, it may feel painful. You may have to design another component, then add more properties to pass the data of datatable and else, redesign the template to render datatable etc.(Sorry for my poor English, I felt didn't explain my opinion very well)
-
Richard Matsen over 5 yearsI think I see your point. To add e.g data table to the modal, it would be useful to have a content-slot. Unfortunately b-modal has header, footer, button slots but no content.
-
Richard Matsen over 5 yearsAt the moment I am thinking about changes to requirements involving state changes (not content), e.g @ace mentions photos, so after customer is deleted perhaps confirm delete of associated photos (yes/no) and handle errors as well. This should (hopefully) be added easily with an array of state objects in the js.
-
Richard Matsen over 5 yearsOn the other hand, that hypothetical change request may also require displaying photo thumbnails so content is also an important consideration. Perhaps my wrapper component should allow a slot for content (not currently provided by b-modal but added somehow by the wrapper).