BIMviewer
1import {Map} from "./Map.js";2
3/** @private */
4class Controller {5
6/**7* @protected
8*/
9constructor(parent, cfg, server, viewer) {10
11this.bimViewer = (parent ? (parent.bimViewer || parent) : this);12this.server = parent ? parent.server : server;13this.viewer = parent ? parent.viewer : viewer;14
15this._children = [];16
17if (parent) {18parent._children.push(this);19}20
21this._subIdMap = null; // Subscription subId pool22this._subIdEvents = null; // Subscription subIds mapped to event names23this._eventSubs = null; // Event names mapped to subscribers24this._events = null; // Maps names to events25this._eventCallDepth = 0; // Helps us catch stack overflows from recursive events26
27this._enabled = null; // Used by #setEnabled() and #getEnabled()28this._active = null; // Used by #setActive() and #getActive()29}30
31/**32* Fires an event on this Controller.
33*
34* @protected
35*
36* @param {String} event The event type name
37* @param {Object} value The event parameters
38* @param {Boolean} [forget=false] When true, does not retain for subsequent subscribers
39*/
40fire(event, value, forget) {41if (!this._events) {42this._events = {};43}44if (!this._eventSubs) {45this._eventSubs = {};46}47if (forget !== true) {48this._events[event] = value || true; // Save notification49}50const subs = this._eventSubs[event];51let sub;52if (subs) { // Notify subscriptions53for (const subId in subs) {54if (subs.hasOwnProperty(subId)) {55sub = subs[subId];56this._eventCallDepth++;57if (this._eventCallDepth < 300) {58sub.callback.call(sub.scope, value);59} else {60this.error("fire: potential stack overflow from recursive event '" + event + "' - dropping this event");61}62this._eventCallDepth--;63}64}65}66}67
68/**69* Subscribes to an event on this Controller.
70*
71* The callback is be called with this component as scope.
72*
73* @param {String} event The event
74* @param {Function} callback Called fired on the event
75* @param {Object} [scope=this] Scope for the callback
76* @return {String} Handle to the subscription, which may be used to unsubscribe with {@link #off}.
77*/
78on(event, callback, scope) {79if (!this._events) {80this._events = {};81}82if (!this._subIdMap) {83this._subIdMap = new Map(); // Subscription subId pool84}85if (!this._subIdEvents) {86this._subIdEvents = {};87}88if (!this._eventSubs) {89this._eventSubs = {};90}91let subs = this._eventSubs[event];92if (!subs) {93subs = {};94this._eventSubs[event] = subs;95}96const subId = this._subIdMap.addItem(); // Create unique subId97subs[subId] = {98callback: callback,99scope: scope || this100};101this._subIdEvents[subId] = event;102const value = this._events[event];103if (value !== undefined) { // A publication exists, notify callback immediately104callback.call(scope || this, value);105}106return subId;107}108
109/**110* Cancels an event subscription that was previously made with {@link Controller#on} or {@link Controller#once}.
111*
112* @param {String} subId Subscription ID
113*/
114off(subId) {115if (subId === undefined || subId === null) {116return;117}118if (!this._subIdEvents) {119return;120}121const event = this._subIdEvents[subId];122if (event) {123delete this._subIdEvents[subId];124const subs = this._eventSubs[event];125if (subs) {126delete subs[subId];127}128this._subIdMap.removeItem(subId); // Release subId129}130}131
132/**133* Subscribes to the next occurrence of the given event, then un-subscribes as soon as the event is handled.
134*
135* This is equivalent to calling {@link Controller#on}, and then calling {@link Controller#off} inside the callback function.
136*
137* @param {String} event Data event to listen to
138* @param {Function} callback Called when fresh data is available at the event
139* @param {Object} [scope=this] Scope for the callback
140*/
141once(event, callback, scope) {142const self = this;143const subId = this.on(event,144function (value) {145self.off(subId);146callback.call(scope || this, value);147},148scope);149}150
151/**152* Logs a console debugging message for this Controller.
153*
154* The console message will have this format: *````[LOG] [<component type> <component id>: <message>````*
155*
156* @protected
157*
158* @param {String} message The message to log
159*/
160log(message) {161message = "[LOG] " + message;162window.console.log(message);163}164
165/**166* Logs a warning for this Controller to the JavaScript console.
167*
168* The console message will have this format: *````[WARN] [<component type> =<component id>: <message>````*
169*
170* @protected
171*
172* @param {String} message The message to log
173*/
174warn(message) {175message = "[WARN] " + message;176window.console.warn(message);177}178
179/**180* Logs an error for this Controller to the JavaScript console.
181*
182* The console message will have this format: *````[ERROR] [<component type> =<component id>: <message>````*
183*
184* @protected
185*
186* @param {String} message The message to log
187*/
188error(message) {189message = "[ERROR] " + message;190window.console.error(message);191}192
193_mutexActivation(controllers) {194const numControllers = controllers.length;195for (let i = 0; i < numControllers; i++) {196const controller = controllers[i];197if (controller) {198controller.on("active", (function () {199const _i = i;200return function (active) {201if (!active) {202return;203}204for (let j = 0; j < numControllers; j++) {205if (j === _i) {206continue;207}208controllers[j].setActive(false);209}210};211})());212}213}214}215
216/**217* Enables or disables this Controller.
218*
219* Fires an "enabled" event on update.
220*
221* @protected
222* @param {boolean} enabled Whether or not to enable.
223*/
224setEnabled(enabled) {225if (this._enabled === enabled) {226return;227}228this._enabled = enabled;229this.fire("enabled", this._enabled);230}231
232/**233* Gets whether or not this Controller is enabled.
234*
235* @protected
236*
237* @returns {boolean}
238*/
239getEnabled() {240return this._enabled;241}242
243/**244* Activates or deactivates this Controller.
245*
246* Fires an "active" event on update.
247*
248* @protected
249*
250* @param {boolean} active Whether or not to activate.
251*/
252setActive(active) {253if (this._active === active) {254return;255}256this._active = active;257this.fire("active", this._active);258}259
260/**261* Gets whether or not this Controller is active.
262*
263* @protected
264*
265* @returns {boolean}
266*/
267getActive() {268return this._active;269}270
271/**272* Destroys this Controller.
273*
274* @protected
275*
276*/
277destroy() {278if (this.destroyed) {279return;280}281/**282* Fired when this Controller is destroyed.
283* @event destroyed
284*/
285this.fire("destroyed", this.destroyed = true);286this._subIdMap = null;287this._subIdEvents = null;288this._eventSubs = null;289this._events = null;290this._eventCallDepth = 0;291for (let i = 0, len = this._children.length; i < len; i++) {292this._children[i].destroy();293}294this._children = [];295}296}
297
298export {Controller};