VirtualSelectBox display

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

VirtualSelectBox display

Wen Yen
This post was updated on .
Hi,

I have made copies of VirtualSelectBox, AbstractVirtualBox and VirtualDropdownList and modified them to accept multiple selections from the dropdown list.  However, I can't seem to figure out how to customize the display in the box.  Doesn't matter what item in the list I pick, the box it self remains blank.

Any thoughts on how/where I should be working on to customize the box display?

Thanks,
Wen
Reply | Threaded
Open this post in threaded view
|

Re: VirtualSelectBox display

Alexander Steitz
Hi Wen,

is it just a problem how the value is displayed or does the widget does not get any value? When yu picked a value from the list you can check the value of the virtual selectbox display by

        virtualboxInstance.getChildControl("atom").getLabel();

This is only the value which the atom widget used within the virtual selectbox has. If this atom widget does have the right value, then it's "only" an appearance problem of the widget.

But I think, it's more a problem that the model of the virtual selectbox is not synced correctly. What does

        virtualboxInstance.getSelection().toArray();

return? This is the current selected value. If this does not match it's a problem of the sync between model and widget.

Regards,
  Alex


-----Original Message-----
From: Wen Yen [mailto:[hidden email]]
Sent: Thursday, December 27, 2012 9:46 PM
To: [hidden email]
Subject: [qooxdoo-devel] VirtualSelectBox display

Hi,

I have modified some code in VirtualSelectBox and it's related components to accept multiple selections from the dropdown list.  However, I can't seem to figure out how to customize the display in the box.  Doesn't matter what item in the list I pick, the box it self remains blank.

Any thoughts on how/where I should be working on to customize the box display?

Thanks,
Wen



--
View this message in context: http://qooxdoo.678.n2.nabble.com/VirtualSelectBox-display-tp7582370.html
Sent from the qooxdoo mailing list archive at Nabble.com.

------------------------------------------------------------------------------
Master Visual Studio, SharePoint, SQL, ASP.NET, C# 2012, HTML5, CSS, MVC, Windows 8 Apps, JavaScript and much more. Keep your skills current with LearnDevNow - 3,200 step-by-step video tutorials by Microsoft MVPs and experts. ON SALE this month only -- learn more at:
http://p.sf.net/sfu/learnmore_122712
_______________________________________________
qooxdoo-devel mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel

------------------------------------------------------------------------------
Master HTML5, CSS3, ASP.NET, MVC, AJAX, Knockout.js, Web API and
much more. Get web development skills now with LearnDevNow -
350+ hours of step-by-step video tutorials by Microsoft MVPs and experts.
SALE $99.99 this month only -- learn more at:
http://p.sf.net/sfu/learnmore_122812
_______________________________________________
qooxdoo-devel mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
Reply | Threaded
Open this post in threaded view
|

Re: VirtualSelectBox display

Wen Yen
This post was updated on .
Alex,

The label is blank and the selection is an empty array.  

Couldn't get the tinyurl button to work on the play ground, so here's the code.

I am only using a regular array with texts here, the production code uses qooxdoo jsons with label and value.


/* ************************************************************************

   qooxdoo - the new era of web development

   http://qooxdoo.org

   Copyright:
     2011 1&1 Internet AG, Germany, http://www.1und1.de

   License:
     LGPL: http://www.gnu.org/licenses/lgpl.html
     EPL: http://www.eclipse.org/org/documents/epl-v10.php
     See the LICENSE file in the project's top-level directory for details.

   Authors:
     * Christian Hagendorn (chris_schmidt)

************************************************************************ */

/**
 * A drop-down (popup) widget which contains a virtual list for selection.
 *
 * @childControl list {qx.ui.list.List} The virtual list.
 *
 * @internal
 */
qx.Class.define("qx.ui.form.core.WenVirtualDropDownList",
{
  extend  : qx.ui.popup.Popup,


  /**
   * Creates the drop-down list.
   *
   * @param target {qx.ui.form.core.AbstractVirtualBox} The composite widget.
   */
  construct : function(target)
  {
    qx.core.Assert.assertNotNull(target, "Invalid parameter 'target'!");
    qx.core.Assert.assertNotUndefined(target, "Invalid parameter 'target'!");
    qx.core.Assert.assertInterface(target, qx.ui.form.core.AbstractVirtualBox,
      "Invalid parameter 'target'!");

    this.base(arguments, new qx.ui.layout.VBox());

    this._target = target;

    this._createChildControl("list");
    this.addListener("changeVisibility", this.__onChangeVisibility, this);

    this.__defaultSelection = new qx.data.Array();
    this.initSelection(this.__defaultSelection);
  },


  properties :
  {
    // overridden
    autoHide :
    {
      refine : true,
      init : false
    },


    // overridden
    keepActive :
    {
      refine : true,
      init : true
    },


    /** Current selected items. */
    selection :
    {
      check : "qx.data.Array",
      event : "changeSelection",
      apply : "_applySelection",
      nullable : false,
      deferredInit : true
    }
  },


  members :
  {
    /** {qx.ui.form.core.AbstractVirtualBox} The composite widget. */
    _target : null,


    /** {var} The pre-selected model item. */
    _preselected : null,


    /**
     * {Boolean} Indicator to ignore selection changes from the
     * {@link #selection} array.
     */
    __ignoreSelection : false,


    /** {Boolean} Indicator to ignore selection changes from the list. */
    __ignoreListSelection : false,


    __defaultSelection : null,


    /*
    ---------------------------------------------------------------------------
      PUBLIC API
    ---------------------------------------------------------------------------
    */


    /**
     * Shows the drop-down.
     */
    open : function()
    {
      this.placeToWidget(this._target, true);
      this.show();
    },


    /**
     * Hides the drop-down.
     */
    close : function() {
      this.hide();
    },


    /**
     * Pre-selects the drop-down item corresponding to the given model object.
     *
     * @param modelItem {Object} Item to be pre-selected.
     */
    setPreselected : function(modelItem)
    {
      if (modelItem != null) {
        this._preselected = modelItem;
        this.__ignoreListSelection = true;
        var listSelection = this.getChildControl("list").getSelection();
        var helper = new qx.data.Array();
        helper.append(modelItem.toArray());
        this.__synchronizeSelection(helper, listSelection);
        helper.dispose();
        this.__ignoreListSelection = false;
      }
    },


    /*
    ---------------------------------------------------------------------------
      INTERNAL API
    ---------------------------------------------------------------------------
    */


    // overridden
    _createChildControlImpl : function(id, hash)
    {
      var control;

      switch(id)
      {
        case "list":
          control = new qx.ui.list.List().set({
            focusable: false,
            keepFocus: true,
            height: null,
            width: null,
            maxHeight: this._target.getMaxListHeight(),
            selectionMode: "additive",
            quickSelection: true
          });

          control.getSelection().addListener("change", this._onListChangeSelection, this);
          control.addListener("mouseup", this._handleMouse, this);
          control.addListener("changeModel", this._onChangeModel, this);
          control.addListener("changeDelegate", this._onChangeDelegate, this);

          this.add(control, {flex: 1});
          break;
      }

      return control || this.base(arguments, id, hash);
    },


    /*
    ---------------------------------------------------------------------------
      EVENT LISTENERS
    ---------------------------------------------------------------------------
    */


    /**
     * Handles the complete keyboard events dispatched on the widget.
     *
     * @param event {qx.event.type.KeySequence} The keyboard event.
     */
    _handleKeyboard : function(event)
    {
      if (this.isVisible() && event.getKeyIdentifier() === "Enter") {
        this.__selectPreselected();
        return;
      }

      var clone = event.clone();
      clone.setTarget(this.getChildControl("list"));
      clone.setBubbles(false);

      this.getChildControl("list").dispatchEvent(clone);
    },


    /**
     * Handles all mouse events dispatched on the widget.
     *
     * @param event {qx.event.type.Mouse} The mouse event.
     */
    _handleMouse : function(event) {
      this.__selectPreselected();
    },


    /**
     * Handler for the local selection change. The method is responsible for
     * the synchronization between the own selection and the selection
     * form the drop-down.
     *
     * @param event {qx.event.type.Data} The data event.
     */
    __onChangeSelection : function(event)
    {
      if (this.__ignoreSelection) {
        return;
      }

      var selection = this.getSelection();
      var listSelection = this.getChildControl("list").getSelection();

      this.__ignoreListSelection = true;
      this.__synchronizeSelection(selection, listSelection);
      this.__ignoreListSelection = false;

      this.__ignoreSelection = true;
      this.__synchronizeSelection(listSelection, selection);
      this.__ignoreSelection = false;
    },


    /**
     * Handler for the selection change on the list. The method is responsible
     * for the synchronization between the list selection and the own selection.
     *
     * @param event {qx.event.type.Data} The data event.
     */
    _onListChangeSelection : function(event)
    {
      if (this.__ignoreListSelection) {
        return;
      }

      var listSelection = this.getChildControl("list").getSelection();

      if (this.isVisible()) {
        this.setPreselected(new qx.data.Array().append(listSelection));
      } else {
        this.__ignoreSelection = true;
        this.__synchronizeSelection(new qx.data.Array().append(listSelection), this.getSelection());
        this.__ignoreSelection = false;
      }
    },


    /**
     * Handler for the own visibility changes. The method is responsible that
     * the list selects the current selected item.
     *
     * @param event {qx.event.type.Data} The event.
     */
    __onChangeVisibility : function(event)
    {
      if (this.isVisible())
      {
        if (this._preselected == null)
        {
          var selection = this.getSelection();
          var listSelection = this.getChildControl("list").getSelection();
          this.__synchronizeSelection(selection, new qx.data.Array().append(listSelection));
        }
        this.__adjustSize();
      } else {
        this.setPreselected(null);
      }
    },


    /**
     * Handler for the model change event.
     *
     * @param event {qx.event.type.Data} The change event.
     */
    _onChangeModel : function(event) {
      this.getSelection().removeAll();
    },


    /**
     * Handler for the delegate change event.
     *
     * @param event {qx.event.type.Data} The change event.
     */
    _onChangeDelegate : function(event) {
      this.getSelection().removeAll();
    },


    /*
    ---------------------------------------------------------------------------
      APPLY ROUTINES
    ---------------------------------------------------------------------------
    */


    // property apply
    _applySelection : function(value, old)
    {
      console.log('10');
      value.addListener("change", this.__onChangeSelection, this);

      if (old != null) {
        old.removeListener("change", this.__onChangeSelection, this);
      }

      this.__synchronizeSelection(
        value, this.getChildControl("list").getSelection(value)
      );
    },


    /*
    ---------------------------------------------------------------------------
      HELPER METHODS
    ---------------------------------------------------------------------------
    */


    /**
     * Helper method to select the current preselected item, also closes the
     * drop-down.
     */
    __selectPreselected : function()
    {
      if (this._preselected != null)
      {
        var selection = this.getSelection();
        console.log('selection ' + selection)
        console.log('this._preselected ' + this._preselected)
        selection.toArray().splice(0, 1, this._preselected);
        this._preselected = null;
      }
    },


    /**
     * Helper method to synchronize both selection. The target selection has
     * the same selection like the source selection after the synchronization.
     *
     * @param source {qx.data.Array} The source selection.
     * @param target {qx.data.Array} The target selection.
     */
    __synchronizeSelection : function(source, target)
    {
      if (source || target == undefined) {
        return;
      }
      if (source.equals(target)) {
        return;
      }

      if (source.getLength() <= 0) {
        target.removeAll();
      }
      else
      {
        var nativeArray = target.toArray();
        qx.lang.Array.removeAll(nativeArray);
        for (var i = 0; i < source.getLength(); i++) {
          nativeArray.push(source.getItem(i));
        }
        target.length = nativeArray.length;

        var lastIndex = target.getLength() - 1;
        // dispose data array returned by splice to avoid memory leak
        var temp = target.splice(lastIndex, 1, target.getItem(lastIndex));
        temp.dispose();
      }
    },


    /**
     * Adjust the drop-down to the available width and height, by calling
     * {@link #__adjustWidth} and {@link #__adjustHeight}.
     */
    __adjustSize : function()
    {
      this.__adjustWidth();
      this.__adjustHeight();
    },


    /**
     * Adjust the drop-down to the available width. The width is limited by
     * the current with from the _target.
     */
    __adjustWidth : function()
    {
      var width = this._target.getBounds().width;
      this.setWidth(width);
    },


    /**
     * Adjust the drop-down to the available height. Ensure that the list
     * is never bigger that the max list height and the available space
     * in the viewport.
     */
    __adjustHeight : function()
    {
      var availableHeigth = this.__getAvailableHeigth();
      var maxListHeight = this._target.getMaxListHeight();
      var list = this.getChildControl("list");
      var itemsHeight = list.getPane().getRowConfig().getTotalSize();

      if (maxListHeight == null || itemsHeight < maxListHeight) {
        maxListHeight = itemsHeight;
      }

      if (maxListHeight > availableHeigth) {
        list.setMaxHeight(availableHeigth);
      } else if (maxListHeight < availableHeigth) {
        list.setMaxHeight(maxListHeight);
      }
    },


    /**
     * Calculates the available height in the viewport.
     *
     * @return {Integer} Available height in the viewport.
     */
    __getAvailableHeigth : function()
    {
      var distance = this.getLayoutLocation(this._target);
      var viewPortHeight = qx.bom.Viewport.getHeight();

      // distance to the bottom and top borders of the viewport
      var toTop = distance.top;
      var toBottom = viewPortHeight - distance.bottom;

      return toTop > toBottom ? toTop : toBottom;
    }
  },

  destruct : function()
  {
    if (this.__defaultSelection) {
      this.__defaultSelection.dispose();
    }
  }
});


/* ************************************************************************

   qooxdoo - the new era of web development

   http://qooxdoo.org

   Copyright:
     2011 1&1 Internet AG, Germany, http://www.1und1.de

   License:
     LGPL: http://www.gnu.org/licenses/lgpl.html
     EPL: http://www.eclipse.org/org/documents/epl-v10.php
     See the LICENSE file in the project's top-level directory for details.

   Authors:
     * Christian Hagendorn (chris_schmidt)

************************************************************************ */

/**
 * Basic class for widgets which need a virtual list as popup for example a
 * SelectBox. It's basically supports a drop-down as popup with a virtual list
 * and the whole children management.
 *
 * @childControl dropdown {qx.ui.form.core.VirtualDropDownList} The drop-down list.
 */
qx.Class.define("qx.ui.form.core.WenAbstractVirtualBox",
{
  extend  : qx.ui.core.Widget,
  include : qx.ui.form.MForm,
  implement : qx.ui.form.IForm,
  type : "abstract",


  /**
   * @param model {qx.data.Array?null} The model data for the widget.
   */
  construct : function(model)
  {
    this.base(arguments);

    // set the layout
    var layout = new qx.ui.layout.HBox();
    this._setLayout(layout);
    layout.setAlignY("middle");

    // Register listeners
    this.addListener("keypress", this._handleKeyboard, this);
    this.addListener("click", this._handleMouse, this);
    this.addListener("blur", this._onBlur, this);
    this.addListener("resize", this._onResize, this);

    this._createChildControl("dropdown");

    if (model != null) {
      this.initModel(model);
    } else {
      this.__defaultModel = new qx.data.Array();
      this.initModel(this.__defaultModel);
    }
  },


  properties :
  {
    // overridden
    focusable :
    {
      refine : true,
      init : true
    },


    // overridden
    width :
    {
      refine : true,
      init : 120
    },


    /** Data array containing the data which should be shown in the drop-down. */
    model :
    {
      check : "qx.data.Array",
      apply : "_applyModel",
      event: "changeModel",
      nullable : false,
      deferredInit : true
    },


    /**
     * Delegation object which can have one or more functions defined by the
     * {@link qx.ui.list.core.IListDelegate} interface.
     */
    delegate :
    {
      apply: "_applyDelegate",
      event: "changeDelegate",
      init: null,
      nullable: true
    },


    /**
     * The path to the property which holds the information that should be
     * displayed as a label. This is only needed if objects are stored in the
     * model.
     */
    labelPath :
    {
      check: "String",
      apply: "_applyLabelPath",
      event: "changeLabelPath",
      nullable: true
    },


    /**
     * A map containing the options for the label binding. The possible keys
     * can be found in the {@link qx.data.SingleValueBinding} documentation.
     */
    labelOptions :
    {
      apply: "_applyLabelOptions",
      event: "changeLabelOptions",
      nullable: true
    },


    /**
     * The path to the property which holds the information that should be
     * displayed as an icon. This is only needed if objects are stored in the
     * model and icons should be displayed.
     */
    iconPath :
    {
      check: "String",
      event : "changeIconPath",
      apply: "_applyIconPath",
      nullable: true
    },


    /**
     * A map containing the options for the icon binding. The possible keys
     * can be found in the {@link qx.data.SingleValueBinding} documentation.
     */
    iconOptions :
    {
      apply: "_applyIconOptions",
      event : "changeIconOptions",
      nullable: true
    },


    /** Default item height. */
    itemHeight :
    {
      check : "Integer",
      init : 25,
      apply : "_applyRowHeight",
      themeable : true
    },


    /**
     * The maximum height of the drop-down list. Setting this value to
     * <code>null</code> will set cause the list to be auto-sized.
     */
    maxListHeight :
    {
      check : "Number",
      apply : "_applyMaxListHeight",
      nullable: true,
      init : 200
    }
  },


  members :
  {
    __defaultModel : null,

    /**
     * @lint ignoreReferenceField(_forwardStates)
     */
    _forwardStates : {
      focused : true,
      invalid : true
    },


    /*
    ---------------------------------------------------------------------------
      PUBLIC API
    ---------------------------------------------------------------------------
    */


    /**
     * Trigger a rebuild from the internal data structure.
     */
    refresh : function()
    {
      this.getChildControl("dropdown").getChildControl("list").refresh();
      qx.ui.core.queue.Widget.add(this);
    },


    /**
     * Shows the drop-down.
     */
    open : function() {
      this._beforeOpen();
      this.getChildControl("dropdown").open();
    },


    /**
     * Hides the drop-down.
     */
    close : function() {
      this._beforeClose();
      this.getChildControl("dropdown").close();
    },


    /**
     * Toggles the drop-down visibility.
     */
    toggle : function() {
      var dropDown =this.getChildControl("dropdown");

      if (dropDown.isVisible()) {
        this.close();
      } else {
        this.open();
      }
    },


    /*
    ---------------------------------------------------------------------------
      INTERNAL API
    ---------------------------------------------------------------------------
    */


    // overridden
    _createChildControlImpl : function(id, hash)
    {
      var control;

      switch(id)
      {
        case "dropdown":
          control = new qx.ui.form.core.WenVirtualDropDownList(this);
          control.addListener("changeVisibility", this._onPopupChangeVisibility, this);
          break;
      }

      return control || this.base(arguments, id, hash);
    },


    /**
     * This method is called before the drop-down is opened.
     */
    _beforeOpen: function() {},


    /**
     * This method is called before the drop-down is closed.
     */
    _beforeClose: function() {},


    /**
     * Returns the action dependent on the user interaction: e. q. <code>open</code>,
     * or <code>close</code>.
     *
     * @param event {qx.event.type.KeySequence} The keyboard event.
     * @return {String|null} The action or <code>null</code> when interaction
     *  doesn't hit any action.
     */
    _getAction : function(event)
    {
      var keyIdentifier = event.getKeyIdentifier();
      var isOpen = this.getChildControl("dropdown").isVisible();
      var isModifierPressed = this._isModifierPressed(event);

      if (
        !isOpen && !isModifierPressed &&
        (keyIdentifier === "Down" || keyIdentifier === "Up")
      ) {
        return "open";
      } else if (
        isOpen && !isModifierPressed && keyIdentifier === "Escape"
      ) {
        return "close";
      } else {
        return null;
      }
    },


    /**
     * Helper Method to create bind path depended on the passed path.
     *
     * @param source {String} The path to the selection.
     * @param path {String?null} The path to the item's property.
     * @return {String} The created path.
     */
    _getBindPath : function(source, path)
    {
      var bindPath = source + "[0]";

      if (path != null && path != "") {
        bindPath += "." + path;
      }

      return bindPath;
    },

    /**
     * Helper method to check if one modifier key is pressed. e.q.
     * <code>Control</code>, <code>Shift</code>, <code>Meta</code> or
     * <code>Alt</code>.
     *
     * @param event {qx.event.type.KeySequence} The keyboard event.
     * @return {Boolean} <code>True</code> when a modifier key is pressed,
     *   <code>false</code> otherwise.
     */
    _isModifierPressed : function(event)
    {
      var isAltPressed = event.isAltPressed();
      var isCtrlOrCommandPressed = event.isCtrlOrCommandPressed();
      var isShiftPressed = event.isShiftPressed();
      var isMetaPressed = event.isMetaPressed();

      return (isAltPressed || isCtrlOrCommandPressed ||
        isShiftPressed || isMetaPressed);
    },


    /*
    ---------------------------------------------------------------------------
      EVENT LISTENERS
    ---------------------------------------------------------------------------
    */


    /**
     * Handler for the blur event of the current widget.
     *
     * @param event {qx.event.type.Focus} The blur event.
     */
    _onBlur : function(event) {
      this.close();
    },


    /**
     * Handles the complete keyboard events for user interaction. If there is
     * no defined user interaction {@link #_getAction}, the event is delegated
     * to the {@link qx.ui.form.core.VirtualDropDownList#_handleKeyboard} method.
     *
     * @param event {qx.event.type.KeySequence} The keyboard event.
     */
    _handleKeyboard : function(event)
    {
      var action = this._getAction(event);
      var isOpen = this.getChildControl("dropdown").isVisible();

      switch(action)
      {
        case "open":
          this.open();
          break;

        case "close":
          this.close();
          break;

        default:
          if (isOpen) {
            this.getChildControl("dropdown")._handleKeyboard(event);
          }
          break;
      }
    },


    /**
     * Handles all mouse events dispatched on the widget.
     *
     * @param event {qx.event.type.Mouse|qx.event.type.MouseWheel} The mouse event.
     */
    _handleMouse : function(event) {},


    /**
     * Updates drop-down minimum size.
     *
     * @param event {qx.event.type.Data} Data event.
     */
    _onResize : function(event){
      this.getChildControl("dropdown").setMinWidth(event.getData().width);
    },


    /**
     * Adds/removes the state 'popupOpen' depending on the visibility of the popup
     *
     * @param event {qx.event.type.Data} Data event
     */
    _onPopupChangeVisibility : function(event)
    {
      event.getData() == "hidden" ? this.addState("popupOpen") : this.removeState("popupOpen");
    },


    /*
    ---------------------------------------------------------------------------
      APPLY ROUTINES
    ---------------------------------------------------------------------------
    */


    // property apply
    _applyModel : function(value, old)
    {
      this.getChildControl("dropdown").getChildControl("list").setModel(value);
      qx.ui.core.queue.Widget.add(this);
    },


    // property apply
    _applyDelegate : function(value, old) {
      this.getChildControl("dropdown").getChildControl("list").setDelegate(value);
    },


    // property apply
    _applyLabelPath : function(value, old)
    {
      this.getChildControl("dropdown").getChildControl("list").setLabelPath(value);
      qx.ui.core.queue.Widget.add(this);
    },


    // property apply
    _applyLabelOptions : function(value, old)
    {
      this.getChildControl("dropdown").getChildControl("list").setLabelOptions(value);
      qx.ui.core.queue.Widget.add(this);
    },


    // property apply
    _applyIconPath : function(value, old)
    {
      this.getChildControl("dropdown").getChildControl("list").setIconPath(value);
      qx.ui.core.queue.Widget.add(this);
    },


    // property apply
    _applyIconOptions : function(value, old)
    {
      this.getChildControl("dropdown").getChildControl("list").setIconOptions(value);
      qx.ui.core.queue.Widget.add(this);
    },


    // property apply
    _applyRowHeight : function(value, old) {
      this.getChildControl("dropdown").getChildControl("list").setItemHeight(value);
    },


    // property apply
    _applyMaxListHeight : function(value, old) {
      this.getChildControl("dropdown").getChildControl("list").setMaxHeight(value);
    }
  },

  destruct : function()
  {
    if (this.__defaultModel) {
      this.__defaultModel.dispose();
    }
  }
});







/* ************************************************************************

   qooxdoo - the new era of web development

   http://qooxdoo.org

   Copyright:
     2011 1&1 Internet AG, Germany, http://www.1und1.de

   License:
     LGPL: http://www.gnu.org/licenses/lgpl.html
     EPL: http://www.eclipse.org/org/documents/epl-v10.php
     See the LICENSE file in the project's top-level directory for details.

   Authors:
     * Christian Hagendorn (chris_schmidt)

************************************************************************ */

/**
 * A form virtual widget which allows a single selection. Looks somewhat like
 * a normal button, but opens a virtual list of items to select when clicking
 * on it.
 *
 * @childControl spacer {qx.ui.core.Spacer} Flexible spacer widget.
 * @childControl atom {qx.ui.basic.Atom} Shows the text and icon of the content.
 * @childControl arrow {qx.ui.basic.Image} Shows the arrow to open the drop-down
 *   list.
 */
qx.Class.define("qx.ui.form.WenBox",
{
  extend : qx.ui.form.core.WenAbstractVirtualBox,
  implement : qx.data.controller.ISelection,

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

    this._createChildControl("atom");
    this._createChildControl("spacer");
    this._createChildControl("arrow");

    // Register listener
    this.addListener("mouseover", this._onMouseOver, this);
    this.addListener("mouseout", this._onMouseOut, this);

    this.__bindings = [];
    this.initSelection(this.getChildControl("dropdown").getSelection());

    this.__searchTimer = new qx.event.Timer(500);
    this.__searchTimer.addListener("interval", this.__preselect, this);
  },


  properties :
  {
    // overridden
    appearance :
    {
      refine : true,
      init : "virtual-selectbox"
    },


    // overridden
    width :
    {
      refine : true,
      init : 120
    },


    /** Current selected items. */
    selection :
    {
      check : "qx.data.Array",
      event : "changeSelection",
      apply : "_applySelection",
      nullable : false,
      deferredInit : true
    }
  },


  members :
  {
    /** {String} The search value to {@link #__preselect} an item. */
    __searchValue : "",


    /**
     * {qx.event.Timer} The time which triggers the search for pre-selection.
     */
    __searchTimer : null,


    /** {Array} Contains the id from all bindings. */
    __bindings : null,


    // overridden
    syncWidget : function(jobs)
    {
      this._removeBindings();
      this._addBindings();
    },


    /*
    ---------------------------------------------------------------------------
      INTERNAl API
    ---------------------------------------------------------------------------
    */


    // overridden
    _createChildControlImpl : function(id, hash)
    {
      var control;

      switch(id)
      {
        case "spacer":
          control = new qx.ui.core.Spacer();
          this._add(control, {flex: 1});
          break;

        case "atom":
          control = new qx.ui.form.ListItem("");
          control.setCenter(false);
          control.setAnonymous(true);

          this._add(control, {flex:1});
          break;

        case "arrow":
          control = new qx.ui.basic.Image();
          control.setAnonymous(true);

          this._add(control);
          break;
      }

      return control || this.base(arguments, id, hash);
    },


    // overridden
    _getAction : function(event)
    {
      var keyIdentifier = event.getKeyIdentifier();
      var isOpen = this.getChildControl("dropdown").isVisible();
      var isModifierPressed = this._isModifierPressed(event);

      if (
        !isOpen && !isModifierPressed &&
        (keyIdentifier === "Enter" || keyIdentifier === "Space")
      ) {
        return "open";
      } else if (isOpen && event.isPrintable()) {
        return "search";
      } else {
        return this.base(arguments, event);
      }
    },

    /**
     * This method is called when the binding can be added to the
     * widget. For e.q. bind the drop-down selection with the widget.
     */
    _addBindings : function()
    {
      var atom = this.getChildControl("atom");

      var modelPath = this._getBindPath("selection", "");
      console.log('modelPath');
      console.log(modelPath);
      var id = this.bind(modelPath, atom, "model", null);
      this.__bindings.push(id);

      var labelSourcePath = this._getBindPath("selection", this.getLabelPath());
      console.log('labelPath');
      console.log(labelSourcePath);
      id = this.bind(labelSourcePath, atom, "label", this.getLabelOptions());
      this.__bindings.push(id);

      if (this.getIconPath() != null) {
        var iconSourcePath = this._getBindPath("selection", this.getIconPath());
        id = this.bind(iconSourcePath, atom, "icon", this.getIconOptions());
        this.__bindings.push(id);
      }
    },


    /**
     * This method is called when the binding can be removed from the
     * widget. For e.q. remove the bound drop-down selection.
     */
    _removeBindings : function()
    {
      while (this.__bindings.length > 0)
      {
        var id = this.__bindings.pop();
        this.removeBinding(id);
      }
    },


    /*
    ---------------------------------------------------------------------------
      EVENT LISTENERS
    ---------------------------------------------------------------------------
    */


    // overridden
    _handleMouse : function(event)
    {
      this.base(arguments, event);

      var type = event.getType();
      if (type === "click") {
        this.toggle();
      }
    },


    // overridden
    _handleKeyboard : function(event) {
      var action = this._getAction(event);

      switch(action)
      {
        case "search":
          this.__searchValue += this.__convertKeyIdentifier(event.getKeyIdentifier());
          this.__searchTimer.restart();
          break;

        default:
          this.base(arguments, event);
          break;
      }
    },


    /**
     * Listener method for "mouseover" event.
     *
     * <ul>
     * <li>Adds state "hovered"</li>
     * <li>Removes "abandoned" and adds "pressed" state (if "abandoned" state
     *   is set)</li>
     * </ul>
     *
     * @param event {Event} Mouse event
     */
    _onMouseOver : function(event)
    {
      if (!this.isEnabled() || event.getTarget() !== this) {
        return;
      }

      if (this.hasState("abandoned"))
      {
        this.removeState("abandoned");
        this.addState("pressed");
      }

      this.addState("hovered");
    },


    /**
     * Listener method for "mouseout" event.
     *
     * <ul>
     * <li>Removes "hovered" state</li>
     * <li>Adds "abandoned" and removes "pressed" state (if "pressed" state
     *   is set)</li>
     * </ul>
     *
     * @param event {Event} Mouse event
     */
    _onMouseOut : function(event)
    {
      if (!this.isEnabled() || event.getTarget() !== this) {
        return;
      }

      this.removeState("hovered");

      if (this.hasState("pressed"))
      {
        this.removeState("pressed");
        this.addState("abandoned");
      }
    },


    /*
    ---------------------------------------------------------------------------
      APPLY ROUTINES
    ---------------------------------------------------------------------------
    */


    // property apply
    _applySelection : function(value, old)
    {
      this.getChildControl("dropdown").setSelection(value);
      qx.ui.core.queue.Widget.add(this);
    },


    /*
    ---------------------------------------------------------------------------
      HELPER METHODS
    ---------------------------------------------------------------------------
    */


    /**
     * Preselects an item in the drop-down, when item starts with the
     * __seachValue value.
     */
    __preselect : function()
    {
      console.log('wtf');
      this.__searchTimer.stop();

      var searchValue = this.__searchValue;
      if (searchValue == null || searchValue == "") {
        return;
      }

      var model = this.getModel();
      var list = this.getChildControl("dropdown").getChildControl("list");
      var selection = list.getSelection();
      var length = list._getLookupTable().length;
      var startIndex = model.indexOf(selection.getItem(0));
      var startRow = list._reverseLookup(startIndex);

      for (var i = 1; i <= length; i++)
      {
        var row = (i + startRow) % length;
        var item = model.getItem(list._lookup(row));
        var value = item;

        if (this.getLabelPath() != null)
        {
          value = qx.data.SingleValueBinding.resolvePropertyChain(item,
            this.getLabelPath());

          var labelOptions = this.getLabelOptions();
          if (labelOptions != null)
          {
            var converter = qx.util.Delegate.getMethod(labelOptions,
              "converter");

            if (converter != null) {
              value = converter(value, item);
            }
          }
        }

        if (
          qx.lang.String.startsWith(value.toLowerCase(), searchValue.toLowerCase())
        )
        {
          selection.push(item);
          break;
        }
      }
      this.__searchValue = "";
    },


    /**
     * Converts the keyIdentifier to a printable character e.q. <code>"Space"</code>
     * to <code>" "</code>.
     *
     * @param keyIdentifier {String} The keyIdentifier to convert.
     * @return {String} The converted keyIdentifier.
     */
    __convertKeyIdentifier : function(keyIdentifier)
    {
      if (keyIdentifier === "Space") {
        return " ";
      } else {
        return keyIdentifier;
      }
    }
  },


  destruct : function()
  {
    this._removeBindings();

    this.__searchTimer.removeListener("interval", this.__preselect, this);
    this.__searchTimer.dispose();
    this.__searchTimer == null;
  }
});

// Creates the model data
var model = new qx.data.Array();
for (var i = 0; i < 300; i++) {
  model.push("Item " + (i+1));
}
     
var selectBox = new qx.ui.form.WenBox(model);

var doc = this.getRoot();

doc.add(selectBox);