[GH #3234] Autocomplete editor not always closing

Tags: #<Tag:0x00007f4527e70838> #<Tag:0x00007f4527e706f8>

I have a table with a variety of cell types and most are working well. However some of the autocomplete cells do not close correctly. They stay open even when the focus has moved to another cell and remain static even if the table is scrolled, as can be seen in this screenshot where I started typing in the Selection Markers column, clicked on gamma in the menu, and then scrolled horizontally:

The autocomplete data source on this (and many of the ones that work correctly) is an external server. The function for that appears to be working properly, as the menu populates and changes correctly. The data for these cells is always text and is rendered correctly. Because the editor never closes, the afterChange function never fires.

The problem is happening in some columns but not others, despite having identical type, source function, and renderer. The only systematic difference is that the ones that work correctly have a data property of the form sourceData.field and the ones that work incorrectly have a data property of the form sourceData.field.subField, but they display the correct text on initial load, so the data is being read correctly.

I haven’t been able to reconstruct this error in a sandbox because I can’t figure out what is different between the things that work and the things that don’t. If there are any particular parts of my configuration that would be helpful to know, I’ll happily provide them. This is running Handsontable 10.0 within Angular 12 using the @handsontable/angular package, also version 10.0

Any ideas of things to check/try would be most appreciated!

Hi @mc17

Those issues are the hardest to identify when they appear randomly.

Did you try to record your screen with an opened console when you looked for those bugs? That helped me a couple of times. That’s an excellent method when the scenario to replicate the bug consists of a couple of steps in the correct order. I guess that is what causes the issue with those editors.

When the editor does not close on another cell selection, usually it cannot process the value underneath. And if that happens in most of the cases, you get some console errors.

Hi Aleksandra,

No console errors or warnings appear here, unfortunately, as that would really help debugging!

I think I may have found the answer and a workaround, in case anyone else runs into this:
For the cells that worked, the data from the original source was a string. For the cells that didn’t work, the data was an array of strings (could be just []). This always displayed reasonably with ['a', 'b', 'c'] rendering as ‘a,b,c’ in the cell, but it was apparently causing problems with the autocomplete.

Looping through the data before passing it to Handsontable and changing all arrays to strings completely solved the issue:

this.dataset.forEach( (rowData, index) => {
  for (const [key, value] of Object.entries(rowData)) {
      if (isArray(value)) {
        let newValue: string;
        if (value.length === 0) {
          newValue = '';
        } else if (value.length === 1) {
          newValue = value[0];
        } else {
          newValue = value.join(', ');
        }
        this.dataset[index][key] = newValue;      
      }
  }
});

Confusingly, using this cell renderer didn’t fix the issue:

stringArrayRenderer = (instance: any, td: HTMLTableCellElement, row: number, col: number, prop: any, value: any, cellProperties: Handsontable.CellProperties): void => {
    let newValue: string;
    if (value.length === 0) {
      newValue = '';
    } else if (value.length === 1) {
      newValue = value[0];
    } else {
      newValue = value.join(', ');
    }
    Handsontable.cellTypes.autocomplete.renderer.apply(this, [instance, td, row, col, prop, newValue, cellProperties]);
}

I would have expected this renderer to fix the issue just as well as cleaning the data beforehand, but it had no effect on the error. So, I think this is a bug, but maybe not within the autocomplete renderer, since a custom one built on it didn’t fix the issue, but at least there’s a pretty straightforward workaround…

I’m glad that you found the cause of the issue.

The renderer is only a visual layer. It does not have any effect on data. the only way it may cause any issues is if it passes anything to the cell editor. But usually, it’s the opposite.

I think I found the real root issue:

In core.js, function populateFromArray lines 917-919:

} else if (orgValue !== null && typeof orgValue === 'object') {
  pushData = false;
}

This check will always set pushData to false if the original value of the cell is an array (even an empty array!). This means that the change does not get passed to setDataAtCell, just a null change. In turn, this empty change causes validateChanges to exit immediately, so no validators get run, which means the postAfterValidate hook doesn’t get called, which means that baseEditor.discardEditor never runs, so the editor stays open and baseEditor stays in a WAITING state.

Using the renderer did not work because, as you pointed out, it just changes the visual and not the underlying data. And orgValue is set using getDataAtCell, so no custom renderers, editors, or validators can be used as a workaround.

I think that this is very much a bug, because Handsontable will happily display string arrays and it looks like they are editable, but they will allow it to get into this broken state. And while I am sure that this code exists to deal with some important case, it seems to me that if the new value is valid it should be able to replace the old value, no matter what the type of the old value is. At the very least, an error or warning should be generated so the user can know to reformat their data!

Hi @mc17

I’ve investigated your recent results and found this topic https://github.com/handsontable/handsontable/issues/3234 where the Author also mentions the following part of the populateFromArray method. In the following subject, you can find the reply from our developer and some ideas for a workaround.