mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Adding UI for reverse columns
Summary: - Adding an UI for two-way reference column. - Reusing table name as label for the reverse column Test Plan: Updated Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D4344
This commit is contained in:
@@ -63,6 +63,11 @@ export interface ColumnRec extends IRowModel<"_grist_Tables_column"> {
|
||||
displayColModel: ko.Computed<ColumnRec>;
|
||||
visibleColModel: ko.Computed<ColumnRec>;
|
||||
|
||||
// Reverse Ref/RefList column for this column. Only for Ref/RefList columns in two-way relations.
|
||||
reverseColModel: ko.Computed<ColumnRec>;
|
||||
// If this column has a relation.
|
||||
hasReverse: ko.Computed<boolean>;
|
||||
|
||||
disableModifyBase: ko.Computed<boolean>; // True if column config can't be modified (name, type, etc.)
|
||||
disableModify: ko.Computed<boolean>; // True if column can't be modified (is summary) or is being transformed.
|
||||
disableEditData: ko.Computed<boolean>; // True to disable editing of the data in this column.
|
||||
@@ -94,6 +99,12 @@ export interface ColumnRec extends IRowModel<"_grist_Tables_column"> {
|
||||
saveDisplayFormula(formula: string): Promise<void>|undefined;
|
||||
|
||||
createValueParser(): (value: string) => any;
|
||||
|
||||
/** Helper method to add a reverse column (only for Ref/RefList) */
|
||||
addReverseColumn(): Promise<void>;
|
||||
|
||||
/** Helper method to remove a reverse column (only for Ref/RefList) */
|
||||
removeReverseColumn(): Promise<void>;
|
||||
}
|
||||
|
||||
export function createColumnRec(this: ColumnRec, docModel: DocModel): void {
|
||||
@@ -138,6 +149,8 @@ export function createColumnRec(this: ColumnRec, docModel: DocModel): void {
|
||||
|
||||
// The display column to use for the column, or the column itself when no displayCol is set.
|
||||
this.displayColModel = refRecord(docModel.columns, this.displayColRef);
|
||||
this.reverseColModel = refRecord(docModel.columns, this.reverseCol);
|
||||
this.hasReverse = this.autoDispose(ko.pureComputed(() => Boolean(this.reverseColModel().id())));
|
||||
this.visibleColModel = refRecord(docModel.columns, this.visibleCol);
|
||||
|
||||
this.disableModifyBase = ko.pureComputed(() => Boolean(this.summarySourceCol()));
|
||||
@@ -184,6 +197,21 @@ export function createColumnRec(this: ColumnRec, docModel: DocModel): void {
|
||||
}
|
||||
return JSON.stringify(options);
|
||||
});
|
||||
|
||||
this.addReverseColumn = () => {
|
||||
return docModel.docData.sendAction(['AddReverseColumn', this.table.peek().tableId.peek(), this.colId.peek()]);
|
||||
};
|
||||
|
||||
this.removeReverseColumn = async () => {
|
||||
if (!this.hasReverse.peek()) {
|
||||
throw new Error("Column does not have a reverse column");
|
||||
}
|
||||
// Remove the other column. Data engine will take care of removing the relation.
|
||||
const column = this.reverseColModel.peek();
|
||||
const tableId = column.table.peek().tableId.peek();
|
||||
const colId = column.colId.peek();
|
||||
return await docModel.docData.sendAction(['RemoveColumn', tableId, colId]);
|
||||
};
|
||||
}
|
||||
|
||||
export function formatterForRec(
|
||||
|
||||
@@ -129,11 +129,10 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
|
||||
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({
|
||||
this.description = this.autoDispose(modelUtil.savingComputed({
|
||||
read: () => this.column().description(),
|
||||
write: (setter, val) => setter(this.column().description, val)
|
||||
});
|
||||
|
||||
}));
|
||||
// displayLabel displays label by default but switches to the more helpful colId whenever a
|
||||
// formula field in the view is being edited.
|
||||
this.displayLabel = modelUtil.savingComputed({
|
||||
@@ -143,17 +142,17 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
|
||||
|
||||
// The field knows when we are editing a formula, so that all rows can reflect that.
|
||||
const _editingFormula = ko.observable(false);
|
||||
this.editingFormula = ko.pureComputed({
|
||||
this.editingFormula = this.autoDispose(ko.pureComputed({
|
||||
read: () => _editingFormula(),
|
||||
write: val => {
|
||||
// Whenever any view field changes its editingFormula status, let the docModel know.
|
||||
docModel.editingFormula(val);
|
||||
_editingFormula(val);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
// CSS class to add to formula cells, incl. to show that we are editing this field's formula.
|
||||
this.formulaCssClass = ko.pureComputed<string|null>(() => {
|
||||
this.formulaCssClass = this.autoDispose(ko.pureComputed<string|null>(() => {
|
||||
const col = this.column();
|
||||
|
||||
// If the current column is transforming, assign the CSS class "transform_field"
|
||||
@@ -175,7 +174,7 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
// The fields's display column
|
||||
this._displayColModel = refRecord(docModel.columns, this.displayCol);
|
||||
@@ -203,10 +202,10 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
|
||||
// Display col ref to use for the field, defaulting to the plain column itself.
|
||||
this.displayColRef = this.autoDispose(ko.pureComputed(() => this._fieldOrColumn().displayCol() || this.colRef()));
|
||||
|
||||
this.visibleColRef = modelUtil.addSaveInterface(ko.pureComputed({
|
||||
this.visibleColRef = modelUtil.addSaveInterface(this.autoDispose(ko.pureComputed({
|
||||
read: () => this._fieldOrColumn().visibleCol(),
|
||||
write: (colRef) => this._fieldOrColumn().visibleCol(colRef),
|
||||
}),
|
||||
})),
|
||||
colRef => docModel.docData.bundleActions(null, async () => {
|
||||
const col = docModel.columns.getRowModel(colRef);
|
||||
await Promise.all([
|
||||
@@ -222,9 +221,13 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
|
||||
|
||||
// Helper for Reference/ReferenceList columns, which returns a formatter according to the visibleCol
|
||||
// associated with this field. If no visible column available, return formatting for the field itself.
|
||||
this.visibleColFormatter = ko.pureComputed(() => formatterForRec(this, this.column(), docModel, 'vcol'));
|
||||
this.visibleColFormatter = this.autoDispose(
|
||||
ko.pureComputed(() => formatterForRec(this, this.column(), docModel, 'vcol'))
|
||||
);
|
||||
|
||||
this.formatter = ko.pureComputed(() => formatterForRec(this, this.column(), docModel, 'full'));
|
||||
this.formatter = this.autoDispose(
|
||||
ko.pureComputed(() => formatterForRec(this, this.column(), docModel, 'full'))
|
||||
);
|
||||
|
||||
this.createValueParser = function() {
|
||||
const fieldRef = this.useColOptions.peek() ? undefined : this.id.peek();
|
||||
@@ -264,8 +267,8 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
|
||||
this.headerFontStrikethrough = this.widgetOptionsJson.prop('headerFontStrikethrough');
|
||||
this.question = this.widgetOptionsJson.prop('question');
|
||||
|
||||
this.documentSettings = ko.pureComputed(() => docModel.docInfoRow.documentSettingsJson());
|
||||
this.style = ko.pureComputed({
|
||||
this.documentSettings = this.autoDispose(ko.pureComputed(() => docModel.docInfoRow.documentSettingsJson()));
|
||||
this.style = this.autoDispose(ko.pureComputed({
|
||||
read: () => ({
|
||||
textColor: this.textColor(),
|
||||
fillColor: this.fillColor(),
|
||||
@@ -277,8 +280,8 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
|
||||
write: (style: Style) => {
|
||||
this.widgetOptionsJson.update(style);
|
||||
},
|
||||
});
|
||||
this.headerStyle = ko.pureComputed({
|
||||
}));
|
||||
this.headerStyle = this.autoDispose(ko.pureComputed({
|
||||
read: () => ({
|
||||
headerTextColor: this.headerTextColor(),
|
||||
headerFillColor: this.headerFillColor(),
|
||||
@@ -290,19 +293,21 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
|
||||
write: (headerStyle: HeaderStyle) => {
|
||||
this.widgetOptionsJson.update(headerStyle);
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
this.tableId = ko.pureComputed(() => this.column().table().tableId());
|
||||
this.tableId = this.autoDispose(ko.pureComputed(() => this.column().table().tableId()));
|
||||
this.rulesList = modelUtil.savingComputed({
|
||||
read: () => this._fieldOrColumn().rules(),
|
||||
write: (setter, val) => setter(this._fieldOrColumn().rules, val)
|
||||
});
|
||||
this.rulesCols = refListRecords(docModel.columns, ko.pureComputed(() => this._fieldOrColumn().rules()));
|
||||
this.rulesColsIds = ko.pureComputed(() => this.rulesCols().map(c => c.colId()));
|
||||
this.rulesCols = this.autoDispose(
|
||||
refListRecords(docModel.columns, ko.pureComputed(() => this._fieldOrColumn().rules()))
|
||||
);
|
||||
this.rulesColsIds = this.autoDispose(ko.pureComputed(() => this.rulesCols().map(c => c.colId())));
|
||||
this.rulesStyles = modelUtil.fieldWithDefault(
|
||||
this.widgetOptionsJson.prop("rulesOptions") as modelUtil.KoSaveableObservable<Style[]>,
|
||||
[]);
|
||||
this.hasRules = ko.pureComputed(() => this.rulesCols().length > 0);
|
||||
this.hasRules = this.autoDispose(ko.pureComputed(() => this.rulesCols().length > 0));
|
||||
|
||||
// Helper method to add an empty rule (either initial or additional one).
|
||||
// Style options are added to widget options directly and can be briefly out of sync,
|
||||
|
||||
Reference in New Issue
Block a user