gristlabs_grist-core/app/client/models/BaseRowModel.js

139 lines
4.9 KiB
JavaScript
Raw Normal View History

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);
return;
}
var index = this._fields.indexOf(oldColId);
if (index === -1) {
console.error("RowModel #RenameColumn %s %s %s: not found", tableId, oldColId, newColId);
return;
}
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;