All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rwt.widgets.base.Parent.js Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2004, 2022 1&1 Internet AG, Germany, http://www.1und1.de,
 *                          EclipseSource and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    1&1 Internet AG and others - original API and implementation
 *    EclipseSource - adaptation for the Eclipse Remote Application Platform
 ******************************************************************************/

/**
 * Abstract base class for all widget which have child widgets (e.g. layout manager)
 *
 * Don't instantiate this class directly.
 */
rwt.qx.Class.define("rwt.widgets.base.Parent",
{
  extend : rwt.widgets.base.Widget,




  /*
  *****************************************************************************
     CONSTRUCTOR
  *****************************************************************************
  */

  construct : function()
  {
    this.base(arguments);

    // Contains all children
    this._children = [];

    // Create instanceof layout implementation
    this._layoutImpl = this._createLayoutImpl();
  },




  /*
  *****************************************************************************
     PROPERTIES
  *****************************************************************************
  */

  properties :
  {
    /** Individual focus handler for all child elements. */
    focusHandler :
    {
      check : "rwt.widgets.util.FocusHandler",
      apply : "_applyFocusHandler",
      nullable : true
    },

    /** The current active child. */
    activeChild :
    {
      check : "rwt.widgets.base.Widget",
      event : "changeActiveChild",
      nullable : true
    },

    /** The current focused child. */
    focusedChild :
    {
      check : "rwt.widgets.base.Widget",
      apply : "_applyFocusedChild",
      event : "changeFocusedChild",
      nullable : true
    },

    /** all visible child widgets */
    visibleChildren :
    {
      _cached      : true,
      defaultValue : null
    }
  },




  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */

  members :
  {
    /*
    ---------------------------------------------------------------------------
      FOCUS HANDLING
    ---------------------------------------------------------------------------
    */

    /**
     * Wether the widget has its own focus handler or uses one of its parent's
     * focus handler.
     *
     * @type member
     * @return {Boolean} whether the widget has its own focus handler
     */
    isFocusRoot : function() {
      return this.getFocusHandler() != null;
    },


    /**
     * Return the responsible focus handler
     *
     * @type member
     * @return {rwt.widgets.util.FocusHandler} TODOC
     */
    getFocusRoot : function()
    {
      if (this.isFocusRoot()) {
        return this;
      }

      if (this._hasParent) {
        return this.getParent().getFocusRoot();
      }

      return null;
    },


    /**
     * Let the widget use its own focus handler
     *
     * @type member
     * @return {void}
     */
    activateFocusRoot : function()
    {
      if (this._focusHandler) {
        return;
      }

      this._focusHandler = new rwt.widgets.util.FocusHandler(this);
      this.setFocusHandler(this._focusHandler);
    },


    /**
     * Delegate keyevent to the focus handler
     *
     * @type member
     * @param e {Event} TODOC
     * @return {void}
     */
    _onfocuskeyevent : function(e) {
      this.getFocusHandler()._onkeyevent(this, e);
    },


    /**
     * TODOC
     *
     * @type member
     * @param value {var} Current value
     * @param old {var} Previous value
     */
    _applyFocusHandler : function(value)
    {
      if (value)
      {
        // Add Key Handler
        this.addEventListener("keypress", this._onfocuskeyevent);

        // Activate focus handling (but keep already configured tabIndex)
        if (this.getTabIndex() < 1) {
          this.setTabIndex(1);
        }

        // But hide the focus outline
        this.setHideFocus(true);

        // Make myself the default
        this.setActiveChild(this);
      }
      else
      {
        // Remove Key Handler
        this.removeEventListener("keydown", this._onfocuskeyevent);
        this.removeEventListener("keypress", this._onfocuskeyevent);

        // Deactivate focus handling
        this.setTabIndex(null);

        // Don't hide focus outline
        this.setHideFocus(false);
      }
    },

    /**
     * TODOC
     *
     * @type member
     * @param value {var} Current value
     * @param old {var} Previous value
     */
    _applyFocusedChild : function(value, old)
    {
      var vFocusValid = value != null;
      var vBlurValid = old != null;

      if (rwt.qx.Class.isDefined("rwt.widgets.util.PopupManager") && vFocusValid)
      {
        var vMgr = rwt.widgets.util.PopupManager.getInstance();

        if (vMgr) {
          vMgr.update(value);
        }
      }

      if (vBlurValid)
      {
        // Dispatch FocusOut
        if (old.hasEventListeners("focusout"))
        {
          var vEventObject = new rwt.event.FocusEvent("focusout", old);

          if (vFocusValid) {
            vEventObject.setRelatedTarget(value);
          }

          old.dispatchEvent(vEventObject);
          vEventObject.dispose();
        }
      }

      if (vFocusValid)
      {
        if (value.hasEventListeners("focusin"))
        {
          // Dispatch FocusIn
          var vEventObject = new rwt.event.FocusEvent("focusin", value);

          if (vBlurValid) {
            vEventObject.setRelatedTarget(old);
          }

          value.dispatchEvent(vEventObject);
          vEventObject.dispose();
        }
      }

      if (vBlurValid)
      {
        if (this.getActiveChild() == old && !vFocusValid) {
          this.setActiveChild(null);
        }

        old.setFocused(false);

        // Dispatch Blur
        var vEventObject = new rwt.event.FocusEvent("blur", old);

        if (vFocusValid) {
          vEventObject.setRelatedTarget(value);
        }

        old.dispatchEvent(vEventObject);

        var vMgr = rwt.widgets.util.ToolTipManager.getInstance();

        if (vMgr) {
          vMgr.handleBlur(vEventObject);
        }

        vEventObject.dispose();
      }

      if (vFocusValid)
      {
        this.setActiveChild(value);
        value.setFocused(true);
        rwt.event.EventHandler.setFocusRoot(this);

        // Dispatch Focus
        var vEventObject = new rwt.event.FocusEvent("focus", value);

        if (vBlurValid) {
          vEventObject.setRelatedTarget(old);
        }

        value.dispatchEvent(vEventObject);

        var vMgr = rwt.widgets.util.ToolTipManager.getInstance();

        if (vMgr) {
          vMgr.handleFocus(vEventObject);
        }

        vEventObject.dispose();
      }
    },

    _applyWidth : function( newValue, oldValue ) {
      this.base( arguments, newValue, oldValue );
      this._layoutX();
    },

    _applyHeight : function( newValue, oldValue ) {
      this.base( arguments, newValue, oldValue );
      this._layoutY();
    },

    _applyBorder : function( newValue, oldValue ) {
      this.base( arguments, newValue, oldValue );
      this._layoutX();
      this._layoutY();
    },

    _applyDirection : function( value ) {
      this.base( arguments, value );
      this._layoutX();
    },

    _layoutX : function() {},

    _layoutY : function() {},


    /*
    ---------------------------------------------------------------------------
      LAYOUT IMPLEMENTATION
    ---------------------------------------------------------------------------
    */

    _layoutImpl : null,


    /**
     * abstract method. Create layout implementation.
     *
     * This method must be overwritten by all subclasses
     *
     * return
     *
     * @type member
     * @return {rwt.widgets.base.BoxLayout} TODOC
     */
    _createLayoutImpl : function() {
      return new rwt.widgets.util.CanvasLayoutImpl( this);
    },


    /**
     * Return the layout implementation.
     *
     * return {rwt.widgets.util.LayoutImpl}
     *
     * @type member
     * @return {var} TODOC
     */
    getLayoutImpl : function() {
      return this._layoutImpl;
    },




    /*
    ---------------------------------------------------------------------------
      CHILDREN MANAGMENT: MANAGE ALL
    ---------------------------------------------------------------------------
    */

    /**
     * Return the array of all children
     *
     * @type member
     * @return {rwt.widgets.base.Widget[]} all children
     */
    getChildren : function() {
      return this._children;
    },


    /**
     * Get number of children
     *
     * @type member
     * @return {Integer} number of children
     */
    getChildrenLength : function() {
      return this.getChildren().length;
    },


    /**
     * Check if the widget has children
     *
     * @type member
     * @return {Boolean} whether the widget has children
     */
    hasChildren : function() {
      return this.getChildrenLength() > 0;
    },


    /**
     * Check if there are any children inside
     *
     * @type member
     * @return {Boolean} whether the number of children is 0
     */
    isEmpty : function() {
      return this.getChildrenLength() === 0;
    },


    /**
     * Get the index of a child widget.
     *
     * @type member
     * @param vChild {rwt.widgets.base.Widget} Child widget to get the index for
     * @return {Integer} index of the child widget
     */
    indexOf : function(vChild) {
      return this.getChildren().indexOf(vChild);
    },


    /**
     * Check if the given widget is a child
     *
     * @type member
     * @param vWidget {rwt.widgets.base.Widget} The widget which should be checked.
     * @return {Boolean | var} TODOC
     */
    contains : function(vWidget)
    {
      switch(vWidget)
      {
        case null:
          return false;

        case this:
          return true;

        default:
          // try the next parent of the widget (recursive until found)
          return this.contains(vWidget.getParent());
      }
    },




    /*
    ---------------------------------------------------------------------------
      CHILDREN MANAGMENT: MANAGE VISIBLE ONES

      uses a cached private property
    ---------------------------------------------------------------------------
    */

    /**
     * Return the array of all visible children
     * (which are configured as visible=true)
     *
     * @type member
     * @return {rwt.widgets.base.Widget[]} all visible children
     */
    _computeVisibleChildren : function()
    {
      var vVisible = [];
      var vChildren = this.getChildren();

      if (! vChildren)
      {
        return 0;
      }

      var vLength = vChildren.length;

      for (var i=0; i 0;
    },


    /**
     * Check whether there are any visible children inside
     *
     * @type member
     * @return {Boolean} whether there are any visible children inside
     */
    isVisibleEmpty : function() {
      return this.getVisibleChildrenLength() === 0;
    },




    /*
    ---------------------------------------------------------------------------
      CHILDREN MANAGMENT: ADD
    ---------------------------------------------------------------------------
    */

    /**
     * Add/Append another widget. Allows to add multiple at
     *  one, a parameter could be a widget.
     *
     * @type member
     * @param varargs {rwt.widgets.base.Widget} variable number of widgets to add
     * @return {Parent} This widget.
     * @throws TODOC
     */
    add : function()
    {
      var vWidget;

      for (var i=0, l=arguments.length; i vTargetIndex) {
        vTargetIndex++;
      }

      return this.addAt(vChild, Math.max(0, vTargetIndex - 1));
    },


    /**
     * Add a widget after another already inserted widget
     *
     * @type member
     * @param vChild {var} widget to add
     * @param vAfter {var} widgert, after which the new widget will be inserted
     */
    addAfter : function(vChild, vAfter)
    {
      var vChildren = this.getChildren();
      var vTargetIndex = vChildren.indexOf(vAfter);

      if (vTargetIndex == -1) {
        throw new Error("Child to add after: " + vAfter + " is not inside this parent.");
      }

      var vSourceIndex = vChildren.indexOf(vChild);

      if (vSourceIndex != -1 && vSourceIndex < vTargetIndex) {
        vTargetIndex--;
      }

      return this.addAt(vChild, Math.min(vChildren.length, vTargetIndex + 1));
    },




    /*
    ---------------------------------------------------------------------------
      CHILDREN MANAGMENT: REMOVE
    ---------------------------------------------------------------------------
    */

    /**
     * Remove one or multiple childrens.
     *
     * @type member
     * @param varargs {rwt.widgets.base.Widget} variable number of widgets to remove
     */
    remove : function( /*children*/ )
    {
      var vWidget;

      for (var i=0, l=arguments.length; ifunction(childWidget, widgetIndex). The first parameter is the child widget
     * and the second the index of the child widget in its parent.
     *
     * @type member
     * @param vFunc {Function} callback function. Signature: function(childWidget, widgetIndex)
     */
    forEachChild : function(vFunc)
    {
      var ch = this.getChildren(), chc, i = -1;

      if (! ch) {
        return;
      }

      chc = ch[++i];
      while( chc ) {
        vFunc.call(chc, i);
        chc = ch[++i];
      }
    },


    /**
     * Call a callbach function for each visible child widget. The callback has the following signature:
     * function(childWidget, widgetIndex). The first parameter is the child widget
     * and the second the index of the child widget in its parent.
     *
     * @type member
     * @param vFunc {Function} callback function. Signature: function(childWidget, widgetIndex)
     */
    forEachVisibleChild : function(vFunc)
    {
      var ch = this.getVisibleChildren(), chc, i = -1;

      if (! ch) {
        return;
      }

      chc = ch[++i];
      while( chc ) {
        vFunc.call(chc, i);
        chc = ch[++i];
      }
    },

    _markInDispose : function() {
      this._isInGlobalDisposeQueue = true;
      if( this.forEachChild ) {
        this.forEachChild( arguments.callee );
      }
    },

    // overridden
    _fireBeforeDispose : function() {
      this.base( arguments );
      if( this.forEachChild ) {
        this.forEachChild( arguments.callee );
      }
    },

    /*
    ---------------------------------------------------------------------------
      APPEAR/DISAPPEAR MESSAGES FOR CHILDREN
    ---------------------------------------------------------------------------
    */

    // overridden
    _beforeAppear : function()
    {
      this.base(arguments);

      this.forEachVisibleChild(function()
      {
        if (this.isAppearRelevant()) {
          this._beforeAppear();
        }
      });
    },


    // overridden
    _afterAppear : function()
    {
      this.base(arguments);

      this.forEachVisibleChild(function()
      {
        if (this.isAppearRelevant()) {
          this._afterAppear();
        }
      });
    },

    // overridden
    _beforeDisappear : function()
    {
      this.base(arguments);

      this.forEachVisibleChild(function()
      {
        if (this.isAppearRelevant()) {
          this._beforeDisappear();
        }
      });
    },


    // overridden
    _afterDisappear : function()
    {
      this.base(arguments);

      this.forEachVisibleChild(function()
      {
        if (this.isAppearRelevant()) {
          this._afterDisappear();
        }
      });
    },


    // overridden
    _afterInsertDom : function() {
      this.base( arguments );
      // NOTE: a "visible" Child is actually every "displayable" child here
      this.forEachVisibleChild( function() {
        // Check if the nodes are actually connected - See Bug 359665
        if(    this.getElement()
            && this.getElement().parentNode === this.getParent()._getTargetNode() )
        {
          this._afterInsertDom();
        }
      } );
    },



    // overridden
    _afterRemoveDom : function() {
      this.base( arguments );
      this.forEachVisibleChild( function() {
        if( this.isInDom() ) {
          this._afterRemoveDom();
        }
      } );
    },



    /*
    ---------------------------------------------------------------------------
      DISPLAYBLE HANDLING
    ---------------------------------------------------------------------------
    */

    /**
     * TODOC
     *
     * @type member
     * @param vDisplayable {var} TODOC
     * @param vParent {var} TODOC
     * @param vHint {var} TODOC
     * @return {void}
     */
    _handleDisplayableCustom : function()
    {
      this.forEachChild(function() {
        this._handleDisplayable();
      });
    },




    /*
    ---------------------------------------------------------------------------
      STATE QUEUE
    ---------------------------------------------------------------------------
    */

    /**
     * TODOC
     *
     * @type member
     * @return {void}
     */
    _addChildrenToStateQueue : function()
    {
      this.forEachVisibleChild(function() {
        this.addToStateQueue();
      });
    },


    // overridden
    recursiveAddToStateQueue : function()
    {
      this.addToStateQueue();

      this.forEachVisibleChild(function() {
        this.recursiveAddToStateQueue();
      });
    },


    // overridden
    _recursiveAppearanceThemeUpdate : function(vNewAppearanceTheme, vOldAppearanceTheme)
    {
      this.base(arguments, vNewAppearanceTheme, vOldAppearanceTheme);

      this.forEachVisibleChild(function() {
        this._recursiveAppearanceThemeUpdate(vNewAppearanceTheme, vOldAppearanceTheme);
      });
    },




    /*
    ---------------------------------------------------------------------------
      CHILDREN QUEUE
    ---------------------------------------------------------------------------
    */

    /**
     * TODOC
     *
     * @type member
     * @param vChild {var} TODOC
     * @return {void}
     */
    _addChildToChildrenQueue : function(vChild)
    {
      if (!vChild._isInParentChildrenQueue && vChild._isDisplayable)
      {
        rwt.widgets.base.Widget.addToGlobalLayoutQueue(this);

        if (!this._childrenQueue) {
          this._childrenQueue = {};
        }

        this._childrenQueue[vChild.toHashCode()] = vChild;
      }
    },


    /**
     * TODOC
     *
     * @type member
     * @param vChild {var} TODOC
     * @return {void}
     */
    _removeChildFromChildrenQueue : function(vChild)
    {
      if (this._childrenQueue && vChild._isInParentChildrenQueue)
      {
        delete this._childrenQueue[vChild.toHashCode()];

        if (rwt.util.Objects.isEmpty(this._childrenQueue))
        {
          this._childrenQueue = {};
          rwt.widgets.base.Widget.removeFromGlobalLayoutQueue(this);
        }
      }
    },


    /**
     * TODOC
     *
     * @type member
     * @return {void}
     */
    _flushChildrenQueue : function()
    {
      if (!rwt.util.Objects.isEmpty(this._childrenQueue))
      {
        this.getLayoutImpl().flushChildrenQueue(this._childrenQueue);
        delete this._childrenQueue;
      }
    },




    /*
    ---------------------------------------------------------------------------
      LAYOUT QUEUE
    ---------------------------------------------------------------------------
    */

    /**
     * TODOC
     *
     * @type member
     * @param p {var} TODOC
     * @return {void}
     */
    _addChildrenToLayoutQueue : function(p)
    {
      this.forEachChild(function() {
        this.addToLayoutChanges(p);
      });
    },


    /**
     * TODOC
     *
     * @type member
     * @param vChild {var} TODOC
     * @return {void}
     */
    _layoutChild : function(vChild)
    {
      if (!vChild._isDisplayable)
      {
        // this.warn("Want to render an invisible child: " + vChild + " -> omitting!");
        return;
      }

      // APPLY LAYOUT
      var vChanges = vChild._layoutChanges;

      if (vChild.renderBorder && vChanges.border ) {
        vChild.renderBorder( vChanges );
      }

      if (vChild.renderPadding)
      {
        if (vChanges.paddingLeft || vChanges.paddingRight || vChanges.paddingTop || vChanges.paddingBottom) {
          vChild.renderPadding(vChanges);
        }
      }

      // WRAP TO LAYOUT ENGINE
      this.getLayoutImpl().layoutChild(vChild, vChanges);

      // POST LAYOUT
      vChild._layoutPost(vChanges);

      // DISPLAY DOM NODE
      // insert dom node (if initial flag enabled)
      if (vChanges.initial)
      {
        vChild._initialLayoutDone = true;
        rwt.widgets.base.Widget.addToGlobalDisplayQueue(vChild);
      }

      // CLEANUP
      vChild._layoutChanges = {};

      delete vChild._isInParentLayoutQueue;
      delete this._childrenQueue[vChild.toHashCode()];
    },

    _layoutPost : function( changes ) {
      this.createDispatchDataEvent( "flush", changes );
    },



    /*
    ---------------------------------------------------------------------------
      DIMENSION CACHE
    ---------------------------------------------------------------------------
    */

    /**
     * TODOC
     *
     * @type member
     * @return {var} TODOC
     */
    _computePreferredInnerWidth : function() {
      return this.getLayoutImpl().computeChildrenNeededWidth();
    },


    /**
     * TODOC
     *
     * @type member
     * @return {var} TODOC
     */
    _computePreferredInnerHeight : function() {
      return this.getLayoutImpl().computeChildrenNeededHeight();
    },


    /**
     * TODOC
     *
     * @type member
     * @param vNew {var} TODOC
     * @param vOld {var} TODOC
     * @return {void}
     */
    _changeInnerWidth : function()
    {
      var vLayout = this.getLayoutImpl();

      if (vLayout.invalidateChildrenFlexWidth) {
        vLayout.invalidateChildrenFlexWidth();
      }

      this.forEachVisibleChild(function()
      {
        if (vLayout.updateChildOnInnerWidthChange(this) && this._recomputeBoxWidth())
        {
          this._recomputeOuterWidth();
          this._recomputeInnerWidth();
        }
      });
    },


    /**
     * TODOC
     *
     * @type member
     * @param vNew {var} TODOC
     * @param vOld {var} TODOC
     * @return {void}
     */
    _changeInnerHeight : function()
    {
      var vLayout = this.getLayoutImpl();

      if (vLayout.invalidateChildrenFlexHeight) {
        vLayout.invalidateChildrenFlexHeight();
      }

      this.forEachVisibleChild(function()
      {
        if (vLayout.updateChildOnInnerHeightChange(this) && this._recomputeBoxHeight())
        {
          this._recomputeOuterHeight();
          this._recomputeInnerHeight();
        }
      });
    },


    /**
     * TODOC
     *
     * @type member
     * @param vChild {var} TODOC
     * @return {var} TODOC
     */
    getInnerWidthForChild : function() {
      return this.getInnerWidth();
    },


    /**
     * TODOC
     *
     * @type member
     * @param vChild {var} TODOC
     * @return {var} TODOC
     */
    getInnerHeightForChild : function() {
      return this.getInnerHeight();
    },






    /*
    ---------------------------------------------------------------------------
      REMAPPING
    ---------------------------------------------------------------------------
    */

    _remappingChildTable : [ "add", "remove", "addAt", "addAtBegin", "addAtEnd", "removeAt", "addBefore", "addAfter", "removeAll" ],

    remapChildrenHandlingTo : function( vTarget ) {
      this._remappingChildTarget = vTarget;
      this._remappingChildTable.forEach( function( remappingChild ) {
        this[ remappingChild ] = function() {
          return this._remappingChildTarget[ remappingChild ].apply( this._remappingChildTarget, arguments );
        };
      }, this );
    }

  },




  /*
  *****************************************************************************
     DESTRUCTOR
  *****************************************************************************
  */

  destruct : function()
  {
    this._disposeObjectDeep("_children", 1);
    this._disposeObjects("_layoutImpl", "_focusHandler");
    this._disposeFields("_childrenQueue", "_childrenQueue", "_remappingChildTable",
      "_remappingChildTarget", "_cachedVisibleChildren");
  }
});




© 2015 - 2025 Weber Informatics LLC | Privacy Policy