Friday, April 20, 2012

Flex 3: Custom List Renderers

Let's just face it. A simple list that shows only plain text isn't sufficient. Therefore today I'm going to show how you can create a list with custom renderers.

Here are the source codes... My Main application 'ListDragAndDrop.mxml'
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
    layout="absolute"
     creationComplete="creationCompleteEvent(event)">
 <mx:Script>
  <![CDATA[
   import event.RendererEvent;
   
   import mx.collections.ArrayCollection;
   import mx.collections.XMLListCollection;
   import mx.events.DragEvent;
   import mx.events.FlexEvent;
   
   import renderer.CustomItemRenderer;
   
   private var dataArray:ArrayCollection;
   //Data for the list on the right
   private var tempRStr:String = "<data>" +
    "<item><name>Item r1</name><id>r1</id></item>" +
    "<item><name>Item r2</name><id>r2</id></item>" +
    "<item><name>Item r3</name><id>r3</id></item>" +
    "<item><name>Item r4</name><id>r4</id></item>" +
    "<item><name>Item r5</name><id>r5</id></item>" +
    "</data>";
   private var tempRXML:XML = null;
   
   //Data for the list on the left
   private var tempLStr:String = "<data>" +
    "<item><name>Item l1</name><id>l1</id></item>" +
    "<item><name>Item l2</name><id>l2</id></item>" +
    "<item><name>Item l3</name><id>l3</id></item>" +
    "<item><name>Item l4</name><id>l4</id></item>" +
    "<item><name>Item l5</name><id>l5</id></item>" +
    "</data>";
   private var tempLXML:XML = null;
   
   //Variable to store the selected item
   private var currComponent:* = null;
   
   //Variable to store the selected item's id.
   private var selectedID:String = "";
   
   //Upon creation complete, populate the list on the left and right.
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    dataArray = new ArrayCollection();
    tempLXML = new XML(tempLStr);
    for each(var item:Object in tempLXML.item);
    {
     item.selected = false;
    }
    list1.dataProvider = tempLXML.item;
    tempRXML = new XML(tempRStr);
    for each(item in tempRXML.item);
    {
     item.selected = false;
    }
    list2.dataProvider = tempRXML.item;
    this.addEventListener(RendererEvent.CLICK_ITEM, dataEvent);
   }
   
   /*
    This function will do a few things.
    //1) Remove the selected state of the previous selected item.
    //2) Grab the info of the selected item.
    //3) CHange the label of the button to indicate where you
    //   will be moving the item to.
   */
   private function dataEvent(evt:RendererEvent):void
   {
    if(currComponent)
    {
     (currComponent as CustomItemRenderer).
      isSelected = false;
    }
    currComponent = null;
    selectedID = "";
    btn.label = "-"
    if(evt.component)
    {
     var selectedStr:String = 
      (evt.component as CustomItemRenderer).id;
     currComponent = evt.component;
     selectedID = selectedStr;
     
     var foundObject:Object;
     foundObject = checkList4Data(list1, selectedID);
     if(foundObject.found)
     {
      list2.selectedIndex = -1;
      btn.label = "->"
     }else{
      list1.selectedIndex = -1;
      btn.label = "<-"
     }
    }
   }
   
   //Once the user drag and drop one of the items, reset the
   //state of the selected item and change the label of the
   //button to the original state.
   protected function dragDropHandler(event:DragEvent):void
   {
    if(currComponent)
    {
     (currComponent as CustomItemRenderer).
      isSelected = false;
     currComponent = null;
    }
    btn.label = "-";
   }
   
   //This function will handle all the moving of selected item
   //from left to right and right to left.
   protected function clickHandler(event:MouseEvent):void
   {
    if(currComponent)
    {
     (currComponent as CustomItemRenderer).
      isSelected = false;
     currComponent = null;
    }
    var foundObject:Object;
    var selectedObject:Object;
    if(selectedID != "")
    {
     foundObject = checkList4Data(list1, selectedID);
     if(!foundObject.found)
     {
      foundObject = checkList4Data(list2, selectedID);
      selectedObject = foundObject.tempXML.
       removeItemAt(foundObject.foundNum);
      list2.dataProvider = foundObject.tempXML;
      foundObject.tempXML = 
       XMLListCollection(list1.dataProvider);
      if(!foundObject.tempXML)
      {
       foundObject.tempXML = new XMLListCollection();
      }
      foundObject.tempXML.addItem(selectedObject);
      list1.dataProvider = foundObject.tempXML;
     }else{
      selectedObject = foundObject.tempXML.
       removeItemAt(foundObject.foundNum);
      list1.dataProvider = foundObject.tempXML;
      foundObject.tempXML = 
       XMLListCollection(list2.dataProvider);
      if(!foundObject.tempXML)
      {
       foundObject.tempXML = new XMLListCollection();
      }
      foundObject.tempXML.addItem(selectedObject);
      list2.dataProvider = foundObject.tempXML;
     }
    }
    selectedID = "";
    btn.label = "-";
   }
   
   //Base of the given list, this function will check for the
   //existance of the item that has the same id that was
   //similar to tempStr;
   private function checkList4Data(tempList:List, 
           tempStr:String):Object
   {
    var tempObject:Object = new Object();
    tempObject.found = false;
    tempObject.tempXML = 
     XMLListCollection(tempList.dataProvider);
    if(tempObject.tempXML)
    {
     for(var i:int = 0; i < tempObject.tempXML.length; i ++)
     {
      if(tempObject.tempXML[i].id == selectedID)
      {
       tempObject.found = true;
       tempObject.foundNum = i;
       break;
      }
     }
    }
    return tempObject;
   }
  ]]>
 </mx:Script>
 <mx:VBox width="100%" height="100%"
    verticalGap="0">
  <mx:Spacer height="100%"/>
  <mx:HBox width="100%" height="100%" 
     horizontalGap="0"
     verticalAlign="middle">
   <mx:Spacer width="100%"/>
   <mx:List id="list1"   
      paddingTop="0"
      paddingBottom="0"
      paddingLeft="0"
      paddingRight="0"
      width="100%"
      dragEnabled="true"
      dragMoveEnabled="true"
      dropEnabled="true" 
      dragDrop="dragDropHandler(event)"
      itemRenderer="renderer.CustomItemRenderer"/>
   <mx:Spacer width="20"/>
   <mx:Button click="clickHandler(event)"
        width="50"
        id="btn"
        label="-"/>
   <mx:Spacer width="20"/>
   <mx:List id="list2"    
      paddingTop="0"
      paddingBottom="0"
      paddingLeft="0"
      paddingRight="0"
      width="100%"
      dragEnabled="true" 
      dragDrop="dragDropHandler(event)"
      dragMoveEnabled="true"
      dropEnabled="true"
      itemRenderer="renderer.CustomItemRenderer"/>
   <mx:Spacer width="100%"/>
  </mx:HBox>
  <mx:Spacer height="100%"/>
 </mx:VBox>
</mx:Application>
event/RendererEvent.as
package event
{
 import flash.events.Event;
 
 import mx.core.UIComponent;
 
 /*
  This function handles all the clicking and selection of
  all the items. Although it's pretty heavy for the 
  demo to store the item that is currently selected,
  but to make things easier, this is a fastest way.
 */ 
 public class RendererEvent extends Event
 {
  public static const CLICK_ITEM:String = "CLICK_ITEM";
  private var _component:UIComponent;

  public function get component():UIComponent
  {
   return _component;
  }

  public function set component(value:UIComponent):void
  {
   _component = value;
  }

  public function RendererEvent(type:String, 
           component:UIComponent,
           bubbles:Boolean=false, 
           cancelable:Boolean=false)
  {
   _component = component;
   super(type, bubbles, cancelable);
  }
 }
}
renderer/CustomItemRenderer.mxml
<s;?xml version="1.0" encoding="utf-8"?>
<s;mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
     mouseDown="mouseDownHandler(event)"
     mouseOut="mouseOutHandler(event)"
     mouseOver="mouseOverHandler(event)"
     click="clickHandler(event)"
     mouseChildren="false" backgroundColor="#FF6666"
     creationComplete="creationCompleteEvent(event)">
 <s;mx:Script>
  <s;![CDATA[
   import mx.events.FlexEvent;
   import event.RendererEvent;
   /*
     This is a Component for Custom Renderer that I will
     be using in a list.
   */
   
   //This property will be used to determine whether
   //this item has been selected.
   private var _isSelected:Boolean = false;

   public function get isSelected():Boolean
   {
    return _isSelected;
   }

   //If it is selected give it a light green background
   //else a light red color will do.
   public function set isSelected(value:Boolean):void
   {
    _isSelected = value;
    if(!_isSelected)
    {
     this.setStyle("backgroundColor", 0xFF6666);
    }else{
     this.setStyle("backgroundColor", 0x66FF66); 
    }
   }

   //Mousedown handler
   protected function mouseDownHandler(event:MouseEvent):void
   {
    if(!_isSelected)
    {
     this.setStyle("backgroundColor", 0x66FF66);
    }else{
     this.setStyle("backgroundColor", 0x66FF66); 
    }
   }
   
   //Mouseout handler 
   //When this item is selected, the background should still 
   //remains in light green, else light red will do.
   protected function mouseOutHandler(event:MouseEvent):void
   {
    if(!_isSelected)
    {
     this.setStyle("backgroundColor", 0xFF6666);
    }else{
     this.setStyle("backgroundColor", 0x66FF66); 
    }
   }
   
   //Mouseover handler 
   //When this item is selected, the background should still 
   //remains in light green, else light red will do.
   protected function mouseOverHandler(event:MouseEvent):void
   {
    if(!_isSelected)
    {
     this.setStyle("backgroundColor", 0x6666FF);
    }else{
     this.setStyle("backgroundColor", 0x66FF66); 
    }
   }
   
   //Mouse click handler 
   //Upon clicking, it will dispatch an event indicating 
   //that this item was been selected or unselected.
   protected function clickHandler(event:MouseEvent):void
   {
    _isSelected = !_isSelected;
    if(_isSelected)
    {
     this.dispatchEvent(new RendererEvent(
      RendererEvent.CLICK_ITEM, this, true));
    }else{
     this.dispatchEvent(new RendererEvent(
      RendererEvent.CLICK_ITEM, null, true));
    }
   }
   
   //Upon creation of this item, based on the data that we are
   //getting, change the color of the background.
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    if(data)
    {
     if(data.selected == "true")
     {
      isSelected = true;
     }else{
      isSelected = false;
     }
    }
   }
   
  ]]>
 <s;/mx:Script>
 <s;!-- 
  data is a property that can work within this component
  If you want to point to any of the property in data, you
  need to create a property like the following.
 -->
 <s;mx:String id="id">{data.id}<s;/mx:String>
 <s;mx:HBox width="100%" height="20" horizontalGap="0">
  <s;mx:Label id="lbl" text="{data.name}"/>
  <s;mx:Spacer width="100%"/>
 <s;/mx:HBox>
<s;/mx:Canvas>
* Click here for the demo.
^ Click here for the source files.

No comments:

Post a Comment