src/Controller.js
import {Map} from "./Map.js";
/** @private */
class Controller {
/**
* @protected
*/
constructor(parent, cfg, server, viewer) {
this.bimViewer = (parent ? (parent.bimViewer || parent) : this);
this.server = parent ? parent.server : server;
this.viewer = parent ? parent.viewer : viewer;
this._children = [];
if (parent) {
parent._children.push(this);
}
this._subIdMap = null; // Subscription subId pool
this._subIdEvents = null; // Subscription subIds mapped to event names
this._eventSubs = null; // Event names mapped to subscribers
this._events = null; // Maps names to events
this._eventCallDepth = 0; // Helps us catch stack overflows from recursive events
this._enabled = null; // Used by #setEnabled() and #getEnabled()
this._active = null; // Used by #setActive() and #getActive()
}
/**
* Fires an event on this Controller.
*
* @protected
*
* @param {String} event The event type name
* @param {Object} value The event parameters
* @param {Boolean} [forget=false] When true, does not retain for subsequent subscribers
*/
fire(event, value, forget) {
if (!this._events) {
this._events = {};
}
if (!this._eventSubs) {
this._eventSubs = {};
}
if (forget !== true) {
this._events[event] = value || true; // Save notification
}
const subs = this._eventSubs[event];
let sub;
if (subs) { // Notify subscriptions
for (const subId in subs) {
if (subs.hasOwnProperty(subId)) {
sub = subs[subId];
this._eventCallDepth++;
if (this._eventCallDepth < 300) {
sub.callback.call(sub.scope, value);
} else {
this.error("fire: potential stack overflow from recursive event '" + event + "' - dropping this event");
}
this._eventCallDepth--;
}
}
}
}
/**
* Subscribes to an event on this Controller.
*
* The callback is be called with this component as scope.
*
* @param {String} event The event
* @param {Function} callback Called fired on the event
* @param {Object} [scope=this] Scope for the callback
* @return {String} Handle to the subscription, which may be used to unsubscribe with {@link #off}.
*/
on(event, callback, scope) {
if (!this._events) {
this._events = {};
}
if (!this._subIdMap) {
this._subIdMap = new Map(); // Subscription subId pool
}
if (!this._subIdEvents) {
this._subIdEvents = {};
}
if (!this._eventSubs) {
this._eventSubs = {};
}
let subs = this._eventSubs[event];
if (!subs) {
subs = {};
this._eventSubs[event] = subs;
}
const subId = this._subIdMap.addItem(); // Create unique subId
subs[subId] = {
callback: callback,
scope: scope || this
};
this._subIdEvents[subId] = event;
const value = this._events[event];
if (value !== undefined) { // A publication exists, notify callback immediately
callback.call(scope || this, value);
}
return subId;
}
/**
* Cancels an event subscription that was previously made with {@link Controller#on} or {@link Controller#once}.
*
* @param {String} subId Subscription ID
*/
off(subId) {
if (subId === undefined || subId === null) {
return;
}
if (!this._subIdEvents) {
return;
}
const event = this._subIdEvents[subId];
if (event) {
delete this._subIdEvents[subId];
const subs = this._eventSubs[event];
if (subs) {
delete subs[subId];
}
this._subIdMap.removeItem(subId); // Release subId
}
}
/**
* Subscribes to the next occurrence of the given event, then un-subscribes as soon as the event is handled.
*
* This is equivalent to calling {@link Controller#on}, and then calling {@link Controller#off} inside the callback function.
*
* @param {String} event Data event to listen to
* @param {Function} callback Called when fresh data is available at the event
* @param {Object} [scope=this] Scope for the callback
*/
once(event, callback, scope) {
const self = this;
const subId = this.on(event,
function (value) {
self.off(subId);
callback.call(scope || this, value);
},
scope);
}
/**
* Logs a console debugging message for this Controller.
*
* The console message will have this format: *````[LOG] [<component type> <component id>: <message>````*
*
* @protected
*
* @param {String} message The message to log
*/
log(message) {
message = "[LOG] " + message;
window.console.log(message);
}
/**
* Logs a warning for this Controller to the JavaScript console.
*
* The console message will have this format: *````[WARN] [<component type> =<component id>: <message>````*
*
* @protected
*
* @param {String} message The message to log
*/
warn(message) {
message = "[WARN] " + message;
window.console.warn(message);
}
/**
* Logs an error for this Controller to the JavaScript console.
*
* The console message will have this format: *````[ERROR] [<component type> =<component id>: <message>````*
*
* @protected
*
* @param {String} message The message to log
*/
error(message) {
message = "[ERROR] " + message;
window.console.error(message);
}
_mutexActivation(controllers) {
const numControllers = controllers.length;
for (let i = 0; i < numControllers; i++) {
const controller = controllers[i];
controller.on("active", (function () {
const _i = i;
return function (active) {
if (!active) {
return;
}
for (let j = 0; j < numControllers; j++) {
if (j === _i) {
continue;
}
controllers[j].setActive(false);
}
};
})());
}
}
/**
* Enables or disables this Controller.
*
* Fires an "enabled" event on update.
*
* @protected
* @param {boolean} enabled Whether or not to enable.
*/
setEnabled(enabled) {
if (this._enabled === enabled) {
return;
}
this._enabled = enabled;
this.fire("enabled", this._enabled);
}
/**
* Gets whether or not this Controller is enabled.
*
* @protected
*
* @returns {boolean}
*/
getEnabled() {
return this._enabled;
}
/**
* Activates or deactivates this Controller.
*
* Fires an "active" event on update.
*
* @protected
*
* @param {boolean} active Whether or not to activate.
*/
setActive(active) {
if (this._active === active) {
return;
}
this._active = active;
this.fire("active", this._active);
}
/**
* Gets whether or not this Controller is active.
*
* @protected
*
* @returns {boolean}
*/
getActive() {
return this._active;
}
/**
* Destroys this Controller.
*
* @protected
*
*/
destroy() {
if (this.destroyed) {
return;
}
/**
* Fired when this Controller is destroyed.
* @event destroyed
*/
this.fire("destroyed", this.destroyed = true);
this._subIdMap = null;
this._subIdEvents = null;
this._eventSubs = null;
this._events = null;
this._eventCallDepth = 0;
for (let i = 0, len = this._children.length; i < len; i++) {
this._children[i].destroy();
}
this._children = [];
}
}
export {Controller};