(core) Forms feature

Summary:
A new widget type Forms. For now hidden behind GRIST_EXPERIMENTAL_PLUGINS().
This diff contains all the core moving parts as a serves as a base to extend this functionality
further.

Test Plan: New test added

Reviewers: georgegevoian

Reviewed By: georgegevoian

Subscribers: paulfitz

Differential Revision: https://phab.getgrist.com/D4130
This commit is contained in:
Jarosław Sadziński
2023-12-12 10:58:20 +01:00
parent 337757d0ba
commit a424450cbe
43 changed files with 4023 additions and 133 deletions

View File

@@ -54,6 +54,16 @@ MetaRowModel.prototype._assignColumn = function(colName) {
MetaRowModel.Floater = function(tableModel, rowIdObs) {
this._table = tableModel;
this.rowIdObs = rowIdObs;
// Some tsc error prevents me from adding this at the module level.
// This method is part of the interface of MetaRowModel.
// TODO: Fix the tsc error and move this to the module level.
if (!this.constructor.prototype.getRowId) {
this.constructor.prototype.getRowId = function() {
return this.rowIdObs();
}
}
// Note that ._index isn't supported because it doesn't make sense for a floating row model.
this._underlyingRowModel = this.autoDispose(ko.computed(function() {

View File

@@ -17,7 +17,9 @@ export interface ViewFieldRec extends IRowModel<"_grist_Views_section_field">, R
widthPx: ko.Computed<string>;
column: ko.Computed<ColumnRec>;
origLabel: ko.Computed<string>;
origCol: ko.Computed<ColumnRec>;
pureType: ko.Computed<string>;
colId: ko.Computed<string>;
label: ko.Computed<string>;
description: modelUtil.KoSaveableObservable<string>;
@@ -101,6 +103,9 @@ export interface ViewFieldRec extends IRowModel<"_grist_Views_section_field">, R
// `formatter` formats actual cell values, e.g. a whole list from the display column.
formatter: ko.Computed<BaseFormatter>;
/** Label in FormView. By default FormView uses label, use this to override it. */
question: modelUtil.KoSaveableObservable<string|undefined>;
createValueParser(): (value: string) => any;
// Helper which adds/removes/updates field's displayCol to match the formula.
@@ -111,11 +116,13 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
this.viewSection = refRecord(docModel.viewSections, this.parentId);
this.widthDef = modelUtil.fieldWithDefault(this.width, () => this.viewSection().defaultWidth());
this.widthPx = ko.pureComputed(() => this.widthDef() + 'px');
this.column = refRecord(docModel.columns, this.colRef);
this.origCol = ko.pureComputed(() => this.column().origCol());
this.colId = ko.pureComputed(() => this.column().colId());
this.label = ko.pureComputed(() => this.column().label());
this.widthPx = this.autoDispose(ko.pureComputed(() => this.widthDef() + 'px'));
this.column = this.autoDispose(refRecord(docModel.columns, this.colRef));
this.origCol = this.autoDispose(ko.pureComputed(() => this.column().origCol()));
this.pureType = this.autoDispose(ko.pureComputed(() => this.column().pureType()));
this.colId = this.autoDispose(ko.pureComputed(() => this.column().colId()));
this.label = this.autoDispose(ko.pureComputed(() => this.column().label()));
this.origLabel = this.autoDispose(ko.pureComputed(() => this.origCol().label()));
this.description = modelUtil.savingComputed({
read: () => this.column().description(),
write: (setter, val) => setter(this.column().description, val)
@@ -249,6 +256,7 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
this.headerFontUnderline = this.widgetOptionsJson.prop('headerFontUnderline');
this.headerFontItalic = this.widgetOptionsJson.prop('headerFontItalic');
this.headerFontStrikethrough = this.widgetOptionsJson.prop('headerFontStrikethrough');
this.question = this.widgetOptionsJson.prop('question');
this.documentSettings = ko.pureComputed(() => docModel.docInfoRow.documentSettingsJson());
this.style = ko.pureComputed({

View File

@@ -37,6 +37,7 @@ import defaults = require('lodash/defaults');
export interface InsertColOptions {
colInfo?: ColInfo;
index?: number;
nestInActiveBundle?: boolean;
}
export interface ColInfo {
@@ -54,6 +55,10 @@ export interface NewColInfo {
colRef: number;
}
export interface NewFieldInfo extends NewColInfo {
fieldRef: number;
}
// Represents a section of user views, now also known as a "page widget" (e.g. a view may contain
// a grid section and a chart section).
export interface ViewSectionRec extends IRowModel<"_grist_Views_section">, RuleOwner {
@@ -103,7 +108,7 @@ export interface ViewSectionRec extends IRowModel<"_grist_Views_section">, RuleO
borderWidthPx: ko.Computed<string>;
layoutSpecObj: modelUtil.ObjObservable<any>;
layoutSpecObj: modelUtil.SaveableObjObservable<any>;
_savedFilters: ko.Computed<KoArray<FilterRec>>;
@@ -268,9 +273,11 @@ export interface ViewSectionRec extends IRowModel<"_grist_Views_section">, RuleO
// Saves custom definition (bundles change)
saveCustomDef(): Promise<void>;
insertColumn(colId?: string|null, options?: InsertColOptions): Promise<NewColInfo>;
insertColumn(colId?: string|null, options?: InsertColOptions): Promise<NewFieldInfo>;
showColumn(colRef: number, index?: number): Promise<void>
showColumn(col: number|string, index?: number): Promise<number>
removeField(colRef: number): Promise<void>;
}
export type WidgetMappedColumn = number|number[]|null;
@@ -834,7 +841,7 @@ export function createViewSectionRec(this: ViewSectionRec, docModel: DocModel):
...colInfo,
'_position': parentPos,
}];
let newColInfo: NewColInfo;
let newColInfo: NewFieldInfo;
await docModel.docData.bundleActions('Insert column', async () => {
newColInfo = await docModel.dataTables[this.tableId.peek()].sendTableAction(action);
if (!this.isRaw.peek() && !this.isRecordCard.peek()) {
@@ -843,19 +850,28 @@ export function createViewSectionRec(this: ViewSectionRec, docModel: DocModel):
parentId: this.id.peek(),
parentPos,
};
await docModel.viewFields.sendTableAction(['AddRecord', null, fieldInfo]);
const fieldRef = await docModel.viewFields.sendTableAction(['AddRecord', null, fieldInfo]);
newColInfo.fieldRef = fieldRef;
}
});
}, {nestInActiveBundle: options.nestInActiveBundle});
return newColInfo!;
};
this.showColumn = async (colRef: number, index = this.viewFields().peekLength) => {
this.showColumn = async (col: string|number, index = this.viewFields().peekLength) => {
const parentPos = fieldInsertPositions(this.viewFields(), index, 1)[0];
const colRef = typeof col === 'string'
? this.table().columns().all().find(c => c.colId() === col)?.getRowId()
: col;
const colInfo = {
colRef,
parentId: this.id.peek(),
parentPos,
};
await docModel.viewFields.sendTableAction(['AddRecord', null, colInfo]);
return await docModel.viewFields.sendTableAction(['AddRecord', null, colInfo]);
};
this.removeField = async (fieldRef: number) => {
const action = ['RemoveRecord', fieldRef];
await docModel.viewFields.sendTableAction(action);
};
}

View File

@@ -33,3 +33,7 @@ export function PERMITTED_CUSTOM_WIDGETS(): Observable<string[]> {
}
return G.window.PERMITTED_CUSTOM_WIDGETS;
}
export function GRIST_FORMS_FEATURE() {
return Boolean(getGristConfig().experimentalPlugins);
}