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

94 lines
3.7 KiB
JavaScript
Raw Normal View History

var _ = require('underscore');
var ko = require('knockout');
var dispose = require('../lib/dispose');
var BaseRowModel = require('./BaseRowModel');
var modelUtil = require('./modelUtil');
var BackboneEvents = require('backbone').Events;
/**
* MetaRowModel is a RowModel for built-in (Meta) tables. It takes a list of field names, and an
* additional constructor called with (docModel, tableModel) arguments (and `this` context), which
* can add arbitrary additional properties to this RowModel.
*/
function MetaRowModel(tableModel, fieldNames, rowConstructor, rowId) {
var colNames = ['id'].concat(fieldNames);
BaseRowModel.call(this, tableModel, colNames);
this._rowId = rowId;
// MetaTableModel#_createRowModelItem creates lightweight objects that all reference the same MetaRowModel but are slightly different.
// We don't derive from BackboneEvents directly so that the lightweight objects created share the same Events object even though they are distinct.
this.events = this.autoDisposeWith('stopListening', BackboneEvents);
// Changes to true when this row gets deleted. This also likely means that this model is about
// to get disposed, except for a floating row model.
this._isDeleted = ko.observable(false);
// Populate all fields. Note that MetaRowModels are never get reassigned after construction.
this._fields.forEach(function(colName) {
this._assignColumn(colName);
}, this);
// Customize the MetaRowModel with a custom additional constructor.
if (rowConstructor) {
rowConstructor.call(this, tableModel.docModel, tableModel);
}
}
dispose.makeDisposable(MetaRowModel);
_.extend(MetaRowModel.prototype, BaseRowModel.prototype);
MetaRowModel.prototype._assignColumn = function(colName) {
if (this.hasOwnProperty(colName)) {
this[colName].assign(this._table.tableData.getValue(this._rowId, colName));
}
};
//----------------------------------------------------------------------
/**
* MetaRowModel.Floater is an object designed to look like a MetaRowModel. It contains observables
* that mirror some particular MetaRowModel. The MetaRowModel currently being mirrored is the one
* corresponding to the value of `rowIdObs`.
*
* Mirrored fields are computed observables that support reading, writing, and saving.
*/
MetaRowModel.Floater = function(tableModel, rowIdObs) {
this._table = tableModel;
this.rowIdObs = rowIdObs;
// Some tsc error prevents me from adding this at the module level.
// This method is part of the interface of MetaRowModel.
// TODO: Fix the tsc error and move this to the module level.
if (!this.constructor.prototype.getRowId) {
this.constructor.prototype.getRowId = function() {
return this.rowIdObs();
}
}
// Note that ._index isn't supported because it doesn't make sense for a floating row model.
this._underlyingRowModel = this.autoDispose(ko.computed(function() {
return tableModel.getRowModel(rowIdObs());
}));
_.each(this._underlyingRowModel(), function(propValue, propName) {
if (ko.isObservable(propValue)) {
// Forward read/write calls to the observable on the currently-active underlying model.
this[propName] = this.autoDispose(ko.pureComputed({
owner: this,
read: function() { return this._underlyingRowModel()[propName](); },
write: function(val) { this._underlyingRowModel()[propName](val); }
}));
// If the underlying observable supports saving, forward save calls too.
if (propValue.saveOnly) {
modelUtil.addSaveInterface(this[propName], (value =>
this._underlyingRowModel()[propName].saveOnly(value)));
}
}
}, this);
};
dispose.makeDisposable(MetaRowModel.Floater);
module.exports = MetaRowModel;