VueJs: How to reuse bootstrap modal dialog - javascript

I've created three simple buttons that will trigger three different bootstrap modal dialog. The modal dialogs are "Add Product", "Edit Product" and "Delete Product". Both the Add and Edit modal dialogs contain a form with two input elements, whereas the Delete modal dialog contains a simple text. I realise that my code becomes very messy and hard to maintain. Hence, I have the following question:
1) How do I reuse the modal dialog, instead of creating 3 separate dialogs?
2) How do I know which modal dialog has been triggered?
Update: I've developed a soultion where I will include conditional statements such as v-if, v-else-if and v-else to keep track of which button the user click. However, I still feel that there is a better solution to this. Can anyone help/advice me?
Below is my current code:
<template>
<div>
<b-button v-b-modal.product class="px-4" variant="primary" #click="addCalled()">Add</b-button>
<b-button v-b-modal.product class="px-4" variant="primary" #click="editCalled()">Edit</b-button>
<b-button v-b-modal.product class="px-4" variant="primary" #click="deleteCalled()">Delete</b-button>
<!-- Modal Dialog for Add Product -->
<b-modal id="product" title="Add Product">
<div v-if="addDialog">
<form #submit.stop.prevent="submitAdd">
<b-form-group id="nameValue" label-cols-sm="3" label="Name" label-for="input-horizontal">
<b-form-input id="nameValue"></b-form-input>
</b-form-group>
</form>
<b-form-group id="quantity" label-cols-sm="3" label="Quantity" label-for="input-horizontal">
<b-form-input id="quantity"></b-form-input>
</b-form-group>
</div>
<div v-else-if="editDialog">
<form #submit.stop.prevent="submitEdit">
<b-form-group id="nameValue" label-cols-sm="3" label="Name" label-for="input-horizontal">
<b-form-input id="nameValue" :value="productName"></b-form-input>
</b-form-group>
</form>
<b-form-group id="quantity" label-cols-sm="3" label="Quantity" label-for="input-horizontal">
<b-form-input id="quantity" :value="productQuantity">5</b-form-input>
</b-form-group>
</div>
<div v-else>
<p class="my-4">Are You Sure you want to delete product?</p>
</div>
</b-modal>
</div>
</template>
<script>
export default {
data() {
return {
productName: "T-Shirt",
productQuantity: 10,
addDialog: false,
editDialog: false,
deleteDialog: false
};
},
methods: {
addCalled() {
this.addDialog = true;
},
editCalled() {
this.editDialog = true;
this.addDialog = false;
this.deleteDialog = false;
},
deleteCalled() {
this.deleteDialog = true;
this.addDialog = false;
this.editDialog = false;
}
}
};
</script>
<style>
</style>

As already mentionned, I would have use slots and dynamic component rendering to accomplish what you're trying to do in a cleaner way.
See snippet below (I didn't make them modals as such but the idea is the same).
This way, you can have a generic modal component that deals with the shared logic or styles and as many modalContentsub-components as needed that are injected via the dedicated slot.
Vue.component('modal', {
template: `
<div>
<h1>Shared elements between modals go here</h1>
<slot name="content"/>
</div>
`
});
Vue.component('modalA', {
template: `
<div>
<h1>I am modal A</h1>
</div>
`
});
Vue.component('modalB', {
template: `
<div>
<h1>I am modal B</h1>
</div>
`
});
Vue.component('modalC', {
template: `
<div>
<h1>I am modal C</h1>
</div>
`
});
new Vue({
el: "#app",
data: {
modals: ['modalA', 'modalB', 'modalC'],
activeModal: null,
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button v-for="modal in modals" #click="activeModal = modal"> Open {{ modal }} </button>
<modal>
<template slot="content">
<component :is="activeModal"></component>
</template>
</modal>
</div>

Update
Now, You might think how will you close your modal and let the parent component know about it.
On click of button trigger closeModal for that
Create a method - closeModal and inside commonModal component and emit an event.
closeModal() {
this.$emit('close-modal')
}
Now this will emit a custom event which can be listen by the consuming component.
So in you parent component just use this custom event like following and close your modal
<main class="foo">
<commonModal v-show="isVisible" :data="data" #close- modal="isVisible = false"/>
<!-- Your further code -->
</main>
So as per your question
A - How do I reuse the modal dialog, instead of creating 3 separate dialogs
Make a separate modal component, let say - commonModal.vue.
Now in your commonModal.vue, accept single prop, let say data: {}.
Now in the html section of commonModal
<div class="modal">
<!-- Use your received data here which get received from parent -->
<your modal code />
</div>
Now import the commonModal to the consuming/parent component. Create data property in the parent component, let say - isVisible: false and a computed property for the data you want to show in modal let say modalContent.
Now use it like this
<main class="foo">
<commonModal v-show="isVisible" :data="data" />
<!-- Your further code -->
</main>
The above will help you re-use modal and you just need to send the data from parent component.
Now second question will also get solved here How do I know which modal dialog has been triggered?
Just verify isVisible property to check if modal is open or not. If isVisible = false then your modal is not visible and vice-versa

Related

Bootstrap vue b-modal does not highlight the field on submission of the data

I am using the bootstrap vue to design my application. Within the application, I am using the b-modal. Some of the fields in b-modal are required so I would like to highlight them if the user has not provided information to them. In normal bootstrap which I have used in other applications, it was highlighting the field and showing a default message field is required but in the bootstrap-vue I am not getting any such message by default. Can someone please tell me what needs to be done about it?
Following is the bootstrap vue modal code I have:
<template>
<b-modal
id="formSubmission"
size="lg"
title="Basic Details"
:visible="visibility"
style="width: 100%"
#cancel="hideModal"
#ok="submitModal($event)"
>
<b-form-select v-model="type" class="form-control" required>
<b-form-select-option value="type1">
Type1
</b-form-select-option>
<b-form-select-option value="type2">
Type2
</b-form-select-option>
</b-form-select>
<div v-if="type == 'type1'">
<input
type="text"
class="form-control"
style="width:200px"
autocomplete="off"
placeholder="Enter Name"
:required="type == 'type1'"
>
</div>
</b-modal>
</template>
<script>
export default {
data () {
return {
visibility: true
}
},
methods: {
hideModal () {
this.visibility = false
},
submitModal (event) {
event.preventDefault()
}
}
}
</script>
<style>
</style>
What I want to do is highlight the field which is required during the submission? I want to know if there is an out-of-the-box way to do it rather than writing the function for each and every field.
Something like this:
The modal doesn't know you have input elements inside it, and that you want to validate it. Which is why nothing happens.
You can solve this in a few ways. The way i would recommend is to first create a form around your input fields with <b-form>.
Then when clicking on the OK button, we need to submit the form, as that will then validate the inputs and show an error if the requirements are filled.
We will then use the modal-footer slot, to overwrite the default footer and replace it with our own buttons. For the cancel button we'll use the cancel method from the slot scope, so that it will function as default.
However, for the OK button, we will use the form attribute and type="submit", to create a submit button for the form inside the modal. The form attribute takes the id from our form.
If the form is submitted succesfully, we'll need to hide the modal manually. In the snippet we use this.$bvModal.hide for this.
new Vue({
el: '#app',
data() {
return {
value: ''
}
},
methods: {
onSubmit() {
const {
value
} = this;
alert(`Submitted: ${value}`);
this.$bvModal.hide('my-modal')
}
}
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" />
<link href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="//unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.min.js"></script>
<div id="app" class="p-4">
<b-btn v-b-modal.my-modal>Show Modal</b-btn>
<b-modal id="my-modal" title="Form Modal" visible>
<b-form id="my-form" #submit.prevent="onSubmit">
<b-input v-model="value" required minlength="3" autofocus placeholder="Write something.."></b-input>
</b-form>
<template #modal-footer="{ cancel }">
<b-btn #click="cancel">Cancel</b-btn>
<b-btn variant="primary" type="submit" form="my-form">OK</b-btn>
</template>
</b-modal>
</div>

accordion plus and minus icon not changing

I have this accordion and a collapse (as a component because I need to loop it). Here's the code:
accordion.vue
<div class="accordion col-lg-8 mx-auto" role="tablist">
<b-card no-body class="mb-1 py-2" v-for="each in questions" :key="each.id">
<Collapses v-bind:each="each"/>
</b-card>
</div>
Collapses.vue
<div>
<b-button #click="isActive = !isActive" role="tab" block v-b-toggle="'accordion-'+each.id">{{ each.question }}
<i class="float-right fa" :class="{ 'fa-plus': !isActive, 'fa-minus': isActive }"></i>
</b-button>
<b-collapse v-bind:id="'accordion-'+each.id" visible accordion="my-accordion" role="tabpanel">
<b-card-body>
<b-card-text>{{ each.answer }}</b-card-text>
</b-card-body>
</b-collapse>
</div>
<script>
export default {
props: ["each"],
data() {
return {
isActive: false
}
}
}
</script>
The accordion works fine except the icons. The accordion only shows one (expanded) collapse at a time. Whenever I click another collapse, the previous collapse closes, but the icon doesn't change (because I did not click it). How do I automatically change the icon whenever the collapse closes?
The b-collapse events include show and hide, which is emitted when the component state changes. Thus, you could use a v-on directive (or # for shortand) to bind an event listener in the template that sets the isActive flag accordingly:
<b-collapse #hide="isActive = false" #show="isActive = true">
Then you could remove the button-click handler as it's already taken care of by the event binding above.
demo
Your data is defined like method, try to do it in more clear way:
{
data: () => ({
isActive: false
})
}

Created not being triggered in Vue js

I'm implementing an application with Vue Js and I've the following code:
<template>
<simple-page title="list-patient" folder="Patient" page="List Patient" :loading="loading">
<list-patients #patientsLoaded="onPatientsLoaded"/>
</simple-page>
</template>
Both simple-page and list-patients are custom components created by me. Inside ListPatients I've an HTTP request on Create callback, as follows:
created() {
axios.get("...").then(response => {
...
this.$emit('patientsLoaded');
})
},
Then, my objective is to handle the patientsLoaded event and uptade the loading prop on the top parent component, as follows:
data() {
return {
loading: true
}
},
methods: {
onPatientsLoaded(params) {
this.loading = false;
}
}
However, the created method is not being triggered inside the list-patients component. The only way I can make this work is by removing :loading.
Any one can help?
Edit 1
Code of simple page:
<template>
<section :id="id">
<!-- Breadcrumb-->
<breadcumb :page="page" :folder="folder"/>
<!-- Breadcrumb-->
<!-- Simple Card-->
<simple-card :title="page" :icon="icon" :loading="loading" v-slot:body>
<slot>
</slot>
</simple-card>
<!-- Simple Card-->
</section>
</template>
Code of simple card:
<b-card>
<!-- Page body-->
<slot name="body" v-if="!loading">
</slot>
<!--Is loading-->
<div class="loading-container text-center d-block">
<div v-if="loading" class="spinner sm spinner-primary"></div>
</div>
</b-card>
Your list-patients component goes in the slot with name "body". That slot has a v-if directive so basically it is not rendered and hooks are not reachable as well. Maybe changing v-if to v-show will somehow help you in that situation. Anyway, you have deeply nested slots and it is making things messy. I usually declare loading variable inside of the component, where fetching data will be rendered.
For example:
data () {
return {
loading: true;
};
},
mounted() {
axios.get('url')
.then(res => {
this.loading = false;
})
}
and in your template:
<div v-if="!loading">
<p>{{fetchedData}}</p>
</div>
<loading-spinner v-else></loading-spinner>
idk maybe that's not best practise solution
v-slot for named slots can be indicated in template tag only
I suppose you wished to place passed default slot as body slot to simple-card component? If so you should indicate v-slot not in simple-card itself but in a content you passed it it.
<simple-card :title="page" :icon="icon" :loading="loading">
<template v-slot:body>
<slot>
</slot>
</template>
</simple-card>

How to disable tooltip opening by hover using Bootstrap Vue

I using tooltip like in exemple Toggle Tooltip:
<template>
<div class="text-center">
<div>
<b-button id="tooltip-button-1" variant="primary">I have a tooltip</b-button>
</div>
<div class="mt-3">
<b-button #click="show = !show">Toggle Tooltip</b-button>
</div>
<b-tooltip :show.sync="show" target="tooltip-button-1" placement="top">
Hello <strong>World!</strong>
</b-tooltip>
</div>
</template>
<script>
export default {
data: {
show: true
}
}
</script>
But I don’t need it to open on the hover.
Can anyone help to figure it out?
In the tooltip component there's a prop called triggers in which you could specify the event that could trigger the tooltip :
<b-tooltip :show.sync="show" target="tooltip-button-1" triggers="click" placement="top">
Hello <strong>World!</strong>
</b-tooltip>

Bootstrap-Vue Modal reopens everytime except in ESC

I am currently writing a Vue component for a project.
I encountered a Problem where a Bootstrap-Vue modal will re open again instead of closing.
I am using Vue.js in Version 2.6.10 and Bootstrap 4.
<template>
<div>
<b-button v-b-modal.rating-modal #click="showModal()">
Click me
<b-modal ref="rating-modal" no-close-on-backdrop centered title="Rate" class="rating-modal" #ok="hideModal" #cancel="hideModal" #close="hideModal">
<div>
Content of the modal...
</div>
</b-modal>
</b-button>
</div>
</template>
<script>
export default {
name: "Rating",
components: {
,
},
methods: {
showModal() {
this.$refs['rating-modal'].show();
},
hideModal() {
this.$refs['rating-modal'].hide();
},
}
}
;
</script>
I expect it to close when I hit either cancel, ok or the cross in the header.
Ok, I resolved the Issue by myself. All I had to do is to move the b-modal tag out of the b-button tag like this:
<template>
<div>
<b-button v-b-modal.rating-modal #click="showModal()">
Click me
</b-button>
<b-modal ref="rating-modal" no-close-on-backdrop centered title="Rate" class="rating-modal" #ok="hideModal" #cancel="hideModal" #close="hideModal">
<div>
Content of the modal...
</div>
</b-modal>
</div>
</template>

Categories