LovelyAndy
LovelyAndy

Reputation: 891

How to give a dynamically rendered element it's own data value & target it in it's parent component?

I have a BaseMenuItem component that has normal button elements as well as special news elements. I have added a ticker effect to the news type els and want to stop the ticker on that element when it's clicked. Currently the click event stops the ticker effect on the whole group.

How can I target a single element from that group?

There are two methods, openNews one showing the specific news article that the element is linked to. And clearItemType that clears the itemType upon recieving the emitted event from the BaseMenuItem component.

I'm just not sure which element to target to change it's itemType. Does Vuejs have a way to make an unique data value for dynamically generated elements?

If you need anymore information please let me know!

Cheers!

BaseMenuItem

<template>
 <q-btn align="left" dense flat class="main-menu-item" v-on="$listeners">
    <div class="flex no-wrap items-center full-width">
      <iconz v-if="iconz" :name="iconz" type="pop" color="black" class="mr-md" />
      <q-icon v-if="menuIcon" :name="menuIcon" class="text-black mr-md" />
      <div @click="$emit('stop-ticker')" v-if="itemType === 'news'" class="ellipsis _ticker">
        <div class="ellipsis _ticker-item">{{ title }}</div>
      </div>
      <div v-else>
        <div class="ellipsis">{{ title }}</div>
      </div>
      <slot>
        <div class="ml-auto"></div>
        <div class="_subtitle mr-md" v-if="subtitle">{{ subtitle }}</div>
        <q-icon name="keyboard_arrow_right" class="_right-side" />
        <ComingSoon v-if="comingSoonShow" />
      </slot>
    </div>
  </q-btn>
</template>


<style lang="sass" scoped>
// $
.main-menu-item
  display: block
  font-size: 15px
  position: relative
  width: 100%
  border-bottom: 1px solid #F5F5F5
  +py(10px)
  ._left-side
    color: #000000
  ._subtitle
    margin-left: auto
    opacity: 0.7

._ticker
  position: absolute
  font-weight: bold
  margin-left: 2em
  width: 82%
  &-item
    display: inline-block
    padding-left: 100%
    animation: ticker 8s linear infinite

@keyframes ticker
  to
    transform: translateX(-100%)
</style>

<script>
import { iconz } from 'vue-iconz'

export default {
  name: 'MainMenuItem',
  components: { iconz },
  props: {
    comingSoonShow: { type: Boolean, default: false },
    title: { type: String, default: 'menu' },
    subtitle: { type: String, default: '' },
    menuIcon: { type: String, default: '' },
    iconz: { type: String, default: '' },
    itemType: { type: String, default: '' },
  }
}
</script>

MainMenuPage

<template>
  <div class="eachMenuGroup" v-if="newsList.length">
          <MainMenuItem
            v-for="news in newsList"
            :key="news.id"
            @click="openNews(news)"
            :title="news.title"
            :itemType="itemType"
            :class="{ readLink: readNewsList[news.id] }"
            menuIcon="contactless"
            @stop-ticker="clearItemType"
          ></MainMenuItem>
   </div>
</template>
<style lang="sass" scoped>
.readLink
  font-weight: 500
</style>

<script>
 methods: {
    openNews(postInfo) {
      dbAuthUser().merge({ seenNewsPosts: { [postInfo.id]: true } })
      Browser.open({ url: postInfo.url, presentationStyle: 'popover' })
    },
    clearItemType() {
      this.itemType = ''
      return
    },
</script>

Upvotes: 0

Views: 197

Answers (1)

Pierre_T
Pierre_T

Reputation: 1302

EDIT: I edited since your latest comment. See below

To target the exact element within your v-for that fired the event, you can use $refs by using an index :

<MainMenuItem v-for="(item, index) in items" :ref="`menuItem--${index}`" @stop-ticker="clearItemType(index)" />

In your method:

clearItemType(index){
   console.log(this.$refs[`menuItem--${index}`])
// this is the DOM el that fired the event

}

Edited version after your comments:

If you pass the exact same prop to each el of your v-for you wont be able to modify its value just for one child in the parent context.

Instead, keep an array of distinctive values in your parent. Each child will receive one specific prop that you can change accordingly in the parent either by listening to an child emitted event or by passing index as click event as I've done below for simplicity.

See snippet below

Vue.config.productionTip = false;

const Item = {
  name: 'Item',
  props: ['name', 'isActive'],
  template: `<div>I am clicked {{ isActive }}!</div>`,
};

const App = new Vue({
  el: '#root',
  components: {
    Item
  },
  data: {
    items: ["item1", "item2"],
    activeItemTypes: []
  },
  methods: {
    toggle(index) {
      this.activeItemTypes = this.activeItemTypes.includes(index) ? this.activeItemTypes.filter(i => i !== index) : [...this.activeItemTypes, index]
    }
  },
  template: `
    <div>
      <Item v-for="(item, i) in items" :name="item"  @click.native="toggle(i)" :isActive="activeItemTypes.includes(i)"/>
    </div>
  `,
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="root"></div>

Upvotes: 1

Related Questions