How to keep track of parent element in v-for loop? - vue.js

I'm trying to change text inside div elements that contain html tags into actual html. I have a v-for loop which lists all the text items into divs.
<li v-for="item in items">
<div id="description" class="content">{{item.description}}</div>
</li>
The whole text just includes the html element rather than turning it into html which is not what I want.
I thought about pumping it through a function that would call document.innerHTML() on it but Im not sure how to make the parent have a unique ID to call it on. I'd like to keep track of the parent the item is from with either an unique ID or as some sort of parameter.

Answering this for anyone who doesn't read the comments like me :-)
v-html will render an HTML snippet to the page.
The second part of your question involves telling Vue that each element is unique. You do this with a :key. These give Vue's Virtual DOM a unique ID for your element. You can use a unique element in your data for the key or an iteration number.
If you will need to access the ID for something else OUTSIDE of Vue's reactivity, use a Vue ref.
So combining all of that your code becomes:
<li v-for="item in items" :key="item.name" ref="myItems">
<div id="description" class="content" v-html="item.description"></div>
</li>
or with a number for the key
<li v-for="(item, n) in items" :key="n" ref="myItems">
<div id="description" class="content" v-html="item.description"></div>
</li>
Note: when ref's are used in a v-for they produce an array. So in this case myItems.length == items.length and myItems[n] is a unique reference.

Related

VueJS 2 - Re-useable parent component that can accept different children

I have a parent component, SearchComponent:
<template>
<div>
<div class="relative pl-8 pr-10 rounded bg-white border focus-within:bg-white focus-within:ring-1">
<input v-focus #keyup.escape="clearSearch" #keyup="doSearch" v-model="searchTerm"
class="w-full ml-4 h-12 pl-1 text-gray-700 text-lg rounded-full border-0 bg-transparent focus:outline-none placeholder-gray-400"
placeholder="Search..." autocomplete="off">
</div>
<ul class="bg-white mt-4">
<quick-search-item v-for="item in searchResults" :key="item.id" :item-data="item.itemData">
</quick-search-item>
</ul>
</div>
</template>
This is responsible for receiving user input and getting results from an ajax call, handling errors etc. and generating the result list.
What I'd like to do is to make this generic so that instead of having a quick-search-item child component I can pass in different types of child component (like car-search-item, person-search-item etc.) depending on the context of where the user is in the app and what they're searching for
I've read a number of tutorials and I couldn't find quite what I'm trying to do. This may mean I'm approaching this in the wrong way - but if anyone could point me in the right direction, or has a better approach, I'd be very grateful.
Thanks,
Lenny.
I would try to make use of the <slot> element. Check out the documentation here
<parent-component>
<slot></slot>
</parent-component>
Hope this can put you on the right path.
Schalk Pretorius was quite right: slots are the answer to this, specifically scoped slots. I found the Vue docs a little confusing as it refers to getting data from the child component and I wanted to do it the other way around, so as an aide memoire for myself and to help anyone else here's what I did:
In my parent component I defined the slot like this:
<slot name="results" v-bind:items="searchResults">
</slot>
The v-bind binds searchResults (a data item in the parent component) to the value 'items'. 'items' then becomes available in the slot.
In my child component I have a simple property setup called items:
props: {
items: {type: Array},
},
Then to hook it all together in my Blade file I did this:
<search-component endpoint="{{ route('quick_search.index') }}">
<template v-slot:results="props">
<food-results :items="props.items">
</food-results>
</template>
</search-component>
This creates the search-component. Inside that -as I'm using named slots - we need a template and use v-slot to tell it which slot to use (results), then the ="props" exposes all the properties we've defined on that slot (in this case just one, 'items').
Inside the template we put our child component and then we can bind items to props.items which will be the searchResults in our parent component.
I'm happy to have this working and I can now create lots of different results components while reusing the search component - and at least I learnt something today!
Cheers,
Lenny.

vuejs conditional rendering leaving extra html markup

I am new to VueJS and I have a simple HTML markup where I iterate through some objects and render them in html like so:
<div v-for="item in some_counter">
<p v-if="item.some_param1 !== 'None'">
[[ item.some_param2 ]]
</p>
</div>
However, I notice that even when the condition evaluates to false, I see an extra HTML <div></div> markup. This seems very odd to me, coming from the Django world.
How do I avoid this extra markup?
The v-if applies to the element you place it on. So if you want to conditionally include the <div> you need to put the v-if on the <div> (or a parent element). It won't remove the <div> just because it is empty.
Technically you can have both v-for and v-if on the same element but it is generally discouraged as it can be confusing trying to understand which is applied first (see https://v2.vuejs.org/v2/guide/list.html#v-for-with-v-if). Instead you should include a wrapping <template> for the v-for:
<template v-for="item in some_counter">
<div v-if="item.some_param1 !== 'None'">
<p>
[[ item.some_param2 ]]
</p>
</div>
</template>
The <template> tag is special and won't add an extra element to the finished DOM.
An alternative approach would be to filter the list of items in a computed property and then iterate over the filtered list in your template.

Custom elements in iteration require 'v-bind:key' directives

In my Nuxt app I have the following line that triggers the error mentioned in the title of this question:
<template v-for="(project, index) in existingProjects">
<span :key="project.projectId"></span>
I tried to have the :key attribute on the template element and I also tried to use just index as the key, to no avail.
Any idea?
There are multiple ways to solve your problem :
You want to iterate on a template :
You have to put a key on all elements in your template because you can not put a key on a template: <template> cannot be keyed. Place the key on real elements instead.
<template v-for="(project, index) in existingProjects">
<span :key="project.projectId">foo</span>
<div :key="project.projectId">bar</div>
</template>
You can iterate on something else than a template : You just put the key on the parent html tag.
<div v-for="(project, index) in existingProjects" :key="project.projectId">
<span>foo</span>
<div>bar</div>
</div>

Getting Error "Elements in iteration expect to have 'v-bind:key' directives vue/require-v-for-key" in index.vue

I am new to vue.js. I have a simple index.vue which tries to connect to contentful and display the entries from contentful. My code in index.vue looks like this:
<template>
<div>
<!-- render data of the person -->
<h1>{{ person.fields.name }}</h1>
<!-- render blog posts -->
<ul>
<li v-for="post in posts">
{{ post.fields.title }}
</li>
</ul>
</div>
</template>
<script>
import {createClient} from '~/plugins/contentful.js'
const client = createClient()
However when I try localhost:3000 from my browser...it comes back with the following error:
ERROR in ./pages/index.vue
Module Error (from ./node_modules/eslint-loader/index.js):
C:\Users\admin\pdress\pages\index.vue
7:7 error Elements in iteration expect to have 'v-bind:key' directives vue/require-v-for-key
✖ 1 problem (1 error, 0 warnings)
I appreciate if someone can help me out to proceed further with my learning on vue.js please. Thanks in advance
What #ljubadr suggested is right. You need to do this:
<li v-for="post in posts" v-bind:key="post.id">
<!-- OR USE SHORTCUT NOTATION -->
<li v-for="post in posts" :key="post.id">
The reason has to do with performance. Attribute key helps Vue determine unique items in a list. Consider the example of sorting. If your UI has a sort button for posts, then your the order of items in post array will change. But does that mean Vue is going to re-render entire list? Of course not! Using :key it can determine if the item was already rendered on UI. It simply shuffles the DOM nodes and saves expensive rendering cycles.
Secondly, if you have complex components within your list when you are using v-for and :key is not provided, then whenever the list is changed or re-ordered, it simply changes the DOM but doesn't destroy existing components and that can cause local state mismatch. That is why it is must to have :key attribute.
Read Vue.js documentation for further information.
Note: Also remember that using v-for index for :key is a bad idea as it is not unique across your collection.
<template>
<div>
<!-- render data of the person -->
<h1>{{ person.fields.name }}</h1>
<!-- render blog posts -->
<ul>
<li v-for="post in posts" :key = "post">
{{ post.fields.title }}
</li>
</ul>
</div>
</template>
If this is not going to work use any unique field from your object for example if you have id in your object then,
:key = "post.id"
You must define a :key atribute for
v-for directive. And for <transition-group> tag.
for this case v-for you must explicit set,
<li v-for="(post, i) in posts" :key="i + 10">
{{ post.fields.title }}
</li>
If you noticed you can define max two argument in the v-for you must define the item (post) and can define the index of the post.

How to access an element inside a v-for loop using ref

To put as simply as possible:
I have a v-for loop. I'd like to access an specific element inside one of the iterations of the loop. For reasons, I can only do this using ref. Is there a way of doing this?
I have tried various different ways of achieving this, but it always returns undefined. My code works fine outside of a v-for loop.
The documentation for vue doesn't cover this instance.
The loop simplified:
<div v-for="(item, i) in items">
<div ref="card"></div>
</div>
The Method
doThing() {
card = elements.create('card');
card.mount(this.$refs.card);
}
Since array refs are not guaranteed to be in the original order, I've found it more helpful to register a ref on the parent, then use the regular DOM API to find the element you want.
Template
<div ref="cards">
<div v-for="(item, i) in items">
<!-- this is a card -->
</div>
</div>
JavaScript
{
methods: {
getCardAt(index) {
return this.$refs.cards.children[index];
}
}
}
You could try this:
<div v-for="(item, i) in items">
<div :ref="`card-${i}`"></div>
</div>
Refs in a v-for loop become arrays with each element at the index it appears in the document. It is covered here ~ https://v2.vuejs.org/v2/api/#ref
When used on elements/components with v-for, the registered reference will be an Array containing DOM nodes or component instances.
So to "access an specific element inside one of the iterations of the loop", you would use something like
card.mount(this.$refs.card[someIndex])