Skip to content
Advertisement

Vuejs transition on table rows

I’m trying to have a transition (animation) on html table row (vue.js) with no success. Here’s the full example

  new Vue({
    el: '#data',
    data: {
      items: [
        {
          data: 'd1',
          more: false
        },
        {
          data: 'd2',
          more: false
        },
      ]
    }

  });
.fade-enter-active, .fade-leave-active {
        transition: opacity 2s
      }
      .fade-enter, .fade-leave-to  {
        opacity: 0
      }
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>


    <div class="container-fluid" id="data">
      <br>
      <br>
      <table border="1" class="table table-bordered">
        <thead class="thead-inverse">
          <tr>
            <th>anim</th>
          </tr>
        </thead>
        <tbody>  
        <template v-for="item, k in items">
          <tr>
            <td><button @click="item.more = !item.more" type="button" 
                         v-bind:class="[item.more ? 'btn-danger' : 'btn-primary']" class="btn">Show the hidden row</button></td>
          </tr>

          <transition name="fade" > 
            <tr  v-bind:key="item" v-if="item.more">
              <td><p >{{k + 1}} - {{item.data}}</p></td>
            </tr>
          </transition>

        </template>
        </tbody>
      </table>
    </div>

I am expecting that the hidden table row should appear with transition/animation on opacity property when it appears, but nothing is happening, how am I supposed to do so? This is perfectly working on another element like span or other.

Advertisement

Answer

So, first off I want to point out that had you used a string template, then the code in your question would work as is.

console.clear()
new Vue({
    el: '#data',
    template: `
     <div>
          <br>
      <br>
      <table border="1" class="table table-bordered">
        <thead class="thead-inverse">
          <tr>
            <th>anim</th>
          </tr>
        </thead>
        <tbody>  
        <template v-for="item, k in items">
          <tr>
            <td><button @click="item.more = !item.more" type="button" 
                         v-bind:class="[item.more ? 'btn-danger' : 'btn-primary']" class="btn">Show the hidden row</button></td>
          </tr>

          <transition name="fade" > 
            <tr  v-bind:key="item" v-if="item.more">
              <td><p >{{k + 1}} - {{item.data}}</p></td>
            </tr>
          </transition>

        </template>
        </tbody>
      </table>
      </div>
    `,
    data: {
      items: [
        {
          data: 'd1',
          more: false
        },
        {
          data: 'd2',
          more: false
        },
      ]
    }

  });
.fade-enter-active, .fade-leave-active {
        transition: opacity 2s
      }
      .fade-enter, .fade-leave-to  {
        opacity: 0
      }
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>


    <div class="container-fluid" id="data">

    </div>

Notice, the only change made in this example is that I turned the template for the Vue into a string instead of defining the template in the DOM. The reason this single change works is because when you use an “in DOM” template, you are subject to the browser parsing the HTML prior to Vue converting the template into a render function. When you are working with a table element, browsers are very picky about what kinds of elements they will allow to be rendered inside the table; generally, only elements related to tables (thead, tbody, tr, td, etc.). transition is not an element it gets along with apparently (though, somewhat surprisingly, it doesn’t choke on template).

String templates, however, are never parsed by the browser, they are directly translated into render functions and so they will work as you wrote them. So my first recommendation would be, just use a string template.

If, however you wanted to continue to use an in DOM template, we need to make a few changes to the code. First, we need to move the transition to a place where the browser will be happy. With a table we can easily do that by moving it to the tbody tag and using Vue’s special is directive. Second, because our transition will now apply to multiple elements, we need to switch it to a transition-group.

Because we’re using a transition-group each of the elements inside the transition must have a key. So, for each row, we just add a key for that row.

console.clear()
new Vue({
  el: '#data',
  data: {
    items: [{
        data: 'd1',
        more: false
      },
      {
        data: 'd2',
        more: false
      },
    ]
  }

});
.fade-enter-active,
.fade-leave-active {
  transition: opacity 2s
}

.fade-enter,
.fade-leave-to {
  opacity: 0
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>

<div class="container-fluid" id="data">
  <br>
  <br>
  <table border="1" class="table table-bordered">
    <thead class="thead-inverse">
      <tr>
        <th>anim</th>
      </tr>
    </thead>
    <tbody  name="fade" is="transition-group">
      <template v-for="item, k in items">
          <tr v-bind:key="`button-${item.data}`">
            <td>
              <button @click="item.more = !item.more" 
                      type="button" 
                      v-bind:class="[item.more ? 'btn-danger' : 'btn-primary']" 
                      class="btn">Show the hidden row
              </button>
            </td>
          </tr>
          <tr  v-bind:key="`detail-${item.data}`" v-if="item.more">
            <td><p >{{k + 1}} - {{item.data}}</p></td>
          </tr>
        </template>
    </tbody>
  </table>
</div>
User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement