mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
137 lines
4.8 KiB
JavaScript
137 lines
4.8 KiB
JavaScript
|
var _ = require('underscore');
|
||
|
var ko = require('knockout');
|
||
|
|
||
|
var gutil = require('app/common/gutil');
|
||
|
|
||
|
var dispose = require('../lib/dispose');
|
||
|
|
||
|
var modelUtil = require('./modelUtil');
|
||
|
|
||
|
|
||
|
/**
|
||
|
* BaseRowModel is an observable model for a record (or row) of a data (DataRowModel) or meta
|
||
|
* (MetaRowModel) table. It takes a reference to the containing TableModel, and a list of
|
||
|
* column names, and creates an observable for each field.
|
||
|
* TODO: We need to have a way to dispose RowModels, and have them dispose individual fields,
|
||
|
* which should in turn unsubscribe from various events on disposal. And it all should be tested.
|
||
|
*
|
||
|
*/
|
||
|
function BaseRowModel(tableModel, colNames) {
|
||
|
this._table = tableModel;
|
||
|
this._fields = colNames.slice(0);
|
||
|
this._index = ko.observable(null); // The index in the observable to which it belongs.
|
||
|
this._rowId = null;
|
||
|
|
||
|
// Create a field for everything in `_fields`.
|
||
|
this._fields.forEach(function(colName) {
|
||
|
this._createField(colName);
|
||
|
}, this);
|
||
|
}
|
||
|
dispose.makeDisposable(BaseRowModel);
|
||
|
|
||
|
// This adds the dispatchAction() method to RowModel.
|
||
|
_.extend(BaseRowModel.prototype, modelUtil.ActionDispatcher);
|
||
|
|
||
|
/**
|
||
|
* Returns the rowId to which this RowModel is assigned. This is also normally available as the
|
||
|
* `rowModel.id` observable.
|
||
|
*/
|
||
|
BaseRowModel.prototype.getRowId = function() {
|
||
|
return this._rowId;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Creates a field for colName. This is either a top level observable like this[colName]
|
||
|
* for MetaRowModels or a property field like this[name][prop] for DataRowModels
|
||
|
*/
|
||
|
BaseRowModel.prototype._createField = function(colName) {
|
||
|
this[colName] = modelUtil.addSaveInterface(ko.observable(), v => this._saveField(colName, v));
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Helper method to send a user action to save a field of the current row to the server.
|
||
|
*/
|
||
|
BaseRowModel.prototype._saveField = function(colName, value) {
|
||
|
var colValues = {};
|
||
|
colValues[colName] = value;
|
||
|
return this.updateColValues(colValues);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Send an update to the server to update multiple columns for this row.
|
||
|
* @param {Object} colValues: Maps colIds to values.
|
||
|
* @returns {Promise} Resolved when the update succeeds.
|
||
|
*/
|
||
|
BaseRowModel.prototype.updateColValues = function(colValues) {
|
||
|
return this._table.sendTableAction(["UpdateRecord", this._rowId, colValues]);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Assigns the field of this RowModel named by `colName` to its corresponding value.
|
||
|
*/
|
||
|
BaseRowModel.prototype._assignColumn = function(colName) {
|
||
|
throw new Error("Not Implemented");
|
||
|
};
|
||
|
|
||
|
//----------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Implements the interface expected by modelUtil.ActionDispatcher. We only implement the
|
||
|
* actions that affect individual rows. Note that BulkUpdateRecord needs to be translated to individual
|
||
|
* UpdateRecords for RowModel to know what to do. Messages not here must be implemented by subclasses.
|
||
|
* Some of these require helper methods defined in subclasses
|
||
|
*/
|
||
|
|
||
|
BaseRowModel.prototype._process_RemoveColumn = function(action, tableId, colId) {
|
||
|
if (!gutil.arrayRemove(this._fields, colId)) {
|
||
|
console.error("RowModel #RemoveColumn %s %s: column not found", tableId, colId);
|
||
|
}
|
||
|
delete this[colId];
|
||
|
};
|
||
|
|
||
|
BaseRowModel.prototype._process_ModifyColumn = function(action, tableId, colId, colInfo) {
|
||
|
// No-op for us, because we don't care about any of the column properties.
|
||
|
};
|
||
|
|
||
|
BaseRowModel.prototype._process_UpdateRecord = function(action, tableId, rowId, columnValues) {
|
||
|
for (var colName in columnValues) {
|
||
|
this._assignColumn(colName);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
BaseRowModel.prototype._process_BulkUpdateRecord = function(action, tableId, rowId, columnValues) {
|
||
|
// We get notified when a BulkUpdateRecord affects us, but since we just update all fields from
|
||
|
// the underlying data, we don't need to find our row in the action.
|
||
|
for (var colName in columnValues) {
|
||
|
this._assignColumn(colName);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// TODO: if AddColumn messages aren't sent for properties, we will need to find a different
|
||
|
// way to create and set the properties than here
|
||
|
BaseRowModel.prototype._process_AddColumn = function(action, tableId, colId, colInfo) {
|
||
|
this._fields.push(colId);
|
||
|
this._createField(colId);
|
||
|
this._assignColumn(colId);
|
||
|
};
|
||
|
|
||
|
BaseRowModel.prototype._process_RenameColumn = function(action, tableId, oldColId, newColId) {
|
||
|
// handle standard renames differently
|
||
|
if (this._fields.indexOf(newColId) !== -1) {
|
||
|
console.error("RowModel #RenameColumn %s %s %s: already exists", tableId, oldColId, newColId);
|
||
|
}
|
||
|
var index = this._fields.indexOf(oldColId);
|
||
|
if (index === -1) {
|
||
|
console.error("RowModel #RenameColumn %s %s %s: not found", tableId, oldColId, newColId);
|
||
|
}
|
||
|
this._fields[index] = newColId;
|
||
|
|
||
|
// Reuse the old observable, but replace its "save" family of functions.
|
||
|
this[newColId] = this[oldColId];
|
||
|
modelUtil.addSaveInterface(this[newColId], this._saveField.bind(this, newColId));
|
||
|
this._assignColumn(newColId);
|
||
|
delete this[oldColId];
|
||
|
};
|
||
|
|
||
|
module.exports = BaseRowModel;
|