Repeat.for for different types of input breaks

Hi,

I have input cell inside au-table plugin. Idea is that for boolean and number values, I show input type number and for string values I show input type text

<tbody>
<tr repeat.for="row of displayData">
    <td>
        <div class="table-cell">
            <!-- boolean value -->
            <input if.bind="row.valueType === 1" type="number" min="0" max="1" value.bind="row.value"/>
             <!-- nubmer value -->
            <input if.bind="row.valueType === 2" type="number" value.bind="row.value" />
            <!-- string value -->
            <input if.bind="row.valueType === 3" type="text" value.bind="row.value" />
        </div>
    </td>
</tr>
</tbody>

If I do this and bind existing string values to the input fields, I get lots of “cannot be parsed, or is out of range” messages. Number values work fine.

If I use only input type text. Everything works but I cannot limit number values.

It seems like it has last iterations input type number and tries to bind value before changing input type.

While writing this, I came up some other workarounds that I could try.

But any help or ideas area appreciated

Try making the comparison with == instead ===

Comparison between integers is not the problem here.

Problems seems to appear when sorting or filtering the au-table.
If I change page size to 1000 so that all values are shown at once, no errors appear on console.

However once I start to filtering content “cannot be parsed, or is out of range” appear. So while filtering/sorting au-table is reusing DOM elements and binding order causes attempt to bind text to number type input which fails.

When you switch page, how did you assign the new displayData?

this.displayData = a_new_array;

If you do this.displayData[i] = an_object; in a loop, Aurelia might have problem to observe the change. Your issue sounds like DOM cache edge case in Aurelia: because all the conditions yield to an input, Aurelia can reuse the DOM element, but type and value may having a small timing issue for their change.

Using this.displayData = a_new_array; should force Aurelia to re-render the whole list again.

Another note, html input type number will still save the number in string format, if you want your model to be number instead of string, use a two-way value converter to convert between string and number.

I have not used au-table, it might be an edge case there.

This is what happens within au-table plugin

  /**
   * Applies all the plugins to the display data
   */
  applyPlugins() {
    if (!this.isAttached || !this.data) {
      return;
    }

    let localData = this.getDataCopy();

    if (this.hasFilter()) {
      localData = this.doFilter(localData);
    }

    if ((this.sortKey || this.customSort) && this.sortOrder !== 0) {
      this.doSort(localData);
    }

    this.totalItems = localData.length;

    if (this.hasPagination()) {
      this.beforePagination = [].concat(localData);
      localData = this.doPaginate(localData);
    }

    this.displayData = localData;
  }

So it already does all operations in local copy and then replaces displayData.

I have already tried bunch of workarounds with no success. I think I just have to set all inputs as type=“text” and add aurelia-validation for now.

After reading au-table plugins source code (which is surprisingly short), I think problem might be reproducible with Aurelia repeat.for and button to filter or reorder it. I might do minimal example of this later if I find time.

In fact, I was testing mixed number and test inputs in a repeat with an updating list. Could not reproduce your issue.

Here is a minimal example. I created new minimal project with aurelia-cli (au new). Then changed app with following:

app.html

<template>
    <h1>${message}</h1>
    <button click.delegate="onReorder()">Reorder!</button>
    <hr />
    <table>
        <thead>
            <tr>
                <th>Id</th>
                <th>Name</th>
                <th>Type</th>
                <th>Value</th>
            </tr>
        </thead>
        <tbody>
            <tr repeat.for="row of displayData">
                <td>
                    <div>${row.id}</div>
                </td>
                <td>
                    <div>${row.name}</div>
                </td>
                <td>
                    <div>${row.valueType}</div>
                </td>
                <td>
                    <div>
                        <!-- boolean value -->
                        <input if.bind="row.valueType === 1" type="number" min="0" max="1" value.bind="row.value" />
                        <!-- nubmer value -->
                        <input if.bind="row.valueType === 2" type="number" value.bind="row.value" />
                        <!-- string value -->
                        <input if.bind="row.valueType === 3" type="text" value.bind="row.value" />
                    </div>
                </td>
            </tr>
        </tbody>
    </table>
</template>

app.js

export class App {
    message = 'Hello World!';
    constructor() {
        this.displayData = [];
    }
    attached() {
        // create test data
        let test = [];
        let count = 100;  // OBS! Count needs to be at least 20 for this to fail!
        for (let i = 1; i < count; i++) {
            test.push({ id: 1000 + i, valueType: 1, name: 'Boolean' + i, value: '' + i % 2 });
            test.push({ id: 2000 + i, valueType: 2, name: 'Numberic' + i, value: '' + i });
            test.push({ id: 3000 + i, valueType: 3, name: 'Text' + i, value: 'Text value ' + i });
        }
        this.displayData = test;
    }

    onReorder() {
        // This is the way au-table getDataCopy() does it
        let copy = [].concat(this.displayData);
        // Sort by value type
        copy.sort((a, b) => {
            if (a.valueType < b.valueType) {
                return 1;
            }
            if (a.valueType > b.valueType) {
                return -1;
            }
            return 0;
        });
        this.displayData = copy;
    }
}

Open developer tools in browser and click “Reorder” button. There should be error messages and some of the text inputs should be empty.

I noticed that everything works if table has less than 50 elements or so. This creates 300 rows which should be enough to reproduce the issue.

In my app there are about 1000 rows with pagination, so issue was visible immediately.

I created a gist for you!