gristlabs_grist-core/app/client/models/TableModel.js
Dmitry S 48e90c4998 (core) Change how formula columns can be converted to data.
Summary:
- No longer convert data columns to formula by typing a leading "=". Instead,
  show a tooltip with a link to click if the conversion was intended.
- No longer convert a formula column to data by deleting its formula. Leave the
  column empty instead.
- Offer the option "Convert formula to data" in column menu for formulas.
- Offer the option to "Clear column"
- If a subset of rows is shown, offer "Clear values" and "Clear entire column".

- Add logic to detect when a view shows a subset of all rows.
- Factor out showTooltip() from showTransientTooltip().

- Add a bunch of test cases to cover various combinations (there are small
  variations in options depending on whether all rows are shown, on whether
  multiple columns are selected, and whether columns include data columns).

Test Plan: Added a bunch of test cases.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2746
2021-03-05 12:42:57 -05:00

119 lines
4.1 KiB
JavaScript

/**
* TableModel maintains the model for an arbitrary data table of a Grist document.
*/
var _ = require('underscore');
var ko = require('knockout');
var dispose = require('../lib/dispose');
var rowset = require('./rowset');
var modelUtil = require('./modelUtil');
function TableModel(docModel, tableData) {
this.docModel = docModel;
this.tableData = tableData;
// Maps groupBy fields to RowGrouping objects.
this.rowGroupings = {};
this.isLoaded = ko.observable(tableData.isLoaded);
this.autoDispose(tableData.dataLoadedEmitter.addListener(this.onDataLoaded, this));
this.autoDispose(tableData.tableActionEmitter.addListener(this.dispatchAction, this));
}
dispose.makeDisposable(TableModel);
_.extend(TableModel.prototype, rowset.RowSource.prototype, modelUtil.ActionDispatcher);
TableModel.prototype.fetch = function(force) {
if (this.isLoaded.peek() && force) {
this.isLoaded(false);
}
return this.tableData.docData.fetchTable(this.tableData.tableId, force);
};
TableModel.prototype.getAllRows = function() {
return this.tableData.getRowIds();
};
TableModel.prototype.getNumRows = function() {
return this.tableData.numRecords();
};
TableModel.prototype.getRowGrouping = function(groupByCol) {
var grouping = this.rowGroupings[groupByCol];
if (!grouping) {
grouping = rowset.RowGrouping.create(null, this.tableData.getRowPropFunc(groupByCol));
grouping.subscribeTo(this);
this.rowGroupings[groupByCol] = grouping;
}
return grouping;
};
TableModel.prototype.onDataLoaded = function(oldRowIds, newRowIds) {
this.trigger('rowChange', 'remove', oldRowIds);
this.trigger('rowChange', 'add', newRowIds);
this.isLoaded(true);
};
/**
* Shortcut for `.tableData.sendTableActions`. See documentation in TableData.js.
*/
TableModel.prototype.sendTableActions = function(actions, optDesc) {
return this.tableData.sendTableActions(actions, optDesc);
};
/**
* Shortcut for `.tableData.sendTableAction`. See documentation in TableData.js.
*/
TableModel.prototype.sendTableAction = function(action, optDesc) {
return this.tableData.sendTableAction(action, optDesc);
};
//----------------------------------------------------------------------
/**
* Called via `this.dispatchAction`.
*/
TableModel.prototype._process_AddRecord = function(action, tableId, rowId, columnValues) {
this.trigger('rowChange', 'add', [rowId]);
};
TableModel.prototype._process_RemoveRecord = function(action, tableId, rowId) {
this.trigger('rowChange', 'remove', [rowId]);
};
TableModel.prototype._process_UpdateRecord = function(action, tableId, rowId, columnValues) {
this.trigger('rowChange', 'update', [rowId]);
this.trigger('rowNotify', [rowId], action);
};
TableModel.prototype._process_ReplaceTableData = function() {
// No-op because TableData.js already translates ReplaceTableData to a 'dataLoaded' event.
};
TableModel.prototype._process_BulkAddRecord = function(action, tableId, rowIds, columns) {
this.trigger('rowChange', 'add', rowIds);
};
TableModel.prototype._process_BulkRemoveRecord = function(action, tableId, rowIds) {
this.trigger('rowChange', 'remove', rowIds);
};
TableModel.prototype._process_BulkUpdateRecord = function(action, tableId, rowIds, columns) {
this.trigger('rowChange', 'update', rowIds);
this.trigger('rowNotify', rowIds, action);
};
// All schema changes to this table should be forwarded to each row.
// TODO: we may need to worry about groupings (e.g. recreate the grouping function) once we do row
// groupings of user data. Metadata isn't subject to schema changes, so that doesn't matter.
TableModel.prototype.applySchemaAction = function(action) {
this.trigger('rowNotify', rowset.ALL, action);
};
TableModel.prototype._process_AddColumn = function(action) { this.applySchemaAction(action); };
TableModel.prototype._process_RemoveColumn = function(action) { this.applySchemaAction(action); };
TableModel.prototype._process_RenameColumn = function(action) { this.applySchemaAction(action); };
TableModel.prototype._process_ModifyColumn = function(action) { this.applySchemaAction(action); };
TableModel.prototype._process_RenameTable = _.noop;
TableModel.prototype._process_RemoveTable = _.noop;
module.exports = TableModel;