May 11 2004

Flex MXML Sample 2: custom container to reposition elements with drag and drop

Published by at 21:19 under Wayback Archive

The Flex documentation does not mention how you can create your custom containers. I decided to post this experiment that I have been working on for some time and hope that you can learn from this. The source code is relatively well commented, so it should get you going.

This custom container “Drag Panel” extends a VBox and adds drag and drop functionality to all it’s children, so that the user can reposition them. Using the feedbackMode attribute you can define how the user gets the drop target feedback whilst dragging; the component can reposition the children whilst dragging (left container in the sample), or show a dropindicator (right container in the sample).

A learned a lot from building this component, but there are still a lot more things that I would like to explore; remember the panel positions by storing it in a shared object, allow drag and drop between multiple containers (move one panel from container a to container b), apply effects when repositioning elements (got that working in a later version already), show and hide panels, etc etc.

Below is the sample and commented code, let me know when you have questions.

PanelDrag.mxml

<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Application xmlns:mx=”http://www.macromedia.com/2003/mxml” xmlns=”*” width=”100%” height=”100%”>
 <mx:HBox>
  <DragPanelContainer id=”myContainer” feedbackMode=”reposition” width=”100″ borderStyle=”solid” backgroundAlpha=”0″ backgroundColor=”#FFFFFF”>
   <mx:Panel id=”panelA” title=”Panel A”>
    <mx:Label text=”My label” />
   </mx:Panel>
   <mx:Panel id=”panelB” title=”Panel B”>
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
   </mx:Panel>
   <mx:Panel id=”panelC” title=”Panel C”>
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
   </mx:Panel> 
   <mx:Panel id=”panelD” title=”Panel D”>
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
   </mx:Panel>
  </DragPanelContainer>
  <DragPanelContainer id=”myContainer2″ feedbackMode=”line” width=”100″ borderStyle=”solid” backgroundAlpha=”0″ backgroundColor=”#FFFFFF”>
   <mx:Panel id=”panelA2″ title=”Panel A”>
    <mx:Label text=”My label” />
   </mx:Panel>
   <mx:Panel id=”panelB2″ title=”Panel B”>
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
   </mx:Panel>
   <mx:Panel id=”panelC2″ title=”Panel C”>
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
   </mx:Panel> 
   <mx:Panel id=”panelD2″ title=”Panel D”>
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
    <mx:Label text=”My label” />
   </mx:Panel>
  </DragPanelContainer>
 </mx:HBox>
</mx:Application>

DragPanelContainer.as

class DragPanelContainer extends mx.containers.VBox{
        /****************************************
                  NOTES:
                  – The box needs to have something in the background to accept a drop
          ****************************************/
          
          /********** Variables **********/
          var dragBetweenContainers:Boolean = false;
          var feedbackMode:String = “line”; // line, reposition
          
          // Insert bar that appears when you call the showDropFeedback method.
          [Embed(symbol=’ListDropIndicator’)]
          var dropIndicatorSkin:String = “ListDropIndicator”;
          
          /********** Private variables **********/
          private var _mDown:Boolean = false;
          private var _dropIndex:Number;
          private var _dragPod; // Not typed because can be mx.core.UIObject, Panel etc.
          private var _originalIndex:Number;
          
          /********** Constructor **********/
        function DragPanelContainer(){
          }
  
          /********** Super methods inheritance/overwriting **********/
        function init(Void):Void{
                  super.init();
                  
                  // Create listeners for container
                  addEventListener(“dragEnter”, this);
                  addEventListener(“dragOver”, this);
                  addEventListener(“dragExit”, this);
                  addEventListener(“mouseUpSomewhere”, mx.utils.Delegate.create(this, onMouseUpSomewhere));
          }
          
        function createChildren(Void):Void {
                  super.createChildren();
                  
                for (var i=0; i<numChildren; i++ ){
                          var theChild = getChildAt(i);
  
                          // TO DO: Do not add to entire pod, but only header or image in header
                          theChild.addEventListener(“mouseDown”, mx.utils.Delegate.create(this, onChildMouseDown));
                          theChild.addEventListener(“mouseMove”, mx.utils.Delegate.create(this, onChildMouseMove));
                          theChild.addEventListener(“dragComplete”, mx.utils.Delegate.create(this, doDragComplete));
                  }
          }
          
        /**********
                  COMPONENT SPECIFIC FUNCTIONS
          **********/
          
          /********** CONTAINER EVENT HANDLERS **********/
          // Can’t these be overwritten this way? Maybe I should use custom eventhandler proxy
          
          // When user enters this container whilst dragging
        function dragEnter(event) {
                if (!dragBetweenContainers) { // Check whether dragged pod is child of this container
                          var pod = event.dragSource.dataForFormat(“item”); // we do not refer to _dragPod here, because it can as well be a pod from an other container
                          
                        if (getChildIndex(pod) != undefined) {
                                  event.handled = true;
                        } else {
                                  event.handled = false;
                          }
                } else {
                          // To Do: Check whether dragged object comes from another DragPanelContainer
                          event.handled = true;
                  }
          }
          
        function dragExit(event) {
                  event.handled = false;
                  hideDropFeedback();
          }
          
          // When user drags something over container
        function dragOver(event){
                  showDropFeedback();
          }
          
          // Called if mouse goes up somewhere, no matter whether it’s above a child or not
        function onMouseUpSomewhere(event){
                  _mDown=false;
                if (mx.managers.DragManager.isDragging){ // restore visibility of pod if user was dragging
                          clearInternalVariables();
                  }
                  _dragPod.visible = true;
          }
          
          /********** CHILD EVENT HANDLERS **********/
  
          // Fires if mouse goes down on a child of this container
        function onChildMouseDown(event){
                  _mDown=true;
          }
          
          // Fires if mouse moves over a child of this container
        function onChildMouseMove(event){
                  // Does user start to drag? We see this when mouse is down
                  // and no DragManager is created yet
                if (_mDown && !mx.managers.DragManager.isDragging) {
                          // Write reference to private variable for internal use
                          _dragPod = event.target;
                          _originalIndex = getChildIndex(_dragPod);
                          
                          // store into dragSource a reference to the object we are dragging
                          // for external drag operation use
                          var ds = new mx.core.DragSource;
                          ds.addData(_dragPod, “item”);
                          
                          mx.managers.DragManager.doDrag(_dragPod, ds, mx.containers.Panel, {title:_dragPod.title, width:_dragPod.width, height:_dragPod.height});
                          
                          // Hide pod that is being dragged
                        if (feedbackMode==”line” || feedbackMode==”reposition”){
                                  _dragPod.visible = false;
                          }
                  }
          }
          
                  // When user drops something in container
        function doDragComplete(event) {
                  hideDropFeedback();
  
                if (event.action == “none”) { // Drag was not successfull
                        if (feedbackMode==”reposition”){
                                  setChildIndex(_dragPod, _originalIndex); // Current index possibly is not the original index, set back to original index
                          }
                          _dragPod.visible=true; // make pod that was dragged visible
                } else { // drag operation was successfull
                          // Reposition pods
                          var pod = event.dragSource.dataForFormat(“item”); // we do not refer to _dragPod here, because it can as well be a pod from an other container
                        if (feedbackMode==”line”) {
                                  var startedAtIndex = getChildIndex(pod);
                                if (startedAtIndex < _dropIndex) {
                                          setChildIndex(pod, _dropIndex-1);
                                } else if (startedAtIndex != _dropIndex && startedAtIndex != _dropIndex-1) {
                                          setChildIndex(pod, _dropIndex);
                                  }
                          }
                          pod.visible=true; // make pod that was dragged visible
                  }
          }
          
          /********** MISC **********/
        function showDropFeedback(){
                if (feedbackMode==”line”){
                          // Create dropIndicator only if not yet created
                          // To do: dropIndicator also needs to be removed some time
                        if (this[“dropIndicator”] == undefined)        {
                                  var o = createObject(dropIndicatorSkin, “dropIndicator”, 9999);
                                  var vm = getViewMetrics();
          
                                  drawFocus(true);
          
                                  o._x = vm.left + 2;
                                  o.setSize(layoutWidth – vm.left – vm.right – 4, 4);
                                  o._visible = true;
                          }
                          this[“dropIndicator”]._y =  calculateDropIndicatorY();
                  }
                  
                if (feedbackMode==”reposition”) {
                          calculateDropIndicatorY(); // we do not need the Y value, but we need to set _dropIndex to the correct value;
                          setChildIndex(_dragPod, _dropIndex);
                  }
          }
          
          // NOTE: This currently does 2 things at once
          //        – return Y
          //        – set _dropIndex
          // this needs to be seperated if possible
        function calculateDropIndicatorY():Number{
                  var yPos:Number=0;
                  var notAfterLastPod:Boolean=false;
                  
                  // Assuming that pods are always ordered on their y position (?)
                  // the first pod which y is bigger than ymouse is the pod above
                  // which to position the indicator
                for (var i=0; i<numChildren; i++){
                        if ((getChildAt(i)._y + getChildAt(i)._height/2 ) > _ymouse) {
                                  notAfterLastPod = true;
                                  break;
                          }
                  }
                  
                if (notAfterLastPod) {
                        if (i==0) { // If above half of first pod –> show on top
                                  yPos = 0; // Distract a few pixels maybe?
                          }
                          // else if dragging over other pod, show line above pod
                          // if y of the dragged pod is higher than the middle
                          // of the pod we are dragging over. If not, show below
                        else {
                                  var bottomOfHeigherPod = getChildAt(i-1).y + getChildAt(i-1).height;
                                  yPos = getChildAt(i).y – (getChildAt(i).y – bottomOfHeigherPod)/2;
                          }                        
                          _dropIndex = i;
                } else {
                          // if below half of last pod –> show on bottom
                          yPos = getChildAt(numChildren-1)._y + getChildAt(numChildren-1)._height;
                          _dropIndex = numChildren;
                  }
  
                  return yPos;
          }
          
        function hideDropFeedback(){
                  // Show pod that is being dragged
                if (feedbackMode==”line” || feedbackMode==”reposition”){
                          this[“dropIndicator”].removeMovieClip();
                  }
                  
                  drawFocus(false);
          }
          
        function clearInternalVariables(){
                  _mDown = false;
                  _dropIndex = undefined;
                  _dragPod = undefined;
          }
  }

7 responses so far

7 Responses to “Flex MXML Sample 2: custom container to reposition elements with drag and drop”

  1. Waldo Smeets says:

    One other cool thing you might want to try is define a new Move effect named myMove which has the background processing disabled, and then apply this effect as moveEffect to the pods. This effect is really cool if you are using "reposition" as the feedbackMode.

    So add the following to your code:
    <mx:Effect>
    <mx:Move name="myMove" suspendBackgroundProcessing="true" duration="500" />
    </mx:Effect>

    And add moveEffect="myMove" to each Panel, like this: <mx:Panel id="panelA" title="Panel A" moveEffect="myMove" >

    This will nicely move your pods around whilst dragging. This defenitely looks better then the wild jump from a to b.

    Now imagine what it looks like when you apply a bounce effect as easing function well:
    <mx:Effect>
    <mx:Move name="myMove" suspendBackgroundProcessing="true" duration="500" easing="myEasingFunction" />
    </mx:Effect>

    and

    <mx:Script>
    <![CDATA[
    function myEasingFunction(t, b, c, d) {
    if ((t /= d) < (1 / 2.75)) {
    return c * (7.5625 * t * t) + b;
    }
    else if (t < (2 / 2.75)) {
    return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
    }
    else if (t < (2.5 / 2.75)) {
    return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
    }
    else {
    return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
    }
    };
    ]]>
    </mx:Script>

  2. It would be better if we have live link of the example here 🙂

  3. Waldo Smeets says:

    Thanks for the feedback Ashvin! Actually, I am already looking into this and hope to have something very soon.

  4. A great example, I must dig deep into it, but I test it and it’s a great resource.
    Thanks Waldo!

    🙂

  5. Peter says:

    Christophe Coenraets has a Flex server up and running apparently, might be able to host your examples there Waldo.

    Sincerely hope you don’t have to fork out a Flex license out of your own pocket to get some live examples online 😉 Great example though!

  6. flexdn says:

    Hi,,this good!
    thank you!
    welcome to we website
    http://www.flexdn.com

  7. flexdn says:

    oh,i’m sorry , I just inputed it wrong..
    “welcome to our website”