From 6b448567c9878102ad75df6f6ee8cf0802e06bf2 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Wed, 8 Dec 2021 00:37:53 +0200 Subject: [PATCH] (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 --- app/client/components/BaseView.js | 2 +- app/client/lib/ReferenceUtils.ts | 3 -- app/client/models/entities/ViewFieldRec.ts | 21 +++------ app/client/widgets/NTextEditor.ts | 2 +- app/common/ValueParser.ts | 50 ++++++++++++++++++++-- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/app/client/components/BaseView.js b/app/client/components/BaseView.js index c089126d..249af265 100644 --- a/app/client/components/BaseView.js +++ b/app/client/components/BaseView.js @@ -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) => { diff --git a/app/client/lib/ReferenceUtils.ts b/app/client/lib/ReferenceUtils.ts index e811b701..84410fd4 100644 --- a/app/client/lib/ReferenceUtils.ts +++ b/app/client/lib/ReferenceUtils.ts @@ -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) { diff --git a/app/client/models/entities/ViewFieldRec.ts b/app/client/models/entities/ViewFieldRec.ts index 0fc9de17..5772e3ae 100644 --- a/app/client/models/entities/ViewFieldRec.ts +++ b/app/client/models/entities/ViewFieldRec.ts @@ -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; - 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|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({ diff --git a/app/client/widgets/NTextEditor.ts b/app/client/widgets/NTextEditor.ts index 096ff35b..574c33c4 100644 --- a/app/client/widgets/NTextEditor.ts +++ b/app/client/widgets/NTextEditor.ts @@ -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()); } diff --git a/app/common/ValueParser.ts b/app/common/ValueParser.ts index e4169167..df9961f9 100644 --- a/app/common/ValueParser.ts +++ b/app/common/ValueParser.ts @@ -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); +}