Vue.JS Passing object to child for use inside slot - vuejs2

I am trying to pass an object that is loaded within DataContainer into a slot, so that the user can customise the view.
<data-container silo-id="5">
<div slot="content"> <!-- I tried :data="siloData" here but no luck -->
Your current balance is {{data.balance}}
</div>
</data-container>
So DataContainer loads the resource via http and sets the value to its 'siloData' property.
DataContainer's template has no content of its own just a placeholder for the slot.
<template>
<div>
<slot name="content"></slot>
</div>
</template>
When I try this the text is not interpolated and just remains as {{siloData.balance}} to the browser.
I have tried some examples from Vue.JS site like the todo list, but I must admit utterly confused, maybe because this is not a collection, but just a single (albeit complex) object.
Hopefully someone can point me in the right direction.
Many thanks
Phil

You can use a $emit event
Vue.component('data-container', {
template: '#data-container',
data() {
return {
siloData: {}
}
},
mounted() {
this.siloData = { name: "Silo", balance: 10 } // loading data
this.$emit('silo-loaded', this.siloData)
}
})
new Vue({
el: '#app',
data() {
return {
data: {}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<data-container class="card" #silo-loaded="val => data = val">
<div slot="content">
Your current balance is {{ data.balance }}
</div>
</data-container>
</div>
<template id="data-container">
<div>
<slot name="content"></slot>
</div>
</template>

Related

VUE3 use different v-for for the same component

I have a JobComponent.vue component where I fetch data from a VUEX Store. This component is used on two separate pages, first page Home.vue and second page AllJobs.vue.
In AllJobs.vue I used JobComponent.vue and everything is works fine, it's rendering all the jobs, but, here comes the problem...
In Home.vue I want to render only the last 5 jobs, so in store I make a getter that slice me only the latest 5 jobs.
How can I use this latestJobs from getters on the same component?
When I import the component in Home.vue page I can't use another v-for direct on the component...
here you can see my project structure and files
Home.vue
<template>
<div class="cards-container">
<JobComponent />
</div>
</template>
JobComponent.vue
<template>
<div v-for="job in allJobs" :key="job.id" class="card">
<div class="position">{{ job.position }}</div>
<div class="department">{{ job.department }}</div>
<div class="location">
<span class="material-symbols-outlined">location_on</span>
{{ job.location }}
</div>
<span class="material-symbols-outlined right-arrow">arrow_right_alt</span>
<span #click="deleteJob(job.id)" class="material-symbols-outlined right-arrow">delete</span>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
methods: {
...mapActions(['fetchJobs', 'deleteJob']),
},
computed: mapGetters(['allJobs']),
created() {
this.fetchJobs();
}
}
</script>
store.js (vuex)
const getters = {
allJobs: (state) => state.jobs,
latestJobs: (state) => {
const response = state.jobs.slice(0, 5);
return response;
}
};
Your component should be as independent as possible from the store. It's role is to display what ever is provided so it could be reused as you want, using props :
JobComponent.vue
<template>
<div class="card">
<div class="position">{{ position }}</div>
<div class="department">{{ department }}</div>
<div class="location">
<span class="material-symbols-outlined">location_on</span>
{{ location }}
</div>
<span class="material-symbols-outlined right-arrow">arrow_right_alt</span>
<span #click="$emit('deleteJob', id)" class="material-symbols-outlined right-arrow">delete</span>
</div>
</template>
<script>
export default {
props: {
id: string,
position: string,
department: string,
location: string
}
}
</script>
In this component you only display the provided data, and leave the responsibility of the parent component to choose how many components to display.
Home.vue
<template>
<div class="cards-container">
<JobComponent v-for="job in jobs" :key="job.id" :id="job.id" :position="job.position" :department="job.department" :location="job.location" #delete-job="deleteJob" />
</div>
</template>
<script>
export default {
created() {
this.$store.dispatch('fetchJobs')
},
computed: {
jobs() {
return this.$store.getters['latestJobs'] // Or allJobs, just make sure your getter returns an array even if no jobs are loaded yet.
}
},
methods: {
deleteJob() {
// Your logic for job delete
}
}
}
</script>

Only show slot if it has content, when slot has no name?

As answered here, we can check if a slot has content or not. But I am using a slot which has no name:
<template>
<div id="map" v-if="!isValueNull">
<div id="map-key">{{ name }}</div>
<div id="map-value">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
name: {type: String, default: null}
},
computed: {
isValueNull() {
console.log(this.$slots)
return false;
}
}
}
</script>
I am using like this:
<my-map name="someName">{{someValue}}</my-map>
How can I not show the component when it has no value?
All slots have a name. If you don't give it a name explicitly then it'll be called default.
So you can check for $slots.default.
A word of caution though. $slots is not reactive, so when it changes it won't invalidate any computed properties that use it. However, it will trigger a re-rendering of the component, so if you use it directly in the template or via a method it should work fine.
Here's an example to illustrate that the caching of computed properties is not invalidated when the slot's contents change.
const child = {
template: `
<div>
<div>computedHasSlotContent: {{ computedHasSlotContent }}</div>
<div>methodHasSlotContent: {{ methodHasSlotContent() }}</div>
<slot></slot>
</div>
`,
computed: {
computedHasSlotContent () {
return !!this.$slots.default
}
},
methods: {
methodHasSlotContent () {
return !!this.$slots.default
}
}
}
new Vue({
components: {
child
},
el: '#app',
data () {
return {
show: true
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button #click="show = !show">Toggle</button>
<child>
<p v-if="show">Child text</p>
</child>
</div>
Why you dont pass that value as prop to map component.
<my-map :someValue="someValue" name="someName">{{someValue}}</my-map>
and in my-map add prop:
props: {
someValue:{default: null},
},
So now you just check if someValue is null:
<div id="map" v-if="!someValue">
...
</div

Using v-model inside scoped slots

I'm using Vue 2.6.9 with the new v-slot syntax. I want to access interact with v-model inside slot. The problem is that showing data inside slot works, but using v-model does not. Here is my code:
Vue.component('base-test', {
template: `
<div>
<slot :foo="foo" :foo2="foo2"></slot>
</div>
`,
data(){
return{
foo: 'Bar',
foo2: 'Bar 2'
}
}
});
// Mount
new Vue({
el: '#app'
});
<div id="app">
<base-test v-slot="sp">
<div>foo2 is {{ sp.foo2 }}</div>
<input type="text" v-model="sp.foo">
<div>foo is {{ sp.foo }}</div>
</base-test>
</div>
Codepen
My question is how to interact with the component data from within slot.
Regarding this issue, Vue core member says that you should not modify the data you pass to a slot.
You should pass a method to change the value. If you agree with this, then follow this answer.
However, there is a tricky way to do it by taking advantage of Javascript reference values
Instead of passing a primitive value (which will not have reactivity), you pass a reactive object which will keeps its reactivity (eg Observer, reactiveGetter, reactiveSetter).
Child component
<template>
<div class="child">
<slot :model="model"></slot>
</div>
</template>
<script>
export default {
data: () => ({
model: {
value: "Initial value",
},
}),
};
</script>
Parent component
<template>
<div id="app">
<Child v-slot="{ model }">
<input type="text" v-model="model.value" />
</Child>
</div>
</template>
<script>
import Child from "./components/Child";
export default {
components: {
Child,
},
};
</script>
See it live on codesandbox.
Ok, it seems that one cannot change the data directly. The way to do it is to pass as slot prop method and basically redo v-model:
<div id="app">
<base-test v-slot="sp">
<div>foo2 is {{ sp.foo2 }}</div>
<input type="text"
:value="sp.foo2" #input="event => sp.onInput(event, 'foo2')">
<div>foo is {{ sp.foo }}</div>
</base-test>
</div>
Vue.component('base-test', {
template: `
<div>
<slot :foo="foo" :foo2="foo2" :onInput="onInput"></slot>
</div>
`,
data(){
return{
foo: 'Bar',
foo2: 'Bar 2'
}
},
methods:{
onInput(event, prop){
this[prop] = event.target.value;
}
}
});
// Mount
new Vue({
el: '#app'
});
Codepen demo

Show child of this parent click vue

I want to show the immediate child after clicking on its parent. Its easy on jquery, hard on vue, how can I do it?
template:
<boxes inline-template>
<div class="white-box" #click="toggleTick">Purchase only
<div v-if="showTick"><i class="fas fa-check"></i></div>
</div>
</boxes>
js
Vue.component('boxes', {
data: function () {
return {
showTick: false
}
},
methods: {
toggleTick () {
this.showTick = !this.showTick
}
}
})
var app = new Vue({
el: '#app',
data: {
}
})
At the moment I have multiple "white-box" div, it shows the child div for all of them, I just want to show the div for the clicked parent's child.
You should have behavior that you expect :
Vue.component('boxes', {
data: function () {
return {
showTick: false
}
}
})
var app = new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<boxes inline-template>
<div class="white-box" #click="showTick = !showTick">
<span>
Purchase only
</span>
<div v-if="showTick">Component 1</div>
</div>
</boxes>
<boxes inline-template>
<div class="white-box" #click="showTick = !showTick">
<span>
Purchase only
</span>
<div v-if="showTick">Component 2</div>
</div>
</boxes>
</div>
Your white-boxes are sharing the same showTick variable, so clicking on one fo them changes the value for all of them.
There are 2 available solutions to this:
Have multiple boxes components and not multiple white-boxes under the same boxes component. Something take ends up looking like this in the DOM
<boxes>...</boxes>
<boxes>...</boxes>
<boxes>...</boxes>
Use an array for showTick and use an index when calling toggleClick. Also note that for the changes in the array to be reactive you need to use Vue.set.
I recommend the former solution.

how to pass data between different template

I'm trying to make a chat and I do not know how to click an item in one template
get the data and transfer it to another.
from #template1 to #template2
const app = new Vue({
el: '#content',
data:{
users: [],
messages: []
}
});
template1
<template lang="html">
<li v-on:click="getMessages()">
<div class="content-container">
<span class="name">{{ user.first_name}} {{user.last_name}}</span>
<span class="txt"></span>
</div>
</li>
</template>
<script>
export default {
props: ['user'],
data: function() {
return {
messages: []
};
},
methods: {
getMessages(id) {
this.$http.get('/admin/chat/messages').then(function(res){
this.messages = res.data;
}
},
}
}
</script>
template2
<template lang="html">
<ul class="chat">
<chat-message v-for="message in messages" :key="+message" :message="message"></chat-message>
</ul>
</template>
<script>
export default {
props: ['messages']
}
</script>
how to pass data between this template
The structure is:
el: #content
<div id="content">
<div class="list-text" id="chatbox">
<user-list :users="users">#template1</user-list>
</div>
<div class="list-chat">
<chat-log :messages="messages">#template2</chat-log>
<chat-composer v-on:messagesent="addMessage"></chat-composer>
</div>
</div>
When I click on the user in the user-list
i'm trying to load messages from this user
and I do not know how to pass them in chat-log
There are a few ways to achieve the communication between the components
Emit and listen to event (if the component are siblings, you will have to go through the parent) - this solution is fine if you have very little components that need to communicate with each other
Use event bus -> every event will go through this bus (good tutorial on how to create an event bus)
Use vuex - state management (just like event bus, but better, if you have a lot of component communication)