view class doc
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558/// FOURJS_START_COPYRIGHT(D,2015)
/// Property of Four Js*
/// (c) Copyright Four Js 2015, 2022. 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('BoxWidget', ['WidgetGroupBase', 'WidgetFactory'],
  function(context, cls) {

    /**
     * Box Widget
     * @publicdoc Widgets
     * @class BoxWidget
     * @memberOf classes
     * @extends classes.WidgetGroupBase
     */
    cls.BoxWidget = context.oo.Class(cls.WidgetGroupBase, function($super) {
      return /** @lends classes.BoxWidget.prototype */ {
        __name: "BoxWidget",
        /** @type {?Boolean} */
        _canHaveSplitter: null,
        _splitters: null,
        _splitterIdentifier: null,
        _ignoreStoredSettings: false,
        /** @type {String} */
        _orientation: "",
        /** @type {Boolean} */
        _isSplit: false,
        /** @type {?Boolean} */
        _noSwipe: null,
        /** @type {Boolean} */
        _splitViewEnabled: false,
        /** @type {Boolean} */
        _navigationArrows: false,
        /** @type {Boolean} */
        _navigationDots: false,

        /**
         * @inheritDoc
         */
        constructor: function(opts) {
          this._splitters = [];
          $super.constructor.call(this, opts);
        },

        /**
         * @inheritDoc
         */
        destroy: function() {
          this._isSplit = false;
          if (this._splitters) {
            for (var i = this._splitters.length - 1; i > -1; i--) {
              var currentChildren = this._splitters[i].widget;
              currentChildren.destroy();
              currentChildren = null;
            }
            this._splitters.length = 0;
          }
          this._noSwipe = null;
          this.disableSwipe();
          if (this._gesture) {
            this._gesture.destroy();
            this._gesture = null;
          }
          if (this._focusRestoredHandler) {
            this._focusRestoredHandler();
            this._focusRestoredHandler = null;
          }
          $super.destroy.call(this);
        },

        /**
         * @inheritDoc
         */
        addChildWidget: function(widget, options) {
          if (!(widget instanceof cls.SplitterWidget)) {
            options = options || {
              position: ((this._children.length || -1) + 1) / 2
            };
            if (Object.isNumber(options.position)) {
              options.position = options.position * 2;
            }
            if (options.position) {
              var splitter = this._createSplitter();
              splitter.activateSplitter(this._canHaveSplitter);
              var onSplit = splitter.when(context.constants.widgetEvents.splitter, this._onSplit.bind(this));
              var onSplitStart = splitter.when(context.constants.widgetEvents.splitterStart, this._onSplitStart.bind(this));
              var onSplitEnd = splitter.when(context.constants.widgetEvents.splitterEnd, this._onSplitEnd.bind(this));
              this.addChildWidget(splitter, {
                position: options.position - 1
              });
              this._splitters.splice(options.position / 2, 0, {
                widget: splitter,
                onSplit: onSplit,
                onSplitStart: onSplitStart,
                onSplitEnd: onSplitEnd
              });
            }
          }
          $super.addChildWidget.call(this, widget, options);
        },

        /**
         * Create a splitter widget
         * @return {classes.HVBoxSplitterWidget} the created splitter widget
         * @protected
         */
        _createSplitter: function() {

          var splitter = cls.WidgetFactory.createWidget("HVBoxSplitter", this.getBuildParameters());
          splitter.setOrientation(this._orientation);
          return splitter;
        },

        /**
         * Get Box orientation
         * @returns {string}
         */
        getOrientation: function() {
          return this._orientation;
        },

        /**
         * _onSplit
         * @param {classes.Event} event the event
         * @param {classes.EventListener} sender the sender
         * @param {*} delta the delta value
         * @private
         */
        _onSplit: function(event, sender, delta) {
          this._layoutEngine.splitting(delta);
          this.emit(context.constants.widgetEvents.splitter);
        },

        /**
         * _onSplitStart
         * @param {classes.Event} event the event
         * @param {classes.EventListener} sender the sender
         * @private
         */
        _onSplitStart: function(event, sender) {
          this._layoutEngine.startSplitting((this._children.indexOf(sender) - 1) / 2);
        },

        /**
         * _onSplitEnd
         * @param {classes.Event} event the event
         * @param {classes.EventListener} sender the sender
         * @private
         */
        _onSplitEnd: function(event, sender) {
          this._layoutEngine.stopSplitting();
          if (!this._ignoreStoredSettings) {
            context.StoredSettingsService.setSplitter(this._splitterIdentifier.formName,
              this._splitterIdentifier.id, this._layoutEngine._referenceSplitHints);
          }
        },

        /**
         * @inheritDoc
         */
        removeChildWidget: function(widget) {
          if (!(widget instanceof cls.SplitterWidget)) {
            var pos = this._children.indexOf(widget) - 1;
            if (pos > 0) {
              this._children[pos].destroy();
            }
          } else {
            var item = this._splitters.find(function(splitter) {
              return splitter.widget === widget;
            });
            if (item) {
              item.onSplit();
              item.onSplitStart();
              item.onSplitEnd();
              this._splitters.remove(item);
            }
          }
          $super.removeChildWidget.call(this, widget);
        },

        /**
         * @inheritDoc
         */
        _addChildWidgetToDom: function(widget, position) {
          this.getLayoutEngine().registerChild(widget, position);
          var widgetHost = document.createElement('div');
          widgetHost.addClass('g_BoxElement');
          widget.getLayoutInformation().setHostElement(widgetHost);
          widgetHost.appendChild(widget._element);
          widgetHost.insertAt(position, this._containerElement);
        },

        /**
         * @inheritDoc
         */
        _removeChildWidgetFromDom: function(widget) {
          this.getLayoutEngine().unregisterChild(widget);
          var info = widget.getLayoutInformation(),
            host = info && info.getHostElement();
          if (host && host.parentNode === this._containerElement) {
            widget._element.remove();
            host.remove();
            host = null;
          }
        },

        /**
         * getIndexOfChild
         * @param {classes.WidgetBase} widget the widget
         * @return {number} the index
         */
        getIndexOfChild: function(widget) {
          var rawIndex = this._children.indexOf(widget);
          return rawIndex / (widget instanceof cls.SplitterWidget ? 1 : 2);
        },

        /**
         * ignoreStoredSettings
         * @param {boolean} ignore ignore stored settings
         */
        ignoreStoredSettings: function(ignore) {
          this._ignoreStoredSettings = Boolean(ignore);
        },

        /**
         * Initialize splitter layout engine hints
         */
        initSplitterLayoutEngine: function() {
          if (!this._ignoreStoredSettings) {
            if (this._layoutEngine.initSplitHints) {
              this._layoutEngine.initSplitHints(context.StoredSettingsService.getSplitter(
                this._splitterIdentifier.formName, this._splitterIdentifier.id));
            }
          }
        },

        /**
         * switchSplitters
         * @param {boolean} canSplit can split
         * @param {*} splitterId splitter id
         */
        switchSplitters: function(canSplit, splitterId) {
          if (this._canHaveSplitter !== canSplit) {
            this._splitterIdentifier = splitterId;

            this.initSplitterLayoutEngine();

            this._canHaveSplitter = canSplit;
            for (var i = 0; i < this._splitters.length; i++) {
              this._splitters[i].widget.activateSplitter(this._canHaveSplitter);
            }
          }
        },

        /**
         * @inheritDoc
         */
        _initLayout: function() {
          //Default is vertical, might change after
          this._layoutInformation = new cls.LayoutInformation(this);
          this._layoutEngine = new cls.HVBoxLayoutEngine(this);
          this._element.addClass("g_VBoxLayoutEngine");
        },

        /**
         * Set the box orientation
         * @param {String} orientation could be horizontal or vertical
         */
        setOrientation: function(orientation) {
          this._orientation = orientation || this.getDefaultOrientation();

          this._refreshSplit();

          if (this._layoutEngine.setOrientation) {
            this._layoutEngine.setOrientation(this._orientation);

            this._splitters.forEach((splitter) => {
              splitter.widget.setOrientation(this._orientation);
            });
          }

        },

        /**
         * Get default orientation
         * @return {string} - horizontal if HBOX, vertical if VBOX or not known
         */
        getDefaultOrientation: function() {
          return "vertical";
        },

        // SPLIT & SWIPE

        /**
         * Set Split VM attribute and enable/disable split view
         */
        setSplit: function(isSplit) {
          if (this._isSplit !== isSplit) {
            this._isSplit = isSplit;
            this._refreshSplit();
          }
        },

        /**
         * Enable/disable split depending of orientation updates and current split value
         * @private
         */
        _refreshSplit: function() {
          if (this._isSplit && this.getOrientation() === "horizontal") {
            this.enableSplitView();
          } else {
            this.disableSplitView();
          }
        },

        /**
         * Enable/disable navigation Arrows 4ST attribute
         * @param active
         */
        setNavigationArrows: function(active) {
          this._navigationArrows = active;
          if (this._gesture) {
            if (active) {
              this._gesture.addArrows();
            } else {
              this._gesture.removeArrows();
            }
          }
        },

        /**
         * Enable/disable navigation dots 4ST attribute
         * @param active
         */
        setNavigationDots: function(active) {
          this._navigationDots = active;
          if (this._gesture) {
            if (active) {
              this._gesture.addDots();
            } else {
              this._gesture.removeDots();
            }
          }
        },

        /**
         * Enable Split view layout of HBOX or VBOX orientation horizontal
         */
        enableSplitView: function() {
          if (!this._splitViewEnabled) {
            this._splitViewEnabled = true;
            var oldEngine = this._layoutEngine;
            this.addClass("splitView");
            this._layoutEngine = new cls.SplitLayoutEngine(this);
            this._layoutInformation.getStretched().setDefaultX(true);
            this._layoutInformation.getStretched().setDefaultY(true);

            // enable swipe gesture
            this.enableSwipe();

            for (var i = 0; i < this.getChildren().length; i++) {
              var child = this.getChildren()[i];
              if (child instanceof cls.SplitterWidget) { // hide splitter in splitview
                child.setHidden(true);
              } else if (!child.isHidden()) {
                oldEngine.unregisterChild(child);
                this._layoutEngine.registerChild(child);
              }
              if (child.isHidden()) {
                if (child.getElement().parentNode) {
                  child.getElement().parentNode.addClass("hidden");
                }
              }

              this._listenToGroupVisibilityChange(child);
            }

            // give user a possibility to switch views
            if ((this._noSwipe || window.hasMouse()) && !this._gesture.hasArrows() && !this._gesture.hasDots()) {
              this._gesture.addDots();
            }

            if (!this._focusRestoredHandler) {
              this._focusRestoredHandler = context.SessionService.getCurrent().getCurrentApplication().focus.when(context.constants
                .widgetEvents.focusRestored,
                function() {
                  this._focusRestoredHandler = null;
                  this._layoutEngine.refreshLayout();
                }.bind(this), true);
            }
          }
        },

        /**
         * Disable Split view layout of HBOX or VBOX orientation horizontal
         */
        disableSplitView: function() {
          if (this._splitViewEnabled) {
            this._splitViewEnabled = false;

            if (this._focusRestoredHandler) {
              this._focusRestoredHandler();
              this._focusRestoredHandler = null;
            }

            var oldEngine = this._layoutEngine;
            this.removeClass("splitView");
            this._layoutEngine = new cls.HVBoxLayoutEngine(this);
            this._layoutInformation.getStretched().setDefaultX(false);
            this._layoutInformation.getStretched().setDefaultY(false);

            this.disableSwipe();

            for (var i = this.getChildren().length - 1; i >= 0; i--) {
              var child = this.getChildren()[i];
              if (child.isHidden()) {
                if (child.getElement().parentNode) {
                  child.getElement().parentNode.removeClass("hidden");
                }
              }
              if (child instanceof cls.SplitterWidget) { // show splitter in splitview
                child.setHidden(false);
              }
              oldEngine.unregisterChild(child);
              this._layoutEngine.registerChild(child);

              this._stopListeningToGroupVisibilityChange(child);
            }

            this._layoutEngine.setOrientation(this.getOrientation(), true);

          }

        },

        /**
         * Listen to group visibility change to be able to add/remove it from swipeable element
         * @param {classes.WidgetBase} widget - SplitView child group
         * @private
         */
        _listenToGroupVisibilityChange: function(widget) {
          if (!(widget instanceof cls.SplitterWidget)) {
            widget.detachVisibilityChangeListener = widget.when(context.constants.widgetEvents.visibilityChange, this._addRemoveGroup.bind(
              this, widget));
            // each child group need to listen to focus to display. Mostly needed for folder pages.
            if (this._gesture) {
              widget.detachSplitViewChangeListener = widget.when(context.constants.widgetEvents.splitViewChange, this._gesture.swipeTo.bind(
                this._gesture, widget, false));
            }
          }
        },

        /**
         * Stop to listen to group visibility change
         * @param {classes.WidgetBase} widget - SplitView child group
         * @private
         */
        _stopListeningToGroupVisibilityChange: function(widget) {
          if (!(widget instanceof cls.SplitterWidget)) {
            if (widget.detachVisibilityChangeListener) {
              widget.detachVisibilityChangeListener();
              delete widget.detachVisibilityChangeListener;
            }
            if (widget.detachSplitViewChangeListener) {
              widget.detachSplitViewChangeListener();
              delete widget.detachSplitViewChangeListener;
            }
          }
        },

        /**
         * Add or remove a group in the list of swipeable groups of the HBox SplitView depending of group visibility
         * @param {classes.WidgetBase} widget - SplitView child group
         * @private
         */
        _addRemoveGroup: function(widget) {
          if (widget.isHidden()) {
            this._layoutEngine.unregisterChild(widget);
            widget.getElement().parentNode.addClass("hidden");
          } else {
            this._layoutEngine.registerChild(widget);
            widget.getElement().parentNode.removeClass("hidden");
          }
        },

        // SWIPE

        /**
         * Set NoSwipe attribute value and enable/disable swipe gestures
         * @param noSwipe
         */
        setNoSwipe: function(noSwipe) {
          if (this._noSwipe !== noSwipe) {
            this._noSwipe = noSwipe;
            if (this.isSwipeable()) { // if we can swipe, then enable it
              this.enableSwipe();
            } else {
              this.disableSwipe();
            }
          }
        },

        /**
         * Determine if swipe gestures are supported on current device
         * @returns {*}
         */
        isSwipeable: function() {
          return this._isSplit && !this._noSwipe && window.isTouchDevice() && this.getOrientation() === "horizontal";
        },

        /**
         * Enable swipe functionality on folder pages
         */
        enableSwipe: function() {
          if (!this._gesture) {
            var params = {
              noSwipe: !this.isSwipeable(),
              arrows: this._navigationArrows,
              dots: this._navigationDots
            };
            this._gesture = new cls.GestureService(this, params);
          } else {
            this._gesture.addTouch();
          }
        },

        /**
         * Disable swipe functionality on folder pages
         */
        disableSwipe: function() {
          if (this._isSplit && this.getOrientation() ===
            "horizontal") { // Both SPLIT and NOSWIPE enabled. Add dots to be able to switch of view
            if (this._gesture) {
              this._gesture.removeTouch();
              if (!this._gesture.hasArrows() && !this._gesture.hasDots()) {
                this._gesture.addDots();
              }
            }
          } else { // can delete all instance of gesture
            if (this._gesture) {
              this._gesture.destroy();
              this._gesture = null;
            }
          }
        }

      };
    });
    cls.WidgetFactory.registerBuilder('Box', cls.BoxWidget);
  });