VueJs - pass slot from child component to parent - vue.js

I am having difficulty to make use of named slot from children in parent component.
So I am making tabs component with main Tabs and child Tab components.
My Tabs:
<template>
<div class="tabsMainContainer">
<div class="tabsContainer">
<ul class="tabsHeader">
<li
v-for="(tab, index) in tabs"
:key="tab.title"
:class="{tabActive: (index == selectedIndex)}"
#click="selectTab(index)"
>
<slot name="icon"/>
{{ tab.title }}
</li>
</ul>
</div>
<div class="tabsContent"><slot/></div>
</div>
</template>
I get tabs object with this.$children
and then my Tab component:
<template>
<div v-show="isActive" class="tab">
<slot name="icon"/>
<slot/>
</div>
</template>
And use of that:
<Tabs class="tabsComponent">
<Tab title="first">
<template v-slot:icon>Icon</template>
Content
</Tab>
<Tab title="second">Content second</Tab>
</Tabs >
while Content as such works fine, it appears in default slot, the icon slot also appears in default slot in main Tab component. How can I get icon slot from children, and display it in icon slot of main Tab component?

I also had a same kind of requirement. What I did was adding slot inside the main component's slot with the same name
In your Tab component
<template>
<div v-show="isActive" class="tab">
<template v-slot:icon>
<slot name="icon" />
</template>
</div>
</template>

First of all, I use google translate. Sorry, I may have spelled it wrong.
https://stackoverflow.com/a/64568036
I got an example from #ingrid-oberbüchler.
My solution
Layout.vue
<Tabs class="tabsComponent">
<Tab title="first">
<template v-slot:icon>Icon</template>
Content
</Tab>
<Tab title="second">Content second</Tab>
</Tabs>
Tabs.vue
<template>
<div class="title">
{{ tabsProvider.tabs[tabsProvider.selectedIndex].props.title }}
</div>
<div class="content">
<slot/>
</div>
<div class="icon">
<component :is="tabsProvider.tabs[tabsProvider.selectedIndex].children.icon"/>
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {
tabsProvider: {
selectedIndex: 0,
tabs: [],
count: 0,
},
};
},
provide() {
return {
$tabsProvider: this.tabsProvider,
};
},
created() {
this.tabsProvider.tabs = this.$slots.default().filter((child) => child.type.name === "Tab");
},
}
</script>
Tab.vue
<template>
<div v-show="isActive">
<slot />
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "Tab",
props: {
title: {
type: String,
},
},
inject: ["$tabsProvider"],
data() {
return {
index: 0,
isActive: false,
};
},
beforeMount() {
this.index = this.$tabsProvider.count;
this.$tabsProvider.count++;
this.isActive = this.index === this.$tabsProvider.selectedIndex;
},
watch: {
"$tabsProvider.selectedIndex": {
handler(val, oldVal) {
this.isActive = this.index === this.$tabsProvider.selectedIndex;
},
deep: true,
},
},
});
</script>

Simplest example
let's admit x component is parent
<div class="x">x component</div>
let's admit y component is child
<div class="y">y component</div>
How to be process?
<Ycomponent>
<slot name="example1"/>
<slot name="example2"/>
<slot name="example3"/>
</Ycomponent>
<Xcomponent>
<Ycomponent>
<button slot="example1">button</button>
<span slot="example2">Span</span>
<img slot="example3" src="/"/>
</Ycomponent>
</Xcomponent>
I hope I could help.

Related

There is no div b in page dom, why?

<template>
<div class="test">
<template v-if="arr && arr.length">
<div class="a">rendered in dom</div>
<div class="b">
<span>not render</span>
<el-button>but this child component is created, we can see it in vue-devtool</el-button>
</div>
</template>
<div class="c" v-else>test</div>
<el-dialog append-to-body :modal="false" :visible="visible" #close="visible = false">1</el-dialog>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
arr: [],
visible: true
};
},
mounted() {
this.arr = [1, 2];
}
};
</script>
There are many solutions for this, e.g:
add key, change a different tag, and so on, but why ?
Most important, if delete the dialog, it could run with expectations!

Vuejs change component element value dynamicaly

I have a component in common between two other components A,B. This shared Comp has a button and its name changes depending on the component i use. How do I set the name dynamic?
I thought v-model solved the problem
What am I missing?
App.vue:
<test-a></test-a>
<test-b></test-b>
sharedComp.vue:
<template>
<div>
{{ btnValue }}
<input type="button" v-model="btnValue" />
</div>
</template>
<script>
export default {
data() {
return {
btnValue: "",
};
},
};
</script>
CompA.vue
<template>
<div>
<shared-comp
v-for="(item, index) in 3"
:key="index"
:value="'A'"
></shared-comp>
</div>
</template>
<script>
import SharedComp from "./SharedComp.vue";
export default {
components: { SharedComp },
};
</script>
CompB.vue
<template>
<div>
<shared-comp :value="'B'"></shared-comp>
</div>
</template>
<script>
import SharedComp from "./SharedComp.vue";
export default {
components: { SharedComp },
};
</script>
You have to define the properties you pass to your component inside of the 'sharedComp'.
Try something like:
<template>
<div>
{{ value }}
<input type="button" v-model="value" />
</div>
</template>
<script>
export default {
props: ['value'],
};
</script>
For further information on Props in Vue check the documentation page: https://v2.vuejs.org/v2/guide/components-props.html

how do i render searched beer/beers?

hello I want to know how ik can render searched beers in vue
I have a component that renders a list of beers. I also have an component that searches beers. what i want to do is that when I search, my beer list component updates the list and shows me only the list of beers i searched.
this is my beer list component
<template>
<div class="container">
<div>
<Search v-bind:allBeers="allBeers" />
</div>
<div>
<b-card
v-bind:key="beer.id"
v-for="beer in allBeers "
:img-src="beer.image_url"
:alt="beer.name"
img-top
tag="article"
style="max-width: 22rem ;"
class="mb-2"
>
<b-card-text class="c-text">
<h4 class="title">{{ beer.name }}</h4>
<p>{{ beer.ingredients.malt[0].name }}</p>
<router-link
:to="{
name: 'BeerDetails',
params: { id: beer.id, beer:beer },
}"
>
<b-button class="link" size="sm" variant="outline-warning">View Beer details</b-button>
</router-link>
</b-card-text>
</b-card>
</div>
</div>
</template>
<script>
import Search from "./Search";
import { mapGetters, mapActions } from "vuex";
export default {
components: { Search },
name: "Beers",
// props: ["beers"],
methods: { ...mapActions(["fetchBeers"]) },
computed: mapGetters(["allBeers"]),
created() {
this.fetchBeers();
}
};
</script>
and this is my search component here I filter beers that match my input
<template>
<div>
<b-form #submit="onSubmit" #reset="onReset">
<b-form-group id="input-group-1">
<b-form-input id="input-1" v-model="form.search" type="text" required placeholder="search"></b-form-input>
</b-form-group>
</b-form>
</div>
</template>>
<script>
export default {
name: "Search",
props: ["allBeers"],
data() {
return {
form: {
search: ""
}
};
},
methods: {
onSubmit(evt) {
evt.preventDefault();
this.allBeers.filter(beer => {
console.log(beer.name.toLowerCase() == this.form.search);
});
//alert(JSON.stringify(this.form));
},
onReset(evt) {
evt.preventDefault();
this.form.search = "";
}
}
};
</script>
<style scoped>
</style>
How would I use the search component in my beer component to display only the searched beers?
In the search component set a property to each beer, something like this:
onSubmit(evt) {
evt.preventDefault();
this.allBeers.forEach(beer => {
beer.hidden = (beer.name.toLowerCase() == this.form.search);
});
},
And in the beers component, hide the irrelevant beers:
<template>
<div class="container">
<div>
<Search v-bind:allBeers="allBeers" />
</div>
<div>
<div v-for="beer in allBeers" v-bind:key="beer.id">
<b-card v-if="!beer.hidden"
...
</b-card>
</div>
</div>
</div>
</template>

Prevent child elements for receiving click event not working

I am trying to make a modal component and dismiss it when I click outside of the component. Here is my current setup:
Auth component with click event set on a div element:
<template> <div>
<transition name="modal">
<div class="modal-mask" #click="$parent.$emit('close')">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">Default Header</slot>
</div>
<div class="model-body">
<slot name="body">Default Body</slot>
</div>
<div class="modal-footer">
<slot name="footer">Default Footer</slot>
</div>
</div>
</div>
</div>
</transition> </div> </template>
SignIn component that injects necessary information:
<template>
<div>
<Auth />
</div>
</template>
Home component that uses the SignIn component:
<template>
<div class="home">
<SignIn v-if="showModal" #close="showModal = false" />
</div>
</template>
Right now when I click outside the modal it behaves ok, the close event is called.
But it is also called when I click inside the modal.
Not I tried to use #click.self , but now it doesn't work anymore even when clicking outside the modal.
<div class="modal-mask" #click.self="$parent.$emit('close')">
I am currently learning VueJs, but I don't understand how this works. I thought self will prevent propagating click event to child elements and thats it.
Anyone has an idea what is going on ?
PS: I am using this setup, because I want to have a SignIn and SignUp using the Auth component.
Either <div class="modal-wrapper"> or <div class="modal-container"> needs #click.prevent.stop
<template>
<div>
<transition name="modal">
<div class="modal-mask" #click="$parent.$emit('close')">
<div class="modal-wrapper">
<div class="modal-container" #click.prevent.stop>
<div class="modal-header">
<slot name="header">Default Header</slot>
</div>
<div class="model-body">
<slot name="body">Default Body</slot>
</div>
<div class="modal-footer">
<slot name="footer">Default Footer</slot>
</div>
</div>
</div>
</div>
</transition>
</div>
</template>
With this code you don't have to worry about click event's propagation #click.stop, for the style purpose I am using bootstrap.css but you can write your own style.
Here is the reusable component BsModal.vue
<template lang="pug">
div(v-if="showModal")
.modal.fade.d-block(tabindex='-1', role='dialog', :class="{'show': addShowClassToModal}")
.modal-dialog(role='document')
.modal-content.border-0
.modal-header.border-0
h5.modal-title
slot(name="title")
button.close(type='button', data-dismiss='modal', aria-label='Close', #click="hideModal")
span ×
.modal-body.p-0
slot
.modal-backdrop.fade(:class="{ 'show': addShowClassToModalBackdrop }")
</template>
<script>
export default {
name: 'BsModal',
props: {
showModal: {
default: false,
type: Boolean,
},
},
data() {
return {
addShowClassToModal: false,
addShowClassToModalBackdrop: false,
};
},
mounted() {
this.toggleBodyClass('addClass', 'modal-open');
setTimeout(() => {
this.addShowClassToModalBackdrop = true;
}, 100);
setTimeout(() => {
this.addShowClassToModal = true;
}, 400);
},
destroyed() {
this.toggleBodyClass('removeClass', 'modal-open');
},
methods: {
hideModal() {
setTimeout(() => {
this.addShowClassToModal = false;
}, 100);
setTimeout(() => {
this.addShowClassToModalBackdrop = false;
this.$emit('hide-modal', false);
}, 400);
},
toggleBodyClass(addRemoveClass, className) {
const elBody = document.body;
if (addRemoveClass === 'addClass') {
elBody.classList.add(className);
} else {
elBody.classList.remove(className);
}
},
},
};
</script>
And use it wherever you need by importing it:
<template lang="pug">
div
button(#click="showModal = true")
| Show Modal
bs-modal(
v-if="showModal",
:show-modal="showModal",
#hide-modal="showModal = false"
).modal
template(slot="title") Modal Title
// Modal Body content here
</template>
<script>
import BsModal from '~/components/BsModal.vue';
export default {
name: 'your component',
components: { BsModal },
data() {
return {
showModal: false,
};
},
};
</script>
If you don't like pug template language then you can convert PUG to HTML here: https://pughtml.com/

Vue. Change the contents of one slot from another

I have a component with certain slots.
<v-split-container ref="splitContainer" class="panel-panel">
<template slot="left">
Test
</template>
<template slot="right">
<router-view></router-view> //Show Compontent A
</template>
<template slot="bottom"> //Slot B
Hello
</template>
</v-split-container>
Can I, from component A, change the contents of slot B by calling a function inside the component?
Hi you can do it with Scoped Slot. I have created an example for you how to do it. Please not that I am using v-slots(Some context here) only usable from vue v2.6.
Please take a look at the code example: https://codesandbox.io/s/2030jo20j0?fontsize=14
Child Component
<template>
<div>
<div>
<slot name="msgSlot">{{msg}}</slot>
</div>
<br>
<slot name="bottom" :updateMsg="updateMsg"></slot>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data: () => ({
msg: "Default Message"
}),
methods: {
updateMsg(text) {
this.msg = text;
}
}
};
</script>
Parent Component
<template>
<div id="app">
<HelloWorld>
<template v-slot:msgSlot></template>
<template v-slot:bottom="{updateMsg}">
<input type="text" v-model="msg">
<button #click="updateMsg(msg)">Change Msg</button>
</template>
</HelloWorld>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
data: () => ({
msg: ""
}),
components: {
HelloWorld
}
};
</script>