mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
48e90c4998
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
119 lines
4.1 KiB
JavaScript
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;
|