(core) Refactor more value parsing code into common

Summary:
Following discussion in https://phab.getgrist.com/D3164:

- Change createParser to accept docData and one or two metadata row IDs and let it extract the metadata, so it's more easily usable in the server.
- Change ViewFieldRec.valueParser observable to a function createValueParser.

Test Plan: Existing tests.

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D3172
This commit is contained in:
Alex Hall 2021-12-08 00:37:53 +02:00
parent 4164d89b84
commit 6b448567c9
5 changed files with 53 additions and 25 deletions

View File

@ -372,7 +372,7 @@ BaseView.prototype._parsePasteForView = function(data, fields) {
});
const updateColIds = updateCols.map(c => c && c.colId());
const updateColTypes = updateCols.map(c => c && c.type());
const parsers = fields.map(field => field && field.valueParser() || (x => x));
const parsers = fields.map(field => field && field.createValueParser() || (x => x));
const docIdHash = tableUtil.getDocIdHash();
const richData = data.map((col, idx) => {

View File

@ -17,9 +17,6 @@ export class ReferenceUtils {
public readonly isRefList: boolean;
constructor(public readonly field: ViewFieldRec, docData: DocData) {
// Note that this constructor is called inside ViewFieldRec.valueParser, a ko.pureComputed,
// and there are several observables here which get used and become dependencies.
const colType = field.column().type();
const refTableId = getReferencedTableId(colType);
if (!refTableId) {

View File

@ -2,7 +2,6 @@ import {ColumnRec, DocModel, IRowModel, refRecord, ViewSectionRec} from 'app/cli
import * as modelUtil from 'app/client/models/modelUtil';
import * as UserType from 'app/client/widgets/UserType';
import {DocumentSettings} from 'app/common/DocumentSettings';
import {getReferencedTableId, isFullReferencingType} from 'app/common/gristTypes';
import {BaseFormatter, createFormatter} from 'app/common/ValueFormatter';
import {createParser} from 'app/common/ValueParser';
import * as ko from 'knockout';
@ -70,7 +69,7 @@ export interface ViewFieldRec extends IRowModel<"_grist_Views_section_field"> {
documentSettings: ko.PureComputed<DocumentSettings>;
valueParser: ko.Computed<(value: string) => any>;
createValueParser(): (value: string) => any;
// Helper which adds/removes/updates field's displayCol to match the formula.
saveDisplayFormula(formula: string): Promise<void>|undefined;
@ -174,20 +173,10 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
createFormatter(this.column().type(), this.widgetOptionsJson(), this.documentSettings());
};
this.valueParser = ko.pureComputed(() => {
const docSettings = this.documentSettings();
const type = this.column().type();
const widgetOpts = this.widgetOptionsJson();
if (isFullReferencingType(type)) {
const vcol = this.visibleColModel();
widgetOpts.visibleColId = vcol.colId() || 'id';
widgetOpts.visibleColType = vcol.type();
widgetOpts.visibleColWidgetOpts = vcol.widgetOptionsJson();
widgetOpts.tableData = docModel.docData.getTable(getReferencedTableId(type)!);
}
return createParser(type, widgetOpts, docSettings);
});
this.createValueParser = function() {
const fieldRef = this.useColOptions.peek() ? undefined : this.id.peek();
return createParser(docModel.docData, this.colRef.peek(), fieldRef);
};
// The widgetOptions to read and write: either the column's or the field's own.
this._widgetOptionsStr = modelUtil.savingComputed({

View File

@ -76,7 +76,7 @@ export class NTextEditor extends NewBaseEditor {
}
public getCellValue(): CellValue {
const valueParser = this.options.field.valueParser.peek();
const valueParser = this.options.field.createValueParser();
return valueParser(this.getTextValue());
}

View File

@ -1,12 +1,14 @@
import {csvDecodeRow} from 'app/common/csvFormat';
import {DocData} from 'app/common/DocData';
import {DocumentSettings} from 'app/common/DocumentSettings';
import {getReferencedTableId, isFullReferencingType} from 'app/common/gristTypes';
import * as gristTypes from 'app/common/gristTypes';
import * as gutil from 'app/common/gutil';
import {safeJsonParse} from 'app/common/gutil';
import {getCurrency, NumberFormatOptions} from 'app/common/NumberFormat';
import NumberParse from 'app/common/NumberParse';
import {parseDateStrict, parseDateTime} from 'app/common/parseDate';
import {TableData} from 'app/common/TableData';
import {MetaRowRecord, TableData} from 'app/common/TableData';
import {DateFormatOptions, DateTimeFormatOptions, formatDecoded, FormatOptions} from 'app/common/ValueFormatter';
import flatMap = require('lodash/flatMap');
@ -99,7 +101,7 @@ class ChoiceListParser extends ValueParser {
/**
* This is different from other widget options which are simple JSON
* stored on the field. These have to be specially derived
* for referencing columns. See ViewFieldRec.valueParser for an example.
* for referencing columns. See createParser.
*/
interface ReferenceParsingOptions {
visibleColId: string;
@ -116,7 +118,7 @@ export class ReferenceParser extends ValueParser {
protected _visibleColId = this.widgetOpts.visibleColId;
protected _tableData = this.widgetOpts.tableData;
protected _visibleColParser = createParser(
protected _visibleColParser = createParserRaw(
this.widgetOpts.visibleColType,
this.widgetOpts.visibleColWidgetOpts,
this.docSettings,
@ -218,7 +220,7 @@ export const valueParserClasses: { [type: string]: typeof ValueParser } = {
* widgetOpts is usually the field/column's widgetOptions JSON
* but referencing columns need more than that, see ReferenceParsingOptions above.
*/
export function createParser(
export function createParserRaw(
type: string, widgetOpts: FormatOptions, docSettings: DocumentSettings
): (value: string) => any {
const cls = valueParserClasses[gristTypes.extractTypeFromColType(type)];
@ -228,3 +230,43 @@ export function createParser(
}
return value => value;
}
/**
* Returns a function which can parse strings into values appropriate for
* a specific widget field or table column.
*
* Pass fieldRef (a row ID of _grist_Views_section_field) to use the settings of that view field
* instead of the table column.
*/
export function createParser(
docData: DocData,
colRef: number,
fieldRef?: number,
): (value: string) => any {
const columnsTable = docData.getMetaTable('_grist_Tables_column');
const fieldsTable = docData.getMetaTable('_grist_Views_section_field');
const docInfoTable = docData.getMetaTable('_grist_DocInfo');
const col = columnsTable.getRecord(colRef)!;
let fieldOrCol: MetaRowRecord<'_grist_Tables_column' | '_grist_Views_section_field'> = col;
if (fieldRef) {
fieldOrCol = fieldsTable.getRecord(fieldRef) || col;
}
const widgetOpts = safeJsonParse(fieldOrCol.widgetOptions, {});
const type = col.type;
if (isFullReferencingType(type)) {
const vcol = columnsTable.getRecord(fieldOrCol.visibleCol);
widgetOpts.visibleColId = vcol?.colId || 'id';
widgetOpts.visibleColType = vcol?.type;
widgetOpts.visibleColWidgetOpts = safeJsonParse(vcol?.widgetOptions || '', {});
widgetOpts.tableData = docData.getTable(getReferencedTableId(type)!);
}
const docInfo = docInfoTable.getRecord(1);
const docSettings = safeJsonParse(docInfo!.documentSettings, {}) as DocumentSettings;
return createParserRaw(type, widgetOpts, docSettings);
}