/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Logan Byam
*/
/**
* @module nmodule/webEditors/rc/wb/mgr/model/MgrModel
*/
define([ 'baja!',
'Promise',
'underscore',
'nmodule/webEditors/rc/wb/mgr/MgrTypeInfo',
'nmodule/webEditors/rc/wb/table/model/ComponentTableModel' ], function (
baja,
Promise,
_,
MgrTypeInfo,
ComponentTableModel) {
'use strict';
function checkNewTypes(newTypes) {
_.each(newTypes, function (typeInfo) {
if (!(typeInfo instanceof MgrTypeInfo)) {
throw new Error('new type is not a MgrTypeInfo instance');
}
});
}
/**
* If component is parented, unparent it and return its old property name.
*
* @inner
* @param {baja.Complex} comp
* @param {baja.comm.Batch} batch
* @returns {Promise}
*/
function unparentAndGetPropName(comp, batch) {
const parent = comp.getParent();
const prop = comp.getPropertyInParent();
return Promise.resolve(parent && parent.remove({ slot: prop, batch: batch }))
.then(function () {
return prop && String(prop);
});
}
/**
* API Status: **Development**
*
* A layer that adds edit/add/remove functionality on top of a
* `ComponentTableModel`.
*
* Due to the incubating status of the manager framework, it is *not
* recommended* that you extend `MgrModel` directly. Instead, extend
* `DeviceMgrModel` or `PointMgrModel`: these will provide more robust
* functionality for most use cases.
*
* @class
* @alias module:nmodule/webEditors/rc/wb/mgr/model/MgrModel
* @extends module:nmodule/webEditors/rc/wb/table/model/ComponentTableModel
* @param {Object} params
* @param {Array.<module:nmodule/webEditors/rc/wb/mgr/MgrTypeInfo>} [params.newTypes]
* array of `MgrTypeInfo` instances representing types that will be offered when creating new
* instances for this model.
* @see module:nmodule/driver/rc/wb/mgr/DeviceMgrModel
* @see module:nmodule/driver/rc/wb/mgr/PointMgrModel
*/
const MgrModel = function MgrModel(params) {
ComponentTableModel.apply(this, arguments);
const newTypes = params.newTypes || [];
checkNewTypes(newTypes);
this.$newTypes = newTypes;
};
MgrModel.prototype = Object.create(ComponentTableModel.prototype);
MgrModel.prototype.constructor = MgrModel;
/**
* @private
* @param {function} Ctor a constructor for a MgrModel subclass
* @param {object} params params to be passed to the constructor
* @param {function} processRowAsync an async function that takes a Row and performs async
* processing on it
* @returns {Promise.<module:nmodule/webEditors/rc/wb/mgr/model/MgrModel>}
*/
MgrModel.$makeAsync = function (Ctor, params, processRowAsync) {
const mgr = new Ctor(Object.assign({ rows: [] }, params));
// referenced in ComponentTableModel
mgr.$processRowAsync = processRowAsync;
const insertRows = mgr.insertRows;
mgr.insertRows = function (rows, ...args) {
return Promise.all(rows.map((row) => {
row = this.makeRow(row);
return processRowAsync(row)
.then(() => row);
}))
.then((rows) => insertRows.call(this, rows, ...args));
};
return mgr.insertRows(mgr.getComponentSource().getComponents())
.then(() => mgr);
};
/**
* Clean up the `MgrModel` when the parent `Manager` is being destroyed. This is
* an opportunity to unhook any remaining event handlers on the model or its data source.
*
* @returns Promise.<*>
*/
MgrModel.prototype.destroy = function () {
const componentSource = this.getComponentSource();
//TODO: proper way of removing column event handlers
this.removeAllListeners('columnsFlagsChanged');
if (componentSource) { componentSource.destroy(); }
return this.removeColumns(this.getColumns());
};
/**
* Get the columns contained in this `MgrModel`.
*
* @override
* @function getColumns
* @memberOf module:nmodule/webEditors/rc/wb/mgr/model/MgrModel
* @param {Number} [flags] if given, only return columns that have these
* flags.
* @returns {Array.<module:nmodule/webEditors/rc/wb/mgr/model/MgrColumn>}
*/
/**
* Get the `MgrTypeInfo` instances representing the types that are acceptable to add to
* this model.
*
* @returns {Array.<module:nmodule/webEditors/rc/wb/mgr/MgrTypeInfo>} array of MgrTypeInfos to add
*/
MgrModel.prototype.getNewTypes = function () {
return this.$newTypes;
};
/**
* Override point to customize how new instances of the selected `MgrTypeInfo`
* are instantiated. The default implementation is to simply delegate to the type
* info's #newInstance() function.
*
* @param {module:nmodule/webEditors/rc/wb/mgr/MgrTypeInfo} typeInfo
* @returns {baja.Value|Promise}
*/
MgrModel.prototype.newInstance = function (typeInfo) {
return typeInfo.newInstance();
};
/**
* Override point to customize how `count` number of new instances of a selected
* `MgrTypeInfo` are instantiated. The default implementation is to simply delegate to
* the model's #newInstance() method.
*
* @since Niagara 4.14
* @param {Object} params the parameters to be used to create new instances
* @param {module:nmodule/webEditors/rc/wb/mgr/MgrTypeInfo} params.typeInfo the `MgrTypeInfo`
* representing the type of the components we're creating
* @param {Number} params.count how many new components to create
* @returns {Promise.<Array.<baja.Value>>}
*/
MgrModel.prototype.newInstances = function (params) {
const that = this;
const count = params.count;
const typeInfo = params.typeInfo;
return Promise.all(Array(count).fill().map(() => that.newInstance(typeInfo)));
};
/**
* Add the instances to this model, to be called when an add dialog or
* other add operation is committed. The default implementation is to add the
* given instances to the underlying `ComponentSource` container, which will
* commit these changes up to the station.
*
* Like Workbench, parented instances will be unparented and re-added to the
* container component. (Workbench does this via a Mark; here it will be
* done using BajaScript remove/add calls.)
*
* @param {Array.<baja.Component>} instances the instances to add
* @param {Array.<String>} [names] the desired slot names for the instances.
* If omitted, default names derived from the instance Types will be used.
* @returns {Promise.<Array.<baja.Component>>} as of Niagara 4.13, this will
* resolve with an array of the components that were successfully added. Or:
* rejected if the add fails.
*/
MgrModel.prototype.addInstances = function (instances, names) {
names = names || [];
const source = this.getComponentSource();
const batch = new baja.comm.Batch();
const container = source.getContainer();
const parent = _.first(instances).getParent();
const displayNamesMap = parent && parent.get('displayNames');
const getDisplayNames = (slotNames) => {
return _.map(instances, (instance, i) => {
return displayNamesMap && displayNamesMap.get(slotNames[i]);
});
};
const getNames = _.map(instances, function (instance, i) {
return unparentAndGetPropName(instance, batch)
.then(function (prop) {
return names[i] || prop;
});
});
batch.commit();
let addedComponents;
return Promise.all(getNames)
.then((names) => {
return source.addComponents(instances, names)
.then((results) => {
addedComponents = results;
// Get the effective slot names after they are added
const compNames = results.map((comp) => comp.getName());
const newDisplayNames = getDisplayNames(compNames);
return container.setDisplayName({ slot: compNames, newDisplayName: newDisplayNames });
});
})
.then(() => {
return addedComponents;
});
};
/**
* Override point, that allows the post-processing of the edited row(s) after they have been
* posted to the server.
* @since Niagara 4.14
* @param {Array.<baja.Component>} instances the instances that were edited
* @returns {Promise<Array.<baja.Component>|undefined>}
*/
MgrModel.prototype.postEdit = function (instances) {
return Promise.resolve(instances);
};
/**
* Override point, that allows the post-processing of the matched row(s) after they have been
* posted to the server.
* @since Niagara 4.14
* @param {Array.<baja.Component>} instances the instances that were matched
* @returns {Promise<Array.<baja.Component>|undefined>}
*/
MgrModel.prototype.postMatch = function (instances) {
return Promise.resolve(instances);
};
return (MgrModel);
});