3

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/

Hendrik
  • 33
  • 6

2 Answers2

0

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.

tony19
  • 125,647
  • 18
  • 229
  • 307
Aletheios
  • 3,960
  • 2
  • 33
  • 46
  • Ahhhh Thank you very much. :-) I also found out that if I remove the second `template` and put the `v-else` into the `tr` directly, it seems to work as well. – Hendrik Nov 14 '16 at 06:42
  • @Hendrik I suppose the `v-else` directly on the `td` also forces Vue to recreate the elements rather than reuse them. – Aletheios Nov 14 '16 at 11:43
0

The problem is probably related to the way you are assigning data to an object using this.renderings = {...}

To assign object properties you should either use Vue.set(this.renderings, key, value) for each new value, or this.renderings = Object.assign({}, this.renderings, newData)

This may work perfectly or not depending on how much nested is the data you are using, you can find more info about it here: https://v2.vuejs.org/v2/guide/reactivity.html

tony19
  • 125,647
  • 18
  • 229
  • 307
TiagoLr
  • 2,782
  • 22
  • 16