How to programmatically inject content in bootstrap-vue modal body and footer?

16,620

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:

  1. deletingState: it indicates whether begin deleting

  2. 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>
Share:
16,620

Related videos on Youtube

ace
Author by

ace

Updated on June 04, 2022

Comments

  • ace
    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
      Sphinx over 5 years
      so 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
    ace over 5 years
    Sphinx 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
    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
    Sphinx over 5 years
    for generic purpose, i think use slott&scoped-slot will be better like Quasar Stepper implemented.
  • Richard Matsen
    Richard Matsen over 5 years
    A 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
    Sphinx over 5 years
    this is one rough demo, my idea, uses template control the content, uses the step-begin and step-end etc to control the modal itself or skip step etc.
  • Richard Matsen
    Richard Matsen over 5 years
    Cheers, 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
    Richard Matsen over 5 years
    It 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
    Sphinx over 5 years
    I 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
    Richard Matsen over 5 years
    I 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
    Richard Matsen over 5 years
    At 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
    Richard Matsen over 5 years
    On 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).