I'm looking to make a global Bootstrap Vue Modal component that I can use for all of my modal needs.
The issue I am facing is that it keeps giving me an error regarding prop mutation. You cannot update them directly in the child component. Which makes sense.
This is what I have so far:
<template>
<b-modal
v-model="show"
size="lg"
scrollable
centered
header-class="event-close-modal-header"
#hide="$emit('close')"
#cancel="$emit('close')"
#ok="$emit('close')"
#show="handleShow"
>
<template #modal-title="{}">
<span class="event-close-modal-header-mc2">{{ title }}</span>
</template>
<b-container fluid class="pb-4">
<h1>Content goes here</h1>
<slot />
</b-container>
</b-modal>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
required: true,
},
title: {
type: String,
required: true,
},
},
data() {
return {};
},
};
</script>
And in my parent I have something like this:
<ModalHelper
:show.sync="modalOne" // modalOne is a data property
title="One"
#close="modalOne = false"
/>
<button #click="modalOne = true">Open Modal One</button>
Error:
You can't use props in v-model. Try watch it, and copy new value into new variable in data:
data() {
return {
showModal: false
}
},
watch: {
show(newValue) {
this.showModal = newValue;
}
}
now use showModal in your code instead of "show".
However, this is no good approach.
You should open-close bootstrap vue modals using:
$bvModal.show()
$bvModal.hide()
Vue Bootstrap Modal
Related
I want to show or hide the item by clicking the button or clicking the item itself, for example:
<template>
<div>
<button #click="show?show = false:show = true">
{{show?"Hide":"Show"}}
</button>
<div #click="show?show = false:show = true" v-if="show">
Vue Js - click here to Hide
</div>
</div>
</template>
<script>
export default {
data() {
return {
show: null,
};
}
};
</script>
but i want to import the item from another component, so i do this:
the parent component:
<template>
<div>
<button #click="show?show = false:show = true">
{{show?"Hide":"Show"}}
</button>
<item :show="show" />
</div>
</template>
<script>
import item from "item.vue"
export default {
components: {
item
},
data() {
return {
show: null,
};
}
};
</script>
the child component:
<template>
<div #click="show?show = false:show = true" v-if="show">
Vue Js - click here to Hide
</div>
</template>
<script>
export default {
props: {
show: Boolean,
}
};
</script>
but of course, it doesn't work well.
When i click on the item it disappears but the show value in the parent component doesn't change and I get an error saying Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "show".
so how to edit the data value of the parent component from the child component?
(I use Vue 2.6.14)
The solution is to use event emitting (Youtube Tutorial) to send updated data up to the parent component from the child component.
so the code becomes like this:
the parent component:
<template>
<div>
<button #click="show?show = false:show = true">
{{show?"Hide":"Show"}}
</button>
<item :show="show" #state="update($event)" />
</div>
</template>
<script>
import item from "item.vue"
export default {
components: {
item
},
data() {
return {
show: null,
};
},
methods: {
update(value) {
this.show = value;
}
}
};
</script>
the child component:
<template>
<div #click="action" v-if="show">
Vue Js - click here to Hide
</div>
</template>
<script>
export default {
props: {
show: Boolean,
},
methods: {
state() {
if (this.show) {
return false;
} else {
return true;
}
},
action() {
this.$emit("state", this.state())
}
}
};
</script>
Thank you #D.Schaller for helping
So this one is a bit tricky for me. I have a Sidebar.vue component with a link(Help). I want to trigger my Modal.vue component with different data for every main view eg. Home, About, Contact etc. So whenever I am on Home I want to trigger help modal with hints via that link for Home.vue, when I am on About I want to show hints for About.vue and so on..
My code:
Sidebar.vue
<template>
<ul>
<li>
<a #click="openHelp">Help</a>
</li>
</ul>
</template>
<script>
export default {
name: 'Sidebar',
methods: {
openHelp() {
this.$emit('help-modal');
},
},
};
</script>
Modal.Vue
<template>
<div class="modal" v-if="open">
<div class="modal-title">
<h4>Help</h4>
</div>
</div>
</template>
<script>
export default {
name: 'Modal',
props: {
open: {
type: Boolean,
default: false,
}
},
methods: {
close() {
this.$emit('closed');
},
},
};
</script>
Home.vue
<template>
<div>
Home
<modal
:open="helpOpen"
#closed="openHelpModal">
<p>Home Help</p>
</modal>
</div>
<template>
<sidebar/>
</template>
</template>
<script>
import Sidebar from '#/components/Sidebar.vue';
export default {
name: 'Home',
components: {
Sidebar,
},
data() {
return {
helpOpen: false,
}
}
methods: {
openHelpModal() {
this.helpOpen = !this.helpOpen;
},
}
}
</script>
I know that vuex would be the best solution but don't have an idea how to approach it. Modal would show only static images with a bit of text for every main view.
One way of doing this would be to simply add a property to the modal component e.g helpData and pass the relevant data to this prop in each of the main pages as shown below
Modal.vue
<template>
<div class="modal" v-if="open">
<div class="modal-title">
<h4>Help</h4>
</div>
</div>
</template>
<script>
export default {
name: 'Modal',
props: {
open: {
type: Boolean,
default: false,
},
helpData: {
type: String,
required: true
}
},
methods: {
close() {
this.$emit('closed');
},
},
};
</script>
Home.vue
<template>
<div>
Home
<modal
:open="helpOpen"
#closed="openHelpModal"
:help-data="This is a sample help data for the home view"
/>
</div>
<template>
<sidebar #help-modal="helpOpen = true"/>
</template>
</template>
<script>
import Sidebar from '#/components/Sidebar.vue';
export default {
name: 'Home',
components: {
Sidebar,
},
data() {
return {
helpOpen: false,
}
}
methods: {
openHelpModal() {
this.helpOpen = !this.helpOpen;
},
}
}
</script>
As indicated by #ljubadr, I have included the logic that would open the modal in the sidebar component of Home.vue. Also, I would recommend you change the name of the function openHelpModal to closeHelpModal (seeing as this function will basically close the modal) or toggleHelpModal (since from the logic of the function, it toggles the modal state).
Can anyone tell me how I can bind the prop of a component to a data property of its own? For example, consider I have a component called ModalComponent
<template> // ModalComponent.vue
<div v-if="showModal">
...
<button #click="showModal = !showModal">close the modal internally</button>
</div>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false
},
},
data() {
return {
showModal: false,
}
}
}
</script>
<style>
</style>
Now consider I am using this modal as a reusable component from inside a parent component using an external button to open up the pop up for the modal.
<template>
<button #click="showChild = !showChild">click to open modal</button>
<ModalComponent :show="showChild" />
</template>
<script>
export default {
components: {ModalComponent},
data() {
return {
showChild: false,
}
}
}
</script>
<style>
</style>
How do I make it so that every time the parent clicks on the button, the modal pops up by binding the local showModal to the prop show ? And when the modal is closed internally by the local close button, if I click on the parent button again it will re-pop up? (I am using Vue 3, just in case)
Any help would be appreciated. Thanks.
The perfect solution for this case is using v-model instead of passing a prop :
in child component define modelValue as prop and emit its value to the parent using $emit function:
<template> // ModalComponent.vue
<div v-if="modelValue">
...
<button #click="$emit('update:modelValue',!modelValue)">close the modal internally</button>
</div>
</template>
<script>
export default {
props: {
modelValue: {
type: Boolean,
default: false
},
},
emits: ['update:modelValue'],
}
</script>
in parent component just use v-model to bind the value :
<template>
<button #click="showChild = !showChild">click to open modal</button>
<ModalComponent v-model="showChild" />
</template>
<script>
export default {
components: {ModalComponent},
data() {
return {
showChild: false,
}
}
}
</script>
I use vue-tables-2 and bootstrap-vue. I've created a component which is a column in vue-tables-2 and it consists of button and modal code.
The problem is that this way, the modal is not opening when you click on the button and I don't know why.
EDIT
I found out that when I hardcode buttons attribute,it works. It just doesn't work when v-b-modal.modal-something is generated by vue.
This is a component:
Vue.component('vue-tables-2-product', {
delimiters: ['[[', ']]'],
props: ['data', 'index', 'column'],
template: `<div>
<b-modal v-bind="modal_attrs" title="BootstrapVue">
<p class="my-4">[[ this.data.name ]]</p>
</b-modal>
<b-button #click="log" v-bind="button_attrs">Detail</b-button></div>`,
methods: {
log: function () {
console.log(this.data)
}
},
computed: {
button_attrs() {
return {
[`v-b-modal.modal-${this.data.id}`]: "",
}
},
modal_attrs() {
return {
[`id`]: "modal-"+this.data.id,
}
},
}
})
And this is templates from the vue app.
templates: {
on_stock: 'boolean',
is_active: 'boolean',
name: 'vue-tables-2-product',
import_export_price_diff: 'vue-tables-2-difference'
}
Do you know where is the problem?
EDIT:
I also tried to add this.$bvModal.show(this.data.id) into the log function and nothing happens.
I noticed that
Aren't you passing empty string as your ID?
What's the reason of doing this?
button_attrs() {
return {
[`v-b-modal.modal-${this.data.id}`]: "",
}
},
It's literally :v-b-modal.modal-some-id=""
This dot might be problematic tho.
Vue accepts variables as modifiers on directives (by placing them in square brackets):
<div>
<b-modal v-bind="modal_attrs" title="BootstrapVue">
<p class="my-4">[[ this.data.name ]]</p>
</b-modal>
<b-button #click="log" v-b-modal.[modal_attrs.id]>Detail</b-button>
</div>
I'm using Vue+Laravel, I want to make a modal window with 2 tabs. I have two buttons, each button must open certain connected tab.
Buttons are places in blade template:
<button #click="openModal('login')">
<button #click="openModal('register')">
//Vue component also is placed in blade template
<auth-component :show="showModal" :active_tab="activeTab" #close="showModal = false"></auth-component>
/resources/assets/js/app.js:
I want to pass openModal() parameter to a component...
const app = new Vue({
el: '#app',
data: {
showModal: false,
activeTab: '',
},
methods: {
openModal(tab) {
this.activeTab = tab;
this.showModal = true;
}
},
});
... and use that parameter as Vue prop to set certain bootstrap-tab as active inside Vue-component:
AuthComponent.vue:
<div class="modal-mask" #click="close" v-show="show">
<div class="modal-wrapper">
<div class="modal-container" #click.stop>
<b-card no-body>
<b-tabs card>
<b-tab title="Login" :active="active_tab === login">
<b-tab title="Register" :active="active_tab === register">
</b-tabs>
</b-card>
</div>
</div>
</div>
...
props: ['show', 'active_tab'],
But my current code does not work. There are no console errors. Just first tab (login) is always active after modal is opened.
UPDATE: I moved to
<b-tab title="Login" :active="active_tab === login">
<b-tab title="Register" :active="active_tab === register">
and I see that in Vue console needed tab get active: true property. But while active: true Register tab is anyway display: none. So I guess setting active property as true isn't enough to actually make tab content visible.
JSFiddle component code
You could try wrapping them in <b-tabs v-model="tabIndex"></b-tabs> and then have a computed method that determines the index based off the prop.
EDIT
computed: {
tabIndex: {
get: function() {
if (this.activeTab === 'register') {
return 1;
} else {
return 0;
}
},
set: function(newValue) {
return newValue;
}
}
}
Try passing openModal.bind(undefined,'login') so you return a function with the bound params instead of invoking that function.
Also, the tabs component is set with the v-model attribute. It uses numeric indexes to set the active tab.
Maybe make a lookup object to simplify things:
const tab_map = { login: 0, register: 1 }
Then you can setup openModal.bind(undefined, tab_map.login)
For the v-model setup on tabs, check this example out: https://bootstrap-vue.js.org/docs/components/tabs/#external-controls