/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Logan Byam
*/
/**
* @module nmodule/webEditors/rc/wb/mgr/model/MgrColumn
*/
define([
'baja!',
'log!nmodule.webEditors.rc.wb.mgr.model.MgrColumn',
'Promise',
'nmodule/webEditors/rc/fe/baja/util/typeUtils',
'nmodule/webEditors/rc/wb/mgr/mgrUtils',
'nmodule/webEditors/rc/wb/table/model/Column' ], function (
baja,
log,
Promise,
typeUtils,
mgrUtils,
Column) {
'use strict';
const { propose, getProposedValue } = mgrUtils;
const { allSameType } = typeUtils;
const logFine = log.fine.bind(log);
/**
* API Status: **Development**
*
* Column for use in a `Manager` workflow. It functions both as a vanilla
* `TableModel` column, and as a defined field for batch editing rows in a
* manager view.
*
* A `MgrColumn` provides for the following workflow:
*
* - Identify a number of components to edit at once.
* - Coalesce corresponding values from those components into a single value.
* - Load the coalesced value into a single editor for editing.
* - Commit the entered value back to the edited components.
*
* @class
* @alias module:nmodule/webEditors/rc/wb/mgr/model/MgrColumn
* @extends module:nmodule/webEditors/rc/wb/table/model/Column
*/
const MgrColumn = function MgrColumn() {
Column.apply(this, arguments);
this.data('context', {});
};
MgrColumn.prototype = Object.create(Column.prototype);
MgrColumn.prototype.constructor = MgrColumn;
MgrColumn.COMMIT_READY = 'commitReady';
/**
* Applies `MgrColumn` functionality to an arbitrary `Column` subclass.
*
* @param {Function} Ctor
*/
MgrColumn.mixin = function (Ctor) {
const prot = Ctor.prototype;
const mprot = MgrColumn.prototype;
prot.coalesceRows = mprot.coalesceRows;
prot.getConfigFor = mprot.getConfigFor;
prot.getProposedValueFor = mprot.getProposedValueFor;
prot.isEditorSuitable = mprot.isEditorSuitable;
prot.mgrValidate = mprot.mgrValidate;
prot.propose = mprot.propose;
prot.commit = mprot.commit;
prot.buildCell = mprot.buildCell;
};
/**
* Set the manager for this column. This is for internal framework use.
*
* @private
* @param {module:nmodule/webEditors/rc/wb/mgr/Manager} mgr
*/
MgrColumn.prototype.$init = function (mgr) {
this.$mgr = mgr;
};
/**
* Creates the cell's contents by calling `toString` on the row's proposed value
* or the current value if there is no proposal. HTML will be safely escaped.
*
* @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
* @param {JQuery} dom
* @returns {Promise}
*/
MgrColumn.prototype.buildCell = function (row, dom) {
return Promise.try(() => {
const value = this.getProposedValueFor(row);
const context = this.$getContext(row);
const str = baja.hasType(value) && context ? value.toString(context) : String(value);
return Promise.resolve(str).then((str) => dom.text(str));
})
.catch((err) => {
logFine('Unable to build cell', err);
dom.text('');
});
};
/**
* Given the set of rows to be edited, coalesce their values into one single
* value to load into an editor.
*
* By default, this will simply read the proposed value from the first row.
* This is appropriate for a use case where one value will be entered and
* written back to all edited components.
*
* If editing one value for all the given rows is not a use case supported by
* your column (a good example is a Name column, because no two components can
* share the same name), throw an error.
*
* @param {Array.<module:nmodule/webEditors/rc/wb/table/model/Row>} rows
* @returns {*} value coalesced from the given rows
* @throws {Error} if rows array not given, or values from rows are not all
* of the same type
*/
MgrColumn.prototype.coalesceRows = function (rows) {
if (!Array.isArray(rows) || !rows.length) {
throw new Error('rows array required');
}
const values = rows.map((row) => this.getProposedValueFor(row));
if (!allSameType(values)) {
throw new Error('values from rows are of differing Types');
}
return values[0];
};
/**
* After coalescing the selected rows into a single value, calculate a
* config object to be given to `fe.makeFor` that will determine how the
* editor will be built to edit that value.
*
* This function will typically not be called directly but serves as an
* override point. By default, it will simply get the coalesced value from
* those rows and have `fe.makeFor` build the default editor for that value.
* Note that this means if the coalesced value is a non-Baja value, like an
* array, this function *must* be overridden.
*
* @param {Array.<module:nmodule/webEditors/rc/wb/table/model/Row>} rows
* @returns {Object} configuration object to be given to `fe.makeFor`
*/
MgrColumn.prototype.getConfigFor = function (rows) {
return { value: this.coalesceRows(rows) };
};
/**
* If an editor has already been built, it may be possible to reuse it,
* simply loading in a new coalesced value rather than destroying and
* rebuilding the existing editor.
*
* This function should return true if the editor is suitable to be reused
* for the given rows. By default, will always return true.
*
* @param {module:nmodule/webEditors/rc/fe/baja/BaseEditor} editor
* @param {Array.<module:nmodule/webEditors/rc/wb/table/model/Row>} rows
* @returns {Boolean}
*/
MgrColumn.prototype.isEditorSuitable = function (editor, rows) {
return true;
};
/**
* Allows this column to validate proposed changes.
*
* @param {module:nmodule/webEditors/rc/wb/mgr/model/MgrModel} model
* the model to which we're about to apply changes.
* @param {Array} data an array of proposed changes to this column, one per
* row in the `MgrModel`. If a value in this array is null, no change has
* been proposed for that row.
* @param {Object} [params]
* @param {module:nmodule/webEditors/rc/fe/baja/BaseEditor} [params.editor]
* the editor from which the proposed values were read. Note that the editor
* may have been used to edit other rows, so the editor's current value may
* not match the proposed new values.
* @returns {Promise} promise that resolves by default
*
* @example
* <caption>Validating this column may require that I examine the changes I'm
* about to make to other columns as well.</caption>
*
* MyMgrColumn.prototype.mgrValidate = function (model, data, params) {
* var that = this,
* rows = model.getRows(),
* otherColumn = model.getColumn('otherColumn');
*
* //search through all MgrModel rows, and check to see that my proposed
* //change is compatible with the proposed change to another column.
* //say, i'm a "password" column, and the other column is a "password
* //scheme" column - i need to make sure that the proposed password is
* //considered valid by the proposed password scheme.
*
* for (var i = 0; i < rows.length; i++) {
* var row = rows[i],
* myValue = data[i],
* otherValue = otherColumn.getProposedValueFor(row);
*
* if (myValue === null) {
* //no changes proposed for this row, so nothing to validate.
* }
*
* if (!isCompatible(myValue, otherValue)) {
* return Promise.reject(new Error('incompatible values'));
* }
* }
* };
*/
MgrColumn.prototype.mgrValidate = function (model, data, params) {
return Promise.resolve();
};
/**
* Should read the value and "tentatively" apply it to the
* selected row. In most cases this will be setting some temporary data
* for display-only purposes.
*
* By default, will set some temporary data on the row using the column's
* name as a key.
*
* @param {*} value
* @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
* @returns {Promise}
*/
MgrColumn.prototype.propose = function (value, row) {
propose(row, this.getName(), value);
return Promise.resolve();
};
/**
* Get the currently proposed value for the given row. If no value proposed
* yet, will return the actual column value (`getValueFor`).
*
* @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
* @returns {*}
*/
MgrColumn.prototype.getProposedValueFor = function (row) {
let proposed = getProposedValue(row, this.getName());
if (proposed === undefined) {
proposed = this.getValueFor(row);
}
return proposed;
};
/**
* Should read the value and "officially" apply it back to the selected rows.
* If `params.batch` is received, then `params.progressCallback` _must_ be
* called with `MgrColumn.COMMIT_READY` when this function is done using that
* batch (even if no network calls are added to it).
*
* Note: sometimes you may want to abort the entire process of saving changes
* to the Manager, for instance, if one of your columns requires the user to
* confirm a dialog before committing. Returning a Promise that rejects will
* show an error dialog to the user, which may not be what you want if you've
* already shown a dialog. Another option is to return a Promise that never
* resolves or rejects, which will drop the user back at the edit screen
* without committing any changes (all commit calls must resolve for any
* changes to post to the station). A more explicit API for this may be
* provided in the future.
*
* @param {*} value the proposed value to commit to the row
* @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
* @param {Object} [params]
* @param {module:nmodule/webEditors/rc/fe/baja/BaseEditor} [params.editor]
* the editor from which the value was read. If the column is not editable,
* this parameter will be `undefined`, as no editor will have been created for
* the value. This situation may occur when a value obtained via discovery is
* set on row for a non-editable column.
* @param {baja.comm.Batch} [params.batch] a batch to use to commit changes
* up to the station
* @param {Function} [params.progressCallback] call this with
* `MgrColumn.COMMIT_READY` when this function is done adding network calls to
* the batch.
* @returns {Promise}
*/
MgrColumn.prototype.commit = function (value, row, params) {
const progressCallback = params && params.progressCallback;
if (progressCallback) {
progressCallback(MgrColumn.COMMIT_READY);
}
return Promise.resolve();
};
/**
* Returns the manager associated with this column.
*
* @private
* @returns {module:nmodule/webEditors/rc/wb/mgr/Manager}
*/
MgrColumn.prototype.getManager = function () {
return this.$mgr;
};
return MgrColumn;
});