First and foremost, I'd like to mention that if you had utilized a string template, the code in your query would have functioned perfectly 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>
In this example, the only modification made was converting the Vue template into a string instead of declaring it in the DOM. The reason why this alteration works is because when using an "in DOM" template, the browser interprets the HTML before Vue transforms the template into a render function. Browsers are particular about which elements can be rendered inside a table, primarily only elements related to tables such as thead
, tbody
, tr
, td
, etc. It seems that browsers do not handle the transition
element well (although interestingly, they don't encounter issues with template
).
On the other hand, string templates bypass browser parsing and are directly converted into render functions, ensuring they work as intended. So, my suggestion would be to opt for a string template.
If you prefer to continue using an in DOM template, certain modifications are necessary. Firstly, we need to relocate the transition to a suitable location where the browser will accept it. For a table, we can accomplish this by moving it to the tbody
tag and utilizing Vue's special is
directive. Also, since our transition now applies to multiple elements, transitioning to a transition-group
is essential.
Given that we're implementing a transition-group
, every element within the transition must possess a key. Hence, for each row, simply add a unique key.
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>