/**
* @copyright 2016 Tridium, Inc. All Rights Reserved.
*/
/*jshint browser:true*/
/**
* A mixin type, used to add learn functionality to a Manager instance.
*
* @module nmodule/webEditors/rc/wb/mgr/MgrLearn
*/
define([ 'baja!',
'log!nmodule.webEditors.rc.wb.mgr.MgrLearn',
'bajaux/commands/CommandGroup',
'bajaux/mixin/mixinUtils',
'Promise',
'underscore',
'nmodule/webEditors/rc/fe/baja/util/DepthSubscriber',
'nmodule/webEditors/rc/wb/mgr/Manager',
'nmodule/webEditors/rc/wb/mgr/MgrLearnTableSupport',
'nmodule/webEditors/rc/wb/mgr/mgrUtils',
'nmodule/webEditors/rc/wb/mgr/commands/AddCommand',
'nmodule/webEditors/rc/wb/mgr/commands/CancelDiscoverCommand',
'nmodule/webEditors/rc/wb/mgr/commands/DiscoverCommand',
'nmodule/webEditors/rc/wb/mgr/commands/LearnModeCommand',
'nmodule/webEditors/rc/wb/mgr/commands/MatchCommand',
'nmodule/webEditors/rc/wb/mgr/commands/MgrCommand',
'nmodule/webEditors/rc/wb/mgr/commands/QuickMatchCommand',
'nmodule/webEditors/rc/wb/mgr/commands/SelectAllCommand',
'nmodule/webEditors/rc/wb/mgr/commands/ShowExistingCommand',
'nmodule/webEditors/rc/wb/mgr/commands/QuickAddCommand',
'nmodule/webEditors/rc/wb/table/tree/TreeTable' ], function (
baja,
log,
CommandGroup,
mixinUtils,
Promise,
_,
DepthSubscriber,
Manager,
mixinLearnTableSupport,
mgrUtils,
AddCommand,
CancelDiscoverCommand,
DiscoverCommand,
LearnModeCommand,
MatchCommand,
MgrCommand,
QuickMatchCommand,
SelectAllCommand,
ShowExistingCommand,
QuickAddCommand,
TreeTable) {
'use strict';
const { applyMixin } = mixinUtils;
const { findCommand, findCommands, toMgrTypeInfos } = mgrUtils;
const logError = log.severe.bind(log);
const MIXIN_NAME = 'MGR_LEARN';
const { ALL, LEARN_CONTEXT_MENU } = MgrCommand.flags;
/**
* API Status: **Development**
*
* A mixin to provide learn support to a bajaux manager view.
*
* To support discovery, in addition to applying this mixin, the target manager object must
* provide several functions that this mixin will use to accomplish the discovery and the
* creation of new components from the discovered items.
*
* The concrete manager must provide a `makeLearnModel()` method. This should return a
* `Promise` that will resolve to a `TreeTableModel`. This will be used as the data model
* for the discovery table. On completion of the discovery job, the manager should use the
* result of the job to insert items into the discovery model.
*
* The concrete manager must also provide an implementation of a `doDiscover()` function
* that will create a job (typically by invoking an action that will submit a job
* and return the ord), and then set the job on the manager via the `setJob()` function.
* This function will accept the job instance or the ord for a job, specified either as
* a `baja.Ord` or a string.
*
* Once the job is complete, a 'jobcomplete' tinyevent will be emitted on the manager. The
* concrete manager will also typically have a handler for that event, which will get the
* discovered items from the job by some means, and then update the discovery table. This
* will normally involve inserting nodes into the learn model. The manager may store arbitrary
* data on those nodes, which it may retrieve later via the node's `value()` function.
*
* The manager must also implement a `getTypesForDiscoverySubject()` function. This will be called
* when dragging an item from the discovery table to the database table or invoking the 'add'
* command. The function may be called several times, each time its argument will be a
* `TreeNode` representing the item to be added into to the database table. The implementation
* of this function is expected to return a single `MgrTypeInfo` instance or any array of them.
* These will be used to create a new component instance of the required type for the discovered
* node.
*
* Also to support the addition of new components, the manager should implement a function
* called `getProposedValuesFromDiscovery()`. This will be passed the tree node that was dragged
* from the discovery table to the database table. The function should obtain any information
* the manager had set on the node at discovery time and use it to create an object containing
* the initial values for the new component. The names of properties on the object returned by
* the function will be compared against the column names in the main database model. For the
* columns that have matching names, the values of those properties will be used to set the
* initial proposed values on the new row(s) when the dialog for editing the new instances is
* displayed.
*
* @alias module:nmodule/webEditors/rc/wb/mgr/MgrLearn
* @mixin
* @extends module:nmodule/webEditors/rc/wb/mgr/Manager
* @param {module:nmodule/webEditors/rc/wb/mgr/Manager} target
* @param {Object} params parameter to be passed down to provide additional information
*
* @example
* <caption>Add the MgrLearn mixin to a Manager subclass to add learn
* functionality.</caption>
* require([...'nmodule/webEditors/rc/wb/mgr/MgrLearn'], function (...MgrLearn) {
* function MyManager() {
* Manager.apply(this, arguments);
* MgrLearn(this);
* }
* MyManager.prototype = Object.create(Manager.prototype);
*
* //implement abstract functions
* MyManager.prototype.doDiscover = function () { ...
* });
*/
const MgrLearn = function MgrLearn(target, params) {
if (!(target instanceof Manager)) {
throw new Error('target must be a Manager instance.');
}
if (!applyMixin(target, MIXIN_NAME, MgrLearn.prototype)) {
return this;
}
params = _.defaults(params || {}, {
tableCtor: TreeTable
});
const superToContextMenuCommandGroup = target.toContextMenuCommandGroup;
const superDoDestroy = target.doDestroy;
/**
* Extension of the manager's doDestroy() function. This will clean up
* the subscription to the discovery job, if it is present.
*
* @returns {*|Promise}
*/
target.doDestroy = function () {
if (target.$jobSubDepth) { delete target.$jobSubDepth; }
return unsubscribeJob(target)
.then(() => superDoDestroy.apply(target, arguments));
};
target.toContextMenuCommandGroup = function (e) {
const isLearnTable = e.currentTarget.classList.contains('mgr-discovery-row');
if (isLearnTable) {
return Promise.resolve(target.getLearnTableCommands())
.then((commands) => new CommandGroup({ commands }));
}
return superToContextMenuCommandGroup.apply(this, arguments);
};
// Automatically mix in support for the learn table, so the target
// manager does not need to do that themselves (or know about that
// module...)
mixinLearnTableSupport(target, {
tableCtor: params.tableCtor
});
};
/**
* Abstract method used to obtain the model for the learn tree table. This
* should return a `TreeTableModel`, or a Promise that resolves to one.
*
* @abstract
* @returns {Promise.<module:nmodule/webEditors/rc/wb/table/tree/TreeTableModel>} a
* tree table model that will be used by the manager's discovery table.
*/
MgrLearn.prototype.makeLearnModel = function () {
throw new Error('makeLearnModel() function not implemented.');
};
/**
* Abstract method used to initiate the discovery process. What this
* implementation does is a matter for the concrete manager, but the typical
* pattern will be to invoke an Action that will submit a job, and then set
* that job or its Ord on the manager via the `#setJob()` function.
*
* @abstract
* @returns {Promise|*} Optionally return a Promise
*/
MgrLearn.prototype.doDiscover = function () {
throw new Error('doDiscover() function not implemented.');
};
/**
* Abstract method to get the Component type(s) that could be created for the
* given discovery node when adding it to the station as a component. If
* returning an array, the first element of the array should be the type that
* represents the best mapping for the discovery item.
*
* @abstract
* @param {*} discovery a discovery object
* @returns {Promise.<string|baja.Type|Array>} a Promise that can resolve to a string,
* a baja Type or an array which can be used to `make` MgrTypeInfos.
*/
MgrLearn.prototype.getTypesForDiscoverySubject = function (discovery) {
throw new Error('getTypesForDiscoverySubject() function not implemented.');
};
/**
* Abstract method to get the initial values for a discovered node when it is
* being added to the station as a new component. This method should return an
* Object instance, with the values to be used by the new instances. The
* returned object may have a property called 'name', which will be used to
* set the slot name of the new component. It may also have a child object
* named 'values'. Each property of this object with a name that matches the
* name of a `Column` in the main table model will have that property's value
* used as the initial value when the component editor is displayed.
*
* The second argument is the corresponding value in the station database table. If using
* the Add command, this will be the brand-new instance about to added to the
* database. If using the Match command, this will be the existing instance already
* in the database.
*
* @example
* <caption>Return the initial values for the component name, and the
* 'version' and 'address' columns</caption>
* MyDeviceMgr.prototype.getProposedValuesFromDiscovery = function (discovery, component) {
* return {
* name: discovery.deviceName,
* values: {
* address: discovery.address,
* version: discovery.firmwareVersionMajor + '.' + discovery.firmwareVersionMinor
* }
* };
* };
*
* @abstract
* @param {*} discovery an object obtained from a node in discovery table.
* @param {*} subject - the subject of the `Row` whose values are to be
* proposed.
* @see module:nmodule/webEditors/rc/wb/table/tree/TreeNode
* @returns {Object|Promise.<Object>} an object literal with the name and
* initial values to be used for the new component.
*/
MgrLearn.prototype.getProposedValuesFromDiscovery = function (discovery, subject) {
throw new Error('getProposedValuesFromDiscovery() function not implemented.');
};
/**
* @function module:nmodule/webEditors/rc/wb/mgr/MgrLearn#isExisting
* @param {*} discovery the discovery item
* @param {baja.Component} component component already existing in local
* database
* @returns {boolean|Promise.<boolean>} true if the local component already
* represents the discovery item
*/
/**
* Get the learn model. The model will have been created via a call to `makeLearnModel()`; a
* function that the concrete manager must provide. This will return the `TreeTableModel`
* resolved from the Promise.
*
* @returns {module:nmodule/webEditors/rc/wb/table/tree/TreeTableModel}
*/
MgrLearn.prototype.getLearnModel = function () {
return this.$learnModel;
};
/**
* @since Niagara 4.14
* @returns {Boolean}
*/
MgrLearn.prototype.isLearnModeEnabled = function () {
return this.$isLearnModeEnabled;
};
/**
* @since Niagara 4.14
* @param {Boolean} learnModeEnabled
*/
MgrLearn.prototype.setLearnModeEnabled = function (learnModeEnabled) {
this.$toggleLearnPane(learnModeEnabled);
this.$isLearnModeEnabled = learnModeEnabled;
};
/**
* Begin the discovery process. This function is not intended to be called directly
* or overridden by concrete manager types. Instead, the concrete manager should provide
* a `doDiscover` function, which will be called from this function. If the function is
* not provided, an Error will be thrown.
*
* @private
*/
MgrLearn.prototype.discover = function () {
const modeCmd = this.$getLearnModeCommand();
let prom;
// Set the learn mode command to selected, if it isn't already. This will cause
// the discovery pane to become visible, and the table heights to be adjusted.
if ((modeCmd) && (!modeCmd.isSelected())) {
prom = Promise.resolve(modeCmd.invoke());
} else {
prom = Promise.resolve();
}
return prom.then(() => this.doDiscover());
};
/**
* Attach a job to this manager, typically as part of a driver discovery process.
* The act of attaching a job will subscribe to it, and cause a 'jobcomplete' event
* to be emitted once the job is complete. A manager will typically update the learn
* model at that point.
*
* @param {Object} params an Object literal containing the parameters for this function.
*
* @param {string|baja.Ord|baja.Component} [params.jobOrOrd] either a BJob instance, an
* Ord referencing a job, or a String containing the ord for a job.
*
* @param {Number} [params.depth] optional parameter that will be used when subscribing to
* the job once it has completed; this allows the job plus its final set of children to
* be subscribed. A depth of 1 will subscribe to the job, 2 will subscribe the job and
* its children, and so on. Subscription will default to a depth of 1 if this parameter
* is not specified.
*
* @returns {Promise}
*/
MgrLearn.prototype.setJob = function (params) {
params = baja.objectify(params, 'jobOrOrd');
const jobOrOrd = params.jobOrOrd;
const depth = params.depth || 1;
// First, clean up any existing job that might already have been set on the manager.
// After that, obtain the new job: we might have been passed one directly, or we might
// need to resolve an ord.
return unsubscribeJob(this)
.then(() => baja.station.sync())
.then(() => {
if (this.$learnJob) { delete this.$learnJob; }
const isJob = baja.hasType(jobOrOrd, 'baja:Job');
return isJob ? jobOrOrd : baja.Ord.make(String(jobOrOrd)).get();
})
.then((job) => {
const discoveryCmd = this.$getDiscoveryCommand();
const cancelCmd = this.$getCancelDiscoveryCommand();
// Now we have a reference to the job, keep track of it with some private
// properties, then load it into the job bar.
this.$learnJob = job;
this.$jobSubDepth = depth;
if (discoveryCmd) { discoveryCmd.setEnabled(false); }
if (cancelCmd) { cancelCmd.setEnabled(true); }
return this.$getJobBar().load(job).then(() => job);
})
.then((job) => {
// Test if the job has already completed, if so, call the
// job complete callback directly. Otherwise, subscribe and
// wait for an event to notify us that the job has finished.
if (isJobComplete(job)) {
// The job has already completed, so subscribe to the job
// and its children to the requested depth. We don't need
// to pass in a change callback, as once it's complete we
// don't care about changes.
return subscribeJob(this, job, depth)
.then(() => {
jobComplete(this, job);
});
} else {
// The job is not yet complete, so subscribe to it with a
// depth of 1. Once the job is complete, we'll resubscribe
// to it and its children to the requested depth. We pass
// a callback here, so we can get the change events.
return subscribeJob(this, job, 1, (p) => { jobStateChanged(this, p); });
}
});
};
/**
* Get the discovery job currently set against the manager.
*
* @returns {baja.Component}
*/
MgrLearn.prototype.getJob = function () {
return this.$learnJob;
};
/**
* Creates a new component instance from the types the manager specified
* for a particular node in the discovery table. If the manager returned
* more than one type, this default implementation will return a new
* instance based on the first type info.
*
* @param {*} discovery an instance of a discovery object (e.g. an
* `ndriver:NDiscoveryLeaf`), dragged from the discovery table and dropped
* onto the database table or selected when the 'Add' command was invoked.
*
* @param {Array.<module:nmodule/webEditors/rc/wb/mgr/MgrTypeInfo>} typeInfos - an
* array of MgrTypeInfos, created from the type or types returned
* by the manager's `getTypesForDiscoverySubject()` implementation.
*
* @returns {Promise} a Promise of new component instance for the discovered item
* based on the provided type information.
*/
MgrLearn.prototype.newInstanceFromDiscoverySubject = function (discovery, typeInfos) {
return this.getModel().newInstance(_.head(typeInfos));
};
/**
* Creates new component instances for the discovered items. The default implementation
* simply iterates over the discovery objects, gets their type info and delegates to
* #newInstanceFromDiscoverySubject method.
*
* @since Niagara 4.14
* @param {*} discoveries an array of discovery objects
*
* @returns {Promise<Array<baja.Component|null>>} a Promise of new component instances
* from the discovered items
*/
MgrLearn.prototype.newInstancesFromDiscoverySubjects = function (discoveries) {
let that = this;
return Promise.all(discoveries.map((discovery) => {
return that.$getMgrTypesForDiscovery(discovery)
.then((typeInfos) => typeInfos ? that.newInstanceFromDiscoverySubject(discovery, typeInfos) : null);
}));
};
/**
* Search for the existing component that matches the given node from the
* discovery table. To match a component, the concrete manager subclass
* must contain a function named `isExisting()` which will be passed the
* discovery object and a component. The function will be used as a predicate and
* should return true if the given component represents the same item as
* the discovery table item, false otherwise. If the manager does not provide
* such a function, all discovery nodes will be considered as not matching any
* existing components.
*
* @param {*} discovery a discovered object
*
* @returns {Promise.<baja.Component|undefined>} the existing component that was found to match
* the given discovery node, or undefined if no such match was found.
*/
MgrLearn.prototype.getExisting = function (discovery) {
const comps = _.invoke(this.getModel().getRows(), 'getSubject');
if (typeof this.isExisting === 'function') {
return Promise.resolve(_.find(comps, (c) => {
return !!this.isExisting(discovery, c);
}));
}
return Promise.resolve(undefined);
};
////////////////////////////////////////////////////////////////
// Discovery Commands
////////////////////////////////////////////////////////////////
/**
* Creates and returns an array of discovery related commands.
* These are the LearnModeCommand (show/hide the learn pane),
* DiscoverCommand, CancelDiscoverCommand, AddCommands, and MatchCommands.
*
* @returns {Array.<module:bajaux/commands/Command>} a new array containing
* the discovery related commands.
*/
MgrLearn.prototype.makeDiscoveryCommands = function () {
return [
new LearnModeCommand(this),
new DiscoverCommand(this),
new CancelDiscoverCommand(this),
new AddCommand(this),
new QuickAddCommand(this),
new MatchCommand(this),
new QuickMatchCommand(this)
];
};
/**
* When the learn table is right-clicked, only learn table-specific commands will be shown. This
* function defines what those commands are.
*
* By default, these will be any commands with the `LEARN_CONTEXT_MENU` flag, plus Show Existing
* and Select All commands, but _not_ including any commands with flags that have not been
* configured (i.e. those commands with flags still set to the default value of `ALL`). You must
* explicitly set the command flags to a value that includes `LEARN_CONTEXT_MENU` to make it
* appear here.
*
* @returns {Promise.<Array.<module:bajaux/commands/Command>>}
* @since Niagara 4.14
* @see module:nmodule/webEditors/rc/wb/mgr/commands/MgrCommand~flags
*/
MgrLearn.prototype.getLearnTableCommands = function () {
const learnCmds = this.getCommandGroup().filter({
include: (cmd) => {
const flags = cmd.getFlags();
// omit flags that default to ALL, to make this less of a breaking change.
// most commands set to ALL are ok to show in the database table context menu, but not in
// the learn table.
return flags !== ALL && (flags & LEARN_CONTEXT_MENU);
}
}).getChildren();
const selectAll = new SelectAllCommand(this);
const hasOneDiscoverySelected = this.getLearnTable().getSelectedRows().length === 1;
if (hasOneDiscoverySelected) {
return ShowExistingCommand.make(this).then((showExisting) => [ ...learnCmds, showExisting, selectAll ]);
} else {
return Promise.resolve([ ...learnCmds, selectAll ]);
}
};
/**
* Get the 'LearnModeCommand' instance from the command group. This will be used by the
* discovery command to enable the learn pane if a discovery is started.
*
* @private
* @returns {module:nmodule/webEditors/rc/wb/mgr/commands/LearnModeCommand}
*/
MgrLearn.prototype.$getLearnModeCommand = function () {
return findCommand(this, LearnModeCommand);
};
/**
* Get the 'DiscoverCommand' instance from the command group. Invoking this will show
* the learn pane and call the `discover()` function on the manager.
*
* @private
* @returns {module:nmodule/webEditors/rc/wb/mgr/commands/DiscoverCommand}
*/
MgrLearn.prototype.$getDiscoveryCommand = function () {
return findCommand(this, DiscoverCommand);
};
/**
* Return the 'CancelDiscoveryCommand' instance from the command group. This is used
* to enable or disable the command as the discovery process starts or stops.
*
* @private
* @returns {module:nmodule/webEditors/rc/wb/mgr/commands/CancelDiscoverCommand}
*/
MgrLearn.prototype.$getCancelDiscoveryCommand = function () {
return findCommand(this, CancelDiscoverCommand);
};
/**
* Return the first 'AddCommand' instance from the command group. This is used
* to add selected items from the discovery table into the database table.
* It's also invoked from the drag and drop operation.
*
* @private
* @returns {module:nmodule/webEditors/rc/wb/mgr/commands/AddCommand}
*/
MgrLearn.prototype.$getAddCommand = function () {
return findCommand(this, AddCommand);
};
/**
* Return any 'AddCommand' instance from the command group. These commands are used
* to add selected items from the discovery table into the database table.
*
* @private
* @since Niagara 4.14
* @returns {Array.<module:nmodule/webEditors/rc/wb/mgr/commands/AddCommand>}
*/
MgrLearn.prototype.$getAddCommands = function () {
return findCommands(this, AddCommand);
};
/**
* Return the 'MatchCommand' instance from the command group. This will
* can be used to update an existing database item from a discovered item.
*
* @private
* @returns {module:nmodule/webEditors/rc/wb/mgr/commands/MatchCommand}
*/
MgrLearn.prototype.$getMatchCommand = function () {
return findCommand(this, MatchCommand);
};
/**
* Return any 'MatchCommand' instance from the command group. These commands are used
* to match selected items from the discovery table with the database table.
*
* @private
* @since Niagara 4.14
* @returns {Array.<module:nmodule/webEditors/rc/wb/mgr/commands/MatchCommand>}
*/
MgrLearn.prototype.$getMatchCommands = function () {
return findCommands(this, MatchCommand);
};
/**
* Return the 'QuickAddCommand' instance from the command group. This is used
* to add selected items from the discovery table into the database table without a dialog.
*
* @private
* @since Niagara 4.14
* @returns {module:nmodule/webEditors/rc/wb/mgr/commands/QuickAddCommand}
*/
MgrLearn.prototype.$getQuickAddCommand = function () {
return findCommand(this, QuickAddCommand);
};
/**
* Return the 'QuickMatchCommand' instance from the command group. This will
* can be used to update an existing database item from a discovered item without
* prompting to update the proposed data.
*
* @private
* @since Niagara 4.14
* @returns {module:nmodule/webEditors/rc/wb/mgr/commands/quickMatchCommand}
*/
MgrLearn.prototype.$getQuickMatchCommand = function () {
return findCommand(this, QuickMatchCommand);
};
/**
* @private
* @param {*} discovery
* @returns {Promise<Array.<module:nmodule/webEditors/rc/wb/mgr/MgrTypeInfo>|boolean>}
*/
MgrLearn.prototype.$getMgrTypesForDiscovery = function (discovery) {
const that = this;
return Promise.resolve(that.getTypesForDiscoverySubject(discovery))
.then((types) => {
return types && !_.isEmpty(types) && toMgrTypeInfos(types);
});
};
/**
* This function is used to determine if the `MatchCommand` should be enabled or not. It does
* this by checking to see if the selection in the main table (mainSelection) is of a type that
* the type from the selection in the learn table supports (learnSelection). What types are
* supported is determined by the `getTypesForDiscoverySubject()` that the manager has
* implemented. If this default behaviour is not enough or a different behaviour is required
* then this function can be overridden in the manager, with the new function either returning a
* `boolean` or a `Promise.<boolean>`.
* Resolves to true if the supplied mainSelection item can be matched to the supplied
* learnSelection item.
* @since Niagara 4.14
* @param {*} learnSelection the subject selected in the learn table
* @param {baja.Component} mainSelection the subject from the main table that the learnSelection
* subject is being matched to
* @returns {boolean|Promise.<boolean>}
*/
MgrLearn.prototype.isMatchable = function (learnSelection, mainSelection) {
const that = this;
return that.$getMgrTypesForDiscovery(learnSelection)
.then((types) => {
return Array.isArray(types) && !!types.find((type) => type.isMatchable(mainSelection));
})
.catch((e) => {
logError(e);
return false;
});
};
/**
* Test for discovery job completion.
* @private
*/
function isJobComplete(job) {
return !baja.AbsTime.DEFAULT.equals(job.get('endTime'));
}
/**
* Callback function used for receiving notifications of changes on the discovery
* job. This is used to listen for notification of the job completion, at which
* point we will emit the 'jobcomplete' event.
*
* @private
* @param mgr the manager instance
* @param prop the property that has changed on the subscribed job.
*/
function jobStateChanged(mgr, prop) {
const job = mgr.$learnJob;
// If the end time has changed, we'll look to see whether the job is
// now finished. If so, we'll emit the event on the manager. We'll
// also re-subscribe to the requested depth provided in the setJob()
// call. We'll previously have been subscribed at depth 1, but we can
// now subscribe to the job's children.
if (prop.getName() === 'endTime' && isJobComplete(job)) {
unsubscribeJob(mgr)
.then(() => subscribeJob(mgr, job, mgr.$jobSubDepth))
.then(() => {
jobComplete(mgr, job);
})
.catch(logError);
}
}
/**
* Subscribe to the discovery job that has been attached to the manager.
* If the job has not yet completed, the depth parameter is expected to
* be 1, to listen for changes to the job's properties. If the job is complete,
* the depth will be the value specified during the call to setJob(), as at
* that point we are in a position to be able to subscribe to the job's children,
* if needed. There is an optional change callback parameter - this is used to get
* our notification that the job has finished.
*
* @private
* @param mgr
* @param {baja.Component} job
* @param {Number} depth
* @param {Function} [changeCallback]
* @returns {Promise}
*/
function subscribeJob(mgr, job, depth, changeCallback) {
const subscriber = new DepthSubscriber(depth);
if (changeCallback) { subscriber.attach('changed', changeCallback); }
mgr.$jobSubscriber = subscriber;
return subscriber.subscribe(job);
}
/**
* Unsubscribe from the job and remove the property on the manager.
*
* @private
* @param mgr
* @returns {Promise}
*/
function unsubscribeJob(mgr) {
const subscriber = mgr.$jobSubscriber;
if (subscriber) {
subscriber.detach();
delete mgr.$jobSubscriber;
return subscriber.unsubscribeAll();
}
return Promise.resolve();
}
/**
* Function called when a job attached via `setJob()` has completed. This
* will emit a 'jobcomplete' event to any registered handlers.
*
* @private
* @param {module:nmodule/webEditors/rc/wb/mgr/MgrLearn} mgr
* @param {baja.Component} job the completed job.
*/
function jobComplete(mgr, job) {
const discoveryCmd = mgr.$getDiscoveryCommand();
const cancelCmd = mgr.$getCancelDiscoveryCommand();
if (discoveryCmd) { discoveryCmd.setEnabled(true); }
if (cancelCmd) { cancelCmd.setEnabled(false); }
mgr.emit('jobcomplete', job);
}
return MgrLearn;
});