vue-resourcevue.jsvuejs2

VueJS 2 re-rendering Table


I've got a problem with VueJS rendering. I'm fetching data per ajax. On initial rendering the table rows are rendered correct, but on re-rendering an updated element, it doesn't get rendered correctly.

Here's a picture: Rendered Table

As you can see, row #1 is rendered right. But in row #2, which is re-rendered, the tds are inverted.

Here's my code:

var renderings = new Vue({
el: '#renderings',

data: function () {
    return {
        hasUnprocessedRenderings: false,
        renderings: {}
    };
},

methods: {
    clearData: function () {
        this.renderings = {};
    },

    updateData: function () {

        this.$http.get('http://some-url.com/renderings').then(function (response) {
            this.renderings = response.body.renderings;
            this.hasUnprocessedRenderings = response.body.hasUnprocessedRenderings;

            if (this.hasUnprocessedRenderings) {
                setTimeout(this.updateData, 10000);
            }
        });

    }
},

mounted: function() {
    this.updateData();
}

});

and my html:

<div id="renderings">
<table class="table table-striped table-hover table-bordered" style="margin bottom: 0;">
<thead>
<tr>
    <th>Personalisierung</th>
    <th>Format</th>
    <th>Download</th>
    <th></th>
</tr>
</thead>
<tbody>
<template v-for="rendering in renderings">
    <tr>
        <td>{{rendering.personalizationName}}</td>
        <td>{{rendering.renderTypeName}}</td>
        <template v-if="rendering.DOCUMENT_URL">
            <td><a :href="rendering.DOCUMENT_URL">{{rendering.DOCUMENT_URL}}</a></td>
            <td>
                <ul class="list-inline" style="margin-bottom: 0;">
                    <li><a :href="'http://some-url.com?pid='+rendering.PROCESS_ID" title="refresh"><i class="fa fa-refresh" aria-hidden="true"></i></a></li>
                    <li><a :href="'http://some-url.com?pid='+rendering.PROCESS_ID" title="delete"><i class="fa fa-trash-o" aria-hidden="true"></i></a></li>
                </ul>
            </td>
        </template>
        <template v-else>
            <td colspan="2" style="text-align: center;">
                <i class="fa fa-cog fa-spin fa-fw"></i>
                <span>In Bearbeitung..</span>
            </td>
        </template>
    </tr>
</template>
</tbody>
</table>
</div>

So my question: How can I fix, that the row is getting re-rendered correctly?

Thanks for any help. :-)

EDIT #1: Here's a jsfiddle, where the problem also occurs: https://jsfiddle.net/dt1kt06g/


Solution

  • It seems as if this behavior is a side effect of the optimizations in Vue's virtual DOM algorithm. When an update occurs, Vue tries to minimize element movement and instead reuses and patches existing DOM nodes.

    When you inspect the generated HTML in your JSFiddle after the data update, you'll notice that the last <td> in the second <tr> still has the attributes colspan="2" style="text-align: center;", which gives us a hint that this <td> element has been incorrectly reused from the previously rendered "In Progress" cell in v-else. (I'm not sure if this could be characterized as a bug in Vue...)

    Anyways, there's a workaround for this issue: Vue has the special key attribute which gives the node diffing algorithm a hint on how to handle the elements (see documentation). In your example, adding a key to the <td> in the v-else branch suffices:

    <template v-else>
        <td colspan="2" style="text-align: center;" :key="rendering.PROCESS_ID">
        ...
    

    Here`s a fixed version of your JSFiddle.