gristlabs_grist-core/app/client/models/ColumnACIndexes.ts
Alex Hall c470c4041b (core) Use visibleCol instead of displayCol with createFormatter
Summary:
Some things (like rendering cells) use the `visibleCol` for `createFormatter`, while other things (like `CopySelection`) used the `displayCol`. For references, the display column has type Any and doesn't know about the original formatting. This resulted in formatting being lost when copying from reference columns even though formatting was preserved when copying from the original (visible) column which looked identical. This diff fixes this and ensures that `createFormatter` is always used with the `visibleCol`. This was agreed on in https://grist.slack.com/archives/C0234CPPXPA/p1639571321043000

Additionally:

- Replaces the functions `createVisibleColFormatter` computed properties `visibleColFormatter` as suggested by a `TODO`.
- Extracts common code from `createVisibleColFormatter` in `ColumnRec` and `ViewFieldRec`

Test Plan: Fixed a test in CopyPaste which displayed the previous inconsistent behaviour.

Reviewers: jarek

Reviewed By: jarek

Differential Revision: https://phab.getgrist.com/D3189
2021-12-16 22:19:36 +02:00

61 lines
2.3 KiB
TypeScript

/**
* Implements a cache of ACIndex objects for columns in Grist table.
*
* The getColACIndex() function returns the corresponding ACIndex, building it if needed and
* caching for subsequent calls. Any change to the column or a value in it invalidates the cache.
*
* It is available as tableData.columnACIndexes.
*
* It is currently used for auto-complete in the ReferenceEditor and ReferenceListEditor widgets.
*/
import {ACIndex, ACIndexImpl} from 'app/client/lib/ACIndex';
import {ColumnCache} from 'app/client/models/ColumnCache';
import {UserError} from 'app/client/models/errors';
import {TableData} from 'app/client/models/TableData';
import {localeCompare, nativeCompare} from 'app/common/gutil';
import {BaseFormatter} from 'app/common/ValueFormatter';
export interface ICellItem {
rowId: number|'new';
text: string; // Formatted cell text.
cleanText: string; // Trimmed lowercase text for searching.
}
export class ColumnACIndexes {
private _columnCache = new ColumnCache<ACIndex<ICellItem>>(this._tableData);
constructor(private _tableData: TableData) {}
/**
* Returns the column index for the given column, using a cached one if available.
* The formatter should be created using field.visibleColFormatter(). It's assumed that
* getColACIndex() is called for the same column with the the same formatter.
*/
public getColACIndex(colId: string, formatter: BaseFormatter): ACIndex<ICellItem> {
return this._columnCache.getValue(colId, () => this._buildColACIndex(colId, formatter));
}
private _buildColACIndex(colId: string, formatter: BaseFormatter): ACIndex<ICellItem> {
const rowIds = this._tableData.getRowIds();
const valColumn = this._tableData.getColValues(colId);
if (!valColumn) {
throw new UserError(`Invalid column ${this._tableData.tableId}.${colId}`);
}
const items: ICellItem[] = valColumn.map((val, i) => {
const rowId = rowIds[i];
const text = formatter.formatAny(val);
const cleanText = text.trim().toLowerCase();
return {rowId, text, cleanText};
});
items.sort(itemCompare);
return new ACIndexImpl(items);
}
}
function itemCompare(a: ICellItem, b: ICellItem) {
return localeCompare(a.cleanText, b.cleanText) ||
localeCompare(a.text, b.text) ||
nativeCompare(a.rowId, b.rowId);
}