view class doc
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439/// FOURJS_START_COPYRIGHT(D,2014)
/// Property of Four Js*
/// (c) Copyright Four Js 2014, 2023. All Rights Reserved.
/// * Trademark of Four Js Development Tools Europe Ltd
///   in the United States and elsewhere
///
/// This file can be modified by licensees according to the
/// product manual.
/// FOURJS_END_COPYRIGHT

'use strict';

modulum('ModelHelper', ['EventListener'],
  function(context, cls) {
    /**
     * Helper to ease AUI tree access for customized widgets,
     * Manages client side life cycle representation of the node.
     * @class ModelHelper
     * @memberOf classes
     * @extends classes.EventListener
     * @publicdoc Base
     */
    cls.ModelHelper = context.oo.Class(cls.EventListener, function($super) {
      return /** @lends classes.ModelHelper.prototype */ {
        __name: 'ModelHelper',

        /** @type {classes.WidgetBase} */
        _widget: null,
        /** @type {?Function[]} */
        _newApplicationsListeners: null,
        /** @type {?Function[]} */
        _closeApplicationsListeners: null,
        /** @type {?Function[]} */
        _sessionEndListeners: null,
        /** @type {?Function[]} */
        _auiUpdateListeners: null,
        /** @type {?Function[]} */
        _currentWindowListeners: null,
        /** @type {?Function[]} */
        _activeListenersHandlers: null,

        /**
         * @constructs
         * @param {classes.WidgetBase} widget the widget to handle
         */
        constructor: function(widget) {
          $super.constructor.call(this);
          this._widget = widget;
        },

        /**
         * Unbind all active listeners and destroy the current instance.
         */
        destroy: function() {
          // unbind all active listeners
          var h = null;
          if (this._activeListenersHandlers) {
            while (this._activeListenersHandlers.length > 0) {
              h = this._activeListenersHandlers.pop();
              if (h) {
                h(); // unregister listener
              }
            }
            h = null;
            this._activeListenersHandlers = null;
          }
          this._newApplicationsListeners = null;
          this._closeApplicationsListeners = null;
          this._sessionEndListeners = null;
          this._auiUpdateListeners = null;
          this._currentWindowListeners = null;

          this._widget = null;
          $super.destroy.call(this);
        },

        /**
         * Registers a listener which will be called when a new application is started
         * @param listener {Function=} - the function to call when a new application is started
         * @returns {function(this:classes.ModelHelper)} The unregister handler. Simply call this function to stop listening for new applications
         * @publicdoc
         */
        addNewApplicationListener: function(listener) {
          if (this._activeListenersHandlers === null) {
            this._activeListenersHandlers = [];
          }
          if (this._newApplicationsListeners === null) {
            this._newApplicationsListeners = [];

            var onApplicationAdded = function(event, sender, app) {
              // for each iteration we recheck if array is defined because modelhelper may have been destroyed in a previous iteration
              for (var i = 0; this._newApplicationsListeners && i < this._newApplicationsListeners.length; ++i) {
                this._newApplicationsListeners[i](app);
              }
            }.bind(this);

            this._activeListenersHandlers.push(context.SessionService.onSessionAdded(function(event, sender, session) {
              // If null, widget may have been destroyed. We stop to create listeners. Otherwise, we attach new one.
              if (this._activeListenersHandlers) {
                this._activeListenersHandlers.push(session.when(context.constants.baseEvents.applicationAdded,
                  onApplicationAdded));
              }
            }.bind(this)));
            var session = context.SessionService.getCurrent();
            if (session) {
              this._activeListenersHandlers.push(session.when(context.constants.baseEvents.applicationAdded, onApplicationAdded));
            }
          }
          this._newApplicationsListeners.push(listener);

          return function() {
            if (this._newApplicationsListeners) {
              this._newApplicationsListeners.remove(listener);
            }
          }.bind(this);
        },

        /**
         * Registers a listener which will be called when an application is closed
         * @param listener {Function=} - the function to call when an application is closed
         * @returns {function(this:classes.ModelHelper)} The unregister handler. Simply call this function to stop listening for closed applications
         * @publicdoc
         */
        addCloseApplicationListener: function(listener) {
          if (this._activeListenersHandlers === null) {
            this._activeListenersHandlers = [];
          }
          if (this._closeApplicationsListeners === null) {
            this._closeApplicationsListeners = [];

            var onApplicationClosed = function(event, sender, app) {
              // for each iteration we recheck if array is defined because modelhelper may have been destroyed in a previous iteration
              for (var i = 0; this._closeApplicationsListeners && i < this._closeApplicationsListeners.length; ++i) {
                this._closeApplicationsListeners[i](app);
              }
            }.bind(this);

            this._activeListenersHandlers.push(context.SessionService.onSessionAdded(function(event, sender, session) {
              // If null, widget may have been destroyed. We stop to create listeners. Otherwise, we attach new one.
              if (this._activeListenersHandlers) {
                this._activeListenersHandlers.push(session.when(context.constants.baseEvents.applicationRemoved,
                  onApplicationClosed));
              }
            }.bind(this)));
            var session = context.SessionService.getCurrent();
            if (session) {
              this._activeListenersHandlers.push(session.when(context.constants.baseEvents.applicationRemoved, onApplicationClosed));
            }
          }
          this._closeApplicationsListeners.push(listener);

          return function() {
            if (this._closeApplicationsListeners) {
              this._closeApplicationsListeners.remove(listener);
            }
          }.bind(this);
        },

        /**
         * Registers a listener which will be called when all applications are closed and session end page is displayed.
         * @param listener {Function=} - the function to call when session end page is displayed
         * @returns {function(this:classes.ModelHelper)} The unregister handler. Simply call this function to stop listening for session end
         * @publicdoc
         */
        addSessionEndListener: function(listener) {
          if (this._activeListenersHandlers === null) {
            this._activeListenersHandlers = [];
          }
          if (this._sessionEndListeners === null) {
            this._sessionEndListeners = [];

            var onSessionEndClosed = function(event, sender, sessionId) {
              // for each iteration we recheck if array is defined because modelhelper may have been destroyed in a previous iteration
              for (var i = 0; this._sessionEndListeners && i < this._sessionEndListeners.length; ++i) {
                this._sessionEndListeners[i](sessionId);
              }
            }.bind(this);

            this._activeListenersHandlers.push(context.SessionService.onSessionAdded(function(event, sender, session) {
              // If null, widget may have been destroyed. We stop to create listeners. Otherwise, we attach new one.
              if (this._activeListenersHandlers) {
                this._activeListenersHandlers.push(session.when(context.constants.baseEvents.displayEnd, onSessionEndClosed));
              }
            }.bind(this)));
            var session = context.SessionService.getCurrent();
            if (session) {
              this._activeListenersHandlers.push(session.when(context.constants.baseEvents.displayEnd, onSessionEndClosed));
            }
          }
          this._sessionEndListeners.push(listener);

          return function() {
            if (this._sessionEndListeners) {
              this._sessionEndListeners.remove(listener);
            }
          }.bind(this);
        },

        /**
         * Registers a listener which will be called when the current window changes
         * @param listener {Function=} - the function to call when the current window changes
         * @returns {function(this:classes.ModelHelper)} The unregister handler. Simply call this function to stop listening for window changes
         * @publicdoc
         */
        addCurrentWindowChangeListener: function(listener) {
          if (this._activeListenersHandlers === null) {
            this._activeListenersHandlers = [];
          }
          if (this._currentWindowListeners === null) {
            this._currentWindowListeners = [];

            this._activeListenersHandlers.push(context.HostService.onCurrentWindowChange(
              /**
               * @param {classes.Event} event event object
               * @param {Object} src event emitter
               * @param {classes.WindowWidget} win data
               */
              function(event, sender, win) {
                var i = 0;
                var windowNode = null;
                var w = win;
                var appHash = null;
                while (w) {
                  if (w._appHash !== undefined) {
                    appHash = w._appHash;
                    break;
                  }
                  w = w.getParentWidget();
                }
                if (appHash !== null) {
                  var app = null;
                  var session = context.SessionService.getCurrent();
                  for (i = 0; i < session._applications.length; ++i) {
                    var a = session._applications[i];
                    if (a.applicationHash === appHash) {
                      app = a;
                      break;
                    }
                  }
                  windowNode = app.model.getNode(win._auiTag);
                }
                // for each iteration we recheck if array is defined because modelhelper may have been destroyed in a previous iteration
                for (i = 0; this._currentWindowListeners && i < this._currentWindowListeners.length; ++i) {
                  this._currentWindowListeners[i](windowNode);
                }
              }.bind(this)));
          }
          this._currentWindowListeners.push(listener);

          return function() {
            if (this._currentWindowListeners) {
              this._currentWindowListeners.remove(listener);
            }
          }.bind(this);
        },

        /**
         * Registers a listener which will be called when any DVM answer is received.
         * You can update your widget in the provided callback
         * This listening method is general to all started applications. If your UI updates are heavy, prefer more fine grained update notification mechanisms
         * @param {Function=} listener - the function to call when a new application is started
         * @returns {function(this:classes.ModelHelper)} The unregister handler. Simply call this function to stop listening for new applications
         * @publicdoc
         */
        addAuiUpdateListener: function(listener) {
          if (this._activeListenersHandlers === null) {
            this._activeListenersHandlers = [];
          }
          if (this._auiUpdateListeners === null) {
            this._auiUpdateListeners = [];

            this._activeListenersHandlers.push(context.SessionService.onSessionAdded(function(event, session) {
              // If null, widget may have been destroyed. We stop to create listeners. Otherwise, we attach new one.
              if (this._activeListenersHandlers) {
                this._activeListenersHandlers.push(session.when(context.constants.baseEvents.applicationAdded, this._onApplicationAdded
                  .bind(this)));
              }
            }.bind(this)));
            var session = context.SessionService.getCurrent();
            if (session) {
              for (var i = 0; i < session._applications.length; ++i) {
                this._activeListenersHandlers.push(session._applications[i].dvm.onOrdersManaged(this._dispatchUpdate.bind(this)));
              }
            }
          }
          this._auiUpdateListeners.push(listener);
          return function() {
            if (this._auiUpdateListeners) {
              this._auiUpdateListeners.remove(listener);
            }
          }.bind(this);
        },

        _dispatchUpdate: function(event, src, data) {
          // for each iteration we recheck if array is defined because modelhelper may have been destroyed in a previous iteration
          for (var i = 0; this._auiUpdateListeners && i < this._auiUpdateListeners.length; ++i) {
            this._auiUpdateListeners[i](src, data);
          }
        },

        _onApplicationAdded: function(event, app) {
          if (this._activeListenersHandlers) {
            this._activeListenersHandlers.push(app.dvm.onOrdersManaged(this._dispatchUpdate.bind(this)));
          }
        },

        /**
         * Get the current application
         * @returns {?classes.VMApplication} the currently visible application or null if it cannot be found.
         * @publicdoc
         */
        getCurrentApplication: function() {
          var session = context.SessionService.getCurrent();
          if (session) {
            return session.getCurrentApplication();
          }
          return null;
        },

        /**
         * Get the widget application
         * @returns {?classes.VMApplication} the VM application to which this widget is attached. null if the widget isn't below a VM application
         * @publicdoc
         */
        getApplication: function() {
          var session = context.SessionService.getCurrent();
          if (session) {
            for (var i = 0; i < session.getApplications().length; ++i) {
              var app = session.getApplications()[i];
              if (app.applicationHash === this._widget._appHash) {
                return app;
              }
            }
          }
          return null;
        },

        /**
         * Get a node with its id
         * @param {number} idRef - the VM id of the node to return
         * @returns {?classes.NodeBase} the requested node or null if it couldn't be found
         * @publicdoc
         */
        getNode: function(idRef) {
          var app = this.getApplication();
          if (app) {
            return app.model.getNode(idRef);
          }
          return null;
        },

        /**
         * Get the UserInterface Node
         * @returns {?classes.NodeBase} the UserInterface node of the current application or null if it coulnd't be found
         * @publicdoc
         */
        getUserInterfaceNode: function() {
          return this.getNode(0);
        },

        /**
         * Get Anchor Node of the widget
         * @returns {?classes.NodeBase} the AUI anchor node of this widget. Generally the corresponding node or the node holding the displayed value
         * @publicdoc
         */
        getAnchorNode: function() {
          if (this._widget._auiTag !== null) {
            var app = this.getApplication();
            if (app) {
              return app.model.getNode(this._widget._auiTag);
            }
          }
          return null;
        },

        /**
         * Applies only to entry fields (FormField, Matrix or TableColumn)
         * @returns {?classes.NodeBase} the field node corresponding to the widget or null if it doesn't apply
         * @publicdoc
         */
        getFieldNode: function() {
          var anchorNode = this.getAnchorNode();
          if (anchorNode) {
            if (anchorNode.getTag() === 'FormField') {
              return anchorNode;
            } else if (anchorNode.getTag() === 'Value') {
              return anchorNode.getParentNode().getParentNode();
            }
          }
          return null;
        },

        /**
         * Applies only to entry fields (FormField, Matrix or TableColumn)
         * A decoration node is the node holding visual information (Edit, CheckBox, ComboBox, etc...)
         * @returns {?classes.NodeBase} the decoration node corresponding to the widget or null if it doesn't apply
         * @publicdoc
         */
        getDecorationNode: function() {
          var fieldNode = this.getFieldNode();
          if (fieldNode) {
            return fieldNode.getChildren()[0];
          }
          return null;
        },

        /**
         * Try to find & execute an action by name (search in the active Dialog/Menu of the current Window).
         * Note that the focused widget with pending changes will send its value to the VM
         * @param {string} name of the action
         * @returns {boolean} true if an action has been found and executed, false otherwise.
         * @publicdoc
         */
        executeActionByName: function(name) {
          var actionExecuted = false;
          var currentApp = this.getCurrentApplication();
          if (currentApp) {
            // TODO GBC-1760 we should use ActionApplicationService to search an action
            var activeWindow = currentApp.getVMWindow();
            if (activeWindow) {
              var activeDialog = activeWindow.getActiveDialog();
              if (activeDialog) {
                var action = activeDialog.getFirstChildWithAttribute(null, 'name', name);
                if (action) {
                  currentApp.action.execute(action.getId(), null, {
                    sendValue: true
                  });
                  actionExecuted = true;
                }
              }
            }
          }
          return actionExecuted;
        }
      };
    });
  }
);