(core) Barely working reference lists in frontend

Summary:
This makes it possible to set the type of a column to ReferenceList, but the UI is terrible

ReferenceList.ts is a mishmash of ChoiceList and Reference that sort of works but something about the CSS is clearly broken

ReferenceListEditor is just a text editor, you have to type in a JSON array of row IDs. Ignore the value that's present when you start editing. I can maybe try mashing together ReferenceEditor and ChoiceListEditor but it doesn't seem wise.
I think @georgegevoian should take over here. Reviewing the diff as it is to check for obvious issues is probably good but I don't think it's worth trying to land/merge anything.

Test Plan: none

Reviewers: dsagal

Reviewed By: dsagal

Subscribers: georgegevoian

Differential Revision: https://phab.getgrist.com/D2914
This commit is contained in:
Alex Hall
2021-07-23 17:29:35 +02:00
parent 8d68c1c567
commit 04e5d90f86
17 changed files with 228 additions and 41 deletions

View File

@@ -49,7 +49,7 @@ export class ChoiceListCell extends ChoiceTextBox {
}
}
const cssChoiceList = styled('div', `
export const cssChoiceList = styled('div', `
display: flex;
align-items: start;
padding: 0 3px;
@@ -63,7 +63,7 @@ const cssChoiceList = styled('div', `
}
`);
const cssToken = styled('div', `
export const cssToken = styled('div', `
flex: 0 1 auto;
min-width: 0px;
margin: 2px;

View File

@@ -25,7 +25,7 @@ import { NewBaseEditor } from "app/client/widgets/NewBaseEditor";
import * as UserType from 'app/client/widgets/UserType';
import * as UserTypeImpl from 'app/client/widgets/UserTypeImpl';
import * as gristTypes from 'app/common/gristTypes';
import * as gutil from 'app/common/gutil';
import { getReferencedTableId, isFullReferencingType } from 'app/common/gristTypes';
import { CellValue } from 'app/plugin/GristData';
import { Computed, Disposable, fromKo, dom as grainjsDom,
Holder, IDisposable, makeTestId, toKo } from 'grainjs';
@@ -115,15 +115,22 @@ export class FieldBuilder extends Disposable {
return gristTypes.isRightType(this._readOnlyPureType()) || _.constant(false);
}, this);
// Returns a boolean indicating whether the column is type Reference.
// Returns a boolean indicating whether the column is type Reference or ReferenceList.
this._isRef = this.autoDispose(ko.computed(() => {
return gutil.startsWith(this.field.column().type(), 'Ref:');
return isFullReferencingType(this.field.column().type());
}));
// Gives the table ID to which the reference points.
this._refTableId = this.autoDispose(ko.computed({
read: () => gutil.removePrefix(this.field.column().type(), "Ref:"),
write: val => this._setType(`Ref:${val}`)
read: () => getReferencedTableId(this.field.column().type()),
write: val => {
const type = this.field.column().type();
if (type.startsWith('Ref:')) {
void this._setType(`Ref:${val}`);
} else {
void this._setType(`RefList:${val}`);
}
}
}));
this.widget = ko.pureComputed({

View File

@@ -5,7 +5,7 @@ import {colors, testId} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
import {IOptionFull, select} from 'app/client/ui2018/menus';
import {NTextBox} from 'app/client/widgets/NTextBox';
import {isVersions} from 'app/common/gristTypes';
import {isFullReferencingType, isVersions} from 'app/common/gristTypes';
import {BaseFormatter} from 'app/common/ValueFormatter';
import {Computed, dom, styled} from 'grainjs';
import * as ko from 'knockout';
@@ -14,10 +14,11 @@ import * as ko from 'knockout';
* Reference - The widget for displaying references to another table's records.
*/
export class Reference extends NTextBox {
protected _formatValue: Computed<(val: any) => string>;
private _refValueFormatter: ko.Computed<BaseFormatter>;
private _visibleColRef: Computed<number>;
private _validCols: Computed<Array<IOptionFull<number>>>;
private _formatValue: Computed<(val: any) => string>;
constructor(field: ViewFieldRec) {
super(field);
@@ -38,7 +39,7 @@ export class Reference extends NTextBox {
label: use(col.label),
value: col.getRowId(),
icon: 'FieldColumn',
disabled: use(col.type).startsWith('Ref:') || use(col.isTransforming)
disabled: isFullReferencingType(use(col.type)) || use(col.isTransforming)
}))
.concat([{label: 'Row ID', value: 0, icon: 'FieldColumn'}]);
});

View File

@@ -0,0 +1,54 @@
import {DataRowModel} from 'app/client/models/DataRowModel';
import {testId} from 'app/client/ui2018/cssVars';
import {isList} from 'app/common/gristTypes';
import {dom} from 'grainjs';
import {cssChoiceList, cssToken} from "./ChoiceListCell";
import {Reference} from "./Reference";
import {choiceToken} from "./ChoiceToken";
/**
* ReferenceList - The widget for displaying lists of references to another table's records.
*/
export class ReferenceList extends Reference {
public buildDom(row: DataRowModel) {
return cssChoiceList(
dom.cls('field_clip'),
cssChoiceList.cls('-wrap', this.wrapping),
dom.style('justify-content', use => use(this.alignment) === 'right' ? 'flex-end' : use(this.alignment)),
dom.domComputed((use) => {
if (use(row._isAddRow) || this.isDisposed() || use(this.field.displayColModel).isDisposed()) {
// Work around JS errors during certain changes (noticed when visibleCol field gets removed
// for a column using per-field settings).
return null;
}
const value = row.cells[use(use(this.field.displayColModel).colId)];
if (!value) {
return null;
}
const content = use(value);
// if (isVersions(content)) { // TODO
// // We can arrive here if the reference value is unchanged (viewed as a foreign key)
// // but the content of its displayCol has changed. Postponing doing anything about
// // this until we have three-way information for computed columns. For now,
// // just showing one version of the cell. TODO: elaborate.
// return use(this._formatValue)(content[1].local || content[1].parent);
// }
const items = isList(content) ? content.slice(1) : [content];
return items.map(use(this._formatValue));
}, (input) => {
if (!input) {
return null;
}
return input.map(token =>
choiceToken(
String(token),
{}, // default colors
dom.cls(cssToken.className),
testId('ref-list-cell-token')
),
);
}),
);
}
}

View File

@@ -0,0 +1,16 @@
import {NTextEditor} from 'app/client/widgets/NTextEditor';
import {CellValue} from "app/common/DocActions";
/**
* A ReferenceListEditor offers an autocomplete of choices from the referenced table.
*/
export class ReferenceListEditor extends NTextEditor {
public getCellValue(): CellValue {
try {
return ['L', ...JSON.parse(this.textInput.value)];
} catch {
return null; // This is the default value for a reference list column.
}
}
}

View File

@@ -237,6 +237,21 @@ var typeDefs = {
},
default: 'Reference'
},
// RefList: {
// label: 'Reference List',
// icon: 'FieldReference',
// widgets: {
// Reference: {
// cons: 'ReferenceList',
// editCons: 'ReferenceListEditor',
// icon: 'FieldReference',
// options: {
// alignment: 'left'
// }
// }
// },
// default: 'Reference'
// },
Attachments: {
label: 'Attachment',
icon: 'FieldAttachment',

View File

@@ -7,6 +7,8 @@ const UserType = require('./UserType');
const {HyperLinkEditor} = require('./HyperLinkEditor');
const {NTextEditor} = require('./NTextEditor');
const {ReferenceEditor} = require('./ReferenceEditor');
const {ReferenceList} = require('./ReferenceList');
const {ReferenceListEditor} = require('./ReferenceListEditor');
const {HyperLinkTextBox} = require('./HyperLinkTextBox');
const {ChoiceTextBox } = require('./ChoiceTextBox');
const {Reference} = require('./Reference');
@@ -26,6 +28,8 @@ const nameToWidget = {
'Reference': Reference,
'Switch': require('./Switch'),
'ReferenceEditor': ReferenceEditor,
'ReferenceList': ReferenceList,
'ReferenceListEditor': ReferenceListEditor,
'ChoiceTextBox': ChoiceTextBox,
'ChoiceEditor': require('./ChoiceEditor'),
'ChoiceListCell': require('./ChoiceListCell').ChoiceListCell,