Andrew C
Andrew C

Reputation: 33

Vuejs bind class to row then later hide/show the row?

I'm trying to create a table where the user can hide rows, and keep them hidden as they delete other rows.

In my html I use vuejs to bind a class when rendering the table:

<tr v-for="item in mylist" :class="{'noFruits': (item.fruits.length == 0)}">

There is a user checkbox to hide rows with that class:

<label><input type="checkbox" v-model="showBlankFruits" @change="setBlankDisplay">Show Blank Fruits</label>

In my Vue instance, the checkbox executes a method to hide/show rows with that class via jquery to attach the css display property:

methods: {
  setBlankDisplay: function() {
    if (this.showBlankFruits) {
     $('.noFruits').css('display', '');
  } else {
     $('.noFruits').css('display', 'none');
  }
},

In my jsfiddle, when a user deletes a row, the hidden row reappears. I see that attaching styles with jquery in this instance is not good... does anyone have a suggestion for a better method?

Upvotes: 3

Views: 4357

Answers (3)

Philip
Philip

Reputation: 3536

You can also write something like this. I've used computed and removed the jQuery part completely. You must declare data as a function instead of an data object (https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function)

You do not need to call the mounted method to set the initial state. It's already set with your data object. In your code, you have to call mounted, because jQuery can only hide the results, when the DOM is loaded.

new Vue({
  el: '#app',
  data() {
    return {
      showBlankFruits: true,
      mylist: [
        {'user': 'Alice', 'fruits': [{'name': 'apple'}]},
        {'user': 'Bob', 'fruits': [{'name': 'orange'}]},
        {'user': 'Charlie', 'fruits': []},
        {'user': 'Denise', 'fruits': [{'name': 'apple'}, {'name': 'orange'}]},
      ]
    }
  },
  computed: {
    list() {
      return this.mylist.filter(item => (item.fruits.length > 0 && !this.showBlankFruits) || (item.fruits.length === 0 && this.showBlankFruits))
    },
  },
  methods: {
    delItem(item) {
      let index = this.mylist.indexOf(item);
      if (index > -1) {
        this.mylist.splice(index, 1);
      }
    }
  }
})
<script src="https://unpkg.com/vue"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

<div id="app">
  <label><input type="checkbox" v-model="showBlankFruits">Show Blank Fruits</label>
  <br> <br>
  <table>
    <tr> <th class="col">User</th> <th class="col">Fruits</th> <th class="col"></th> </tr>
    <tr v-for="item in list">
      <td>{{ item.user }}</td>
      <td> <span v-for="f in item.fruits"> {{ f.name }} </span> </td>
      <td> <button @click="delItem(item)">Delete</button> </td>
    </tr>
  </table>
</div>

Upvotes: 2

Lee Comstock
Lee Comstock

Reputation: 319

Mixing Vue and jQuery is not recommended, as you can do pretty much everything just using Vue and you don't get any conflicting operations that don't know what the other library/framework is doing.

The following will show the row if either the fruits array length is truthy, in other words not 0, or if showBlankFruits is true:

<tr v-for="item in mylist" v-show="item.fruits.length || showBlankFruits">

The following will toggle showBlankFruits when clicking the checkbox:

<label><input type="checkbox" v-model="showBlankFruits">Show Blank Fruits</label>

Full code example:

JSFiddle

Upvotes: 2

cstricklan
cstricklan

Reputation: 661

This is a great example of the power for declarative rendering rather than using DOM manipulation in components. The problem you are running into is when the Vue engine re-renders your list it doesn't know about the manual manipulation of elemets you did in the setBlankDisplay method. The way to get around that is to use component logic in the definition of the view itself, sort of like you did to set the noFruits class in the first place.

So, I propose you get rid of setBlankDisplay and replace it with the method:

itemDisplay(item) {
    if (item.fruits.length === 0 && !this.showBlankFruits) {
    return 'none';
  }
  return '';
},

Then you can reference it in the definition of your tr elements linked to a css display property, like so:

<tr v-for="item in mylist" :class="{'noFruits': (item.fruits.length == 0)}" :style="{display: itemDisplay(item)}">

I've updated the jsfiddle with this modification, showing that the state of hidden fruits remains when other items are deleted.

Take this as a general example of the dangers of using jquery to change the state of the view. Every effort should be taken to define the entire view in terms of component logic.

Upvotes: 1

Related Questions