I just learnt about slot today on Vue official tutorial. As I read through, I came across this example on scoped slot: https://v2.vuejs.org/v2/guide/components.html#Scoped-Slots.
I experimented a little by adding an input field to each item.
Parent:
<my-list :items="items">
<template slot="item" scope="props">
<li class="my-fancy-item">{{ props.text }}</li>
</template>
</my-list>
Child template:
<ul>
<slot name="item" v-for="item in items" :text="item.text">
</slot>
<div> // this will appear at the end of list
<input v-model = "message">
</div>
<p>{{message}}</p>
</ul>
My first attempt was to move the input to the parent scope and called a child function by passing into it the index and input value using props to modify the original array. It worked as intended.
Another attempt is to do binding on parent scope but it won't work because parent scope can't see child property: https://v2.vuejs.org/v2/guide/components.html#Compilation-Scope
What is the best way to insert this input so that it will appear in every item and still be able to bind input to child property?
Base on our discussion, I think essentially what you want is this. Because you want the message to be independently editable, it needs to be part of the item. If you make message part of my-list, then all the messages will be the same. After that, all you need to do is pass the item to the scoped template.
Vue.component("my-list",{
props:["items"],
template: "#my-list-template",
})
new Vue({
el:"#app",
data:{
items:[
{text: "item 1", message: "message 1"},
{text: "item 2", message: "message 2"},
{text: "item 3", message: "message 3"}
]
}
})
And the templates:
<div id="app">
{{items}}
<my-list :items="items">
<template slot="item" scope="props">
<li class="my-fancy-item">{{ props.item.text }}</li>
<div>
<input v-model="props.item.message">
</div>
<p>{{props.item.message}}</p>
</template>
</my-list>
</div>
<template id="my-list-template">
<ul>
<slot name="item"
v-for="item in items"
:item="item">
</slot>
</ul>
</template>
Here is the working example.
Related
I have my parent component using a MyGrid component and slotting in data. Inside MyGrid, I am checking the item type and if it is a certain type, I am giving it a class typeSquare.
In the interest of keeping MyGrid "dumb", is there a way I can check the type and then have MyGrid pass in a prop for the class?
Parent
<MyGrid
:items="items"
columnGap="12"
rowGap="14"
>
<template v-slot="slotProps">
<Cover
v-if="slotProps.typename === 'NewOne'"
:project="slotProps.item.data"
/>
<Cover2 v-else-if="slotProps.typename != 'NewOne'" :project="slotProps.item.data"/>
</template>
</MyGrid>
MyGrid.vue
<template>
<div :class="$style.root">
<div :class="$style.gridContainer">
<template v-for="(item, index) in items">
<div
:key="index"
:class="[{ [$style.gridItem]: true }, { [$style.typeSquare]: item.typename === 'NewOne' }]"
:style="{
padding: `${columnGap}px ${rowGap}px`,
}"
>
<slot :item="item"></slot>
</div>
</template>
</div>
</div>
</template>
If your goal is to only pass items that are of a certain criteria you can filter them out using computed properties. In your script add computed object like so
computed:{
filteredItems(){
return this.items.filter(item=>item.typename === 'NewOne')
}
},
And after in your template you can pass computed property instead of the whole list
<MyGrid
:items="filteredItems"
columnGap="12"
rowGap="14"
>
It is actually a three problems in one:
[vue/no-multiple-template-root]
The template root disallows 'v-for' directives.eslint-plugin-vue
[vue/no-parsing-error]
Parsing error: Expected to be an alias, but got empty.eslint-plugin-vue
[vue/valid-v-for]
Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive.eslint-plugin-vue
Can anyone help me please I am so fed with searching online for it everywhere
enter code
<template>
<div class="post" v-for="post" in posts >
<div><strong>Title</strong>{{post.title}}</div>
<div><strong>Desctiption</strong>{{post.body}}</div>
</div>
</template>
<script>
export default{
data(){
return{
posts:[
{ id: 1, title: 'javascript', body: "the desctiption"},
{ id: 2, title: 'javascript2', body: "the desctiption"},
{ id: 3, title: 'javascript3', body: "the desctiption"},
]
}
}
}
Vue.js must have a single element at the root of the template. If you have av-for directive, when the DOM is populated there will be multiple <div> elements at the root, which Vue does not allow.
So you just need to add another <div> element to surround your v-for div.
Then, move the in posts within your quotes and add a :key
<template>
<div>
<div class="post" v-for="post in posts" :key="post.id">
<div><strong>Title</strong>{{post.title}}</div>
<div><strong>Desctiption</strong>{{post.body}}</div>
</div>
</div>
</template>
In vue two you should one root element, using v-for loop will render multiple elements in the template like :
<div class="post" >
...
</div>
<div class="post" >
...
</div>
to avoid this add an extra div and bind key to the post id :key="post.id":
<template>
<div class="posts">
<div class="post" v-for="post in posts" :key="post.id">
<div><strong>Title</strong>{{post.title}}</div>
<div><strong>Desctiption</strong>{{post.body}}</div>
</div>
</div>
</template>
There's one suggested answer on this, but the solution isn't working for me. I have a nested v-for and would like to animate the innermost li elements as they are removed or added by my computed statement. My current code looks like so:
<transition-group #before-enter="beforeEnter" #enter="enter" #leave="leave" tag="ul" v-if="computedProviders">
<li v-for="(letter, index) in computedProviders" :key="index">
<div>
<p>{{index.toUpperCase()}}</p>
</div>
<transition-group :letter="letter" tag="ul" class="list" #before-enter="beforeEnter" #enter="enter" #leave="leave">
<li v-for="provider in letter" :key="provider.last_name">
<div>
<a :href="provider.permalink">
{{provider.thumbnail}}
</a>
<div>
<h3>
<a :href="provider.permalink">
{{provider.last_name}}, {{provider.first_name}} <span>{{provider.suffix}}</span><br>
<p>{{provider.specialty}}</p>
</a>
</h3>
</div>
</div>
</li>
</transition-group>
</li>
</transition-group>
</div>
The outer transition-group works fine, but when I set up the inner one I get
ReferenceError: letter is not defined.
I tried adding :letter="letter" as suggested here, but it's still not working for me. Any suggestions? I'm happy to reformat the code if there's a way that makes better sense.
Edit: in response to a couple of the comments here, first of all, I'm injecting Vue into a PHP-based Wordpress template, so I'm not able to create separate components. I don't know if that's part of what's causing the issue or why some of you can run the code with no errors.
Here's a sample of the JSON this is iterating over:
{
a: [
{
first_name: 'John',
last_name: 'Apple',
suffix: 'DDS',
permalink: 'www.test.com',
thumbnail: '<img src="test.com" />',
specialty: 'Some specialty'
},
{
first_name: 'Jane',
last_name: 'Apple',
suffix: 'DDS',
permalink: 'www.test.com',
thumbnail: '<img src="test.com" />',
specialty: 'Some specialty'
}
],
d: [
{
first_name: 'John',
last_name: 'Doe',
suffix: 'DDS',
permalink: 'www.test.com',
thumbnail: '<img src="test.com" />',
specialty: 'Some specialty'
},
{
first_name: 'Jane',
last_name: 'Doe',
suffix: 'DDS',
permalink: 'www.test.com',
thumbnail: '<img src="test.com" />',
specialty: 'Some specialty'
}
]
}
One of the caveats with using in-DOM templates is that the browser parses the DOM before Vue gets to it. The browser doesn't know what <transition-group tag="ul"> is, so that just gets ignored. Instead, it sees an <li> inside another <li>. Since <li> elements normally cannot be nested, the browser hoists <li v-for="provider in letter" :key="provider.last_name"> outside its parent <li> that defines letter.
Removing Vue and inspecting the DOM will reveal the problem:
When Vue processes that template, it encounters letter, which is technically undeclared outside of the v-for, resulting in the error you're seeing.
Solution 1: <template> wrapper
If you need to use in-DOM templates, wrap the inner <transition-group> with a <template> so that the browser will ignore it:
<transition-group tag="ul">
<li v-for="(letter, index) in computedProviders" :key="index">
<template> 👈
<transition-group tag="ul">
<li v-for="provider in letter" :key="provider.last_name"></li>
</transition-group>
</template>
</li>
</transition-group>
demo 1
Solution 2: String templates
Move the template into a string (using the template option), which avoids the DOM parsing caveats:
new Vue({
template:
`<transition-group tag="ul">
<li v-for="(letter, index) in computedProviders" :key="index">
<transition-group tag="ul">
<li v-for="provider in letter" :key="provider.last_name"></li>
</transition-group>
</li>
</transition-group>`
//...
}).$mount('#providers')
demo 2
Don't really know what's causing the error, but try creating a new component that accepts letter as a prop and contains your inner <transition-group>. Then embed this new component in your root v-for.
It should give something like that:
<transition-group #before-enter="beforeEnter" #enter="enter" #leave="leave" tag="ul" v-if="computedProviders">
<li v-for="(letter, index) in computedProviders" :key="index">
<div>
<p>{{index.toUpperCase()}}</p>
</div>
<my-component :letter="letter" />
</li>
</transition-group>
I am using vue-bootstrap. I try to create dynamic a grid component that gets headers and datas. Since we will not know how many column passed to the component, we should check every item that are passed.
<template>
<b-table striped hover :items="items"></b-table>
<div v-for="title in items">
<template slot="title.key" slot-scope="data">
<input v-if="title.isActive" type="text" v-model="data.value">
<textarea v-else type="text" v-model="data.value"></textarea>
</template>
</div>
</b-table>
</template>
<script>
const items =[
{'label': 'Description', 'key': 'description'},
{'label': 'Name', 'key': 'name', 'isActive': true},
]
So, if isActive is true, then this template should be textarea(Column should be changed with textarea instead of input) However it is not working and no columns changed neither inputbox nor textarea and stay default template
Could you please help on these question.
Thank you
I think you should separate title in v-for and slot-scope variable to avoid confusing:
<template v-for="title in items" :key="title.key">
<template :slot="title.key" slot-scope="item">
<input v-if="item.isActive" type="text" v-model="item.value">
<textarea v-else type="text" v-model="item.value"></textarea>
</template>
</template>
Since, the isActive property is present inside the object in items array, you need to access it as a property of an iteratee.
So the code becomes:
<div v-for="title in items">
<template slot="{{title.key}}" slot-scope="{title}">
<input v-if="title.isActive" type="text" v-model="title.value">
<textarea v-else type="text" v-model="title.value"></textarea>
</template>
</div>
Thanks to ittus's answer,after minor modification following way is worked.
<template v-for="column in columns" :slot="column.key" slot-scope="item">
<input v-if="item.isSelect" type="text" v-model="item.value">
<textarea v-else type="text" v-model="item.value"></textarea>
</template>
Thank you all.
You can check out this example for dynamic columns.
new Vue({
el: "#app",
data: {
fields: [{
key: "id",
label: "Id",
colType: "span"
}, {
key: "name",
label: "Name",
colType: "button"
}, {
key: "uhh",
label: "uhh..",
colType: "idk"
}],
items: [{
id: 0,
name: "Test 0"
}, {
id: 1,
name: "Test 1"
}, {
id: 2,
name: "Test 2"
}]
}
});
<div id="app">
<b-table :items="items" :fields="fields">
<template v-for="(field, index) in fields" :slot="field.key" slot-scope="data">
<div v-if="field.colType === 'button'">
<h5>{{data.item.name}}</h5>
<b-button>Am Button</b-button>
</div>
<div v-else-if="field.colType === 'span'">
<h5>{{data.item.name}}</h5>
<span>Am Span</span>
</div>
<div v-else>
<h5>{{data.item.name}}</h5>
Am Confused
</div>
</template>
</b-table>
</div>
I didn't get these answers to work. Which actually helped me, was this:
https://forum.vuejs.org/t/how-to-pass-cell-templates-to-a-component-with-b-table/106283
and this fiddle:
https://jsfiddle.net/skirtle/6of9dnpz/
toCellName (slot) {
return `cell(${slot})`
}
The key is that the toCellName returns the correct slot name.
You have to use below snippet.
<input v-if="title.isActive" type="text" v-model="title.value">
<textarea v-else type="text" v-model="title.value"></textarea>
Here is my Vue
<div class="drag">
<h2>List 1 Draggable</h2>
<ul>
<li v-for="category in data">
<draggable v-bind:id="category" v-model="category" :move="checkMove" class="dragArea" :options="{group:'items'}">
<div v-for="item in category" style="border: 3px;">${ item.name }</div>
</draggable>
</li>
</ul>
</div>
</div>
<script>
var vm = new Vue({
el: "#main",
delimiters:['${', '}'],
data: {{ data | tojson | safe }},
methods:{
checkMove: function(evt){
return true }
}
});
I want each of the div in my v-for to have an id. Based on this; https://v2.vuejs.org/v2/guide/list.html
I think I need something like v-bind:key="category.id" in the li tag (the one with the v-for. That makes my div id [Object object][Object object][Object object].
I think this is for each of the items in my category. I would like to add a div id to each category as well as a div id to each item in category. These should be something like "category.name" (the name of the category, so uncategorized, and "item.name" in the item itself. My data object looks like this:
{"data": {"uncategorized": [{"id": 0, "name": ""}, {"id": 1, "name": "first"}, {"id": 2, "name": "another"}]}}
I solved this using v:bind-id or :id for short. Here is my full code:
<ul>
<li class='category-item' v-for="(object,category) in data">
<h1>${ object.name }</h1>
<a style id='add-item-btn' v-bind:href="'/create/'+object.category_id"><img width='10' src="{{ url_for('static',filename='images/add-item.png') }}"/></a>
<draggable :id="'category-' + object.category_id" v-model="object.items" :move="checkMove" class="dragArea" :options="{group:'items'}">
<div class="list-item" v-for="(item,key) in object.items" :id="item.id">
<a v-bind:href="'/edit/'+item.id"> ${ item.name }</a>
</div>
</draggable>
</li>
</ul>
If a data value is {uncategorized: [....]}, you want object v-for, which can give you the value part (the array, here) in the first associated variable and the key ('uncategorized') in the second associated variable. That's the category name you want, if I understand correctly. So maybe:
<li v-for="items, category in data">
<draggable :id="category" :key="category" v-model="category" :move="checkMove" class="dragArea" :options="{group:'items'}">
<div v-for="item in items" :id="item.name" style="border: 3px;">${ item.name }</div>
</draggable>
</li>
That would bind the category name as the id of the draggable, and the item name as the id of the inner div. Binding to key is a hint Vue uses in doing its update magic.
I don't know what you want to use in the v-model, but since you didn't ask about it, I assume you know what to do.