Vue JS loop with different components - vue.js

I am using Vue JS to make a list that has one generic list item component. If there exists a none generic component that meets the correct type a custom component will be used.
<email-queue-item v-for="item in queue"
:key="item.id"
:item="item"
v-if="type == 'EmailMessage'"></email-queue-item>
<queue-item v-for="item in queue"
:key="item.id"
:item="item"
v-else></queue-item>
The code above better illustrates what I am trying to do. The problem I seem to have is due loops first creating two list and then checks the conditional. Is there a way in due to pick the right component vase on the type and then loop through the list?
The data Used to display these components is like this:
{
name: Email,
type: EmailMessage,
data:[
{...},
{...},
...
]
}

Dynamic components make this pretty easy in the template:
<component
:is="type == 'EmailMessage' ? 'email-queue-item' : 'queue-item'"
v-for="item in queue"
:key="item.id"
:item="item"
/>

If I undersstand correctly, you'd like v-for with dynamic component.
so check Vue Official Guide: dynamic component, then the demo will be like below which uses v-bind:is:
Vue.config.productionTip = false
Vue.component('email-queue-item', {
template: `<div><h3 :style="{'background-color':color}">Email: {{color}}</h3></div>`,
props: ['color']
})
Vue.component('message-queue-item', {
template: `<div><h1 :style="{'background-color':color}">Message: {{color}}</h1></div>`,
props: ['color']
})
new Vue({
el: '#app',
data() {
return {
items: [
{'component':'email-queue-item', 'color':'red'},
{'component':'message-queue-item', 'color':'blue'},
{'component':'email-queue-item', 'color':'green'}
]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div v-for="(item, index) in items" :key="index" :color="item.color" :is="item.component"></div>
</div>

Related

Passing props to deactivated component in Vue

I've implemented the Tab feature using "keep-alive", like below, I want to pass "items2" data to the component when the selected currentTabComponent is 'Old', how do i make this work? is there any workaround?
<template>
<div>
<button #click="currentTabComponent = 'New'"> New </button>
<button #click="currentTabComponent = 'Old'"> Old </button>
</div>
<keep-alive>
<component :is="currentTabComponent" :items="currentTabComponent === 'New' ? items : items2"></component>
</keep-alive>
</template>
In the logic, i have,
<script>
export default {
data() {
return {
currentTabComponent: "New",
items:['one','two','three'],
items2:['five','six','seven']
}
}
}
</script>
Even if you use keep-alive props will be passed in the usual way, dynamic or not. So if there is a change in props it should reflect in the subcomponent. keep-alive specifically helps in preserving state changes when the component is not used, and not resetting the state when the component is shown again. But in both cases, props will work fine.
Check the below code:
<div id='component-data'>
<button
v-for="tab in tabs"
v-bind:key="tab"
v-on:click="currentTab = tab">
{{ tab }}
</button>
<keep-alive>
<component v-bind:is="currentTab"
:items="currentTab == 'Bags' ? bags : shirts"
class="tab"></component>
</keep-alive>
</div>
<script>
Vue.component('Bags', {
props: ['items'],
template: "<div>Showing {{ items.toString() }} items in bags.</div>"
});
Vue.component('Shirts', {
props: ['items'],
template: "<div>Showing {{ items.toString() }} items in shirts.</div>"
});
new Vue({
el: "#component-data",
data: {
tabs: ['Bags', 'Shirts'],
currentTab: 'Bags',
bags: ['Bag one', 'Bag two'],
shirts: ['Shirt one', 'Shirt two']
}
});
</script>
You should make sure that the sub-components 'New' and 'Old' have declared the items props in their component definition. Also I hope 'New' and 'Old' are the registered names of the components you are using for tabs.

Share data between two child components Vue js

i am trying to send and render some data from a child component to another child component & both components are rendered using one main component , how can i pass the data between two child components (in my vue app)?
exg
I have two child components A & B , "B" has click add to cart click event and have data of cart items , & i have template for cart item in component "A"
In this situation, as both components share the same parent, it's common to move whatever shared state you need into the parent component and pass to the children as props.
Where components don't shared a direct parent, you can use an event bus (for very small apps) or Vuex. Vuex lets you share state between components in your app without having to pass props down through multiple levels.
There are a lot of ways to achieve this. I am explaining with global even bus concept. Following is an example. Here, child A and child B are communicating through event bus
const eventBus = new Vue ()
Vue.component('ChildB',{
template:`
<div id="child-b">
<h2>Child B</h2>
<pre>data {{ this.$data }}</pre>
<hr/>
</div>`,
data() {
return {
score: 0
}
},
created () {
eventBus.$on('updatingScore', this.updateScore) // 3.Listening
},
methods: {
reRender() {
this.$forceUpdate()
},
updateScore(newValue) {
this.score = newValue
}
}
})
Vue.component('ChildA',{
template:`
<div id="child-a">
<h2>Child A</h2>
<pre>data {{ this.$data }}</pre>
<hr/>
<button #click="changeScore">Change Score</button>
<span>Score: {{ score }}</span>
</div>`,
props: ["score"],
methods: {
changeScore() {
this.score +=200;
eventBus.$emit('updatingScore', this.score+ 200)
}
}
})
Vue.component('ParentA',{
template:`
<div id="parent-a">
<h2>Parent A</h2>
<pre>data {{ this.$data }}</pre>
<hr/>
<child-a :score="score"/>
<child-b/>
</div>`,
data() {
return {
score: 100
}
}
})
Vue.component('GrandParent',{
template:`
<div id="grandparent">
<h2>Grand Parent</h2>
<pre>data {{ this.$data }}</pre>
<hr/>
<parent-a/>
</div>`,
})
new Vue ({
el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<grand-parent/>
</div>
Ideally, you should use Vuex as state management pattern.
But if your application is very simple and you don't need to do those operations often, you can pass data in emit payload from child to parent, and then parent component should pass it to another child via props

Vue - How do you render sub-rows in a table when you are only allowed one root component?

I'm trying to figure out the best way to render sub rows in a table.
I'm developing what's essentially a table+accordion. That is, the table adds more rows when you click on it to show more details for that entry.
My table is located in the Researcher Groups component, which has a sub-component: "app-researcher-group-entry (ResearcherGroupEntry).
I'm using Vue-Material, but the problem would be the same if I was using a plain .
Anyway, my structure is kind of like:
// ResearcherGroups.vue
<template>
<md-table>
<md-table-row>
<md-table-head></md-table-head>
<md-table-head>Researcher</md-table-head>
<md-table-head>Type</md-table-head>
<md-table-head></md-table-head>
<md-table-head></md-table-head>
</md-table-row>
<app-researcher-group-entry v-for="(group, index) in researcherGroups" :key="group.label" :groupData="group" :indexValue="index"></app-researcher-group-entry>
</md-table>
</template>
And in ResearcherGroupEntry.vue:
<template>
<md-table-row>
<md-table-cell>
<md-button class="md-icon-button md-primary" #click="toggleExpansion">
<md-icon v-if="expanded">keyboard_arrow_down</md-icon>
<md-icon v-else>keyboard_arrow_right</md-icon>
</md-button>
<md-button #click="toggleExpansion" class="index-icon md-icon-button md-raised md-primary">{{indexValue + 1}}</md-button>
</md-table-cell>
<md-table-cell>{{groupData.label}}</md-table-cell>
<md-table-cell>Group</md-table-cell>
<md-table-cell>
<md-button #click="openTab" class="md-primary">
<md-icon>add_box</md-icon>
Add Client / Client Type
</md-button>
</md-table-cell>
<md-table-cell>
<md-button class="md-primary">
<md-icon>settings</md-icon>
Settings
</md-button>
</md-table-cell>
<app-add-client-to-group-tab :indexValue="indexValue" :groupData="groupData" :closeTab="closeTab" :addClientToGroupTab="addClientToGroupTab"></app-add-client-to-group-tab>
</md-table-row>
</template>
Here's where the problem comes in. I want to be able to expand the clients like this from our mockup:
This would be trivially easy if I could just add another into the ResearcherGroupEntry component. Unfortunately, wrapping with a div or span tag won't work here (it messes up the table).
If I wasnt using Vue Material, I might have considered using JSX for this component, because then I could simply use .map in the parent to create an array of components based on the two variables. I might still be able to do that with v-for directives, but I'm not sure. What I may have to do is create, from props, a crazy pseudo array merging in parents and children, but that's UUUUUGLY code.
The key here is that the row component doesn't render its own sub-rows. You can use <template> tags to provide entity-less v-for and v-if wrappers. Do your v-for in a template, so that you can output the row tr and also some optional sub-row trs. Simple example:
new Vue({
el: '#app',
data: {
rows: [{
id: 1,
name: 'one',
subrows: ['a', 'b', 'c']
},
{
id: 2,
name: 'two',
subrows: ['d', 'e', 'f']
}
],
expanded: {}
},
methods: {
expand(id) {
this.$set(this.expanded, id, true);
}
},
components: {
aRow: {
template: '<tr><td>{{name}}</td><td><button #click="expand">Expand</button></td></tr>',
props: ['name', 'id'],
methods: {
expand() {
this.$emit('expand', this.id);
}
}
},
subRow: {
template: '<tr><td colspan=2>{{value}}</td></tr>',
props: ['value']
}
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<table id="app" border=1>
<tbody>
<template v-for="r in rows">
<a-row :id="r.id" :name="r.name" #expand="expand"></a-row>
<template v-if="r.id in expanded">
<sub-row v-for="s in r.subrows" :value="s"></sub-row>
</template>
</template>
</tbody>
</table>

Passing a prop down 2 layers of children?

I have a component and I pass in an id as a prop:
<comments myId="1"></comments>
And on the comments component I have it as a prop:
props: [
'myId',
],
Inside this comments component template I have another component
<btn id="{{ this.myId }}"></btn>
But i cannot seem to pass the prop down - I get the error:
Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead. For example, instead of <div id="{{ val }}">, use <div :id="val">.
I don't see why I need to use :, I'm happy to pass the id as a string.
How can I resolve the error, and pass down the prop?
you can write down
<btn :id="myId"></btn>
to pass the props in to component.
syntax for passing props is this we can bind variable to component using bind we don't need to interpolate values there.
Vue.component('child', {
template: '#child',
props: ['id']
});
Vue.component('childchild', {
template: '#childchild',
props: ['id']
});
new Vue({
el: '#app',
data: {
},
created: function() {
},
methods: {
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<child id="1000"></child>
</div>
<template id="child">
<childchild :id="id"></childchild>
</template>
<template id="childchild">
<h1>{{ id }}</h1>
</template>

How to pass props using slots from parent to child -vuejs

I have a parent component and a child component.
The parent component's template uses a slot so that one or more child components can be contained inside the parent.
The child component contains a prop called 'signal'.
I would like to be able to change data called 'parentVal' in the parent component so that the children's signal prop is updated with the parent's value.
This seems like it should be something simple, but I cannot figure out how to do this using slots:
Here is a running example below:
const MyParent = Vue.component('my-parent', {
template: `<div>
<h3>Parent's Children:</h3>
<slot :signal="parentVal"></slot>
</div>`,
data: function() {
return {
parentVal: 'value of parent'
}
}
});
const MyChild = Vue.component('my-child', {
template: '<h3>Showing child {{signal}}</h3>',
props: ['signal']
});
new Vue({
el: '#app',
components: {
MyParent,
MyChild
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<my-parent>
<my-child></my-child>
<my-child></my-child>
</my-parent>
</div>
You need to use a scoped slot. You were almost there, I just added the template that creates the scope.
<my-parent>
<template slot-scope="{signal}">
<my-child :signal="signal"></my-child>
<my-child :signal="signal"></my-child>
</template>
</my-parent>
Here is your code updated.
const MyParent = Vue.component('my-parent', {
template: `<div>
<h3>Parent's Children:</h3>
<slot :signal="parentVal"></slot>
</div>`,
data: function() {
return {
parentVal: 'value of parent'
}
}
});
const MyChild = Vue.component('my-child', {
template: '<h3>Showing child {{signal}}</h3>',
props: ['signal']
});
new Vue({
el: '#app',
components: {
MyParent,
MyChild
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<my-parent>
<template slot-scope="{signal}">
<my-child :signal="signal"></my-child>
<my-child :signal="signal"></my-child>
</template>
</my-parent>
</div>
The release of Vue 2.6 introduces a unified v-slot directive which can be used for normal or scoped slots. In this case, since you're using the default, unnamed slot, the signal property can be accessed via v-slot="{ signal }":
<my-parent>
<template v-slot="{ signal }">
<my-child :signal="signal"></my-child>
<my-child :signal="signal"></my-child>
</template>
</my-parent>
I added this code inside of <v-data-table></v-data-table>
<template
v-for="slot in slots"
v-slot:[`item.${slot}`]="{ item }"
>
<slot
:name="slot"
:item="item"
/>
</template>
And I added a props called slots. When I call the component I send a slots like:
<my-custom-table-component :slots="['name']">
<template v-slot:name="{ item }">
{{ item.first_name + item.last_name}}
</template>
</my-custom-table-component>
You may try this technique.
In this example.
Let assume a parent component wants to share prop foo with value bar.
Parent component
<parent>
<template v-slot:default="slotProps">
// You can access props as object slotObjects {foo"bar"}
<p>{{slotProps.foo}}</p>
</template>
<parent>
Child
<template>
<div class="parent">
<slot :foo="bar" />
</div>
</template>
Got the idea from this video
I hope it helped you accomplish your tasks.