skip to Main Content

I have made a ModalComponent.vue file which I want to reuse everywhere. The modal has slots for the title, body and footer. It has a standard close modal button, which works fine.

I want to have another button next to it, to do calls to my Pinia stores.

So for example, I have a table with items. I want to delete one item. I click the trash can icon and the modal opens (this works for the specific item).

Inside the modal, I have the confirm button which can be different for every table (Save, Continue, Delete etc).

So now, I want the specific item to be deleted. Click the button in the component, the store is called and correctly removes the item (I see it happening in the table underneath the backdrop).

Now I want the modal to close. So after the store call, I want the modal to close. I have tried everything under the sun, but it is not closing.

I tried to put dataModal.value to false, nothing happens or just the backdrop remains. I tried emitting an event from the Pinia store, nothing. No error, no console.logs..

How can I make this happen?

Here is my ModalComponent.vue:

    <template>
      <div id="myModal" class="modal fade" ref="myModal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog">
          <div class="modal-content">
            <div class="modal-header">
              <h5 class="modal-title" id="myModalLabel">
                <slot name="title"></slot>
              </h5>
              <button type="button" class="btn-close" @click="hideModal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
              <slot name="body"></slot>
            </div>
            <div class="modal-footer">
              <Button :content='$t("buttons.cancel")' variant="btn-danger" icon="x-lg" :circle="false" @click="hideModal" />
              <slot name="button"></slot> // here is where the dynamic button will be placed
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script setup>
    import { Modal } from 'bootstrap';
    import Button from '@/components/Buttons/Button.vue';
    import { watchEffect, onMounted, ref } from 'vue'
    
    const props = defineProps({
      showModal: {
        type: Boolean,
        default: false
      }
    });
    
    const modalInstance = ref(null);
    
    onMounted(() => {
      modalInstance.value = new Modal(document.getElementById('myModal'), {
        target: "#myModal",
        backdrop: "static"
      });
    });
    
    watchEffect(() => {
      if (props.showModal === true && modalInstance.value) {
        modalInstance.value.show();
      }
    });
    
    const emit = defineEmits('close-modal');
    
    function hideModal() {
      if (modalInstance.value) {
        modalInstance.value.hide();
        emit('close-modal');
      }
    }
    
    </script>

Here is the component that loads in the component and the table:

<template>

    <modal-component @close-modal="dataModal = false" :showModal="dataModal" @item-deleted="dataModal = false">
      <template v-slot:title>
        {{ $t('itemTitle') }}
      </template>
      <template v-slot:body>
        {{ $t('itemBody') }}
      </template>
      <template v-slot:button>
        <Button :content='$t("buttons.delete")' variant="btn-secondary" icon="trash-fill" :circle="false" @click="onDelete" />
      </template>
    </modal-component>

    <Table />
</template>

<script setup>

import Button from '@/components/Buttons/Button.vue'

import ModalComponent from '@/components/ModalComponent'

import { computed, ref, watch, onMounted } from 'vue'
import { useItemsStore } from '@/stores/store-items'
import { storeToRefs } from 'pinia'


const { getItems, addItems, removeItems, updateItems } = useItemsStore()

const { isLoading, items } = storeToRefs(useItemsStore())

getItems()

const dataModal = ref(false)

function onDelete(){
  removeItems(itemToBeDeleted.id) // <-- call to the store
  getItems(); 
}

</script>

And here is my Pinia store file:

import { defineStore } from "pinia";
import { apiClient } from "@/services/api-client";
import mitt from 'mitt'

const emitter = mitt()


export const useItemsStore = defineStore('ItemsStore', {
    state: () => {
        return {
            items: [],
            isLoading: false
        }
    },
 
    actions: {
        
        async removeItems(id) {
            this.isLoading = true;
            
            return apiClient.RemoveItem({id:id})
                .then(response => {
                    // if(!hasError(dispatch, commit, response)){
                        let index = 0;
                        for(let i=0; i < this.items.length; i++){
                            if(this.items[i].id === id){
                                index = i;
                            }   
                        }
                    
                        this.items.splice(index, 1);
                        this.isLoading = false;

                        emitter.emit('item-deleted')

                        return response.succes
                    // }
                    //
                    // return false;
                })
        }
    }
})

2

Answers


  1. Chosen as BEST ANSWER

    Ok, after another 16 hours of slowly going insane, here is my not so clean but working solution. This code shows and hides the Bootstrap 5 modal with transitions and the backdrop:

    ModalComponent.vue:

    <template>
      <div :id="id" class="modal fade" ref="myModal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog">
          <div class="modal-content">
            <div class="modal-header">
              <h5 class="modal-title" id="myModalLabel">
                <slot name="title"></slot>
              </h5>
              <button type="button" class="btn-close" @click="hideModal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
              <slot name="body"></slot>
            </div>
            <div class="modal-footer">
              <Button :content='$t("buttons.cancel")' variant="btn-danger" icon="x-lg" :circle="false" @click="hideModal" />
              <slot name="button"></slot>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script setup>
    import { Modal } from 'bootstrap';
    import Button from '@/components/Buttons/Button.vue';
    import { watchEffect, onMounted, ref } from 'vue'
    
    const props = defineProps({
      id: {
        type: String
      }
    });
    
    const modalInstance = ref(null);
    
    const emit = defineEmits(['get-modal'])
    
    onMounted(() => {
      modalInstance.value = new Modal(document.getElementById(props.id), {
        target: props.id,
        backdrop: "static",
      });
    
      emit('get-modal', modalInstance.value)
    });
    
    
    
    function hideModal() {
      if (modalInstance.value) {
        modalInstance.value.hide();
      }
    }
    
    </script>
    

    The component that calls the modal component:

    <template>
    
        <modal-component @get-modal="modalGetted" :id="'deleteItemmodal'">
          <template v-slot:title>
            {{ $t('ModalTitle') }}
          </template>
          <template v-slot:body>
            {{ $t('ModalBody') }}
          </template>
          <template v-slot:button>
            <Button :content='$t("buttons.delete")' variant="btn-secondary" icon="trash-fill" :circle="false" @click="onDelete" />
          </template>
        </modal-component>
        
    </template>
    
    <script setup>
    import Button from '@/components/Buttons/Button.vue'
    import ModalComponent from '@/components/ModalComponent'
    
    
    const { isLoading, items } = storeToRefs(useItemsStore())
    
    
    let modalInstance = ref(null)
    
    function modalGetted(modal) {
      modalInstance = modal // set the modal
    }
    
    function onShowDeleteModal(item) {
      itemToBeDeleted = item
      modalInstance.show() //show the modal
    }
    
    
    function onDelete(){
      removeItems(itemToBeDeleted.id)
      modalInstance.hide() // hide the modal
      getItems();
    }
    
    </script>
    

  2. Does data-modal control the display of your modal if yes then put:

    dataModal.value = false

    after the call of your functions ‘removeItems’ or even after ‘getItems’ in your function ‘onDelete’.

    If this is not the case create a ref ‘showModal’ in your script and use ‘v-show’ or ‘v-if’ to control the visibility of your modal

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search