Showing truncated text only on hover with Vue

2019-02-27 21:31发布

I tried it like this:

<template>
  ...
  <b-card-group deck v-for="row in formattedClubs">
    <b-card v-for="club in row"
            img-src="http://placehold.it/130?text=No-image"
            img-alt="Img"
            img-top>
      <h4 class="card-title"
          @mouseover="showAll = true"
          @mouseout="showAll = false">
        {{getWord(club.description)}}
      </h4>
      <p class="card-text">
          {{club.price}}
      </p>
      <p class="card-text">
          {{club.country}}
      </p>
      <div slot="footer">
          <b-btn variant="primary" block>Add</b-btn>
      </div>
    </b-card>
  </b-card-group>
  ...
</template>

<script>
export default {
  data () {
    return {
      showAll: false,
      clubs: [
        {id:1, description:'chelsea is the best club in the world and chelsea has a great player', price:1000, country:'england'},
        {id:2, description:'liverpool has salah', price:900, country:'england'},
        {id:3, description:'mu fans', price:800, country:'england'},
        {id:4, description:'city has a great coach. Thas is guardiola', price:700, country:'england'},
        {id:5, description:'arsenal player', price:600, country:'england'},
        {id:6, description:'tottenham in london', price:500, country:'england'},
        {id:7, description:'juventus stadium', price:400, country:'italy'},
        {id:8, description:'madrid sell ronaldo', price:300, country:'spain'},
        {id:9, description:'barcelona in the spain', price:200, country:'spain'},
        {id:10, description:'psg buys neymar at a fantastic price', price:100, country:'france'}
      ]
    }
  },
  computed: {
    formattedClubs () {
      return this.clubs.reduce((c, n, i) => {
        if (i % 4 === 0) c.push([]);
        c[c.length - 1].push(n);
        return c;
      }, []);
    }
  },
  methods: {
    getWord (desc) {
      if (this.showAll) return desc

      let value = desc;
      let length = 30;
      if (value.length <= length) {
        return value;
      } else {
        return value.substring(0, length) + '...';
      }
    }
  }
}
</script>

That almost works. But when I hover over the description in box 1, the description on all the other boxes also hover. It should only hover showing the truncated text on the box 1.

How can I solve this problem?

3条回答
老娘就宠你
2楼-- · 2019-02-27 21:57

You can create an array of boolean where each value corresponds to a team.

let formattedClubs= [{name: "team1", description: "desc team1"}, {name: "team2", description: "desc team2"}];
let showDescription = Array.from(formattedClubs, show => false);

You have the initial team array. You can create an array of the same size with values initialised to false.

In your template

<b-card-group deck deck v-for="(row, index) in formattedClubs">

Now you can match a team in the array formattedClubs[index] with a value in showDescription[index]

@mouseover="showDescription[index] = true" @mouseout="showDescription[index] = false"

Same in your events.

查看更多
Anthone
3楼-- · 2019-02-27 22:12

Try using key attribute for each item. If you set a mouseover for showAll, it will certainly display all the descriptions as it will return true for all. That's why, you should do dynamic list rendering here that Vue supports, similarly like this:

<div v-for="club in row" :key="club.id">

Also, I recommend you to have a look at the official documentation about dynamic list rendering :

https://vuejs.org/v2/guide/list.html

查看更多
Bombasti
4楼-- · 2019-02-27 22:15

The problem is that you have only one property to control the truncation of all items.

Firstly, you need to ensure that each club has its own boolean to control the text truncation. Lets use your already existing computed property to add a new reactive property for each club:

formattedClubs () {
  return this.clubs.reduce((c, n, i) => {
    if (i % 4 === 0) c.push([]);
    c[c.length - 1].push(n);
    this.$set(n, 'truncate', true); // Here we add the new reactive property.
    return c;
  }, []);
}

Secondly, let's use the <template> to handle visual things, keeping the right separation of concerns, using the new individual club.truncate property with a v-if/v-else block:

<h4 class="card-title"
    @mouseenter="club.truncate = false"
    @mouseleave="club.truncate = true">
  <template v-if="club.truncate">{{trucateText(club.description)}}</template>
  <template v-else>{{club.description}}</template>
</h4>

And now, the trucateText method only needs to care about returning the truncated text, since it's only called if we're truncating a description of one club:

methods: {
  trucateText (value) {
    const length = 30;
    return value.length <= length ?
      value : value.substring(0, length) + "...";
  }
}

Take a look at the fully working code here if any doubts persists.

查看更多
登录 后发表回答