In my application I have many buttons. When I press it the button I would like to load a template (that replaces the selected button):
Templates:
Vue.component('component-1', {...});
Vue.component('component-2', {...});
Buttons:
<div id="toReplace">
<button>Button1</button>
<button>Button2</button>
</div>
In this case, when I press Button1 the content of the div toReplace should be component-1.
Sure, each component should have a "close" button that will show the buttons again (in short, the div toReplace) on press.
You need to bind a variable to :is property. And change this variable on button click. Also you will need to combine it with some v-show condition. Like so:
<div id="toReplace">
<div :is="currentComponent"></div>
<div v-show="!currentComponent" v-for="component in componentsArray">
<button #click="swapComponent(component)">{{component}}</button>
</div>
</div>
<button #click="swapComponent(null)">Close</button>
new Vue({
el: 'body',
data: {
currentComponent: null,
componentsArray: ['foo', 'bar']
},
components: {
'foo': {
template: '<h1>Foo component</h1>'
},
'bar': {
template: '<h1>Bar component</h1>'
}
},
methods: {
swapComponent: function(component)
{
this.currentComponent = component;
}
}
});
Here is quick example:
http://jsbin.com/miwuduliyu/edit?html,js,console,output
You can use v-bind:is="CurrentComponent" and #click=ChangeComponent('SecondComponent'). The argument has to be a string. To use the <component/> make sure your ExampleComponent has a slot
<Template>
<div>
<ExampleComponent>
<component v-bind:is = "CurrentComponent"/>
</ExampleCompoent>
<button #click="ChangeComponent('SecondComponent')">
</button>
</div>
</template>
<script>
import ExampleComponent from '#/components/ExampleComponent.vue'
import SecondComponent from '#/components/SecontComponent.vue'
export default{
...
data(){
return{
CurrentComponet:null
}
}
methods:{
ChangeComponent(NewComponent){
this.CurrentComponent = NewComponent
}
Related
In my parent component I have a button and child component:
<div> #click="editMe(edit)" />
<edit-team :openModal="openModal"/>
</div>
export default {
data() {
return {
openModal: false,
};
},
method: {
editMe(edit) {
this.openModal = true;
},
}
}
So after I click the editMe button, I am expecting openModal becomes true and goes true to child component.
<template>
<el-dialog
:visible.sync="modal"
/>
</template>
<script>
export default {
props: {
openModal: {
type: Boolean,
required: true,
},
},
data() {
return {
modal: this.openModal,
};
},
</script>
But unfortunately, the modal is not opening because props comes as false always. I assigned openModal to new variable because it was giving me a warning about mutating props. So how do you think I can send the props in right value?
In child just try this
<template>
<el-dialog
:visible.sync="openModal"
/>
</template>
<script>
export default {
props: {
openModal: {
type: Boolean,
required: true,
},
},
},
</script>
If you have sibilngs components and you need to retrieve data, you can use the emit keyword and emit events
Then it will work like this :
The sibling emit the event to show the modal
The parent update the showModalData to true
This child is re-rendered
Vue.component('modal', {
template: '#modal',
props: ['show'],
});
Vue.component('buttonModal', {
template: '#buttonModal',
methods: {
showModal(){
this.$emit('show-modal-button', true)
}
}
});
new Vue({
el: '#app',
data: () => ({
showModalData: false
}),
methods: {
editMe() {
this.showModalData = true
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<p>this is the parent</p>
<hr>
<button-modal #show-modal-button="editMe">Show modal</button-modal>
<hr>
<modal :show="showModalData" />
</div>
<template id="modal">
<div>
props show : {{ show }}
<h2 v-if="show">This is the modal</h2>
</div>
</template>
<template id="buttonModal">
<div>
<p>This is the sibilng</p>
<button #click="showModal"> Show the modal through sibiling components </button>
</div>
</template>
I want to put several Buttons, each one opening a different Popup. A Popup would print data concerning its Button.
I have a Popup.vue component :
<template>
<div class="popup">
<div class="popup-inner">
<slot />
<Button class="popup-close" #click="TogglePopup()">
Close
</Button>
</div>
</div>
</template>
<script>
export default {
props: ['TogglePopup']
}
</script>
And in another .vue I call it like that :
<template>
<div v-for="infoItem in data" :key="infoItem.name"> // loop to create several buttons
<Button
icon="pi pi-eye"
#click="() => TogglePopup('buttonTriggerDetail')">
</Button>
<Popup
v-if="popupTriggers.buttonTriggerDetail"
:TogglePopup="() => TogglePopup('buttonTriggerDetail')"
>
{{ infoItem.control }}
</Popup>
</div>
</template>
<script>
import ...
export default {
computed: {
data() {
return this.$store.state.data;
},
},
mounted() {
this.$store.dispatch("getData");
},
setup() {
const popupTriggers = ref({
buttonTriggerDetail: false
});
const TogglePopup = (trigger) => {
popupTriggers.value[trigger] = !popupTriggers.value[trigger];
};
return {
Popup,
TogglePopup,
popupTriggers,
};
},
};
</script>
So it prints several Button but when I click on one, it don't open the Popup with the data of this Button, it always prints the data of the last Button. I think in reality it places all the pop up on top of each others.
How can I do to open only the good Popup with the good data ?
Thanks
The mistake is that you are rendering 3 popups when you need only one with certain props. Think of your popups as of tabs: let popup be rendered by clicking on certain button and pass in props you need, for example:
<template>
<div v-for="infoItem in data" :key="infoItem.name"> // loop to create several buttons
<Button
icon="pi pi-eye"
#click="clickHandler">
</Button>
</div>
<Popup
v-if="popupTriggered"
:TogglePopup="() => TogglePopup('buttonTriggerDetail')"
>
{{ data[currentActiveItem].control }}
</Popup>
</template>
<script>
import ...
export default {
data () {
currentActiveItem: 0,
popupTriggered: false
},
methods: {
clickHandler (index) {
this.popupTriggered = true
this.currentActiveItem = index
}
}
... // other component data
};
</script>
I wrote my example in Vue 2 style because I haven't worked with Composition API yet but I hope you got the idea.
I have in Layout.vue to components one TheSidebar second TheHeader, there is a button in TheHeader to open the sidebar in TheSidebarcomponent.
I need to when I click the button in header open the sidebar:
My try:
in TheHeader:
methods: {
openSidebar() {
this.$root.$emit("open-sidebar");
},
},
in TheSidebar
data() {
return {
sidebarOpen: false,
};
},
mounted() {
this.$root.$on("open-sidebar", (this.sidebarOpen = true));
},
I'm using VUE 3 so I got this error in console: TypeError: this.$root.$on is not a function so How can communicate ?
you can use something like tiny emitter it works fine and doesn't care about parent child relationship
var emitter = require('tiny-emitter/instance');
emitter.on('open-sidebar', ({isOpen}) => {
//
});
emitter.emit('open-sidebar', {isOpen : true} );
You can only pass props to a direct child component, and
you can only emit an event to a direct parent. But
you can provide and eject from anywhere to anywhere
Per another answer, provide and eject may be your best bet in Vue 3, but I created a simple example of how to implement with props/events. Built with Vue 2 as I haven't worked with 3 yet, but should be usable in Vue 3 as well.
Parent.vue
<template>
<div class="parent">
<div class="row">
<div class="col-md-6">
<h4>Parent</h4>
<hr>
<child-one #show-child-two-event="handleShowChildTwoEvent" />
<hr>
<child-two v-if="showChildTwo" />
</div>
</div>
</div>
</template>
<script>
import ChildOne from './ChildOne.vue'
import ChildTwo from './ChildTwo.vue'
export default {
components: {
ChildOne,
ChildTwo
},
data() {
return {
showChildTwo: false
}
},
methods: {
handleShowChildTwoEvent() {
this.showChildTwo = true;
}
}
}
</script>
ChildOne.vue
<template>
<div class="child-one">
<h4>Child One</h4>
<button class="btn btn-secondary" #click="showChildTwo">Show Child Two</button>
</div>
</template>
<script>
export default {
methods: {
showChildTwo() {
this.$emit('show-child-two-event');
}
}
}
</script>
ChildTwo.vue
<template>
<div class="child-two">
<h4>Child Two</h4>
</div>
</template>
I have an vue component which uses Vue Draggable .
<template>
<div class="row my-5">
<div v-for="column in columns" :key="column.title" class="col">
<p class="font-weight-bold text-uppercase">{{column.title}}</p>
<!-- Draggable component comes from vuedraggable. It provides drag & drop functionality -->
<draggable :list="column.tasks" :animation="200" ghost-class="ghost-card" group="tasks" :move="checkMove">
<transition-group>
<task-card
v-for="(task) in column.tasks"
:key="task.id"
:task="task"
class="mt-3 cursor-move"
></task-card>
<!-- </transition-group> -->
</transition-group>
</draggable>
</div>
</div>
</template>
<script>
import draggable from "vuedraggable";
import TaskCard from "../board/TaskCard";
export default {
name: "App",
components: {
TaskCard,
draggable,
},
data() {
return {
columns: [
.....
],
};
},
methods: {
checkMove: function(evt){
console.log('moved');
}
},
};
</script>
In TaskCard Component -
<template>
<div class="bg-white shadow rounded p-3 border border-white">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>{{task.id}}</h2>
<span>{{task.date}}</span>
</div>
<p class="font-weight-bold">{{task.title}}</p>
</div>
</template>
<script>
export default {
props: {
task: {
type: Object,
default: () => ({}),
},
},
};
</script>
When I move an item, I want a modal that confirms the change and only then move the item.
(ie. if I click on the cancel button inside the modal, the item should not be moved.)
How can this be achieved using the checkMove() function provided?
I don't think you can achieve this by using onMove event. The onEnd event it seems more suitable but unfortunately it doesn't have any cancel drop functionality.
So I think the only solution here is revert it back if the user decides to cancel.
You can listen on change event (See more in documentation)
<draggable
group="tasks"
v-model="column.tasks"
#change="handleChange($event, column.tasks)">
...
</draggable>
...
<button #click="revertChanges">Cancel</button>
<button #click="clearChanges">Yes</button>
And
...
handleChange(event, list) {
this.changes.push({ event, list })
this.modal = true
},
clearChanges() {
this.changes = []
this.modal = false
},
revertChanges() {
this.changes.forEach(({ event, list }) => {
if (event.added) {
let { newIndex } = event.added
list.splice(newIndex, 1)
}
if (event.removed) {
let { oldIndex, element } = event.removed
list.splice(oldIndex, 0, element)
}
if (event.moved) {
let { newIndex, oldIndex, element } = event.moved
list[newIndex] = list[oldIndex]
list[oldIndex] = element
}
})
this.changes = []
this.modal = false
}
...
JSFiddle example
I'm building simple modal component with Vue. I want to use this component in a parent components and to be able to toggle it from the parent.
This is my code now of the modal component:
<script>
export default {
name: 'Modal',
data() {
return {
modalOpen: true,
}
},
methods: {
modalToggle() {
this.modalOpen = !this.modalOpen
},
},
}
</script>
<template>
<div v-if="modalOpen" class="modal">
<div class="body">
body
</div>
<div class="btn_cancel" #click="modalToggle">
<i class="icon icon-cancel" />
</div>
</div>
</template>
I use the v-if to toggle the rendering and it works with the button i created inside my modal component.
However my problem is: I don't know how to toggle it with simple button from parent component. I don't know how to access the modalOpen data from the modal component
Ok, let's try to do it right. I propose to make a full-fledged component and control the opening and closing of a modal window using the v-model in parent components or in other includes.
1) We need declare prop - "value" in "props" for child component.
<script>
export default {
name: 'Modal',
props: ["value"],
data() {
return {
modalOpen: true,
}
},
methods: {
modalToggle() {
this.modalOpen = !this.modalOpen
},
},
}
</script>
2) Replace your "modalToggle" that:
modalToggle() {
this.$emit('input', !this.value);
}
3) In parent components or other includes declare "modal=false" var and use on component v-model="modal" and any control buttons for modal var.
summary
<template>
<div v-if="value" class="modal">
<div class="body">
body
</div>
<div class="btn_cancel" #click="modalToggle">
<i class="icon icon-cancel" />
</div>
</div>
</template>
<script>
export default {
name: "Modal",
props: ["value"],
methods: {
modalToggle() {
this.$emit("input", !this.value);
}
}
};
</script>
Example:
Vue.component('modal', {
template: '<div v-if="value" class="modal"><div class="body">modal body</div><div class="btn_cancel" #click="modalToggle">close modal<i class="icon icon-cancel" /></div></div>',
props: ["value"],
methods: {
modalToggle() {
this.$emit('input', !this.value);
}
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app',
data:() =>({
modal: false
}),
methods: {
openModal() {
this.modal = !this.modal;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div #click="openModal">Btn modal</div>
<modal v-model="modal"></modal>
</div>
With your current implementation I would suggest you to use refs
https://v2.vuejs.org/v2/guide/components-edge-cases.html#Accessing-Child-Component-Instances-amp-Child-Elements
So in your parent component add ref="child" to modal (child) component and then open your modal by calling this.$refs.child.modalToggle()
You can use an "activator" slot
You can use ref="xxx" on the child and access it from the parent