/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Logan Byam
*/
/**
* @module nmodule/webEditors/rc/wb/mgr/Manager
*/
define([ 'jquery',
'Promise',
'underscore',
'bajaux/Widget',
'bajaux/util/CommandButtonGroup',
'nmodule/webEditors/rc/fe/fe',
'nmodule/webEditors/rc/fe/baja/BaseEditor',
'nmodule/webEditors/rc/fe/baja/util/typeUtils',
'nmodule/webEditors/rc/wb/mgr/MgrStateHandler',
'nmodule/webEditors/rc/wb/mgr/model/MgrColumn',
'nmodule/webEditors/rc/wb/mgr/model/MgrModel',
'nmodule/webEditors/rc/wb/mixin/mixinUtils',
'nmodule/webEditors/rc/wb/mixin/TransferSupport',
'nmodule/webEditors/rc/wb/table/Table',
'nmodule/webEditors/rc/wb/table/menu/DefaultTableContextMenu',
'nmodule/webEditors/rc/util/htmlUtils' ], function (
$,
Promise,
_,
Widget,
CommandButtonGroup,
fe,
BaseEditor,
typeUtils,
MgrStateHandler,
MgrColumn,
MgrModel,
mixinUtils,
TransferSupport,
Table,
DefaultTableContextMenu,
htmlUtils) {
'use strict';
var isComponent = typeUtils.isComponent,
contextMenuOnLongPress = htmlUtils.contextMenuOnLongPress,
hasMixin = mixinUtils.hasMixin;
/**
* Return the default HTML used for the manager view. If the learn mixin
* is applied, this will be overridden by a template that describes the
* multi table split pane markup.
*
* @returns {String}
*/
function defaultHtml(mgr) {
return '<div class="mainTable">' +
'<div class="tableContainer"></div>' +
'</div>' +
'<div class="showHideMenu mgr-show-hide-menu mgr-show-hide-menu-main"></div>' +
'<div class="commandContainer"></div>';
}
/**
* API Status: **Development**
*
* View for managing groups of components, monitoring their current state
* and adding/removing components to the group. The concrete manager type
* must provide the `moduleName` and `keyName` parameters if it requires
* state to be saved between hyperlinks and page reloads, as these values
* will be used when generating the key used to index the cached state data.
*
* Due to the incubating status of the manager framework, it is *not
* recommended* that you extend `Manager` directly. Instead, extend
* `DeviceMgr` or `PointMgr`: these will provide more robust functionality
* for most use cases.
*
* @param {Object} params
* @param {String} params.moduleName - The module name, used for accessing values from the lexicon
* and also used to generate the key for saving state information for a manager type.
* @param {String} params.keyName - The key name, used for accessing values from the lexicon and
* also used to generate the key for saving state information for a manager type.
* @see module:nmodule/driver/rc/wb/mgr/DeviceMgr
* @see module:nmodule/driver/rc/wb/mgr/PointMgr
*
* @class
* @abstract
* @alias module:nmodule/webEditors/rc/wb/mgr/Manager
* @extends module:nmodule/webEditors/rc/fe/baja/BaseEditor
*/
var Manager = function Manager(params) {
var that = this;
BaseEditor.apply(that, arguments);
that.$getStateHandler = _.once(function () {
return Promise.resolve(params && params.moduleName && params.keyName &&
that.makeStateHandler(params.moduleName, params.keyName));
});
TransferSupport(this);
};
Manager.prototype = Object.create(BaseEditor.prototype);
Manager.prototype.constructor = Manager;
/**
* @param {JQuery} dom
* @returns {Array|null} the selected subject of the table being clicked
*/
Manager.prototype.getSubject = function (dom) {
const tableDom = dom.closest('.TableWidget');
if (!$.contains(this.jq()[0], tableDom[0])) { return null; }
const table = Widget.in(tableDom);
if (!table) { return null; }
return table.getSubject(dom);
};
/**
* Get the main Table widget.
*
* @returns {module:nmodule/webEditors/rc/wb/table/Table}
* @since Niagara 4.6
*/
Manager.prototype.getMainTable = function () {
// noinspection JSValidateTypes
return Widget.in(this.$getMainTableElement()
.children('.tableContainer').children('table'));
};
/**
* Get the element that holds the main table.
* @private
* @returns {jQuery}
*/
Manager.prototype.$getMainTableElement = function () {
return this.jq().find('.mainTable');
};
/**
* Get the element that holds the command buttons.
* @private
* @returns {jQuery}
*/
Manager.prototype.$getCommandContainerElement = function () {
return this.jq().find('.commandContainer');
};
/**
* Get the CommandButtonGroup widget.
*
* @private
* @returns {module:bajaux/util/CommandButtonGroup}
*/
Manager.prototype.$getCommandButtonGroup = function () {
// noinspection JSValidateTypes
return Widget.in(this.$getCommandContainerElement()
.children('.CommandButtonGroup'));
};
/**
* Set up elements for the main table and command group.
*
* @param {JQuery} dom
* @param {Object} [params] the initialization parameters
* @returns {Promise}
*/
Manager.prototype.doInitialize = function (dom, params) {
// Look for a (private) optional parameter allowing custom HTML
// markup to be specified for the manager. If not defined, the
// default HTML will be used, with a single main manager table,
// show/hide menu and the command button group.
var that = this,
html = (params && params.html) || defaultHtml(that);
dom.html(html).addClass('Manager');
contextMenuOnLongPress(dom, '.tableContainer');
// The 'MGR_COMMAND' mixin has the option to not show commands in
// the bar at the bottom of the view. This function will filter out
// the ones that aren't required.
function filterCommands(cmd) {
return (hasMixin(cmd, 'MGR_COMMAND')) ? cmd.isShownInActionBar() : true;
}
return Promise.all([
fe.buildFor({
dom: $('<div/>').appendTo(dom.children('.commandContainer')),
type: CommandButtonGroup,
value: this.getCommandGroup().filter({ include: filterCommands })
}),
that.$getStateHandler().then(function (handler) {
that.$stateHandler = handler;
})
]);
};
/**
* Private framework override point called when the main model has been created, but before
* it has been loaded into the main table. The deserialized manager state is passed as a
* parameter, allowing any model configuration to be restored from the manager state.
*
* This is intended for internal framework use only.
*
* @private
* @param {module:nmodule/webEditors/rc/wb/mgr/model/MgrModel} model - the main table model
* @param {Object} deserializedState
* @returns {Promise.<*>}
*/
Manager.prototype.$beforeMainTableLoaded = function (model, deserializedState) {
return Promise.resolve();
};
/**
* Private framework override point called after the main table model has been loaded
* into the table widget. The deserialized manager state is passed as a parameter; this
* is called before the main state restoration is performed.
*
* This is intended for internal framework use only.
*
* @private
* @param {module:nmodule/webEditors/rc/wb/mgr/model/MgrModel} model - the main table model
* @param {Object} deserializedState
* @returns {Promise.<*>}
*/
Manager.prototype.$afterMainTableLoaded = function (model, deserializedState) {
return Promise.resolve();
};
/**
* Get the `MgrModel` backing this manager. This will return `undefined` until
* `load()` is called. It can safely be called from inside `doLoad()`.
*
* @returns {module:nmodule/webEditors/rc/wb/mgr/model/MgrModel}
* @since Niagara 4.6
*/
Manager.prototype.getModel = function () {
return this.$mgrModel;
};
/**
* Abstract method to create the `MgrModel` for the main database table. The method
* should return a `Promise` that will resolve to a `MgrModel` instance for the
* `ComponentSource` provided in the parameter. This is used to convert the value
* being loaded into the widget (e.g. a network) into the model used for the table
* widget.
*
* @abstract
*
* @param {baja.Component} value - the value being loaded into the `Widget`.
* @returns {Promise.<module:nmodule/webEditors/rc/wb/mgr/model/MgrModel>} the model for the main table.
*/
Manager.prototype.makeModel = function (value) {
throw new Error('Manager does not provide a makeModel() function.');
};
Manager.prototype.load = function (comp) {
var that = this, args = arguments;
if (comp === that.value()) { return Promise.resolve(); }
// Read and deserialize the state from storage, but we don't do the full restore
// until the end of loading. We get the state at this point so we can find out
// any state that might need to be restored on the model prior to loading. The
// full manager state is restored *after* the model has been loaded into the
// table widget.
that.$deserializedState = (that.$stateHandler && that.$stateHandler.deserializeFromStorage());
return Promise.resolve(that.makeModel(comp))
.then(function (model) {
that.$mgrModel = model;
return BaseEditor.prototype.load.apply(that, args);
})
.finally(function () {
if (that.hasOwnProperty('$deserializedState')) {
delete that.$deserializedState;
}
});
};
/**
* Initializes and loads the main table with the `MgrModel`. If overriding,
* be sure to call the super method.
*
* @returns {Promise}
*/
Manager.prototype.doLoad = function () {
var that = this,
jq = this.jq(),
model = that.getModel(),
tableContainer = jq.find('.mainTable').children('.tableContainer');
_.each(model.getColumns(), function (col) {
if (col instanceof MgrColumn) {
col.$init(that);
}
});
// Create functions to be passed to the main table that will give us a hook
// into the cell construction/destruction process, should there be need to
// override any of the main table's default behavior, such as manipulating
// the css for a particular view.
return that.$beforeMainTableLoaded(model, that.$deserializedState)
.then(function () {
var mainTable = that.getMainTable();
return mainTable && mainTable.destroy();
})
.then(function () {
tableContainer.empty();
return fe.buildFor({
dom: $('<table class="ux-table no-stripe"></table>').appendTo(tableContainer),
type: Table,
value: model,
initializeParams: {
buildCell: _.bind(that.buildMainTableCell, that),
destroyCell: _.bind(that.destroyMainTableCell, that),
finishBuildingRow: _.bind(that.finishMainTableRow, that)
}
});
})
.then(function (table) {
new DefaultTableContextMenu(table).arm(jq, '.mgr-show-hide-menu-main');
return that.$afterMainTableLoaded(model, that.$deserializedState);
})
.then(function () {
return that.restoreState();
});
};
/**
* If the loaded `MgrModel` is backed by a mounted Component, then use that
* Component to resolve ORDs.
*
* @returns {Promise.<baja.Component|undefined>}
*/
Manager.prototype.getOrdBase = function () {
var component = this.value();
return Promise.resolve(isComponent(component) ? component : undefined);
};
/**
* Update the height of the main table element so that the command buttons
* are always visible.
*/
Manager.prototype.doLayout = function () {
this.$getMainTableElement().css({
bottom: this.$getCommandContainerElement().outerHeight()
});
};
/**
* Overrides the base `destroy` method to give the manager a chance to save its state
* before the content (such as the child table widgets) is destroyed.
*/
Manager.prototype.destroy = function () {
this.saveState();
return BaseEditor.prototype.destroy.apply(this, arguments);
};
/**
* Destroy child editors, the main table, and its model.
*
* @returns {Promise}
*/
Manager.prototype.doDestroy = function () {
var that = this,
model = that.getModel();
that.jq().removeClass('Manager');
return that.getChildWidgets().destroyAll()
.then(function () {
return (model instanceof MgrModel) && model.destroy();
});
};
/**
* Invoke the handler created by `makeStateHandler()` to restore the Manager's
* current state when the Manager is loaded.
*
* @returns {Promise}
*/
Manager.prototype.restoreState = function () {
var handler = this.$stateHandler,
state = this.$deserializedState;
return Promise.resolve(handler && state && this.getModel() && handler.restore(this, state));
};
/**
* Invoke the handler created by `makeStateHandler()` to save the Manager's
* current state when the Manager is destroyed.
*/
Manager.prototype.saveState = function () {
var handler = this.$stateHandler;
if (handler && this.getModel()) { handler.save(this); }
};
/**
* Make a state handler instance for saving and restoring the Manager's
* state.
* @returns {module:nmodule/webEditors/rc/wb/mgr/MgrStateHandler}
*/
Manager.prototype.makeStateHandler = function (keyName, moduleName) {
return MgrStateHandler.make(keyName + '.' + moduleName);
};
/**
* Override point, allowing a `Manager` to customize the building of
* a cell within its main table. This will default to delegating the
* cell building to the column.
*
* @param {module:nmodule/webEditors/rc/wb/table/model/Column} column
* @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
* @param {jQuery} dom
* @returns {Promise|*}
*/
Manager.prototype.buildMainTableCell = function (column, row, dom) {
return column.buildCell(row, dom);
};
/**
* Override point, allowing a `Manager` to customize the destruction
* of a cell that it created in `#buildMainTableCell`. The default
* behavior is to delegate to the column.
*
* @param {module:nmodule/webEditors/rc/wb/table/model/Column} column
* @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
* @param {jQuery} dom
* @returns {Promise|*}
*/
Manager.prototype.destroyMainTableCell = function (column, row, dom) {
return column.destroyCell(row, dom);
};
/**
* Override point, allowing a `Manager` to customize the dom for
* a `Row` after the cells have been built, but before it is inserted
* into the main table. This allows sub-classes to perform any CSS
* customizations they may require at an individual row level.
*
* @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
* @param {jQuery} dom
* @returns {Promise}
*/
Manager.prototype.finishMainTableRow = function (row, dom) {
return Promise.resolve(dom);
};
return Manager;
});