Get which position a child has in its parent in Vue JS - vue.js

Given I have:
<Parent>
<Child />
<Child /> <!-- this is Child 2 -->
<Child />
<Child /> <!-- this is Child 4 -->
</Parent>
how do I know within the Child component which position it has in its parent. I obviously could pass it down like this:
<Child :position="2" /> but that creates unnecessary markup when there is a computed way to do this.
I want to access within Child its -nth position in the parent.

It depends on why do you need this information: if you need some specific styles for nth component, it's better to use :nth-child or :nth-of-type CSS selectors.
Passing the position number within a prop is also a nice way to do it.
You also may refer to $parent and iterate over its children to find the current element, like [...this.$parent.$el.children].findIndex(child => child === this.$el) (make sure both parent and child have already been mounted), but this code is quite fragile so use it at your own risk.

Related

How to use `v-if` and `v-for` on the same element?

Hi I'm trying to figure out how to use v-if on a iterated element which also uses v-for. I need to check if the current element has any of a series of classes, which are numbers.
so the classes of each article would be:
<article class="post-item post-guide 12 22 19 30 55">...
this is the HTML that renders all:
<article v-if="showIfHasClass" :class="'post-item post-guide ' + guide.categories.toString().replace(/,/g, ' ')"
v-for="(guide, index) in guides" :key="index">
<header>
<h1 class="post-title">
{{ guide.title.rendered}}
</h1>
</header>
</article>
I have tried with methods that check the class of all elements, that works, but i'm trying to use a clean Vue built-in solution with v-if without success, i'm not able to retrieve the class of this in a successful way.
Should showIfHasClass be a computed property? I have tried with that too... but it seems, I'm missing something along the way.
my data I have to check against is an array:
data:{
guides: [...]
selectedCategories: [1, 22, 33, 100, 30];
}
or maybe it is better to directly loop over the guides and check if they have the selectedCategory or not, then remove the element from the guides data array?
What is more effective?
Besides the option to create an additional filtered computed (effectively eliminating the need to use v-for and v-if on the same element), you also have a template level way of dealing with such edge-cases: the <template> tag.
The <template> tag allows you to use arbitrary template logic without actually rendering an extra element. Just remember that, because it doesn't render any element, you have to place the keys from the v-for on the actual elements, like this:
<template v-for="(guide, index) in guides">
<article v-if="isGuideVisible(guide)"
:key="index"
class="post-item post-guide"
:class="[guide.categories.toString().replace(/,/g, ' ')]">
<header>
<h1 v-text="guide.title.rendered" />
</header>
</article>
</template>
isGuideVisible should be a method returning whether the item is rendered, so you don't have to write that logic inside your markup. One advantage of this method is that you can follow your v-if element with a fallback v-else element, should you want to replace the missing items with fallback content. Just remember to also :key="index" the fallback element as well.
Apart from the above use-case, <template> tags come in handy when rendering additional wrapper elements is not an option (would result in invalid HTML markup) (i.e: table > tr > td relations or ol/ul > li relations).
It's mentioned here as "invisible wrapper", but it doesn't have a dedicated section in the docs.
Side note: since you haven't actually shown what's inside guide.categories, I can't advise on it, but there's probably a cleaner way to deal with it than .toString().replace(). If guide.categories is an array of strings, you could simply go: :class="guide.categories".
I think the most Vue way is to create a computed property with filtered items from selected categories, then use that in v-for loop (the idea is to move the business logic away from template).
computed: {
filteredItems(){
return this.guides.filter(e => this.selectedCategories.includes(e.category))
}
}
Also, as a note, it is not recommended to use v-if and v-for on the same element, as it may interfere with the rendering and ordering of loop elements. If you don't want to add another level of nesting, you can loop on a 'template' element.
<template v-for="item in items">
// Note the key is defined on real element, and not on template
<item-element v-if='condition' :key="item.key"></item-element>
</template>

Listen to all child component emitted events in Vue

I have a child component that wraps a 3rd party video component. How do I listen to all events emitted from this child component?
<event-emitter
v-on:play="handleEvent"
v-on:stop="handleEvent"
v-on:ad-pause="handleEvent"
v-on:ad-play="handleEvent"
v-on:video-quartile-25="handleEvent"
v-on:video-quartile-50="handleEvent"
... this could many more lines ...
></event-emitter>
an event could look like this { type: "play", time: 28, ... }
Currently I have this
<event-emitter v-on:emitter-events="handleEvent"></event-emitter>
Inside the <event-emitter> I consolidate all the emitted events as single event, with a type property. There's a draw back now because the handleEvent function is likely to become long switch statement. Is there a means to be more declarative?
E.g
<event-emitter
v-on:listen-to-all-events="normalHander"
v-on:something-unusal-has-happend="aHandlerForThisVariationOfEvent"
></event-emitter>
In the end I just wrote it all inside the parent component, where the <parent> acted as a controller to the rest app. Not declarative as I would hoped — and definitely doesn't answer the question though. I don't think it's possible as described.
That event bus link that #Gander provided, is still useful though.
<parent> <-- I handle the EmitterEvents
<somthing-else />
<event-emitter v-bind:emitter-events="handleEmitterEvents" />
<another-component />
</parent>

how to pass a property from parent template to slot in vuejs

i have a component A which wraps the component B using slot
component-A declaration
<component-A>
<slot></slot>
</component-A>
component-B Usage:
<component-A :some-prop="foo">
<component-B></component-B>
</component-A>
when i pass this prop (someProp) to component-A, is there any way to access it in component-B?
if you want to share information between the father and whatever have in your slot, Vue offers a slot props propertie, something like this:
<slot name="icon-order" :someProp="some-prop" :someProp2="some-prop2">
Once you inject something on component slot, you'll be able to access that propertie like:
<component-A :some-prop="foo">
<component-B slot-scope="slotProps"></component-B>
</component-A>
Then, inside component-B you'll have access to slotProps with every declared propertie set on component-A, just use: slotProps.someProp or slotProps.someProp2.
You can have a better look at Vue documentation:
https://v2.vuejs.org/v2/guide/components-slots.html#Scoped-Slots
hope that helps
If component-B needs to be passed some data, then it must receive it through a prop.
What you're asking is for component-B to also receive foo; just pass it via a prop in the same way you did for component-A, the slotting here makes no difference.
<component-A :some-prop="foo">
<component-B :some-prop="foo"></component-B>
</component-A>
However I suspect this isn't exactly your issue; if not please clarify how the slotting here is relevant.

How does a single file component pass context.data?

I loop functional components in the transition-group, and because I didn't pass a key reference to the root element of the tag component to the tag component.
But how does a single file component pass the context.data?
The sample link
https://codesandbox.io/s/rjjmpvwm4n/
https://github.com/vuejs/vue/issues/7777
The <tag> component needs a key to use as its child <span>'s id.
Since <tag> is a functional component, you will have to access to the data via the data. prefix.
So, since you are using <tag v-for="item in list" :key="item"></tag>, inside the <tag>'s template, you can access the key (of context.data) automatically using data.key:
Add :key="data.key" in tag.vue:
<template functional>
<span :key="data.key">tag content</span>
</template>
Demo CodeSandbox: https://codesandbox.io/s/wxmvxnojl?module=%2Fsrc%2Fcomponents%2Ftag.vue

Keep list components alive in Vue

I have a list of components that I render using v-for. Not all the components are shown simultaneously. I page the the array of rendered components by using slice.
These components shouldn't be rerendered, as some of them have user inputted data and some of them do network related tasks.
I tried to use <keep-alive>. However, this renders only the first component.
<keep-alive>
<component :is="component.type" :key="component.id" v-for="component in components">
</keep-alive>
How do I keep a dynamic list of components alive?
<div v-for="comp in compList" :key="'container'+comp.keyId" >
<keep-alive>
<component :is="comp.type" :key="comp.keyId"></component>
</keep-alive>
</div>
this above works for me . Pushing elements to compList correctly creates new instances of their respective components. Moving an element's index within the array , maintaining key, does not call destroy/create and maintains state within each component
Tested the answer above in a fiddle and doesn't work. https://jsfiddle.net/z11fe07p/680/
<div v-for="component in myComponents" :key="component.id" >
<keep-alive>
<component :is="component.type"
:name="component.name">
</component>
</keep-alive>
</div>
Also i would avoid using vue reserved words such as components because there is a components key in the vue instance which tells what components the instance is using.
I read source code of Vue's <keep-alive>, and I created new Component which works very well with list.
package name is vue-keep-alive-global. Here is a link, https://www.npmjs.com/package/vue-keep-alive-global
How to use :
<KeepAliveGlobal>
<YourComponent
:key="uniqueKey"
/>
</KeepAliveGlobal>
With Array,
<template v-for="(item, index) of array">
<KeepAliveGlobal :key="`blah-blah-${index}`">
<YourComponent
:item="item"
:key="`your-component-${index}`"
/>
</KeepAliveGlobal>
</template>
KeepAliveGlobal will cache component by key.
There's note at Vue docs about your use case
Note, <keep-alive> is designed for the case where it has one direct
child component that is being toggled. It does not work if you have
v-for inside it. When there are multiple conditional children, as
above, ` requires that only one child is rendered at a
time.
https://v2.vuejs.org/v2/api/#keep-alive
Try v-once directive instead.
https://v2.vuejs.org/v2/api/#v-once