Showing posts with label Flash AS3. Show all posts
Showing posts with label Flash AS3. Show all posts

Friday, August 2, 2013

Flex: Creating the Sudoku Base Grid

I have been trying out all sorts of method in creating a Sudoku Grid and I finally managed to do it. The solution was far more easier than I thought ...

Let's take a look at the source code - SimpleSudokuGrid.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx" 
      creationComplete="creationCompleteEvent(event)">
 <fx:Declarations>
  <!-- Place non-visual elements (e.g., services, value objects) here -->
 </fx:Declarations>
 <fx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;
   import mx.events.FlexEvent;
   
   import spark.components.Button;
   import spark.events.IndexChangeEvent;
   
   /**
    * Holds the data to form a Grid
    */
   private var baseGrid:ArrayCollection;
   
   /**
    * Boolean to determine if there's a need to recreate the grid
    */
   private var needToReset:Boolean = true;
   
   /**
    * Number to count the number of failure in the current
    * grid data forming.
    */
   private var tries:Number = 0;
   
   /**
    * Maximum number of failure before recreate.
    */
   private static const MAX_TRIES:Number = 9;
   
   /**
    * A set of data that will be used to populate the ButtonBar
    */
   [Bindable]
   private var dummyData:ArrayCollection = new ArrayCollection(
    [{label: "1", value:1},
     {label: "2", value:2},
     {label: "3", value:3},
     {label: "4", value:4},
     {label: "5", value:5},
     {label: "6", value:6},
     {label: "7", value:7},
     {label: "8", value:8},
     {label: "9", value:9}]);
   
   /**
    * Upon Create Complete, we will create a Sudoku(9x9) Grid
    */
   protected function creationCompleteEvent(e:FlexEvent):void
   {
    //Will keep recreating the grid if 'needToReset' is
    //true
    while(needToReset)
    {
     //Set 'needToReset' to false
     needToReset = false;
     createGrid();
     createNumbers();
    }
    
    createGridUI();
    btnBar.dispatchEvent(
     new IndexChangeEvent(IndexChangeEvent.CHANGE));
   }
   
   /**
    * Create new (9*9) DataCollection and fill it up with 0.
    */
   private function createGrid():void
   {
    baseGrid = new ArrayCollection();
    
    var tempCol:ArrayCollection;
    for(var i:int = 0; i < 9; i ++)
    {
     tempCol = new ArrayCollection();
     for(var j:int = 0; j < 9; j++)
     {
      tempCol.addItem(0);
     }
     baseGrid.addItem(tempCol);
    }
   }
   
   /**
    * Start populating the numbers from 1 - 9.
    * If 'needToReset' is true, move out of this function.
    */ 
   private function createNumbers():void
   {
    for(var i:int = 1; i < 10; i ++)
    {
     insertNumber(i);
     
     if(needToReset)
     {
      break; 
     }
    }
    
    if(needToReset)
    {
     return; 
    }
    
    printNumbers();
   }
   
   /**
    * Find the empty boxes that you can slot the value
    * @param value a value that you are going to slot in
    */   
   private function insertNumber(value:Number):void
   {
    //Used to store the rows avaliable in a column
    var numbersFree:Array;
    //Is the 1st segment taken?
    var fromSegment1:Boolean = false;
    //Rows that we will need to remove if 1st segment
    //is taken
    var seg1Array:Array = [0,1,2];
    //Is the 2nd segment taken?
    var fromSegment2:Boolean = false;
    //Rows that we will need to remove if 2nd segment
    //is taken
    var seg2Array:Array = [3,4,5];
    //Is the 3rd segment taken?
    var fromSegment3:Boolean = false;
    //Rows that we will need to remove if 3rd segment
    //is taken
    var seg3Array:Array = [6,7,8];
    //Stores the current selected row of a column
    var rand:Number;
    //Stores all the boxes that has the value 
    var tempArray:Array = new Array();
    for(var i:int = 0; i < 9; i ++)
    {
     //In sudoku, it's divided into 9 (3*3) boxes.
     //Each each of this box, the same number can 
     //appear once.
     //Therefore every 3 column, we need to reset
     //these 3 values.
     if(i % 3 == 0)
     {
      fromSegment1 = false;
      fromSegment2 = false;
      fromSegment3 = false;
     }
     //Find the empty slots of the column first
     numbersFree = findEmptySlot(i);
     //Remove the rows based on the previous selections
     if(fromSegment1)
     {
      numbersFree = removeSlot(numbersFree, seg1Array);
     }
     if(fromSegment2)
     {
      numbersFree = removeSlot(numbersFree, seg2Array);
     }
     if(fromSegment3)
     {
      numbersFree = removeSlot(numbersFree, seg3Array);
     }
     //Remove the rows that have been selected already.
     if(tempArray.length > 0)
     {
      numbersFree = removeSlot(numbersFree, tempArray);
     }
     
     //If there are rows left
     if(numbersFree.length > 0)
     {
      //Select one of them
      rand = Math.floor(Math.random() 
       * numbersFree.length);
      rand = numbersFree[rand];
      //Check which Segment the row number is in
      if(rand < 3)
      {
       fromSegment1 = true;
      }else if(rand < 6)
      {
       fromSegment2 = true;
      }else{
       fromSegment3 = true;
      }
      //Add it to the list of boxes with the 
      //same value
      tempArray.push(rand);
      
      //Update the value in the main DataCollection
      insertSlot(i, rand, value);
     }else{
      //else if '0' row is avaliable
      //increase the number of failures/tries
      tries ++;
      //Revert back all the slots
      revertSlots(tempArray);
      tempArray = new Array();
      i = -1;
      //Check if it has reach the Maximum tries/failures
      if(tries == MAX_TRIES)
      {
       //Need to recreate the grid again.
       tries = 0;
       needToReset = true;
       return;
      }
     }
    }
   }
   
   /**
    * Find all the empty rows of a selected Column
    * @param col the selected column index
    * @return Array of rows that currently has a value of '0'
    */
   private function findEmptySlot(col:Number):Array
   {
    var tempArray:Array = new Array();
    var tempArrayCol:ArrayCollection;
    tempArrayCol = ArrayCollection(baseGrid.getItemAt(col));
    for(var i:int = 0; i < tempArrayCol.length; i ++)
    {
     if(Number(tempArrayCol.getItemAt(i)) == 0)
     {
      tempArray.push(i);
     }
    }
    return tempArray;
   }
   
   /**
    * Compare 2 Arrays.
    * @param tempArray1 Array of values
    * @param tempArray2 Array of values
    * @return the Array of values tempArray1 that are different from 
    * tempArray2
    */ 
   private function removeSlot(tempArray1:Array, tempArray2:Array):Array
   {
    var tempArray:Array = new Array();
    for(var i:int = 0; i < tempArray1.length; i ++)
    {
     tempArray.push(tempArray1[i]);
    }
    for(i = 0; i < tempArray.length; i ++)
    {
     for(var j:int = 0; j < tempArray2.length; j ++)
     {
      if(tempArray[i] == tempArray2[j])
      {
       tempArray.splice(i, 1);
       i --;
       break;
      }
     }
    }
    return tempArray;
   }
   
   /**
    * Replace a particular slot with a new value.
    * @param col selected column
    * @param row selected row
    * @param value the new value
    */
   private function insertSlot(col:Number, row:Number, value:Number):void
   {
    var tempArrayCol:ArrayCollection;
    tempArrayCol = ArrayCollection(baseGrid.getItemAt(col));
    tempArrayCol.setItemAt(value, row);
   }
   
   /**
    * This function will change the newly numbers of the same group 
    * back to 0. For example, halfway through the number 7, a problem 
    * has occur, therefore need to replace all the number 7 from the 
    * first box again. Hence, need to reset all the existing number 
    * 7 box to '0' first.
    * @param tempArray1 Boxes that requires the values to be reset to '0'
    */
   private function revertSlots(tempArray1:Array):void
   {
    var tempArrayCol:ArrayCollection;
    for(var i:int = 0; i < tempArray1.length; i ++)
    {
     tempArrayCol = ArrayCollection(baseGrid.getItemAt(i));
     tempArrayCol.setItemAt(0,tempArray1[i])
    }
   }
   
   /**
    * This is merely for debugging purposes. You can use
    * this function to track the valus in the (9x9) DataCollection.
    */
   private function printNumbers():void
   {
    var tempArrayCol:ArrayCollection;
    var tempArray:Array = new Array();
    for(var i:int = 0; i < 9; i ++)
    {
     tempArray.push("");     
    }
    for(var col:int = 0; col < baseGrid.length; col ++)
    {
     tempArrayCol = 
      ArrayCollection(baseGrid.getItemAt(col));
     for(var row:int = 0; row < tempArrayCol.length; row ++)
     {
      tempArray[row] += 
       Number(tempArrayCol.getItemAt(row));
     }
    }
    for(i = 0; i < tempArray.length; i ++)
    {
     trace(tempArray[i]);
    }
   }
   
   /**
    * Create all the buttons base on the values in the ArrayCollection
    */ 
   private function createGridUI():void
   {
    var btn:Button;
    var tempArrayCol:ArrayCollection;
    
    for(var row:int = 0; row < 9; row ++)
    {
     for(var col:int = 0; col < baseGrid.length; col ++)
     {
      tempArrayCol = 
       ArrayCollection(baseGrid.getItemAt(col));
      btn = new Button();
      btn.width = 30;
      btn.height = 30;
      btn.label = 
       Number(tempArrayCol.getItemAt(row)).toString();
      btn.name = "btn_" + row + "_" + col; 
      grpGrid.addElement(btn);
     }
    }
   }
   
   /**
    * Upon clicking on one of the buttons of the ButtonBar, we will
    * show buttons with the same value with a alpha value of 1 and
    * those that are different will have a alpha value of 0.3.
    */
   protected function changeEvent(event:IndexChangeEvent):void
   {
    var tempBar:ButtonBar = ButtonBar(event.target);
    var tempObj:* = tempBar.selectedItem;
    var selectedValue:Number = -1;
    if(tempObj)
    {
     selectedValue = tempObj.value;
    }
    
    var btn:Button;
    for (var i:int = 0; i < grpGrid.numElements; i ++)
    {
     btn = Button(grpGrid.getElementAt(i));
     if(Number(btn.label) == selectedValue)
     {
      btn.alpha = 1;
     }else{
      btn.alpha = 0.3;
     }
    }
   }
  ]]>
 </fx:Script>
 <s:VGroup verticalCenter="0" 
     horizontalCenter="0" 
     horizontalAlign="center">
  <s:Group id="grpGrid" >
   <s:layout>
    <s:TileLayout requestedRowCount="9" 
         requestedColumnCount="9"
         horizontalGap="3" 
         verticalGap="3"/>
   </s:layout>
  </s:Group>
  <s:Label text="Click on the following to show the buttons with the same value."/>
  <s:ButtonBar id="btnBar" 
      dataProvider="{dummyData}"  
      selectedIndex="-1"
      labelField="label"
      change="changeEvent(event)"/>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Saturday, June 1, 2013

AS 3: Opening a url with utf-8 encode parameters

Not sure if any of you out there might encounter the following problem, but with all these social networking sites floating around the Internet, there might be a requirement that requires you to open up a website with some parameters. Therefore...

Here's the source code of my main application - SimpleTwitterPost.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      backgroundColor="#CDCDCD">
 
 <fx:Script>
  <![CDATA[
   //A default message.
   private var msg:String = "僕は男性です。";
   //URL of Twitter.
   private var strTwitter:String = "http://www.twitter.com";
   
   //Label
   private var strLabel:String = "Please enter your " +
    "message in the text box provided below."
   
   //Upon clicking on one of the buttons,
   protected function clickHandler(event:MouseEvent):void
   {
    var tempStr:String = txtInput.text;
    if(event.currentTarget == btn1)
    {
     //encode the message in utf-16,
     //which is not supported across most browsers.
     tempStr = escape(tempStr);
    }else{
     //encode the message in utf-8,
     //which is supported across all browsers.
     tempStr = encodeURIComponent(tempStr);
    }
    tempStr = strTwitter + "?status=" + tempStr;
    
    var tempURLReq:URLRequest;
    tempURLReq = new URLRequest(tempStr);
    
    //Open the URL in a new window.
    navigateToURL(tempURLReq, "_blank");
   }
  ]]>
 </fx:Script>
 <fx:Declarations>
  <!-- Place non-visual elements (e.g., services, value objects) here -->
 </fx:Declarations>
 <s:VGroup verticalAlign="middle" 
     horizontalAlign="center" 
     width="100%" 
     height="100%">
  <s:Spacer height="100%"/>
  <s:Label text="{strLabel}"/>
  <s:TextArea maxChars="140"  
     id="txtInput"
     heightInLines="4"
      width="50%" 
     text="{msg}"/>
  <s:Button label="Using escape()" 
      click="clickHandler(event)" 
      width="200"
      id="btn1"/>
  <s:Button label="Using encodeURIComponent()"
      click="clickHandler(event)"
      width="200"
      id="btn2"/>
  <s:Spacer height="100%"/>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Saturday, April 13, 2013

FB + Adobe AIR: Posting Bug

My friends were asking me to help them to debug an interest problem that they are facing with the Facebook Graph Desktop API.

The scenario as follows. A user log in to the desktop application (Adobe AIR) through Facebook and he decided to post something on his Facebook wall through the application and decides to log out of the Facebook and Application. If a second user tries to log in and post something to his Facebook wall using the application, the message will appear on the wall on the first user, rather than the second user. Therefore here's a fix to that issue if you are using the source files of the 'Facebook Graph Desktop API'.
You have to modify the following function of the following file - com\facebook\graph\FacebookDesktop.as From
    public static function api(method:String,
                     callback:Function,
                     params:* = null,
                     requestMethod:String = 'GET'
    ):void {
      getInstance().api(method,
        callback,
        params,
        requestMethod
      );
    }
To
    public static function api(method:String,
                     callback:Function,
                     params:* = null,
                     requestMethod:String = 'GET'
    ):void {

      if(params != null)
      {
        if (getInstance().session != null) {
          params.access_token = getInstance().session.accessToken;
        }
      }
      getInstance().api(method,
        callback,
        params,
        requestMethod
      );
    }
* Click here for the updated file 'FacebookDesktop.as'.
^ Click here to find out more about the 'Facebook Graph Desktop API'.

Saturday, March 23, 2013

AS3: How to enable Double Clicking

Well, probably everyone knows about this, but just if case you are new to the as3 world... Here goes.

Here's the codes for my main application file.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      backgroundColor="#CDCDCD">
 <fx:Script>
  <![CDATA[
   [Bindable]
   private var _enabledDoubleClick:Boolean = false;
   
   [Bindable]
   private var _bkgdColor:uint = 0xFFFFFF;
   
   //Upon each successful double click, we will change the 
   //background color of the box. :D
   protected function doubleClickHandler(event:MouseEvent):void
   {
    _bkgdColor = Math.random() * 0xFFFFFF; 
   }
  ]]>
 </fx:Script>
 <s:VGroup width="100%" 
     height="100%"
     verticalAlign="middle"
     horizontalAlign="center">
  <s:Spacer height="100%"/>
  <s:HGroup verticalAlign="middle" 
      horizontalAlign="center"
      width="100%">
   <s:TextArea borderVisible="false"
      contentBackgroundAlpha="0"
      width="50%" height="50">
    Double Clicking will only work when the CheckBox is selected. 
    (When the checkbox is selected, it will bind the value of the 
    CheckBox to a Boolean variable that will change the value of 
    the property doubleClickEnabled of the box below.)
   </s:TextArea>
  </s:HGroup>
  <s:HGroup verticalAlign="middle" 
      horizontalAlign="center"
      width="100%">
   <s:CheckBox selected="@{_enabledDoubleClick}"
       label="Enable Double Clicking?"/>
  </s:HGroup>
  <s:HGroup verticalAlign="middle" 
      horizontalAlign="center"
      width="100%">
   <s:BorderContainer width="50%"
          height="50%"
          backgroundColor="{_bkgdColor}"
          cornerRadius="10"
          mouseChildren="false"
          doubleClickEnabled="{_enabledDoubleClick}"
          doubleClick="doubleClickHandler(event)">
    <s:layout>
     <s:HorizontalLayout horizontalAlign="center"
           verticalAlign="middle"/>
    </s:layout>
    <s:Label text="Try Double Clicking Me!"/>
   </s:BorderContainer>
  </s:HGroup>
  <s:Spacer height="100%"/>
 </s:VGroup>
</s:Application>

* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Friday, March 15, 2013

AS3: event.stopImmediatePropagation() v.s. event.stopPropagation()

When you are working on a flash/flex project and there's multiple layers inside the project, there ought to be changes where you have multiple places listening for the same Events. Luckily for us, there are ways to stop the Events from dispatching upwards. However there seems to be 2 different methods to stop it and what's the difference between the 2 methods event.stopImmediatePropagation() and event.stopPropagation()? I have created a simple demo to show you the differences between both methods.

Source code of the Main Application file - stopBubbleEventTest.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" 
    minWidth="955" minHeight="600" 
    xmlns:Comp="*" creationComplete="creationCompleteEvent(event)" 
    name="stage">
 <!-- Styling the VBox -->
 <mx:Style>
  VBox{
   paddingLeft:10;
   paddingRight:10;
   backgroundAlpha:0.2;
   backgroundColor:#00FF00;
  }
 </mx:Style>
 <mx:Script>
  <![CDATA[
   import mx.events.FlexEvent;
   
   //Upone creation complete of this mxml files
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    //Add an Event Listener to the button / 
    //Upon clicking on the button, run the actions in clickEvent function
    btnClick.addEventListener(MouseEvent.CLICK, clickEvent);
    //Change the state of the checkbox of the highest level
    stageControls.chkOption.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
   }
   
   private function clickEvent(event:Event):void{
    //Reset all the values in the textfields
    stageControls.txtResult.text = "";
    level0Controls.txtResult.text = "";
    level1Controls.txtResult.text = "";
    level2Controls.txtResult.text = "";
    level3Controls.txtResult.text = "";
    level4Controls.txtResult.text = "";
    stageControls.txtResult2.text = "";
    level0Controls.txtResult2.text = "";
    level1Controls.txtResult2.text = "";
    level2Controls.txtResult2.text = "";
    level3Controls.txtResult2.text = "";
    level4Controls.txtResult2.text = "";
    //Dispatch an event that will bubble / move all the way to the highest level
    btnClick.dispatchEvent(new DataEvent(DataEvent.DATA,true,false,"Click"));
   }
   
  ]]>
 </mx:Script>
 <mx:HBox width="100%" horizontalAlign="center">
  <mx:CheckBox id="chkEventType" selected="false"
      label="Are we using event.stopImmediatePropagation()?"/>
 </mx:HBox>
 <Comp:FormControls id="stageControls" 
        useImmediate="{chkEventType.selected}"
        top="30"/>
 <mx:VBox name="level0" width="100%" height="100%" verticalGap="0" top="80">
  <Comp:FormControls id="level0Controls"
         useImmediate="{chkEventType.selected}"/>
  <mx:VBox name="level1" width="100%" height="100%" verticalGap="0">
   <Comp:FormControls id="level1Controls"
          useImmediate="{chkEventType.selected}"/>
   <mx:VBox name="level2" width="100%" height="100%" verticalGap="0">
    <Comp:FormControls id="level2Controls"
           useImmediate="{chkEventType.selected}"/>
    <mx:VBox name="level3" width="100%" height="100%" verticalGap="0">
     <Comp:FormControls id="level3Controls"
            useImmediate="{chkEventType.selected}"/>
     <mx:VBox name="level4" width="100%" height="100%" verticalGap="0">
      <Comp:FormControls id="level4Controls"
             useImmediate="{chkEventType.selected}"/>
      <mx:Button id="btnClick" label="Click me..."/>
     </mx:VBox>
    </mx:VBox>
   </mx:VBox>
  </mx:VBox>
 </mx:VBox>
</mx:Application>
SOurce code of my custom component - FormControls.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" creationComplete="creationCompleteEvent(event)">
 <mx:Style>
  
 </mx:Style>
 <mx:Script>
  <![CDATA[
   import mx.events.FlexEvent;
   
   //This bindable variable will be used to determine the
   //following:
   //_useStopWithImmediate = false => 
   // we use event.stopPropagation();
   //_useStopWithImmediate = true => 
   // we use event.stopImmediatePropagation();
   [bindable]
   private var _useImmediate:Boolean = false;

   public function get useImmediate():Boolean
   {
    return _useImmediate;
   }

   public function set useImmediate(value:Boolean):void
   {
    _useImmediate = value;
   }
   
   //Upone creation complete of this view
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    //Add the listeners for DATA
    this.parent.addEventListener(DataEvent.DATA, displayEvent);
    this.parent.addEventListener(DataEvent.DATA, display2Event);
   }
   
   //When the parent view had capture a call for DataEvent.DATA
   private function displayEvent(event:Event):void{
    if(chkOption.selected){
     txtResult.text = "Event had been listened at " +
      "first field of " + this.parent.name + ".";
     //Stops the event from bubbling / moving upwards
     //Remove this line to see how you can listen for the 
     //same event in all the selected levels
     if(_useImmediate)
     {
      event.stopImmediatePropagation();
     }else{
      event.stopPropagation(); 
     }
    }
   }  
   
   //When the parent view had capture a call for DataEvent.DATA
   private function display2Event(event:Event):void{
    if(chkOption2.selected){
     txtResult2.text = "Event had been listened at " +
      "second field of " + this.parent.name + ".";
    }
   }  
  ]]>
 </mx:Script>
 <mx:VBox width="100%">
  <mx:HBox width="100%">
   <mx:Text id="txtResult" text=""/>
   <mx:Spacer width="100%"/>
   <mx:CheckBox id="chkOption" 
       label="Select me to check for Event 1"/>
  </mx:HBox>
  <mx:HBox width="100%">
   <mx:Text id="txtResult2" text=""/>
   <mx:Spacer width="100%"/>
   <mx:CheckBox id="chkOption2" 
       label="Select me to check for Event 2"/>
  </mx:HBox>
 </mx:VBox>
</mx:Canvas>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Wednesday, November 28, 2012

Empty String != null

For some of us out there, we might be assigning an empty string to various types of variables thinking that nothing might go wrong. However, that isn't the case. For different types of objects / components and for different types of variables or properties, by assigning an empty string might give you some unnecessary troubles. For example...

Time for some source codes: ImageSourceIssue.mxml


 
 
  
 
 
 
  
 
 
  
   
  
  
  
  
   
   
   
   
   
   
   
   
   
   
  
  
   
   
  
  
  
 


If you are using firebug or Charles or any other types of Web Developer Tools, you can easily identify the differences in the results for the 3 buttons. :)

* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Friday, November 9, 2012

Flex: Playing with multiple column chart

Well, I was trying to figure out how to render multiple column chart some time back. After spending a few days playing around it, I finally found a way to work around it. :)

Tim for some source files - 'SimpleMultipleColumn.mxml'
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      backgroundColor="#CDCDCD"
      creationComplete="creationCompleteEvent(event)"> 
 <fx:Declarations>
  <!-- 
  We need to specify the types of animations over here
  -->
  <s:Parallel id="parallelEffect">
   <s:Fade duration="1000" alphaFrom="0" alphaTo="1"/>
   <mx:SeriesSlide duration="1000" direction="up"/>
  </s:Parallel>
  <s:Parallel id="parallelCarEffect">
   <s:Fade duration="1000" alphaFrom="0" alphaTo="1"/>
  </s:Parallel>
 </fx:Declarations>
 <fx:Script>
  <![CDATA[
   import flashx.textLayout.conversion.TextConverter;
   
   import mx.charts.HitData;
   import mx.charts.chartClasses.IChartElement2;
   import mx.charts.chartClasses.Series;
   import mx.charts.series.ColumnSeries;
   import mx.charts.series.LineSeries;
   import mx.collections.ArrayCollection;
   import mx.collections.Sort;
   import mx.collections.SortField;
   import mx.events.FlexEvent;
   import mx.formatters.DateFormatter;
   import mx.graphics.Stroke;
   
   import spark.events.IndexChangeEvent;
   
   //Records used in the chart
   [Bindable]
   private var myData:XML = 
    <records> 
     <record>
      <date>01/09/2013</date>
      <people>63</people>
      <car>23</car>
     </record>
     <record>
      <date>02/09/2013</date>
      <people>61</people>
      <car>81</car>
     </record>
     <record>
      <date>03/09/2013</date>
      <people>67</people>
      <car>47</car>
     </record>
     <record>
      <date>04/09/2013</date>
      <people>75</people>
      <car>95</car>
     </record>
     <record>
      <date>05/09/2013</date>
      <people>65</people>
      <car>45</car>
     </record>
     <record>
      <date>06/09/2013</date>
      <people>32</people>
      <car>52</car>
     </record>
     <record>
      <date>07/09/2013</date>
      <people>66</people>
      <car>46</car>
     </record>
     <record>
      <date>08/09/2013</date>
      <people>85</people>
      <car>105</car>
     </record>
     <record>
      <date>09/09/2013</date>
      <people>37</people>
      <car>57</car>
     </record>
     <record>
      <date>10/09/2013</date>
      <people>80</people>
      <car>100</car>
     </record>
    </records>;
   
   private var localSeries:ColumnSeries = new ColumnSeries();
   private var localCarSeries:ColumnSeries = new ColumnSeries();
   
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    //Create the SortField object for the "time" field in 
    //the ArrayCollection object, and make sure we do a 
    //numeric sort.
    var dataSortField:SortField = new SortField();
    dataSortField.name = "time";
    dataSortField.numeric = true;
    
    //Create the Sort object and add the SortField object 
    //created earlier to the array of fields to sort on.
    var numericDataSort:Sort = new Sort();
    numericDataSort.fields = [dataSortField];
    
    // Parsing the xml data into ArrayCollection
    var objArray:ArrayCollection = new ArrayCollection();
    var tempObj:Object;
    var dateArray:Array;
    var tempDate:Date;
    for(var i:int = 0; i < myData.record.length(); i ++)
    {
     tempObj = new Object();
     dateArray = String(myData.record[i].date).split("/");
     //Convert the date data into a Date Object
     tempDate = new Date(dateArray[2], 
      Number(dateArray[1]) - 1, 
      dateArray[0]);
     tempObj.date = tempDate;
     tempObj.time = tempDate.time;
     tempObj.people = myData.record[i].people;
     tempObj.label = dateFormatter(tempDate);
     objArray.addItem(tempObj);
    }
    
    objArray.sort = numericDataSort;
    objArray.refresh();
    
    //Create the new series and set its properties.
    localSeries.dataProvider = objArray;
    localSeries.yField = "people";
    localSeries.xField = "date";
    //Create alternate colors for the columns
    localSeries.setStyle("fill", 0xCDFFCD);
    //Create the strokes for the columns
    localSeries.setStyle("stroke", 
     new Stroke(0xFFFFFF, 0.1, 0.5));
    localSeries.displayName = "col_people";
    //Set the width(%) of the column 
    localSeries.columnWidthRatio = 0.3;
    //Set the offset
    localSeries.offset = -0.15;
    
    objArray = new ArrayCollection();
    for(i = 0; i < myData.record.length(); i ++)
    {
     tempObj = new Object();
     dateArray = String(myData.record[i].date).split("/");
     //Convert the date data into a Date Object
     tempDate = new Date(dateArray[2], 
      Number(dateArray[1]) - 1, 
      dateArray[0]);
     tempObj.date = tempDate;
     tempObj.time = tempDate.time;
     tempObj.car = myData.record[i].car;
     tempObj.label = dateFormatter(tempDate);
     objArray.addItem(tempObj);
    } 
    
    objArray.sort = numericDataSort;
    objArray.refresh();
    
    //Create the new series and set its properties.
    localCarSeries.dataProvider = objArray;
    localCarSeries.yField = "car";
    localCarSeries.xField = "date";
    //Create alternate colors for the columns
    localCarSeries.setStyle("fill", 0xCDCDFF);
    //Create the strokes for the columns
    localCarSeries.setStyle("stroke", 
     new Stroke(0xFFFFFF, 0.1, 0.5));
    localCarSeries.displayName = "line_car";
    //Set the width(%) of the column 
    localCarSeries.columnWidthRatio = 0.3;
    //Set the offset
    localCarSeries.offset = 0.15;
    
    //We will remove all the series attach to the chart
    //first
    chart.series = null;
    
    //End all the effects first, else some glich will
    //appear.
    parallelEffect.end();
    parallelCarEffect.end();
    
    //Base on the type of animation selected, attach 
    //the effect to the column
    localSeries.setStyle("creationCompleteEffect", 
     parallelEffect);
    localCarSeries.setStyle("creationCompleteEffect", 
     parallelCarEffect);
    
    // Back up the current series on the chart.
    var currentSeries:Array = chart.series;
    // Add the new series to the current Array of series.
    currentSeries.push(localCarSeries);
    currentSeries.push(localSeries);
    // Add the new Array of series to the chart.
    chart.series = currentSeries;
   }
   
   //This function will return a string based on the
   //Date format DD/MM/YYYY.
   private function dateFormatter(tempDate:Date):String
   {
    var fmt:DateFormatter = new DateFormatter();
    fmt.formatString = "DD/MM/YYYY";
    return fmt.format(tempDate);
   }
   
   //We are customizing the datatip / tool tip of the
   //chart data.
   public function myDataTipFunction(e:HitData):String {
    var s:String = "";
    var tempDate:Date = e.item.date as Date;
    s += "Date: " + dateFormatter(tempDate) + "<br>";
    if(Series(e.element).displayName == "col_people")
    {
     s += "No. of People: " + e.item.people;
    }else{
     s += "No. of Cars: " + e.item.car;
    }
    return s;
   }
   
   //This function will be used to change the date labels of
   //the chart to match the data.
   public function createDate(s:Date):Date {    
    var newDate:Date = new Date();
    newDate.time = s.time;
    //We need to increase a day to the labels.
    newDate.date += 1;
    return newDate;
   }  
   
   //This function will toggle the visibility of the chart
   //Data based on the values of the respective check boxes.
   protected function chkChartChangeEvent(event:Event):void
   {
    var showCarData:Boolean = chkCar.selected;
    var showPeopleData:Boolean = chkPeople.selected;
    for(var i:int = 0; i < chart.series.length; i ++)
    {
     if(chart.series[i].displayName == "line_car")
     {
      chart.series[i].visible = showCarData;
     }
     if(chart.series[i].displayName == "col_people")
     {
      chart.series[i].visible = showPeopleData;
     }
    }
   }
   
  ]]>
 </fx:Script>
 <s:VGroup width="100%" 
     height="100%"
     verticalAlign="middle"
     horizontalAlign="center">
  <s:BorderContainer width="100%"
         backgroundAlpha="0"
         borderVisible="false">
   <s:HGroup verticalAlign="middle" horizontalAlign="center"
       width="100%"
       height="100%">
    <!-- Need to set the gutterLeft and 
    gutterTop of the chart -->
    <mx:CartesianChart id="chart"
           gutterTop="0"
           gutterLeft="50"
           showDataTips="true" 
           width="80%"
           height="80%"
           dataTipFunction="myDataTipFunction">
     <mx:horizontalAxis>
      <mx:DateTimeAxis dataUnits="days" id="dateAxis" 
           alignLabelsToUnits="false" 
           parseFunction="createDate"/> 
     </mx:horizontalAxis>
    </mx:CartesianChart>
   </s:HGroup>
  </s:BorderContainer>
  <s:HGroup width="100%" horizontalAlign="center" 
      verticalAlign="middle">
   <s:CheckBox id="chkCar"
      change="chkChartChangeEvent(event)" 
      selected="true"
      label="Show Car Data"/>
   <s:CheckBox id="chkPeople"
      change="chkChartChangeEvent(event)" 
      selected="true"
      label="Show People Data"/>
  </s:HGroup>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Sunday, October 21, 2012

AS3: Creating a Clone

In the AS3 world, there might be numerous situations that requires you to create a clone of a very complex component of object. However it will be kinda crazy if you are going to run a loop and copy all the variables one by one. Therefore here's a class that will help you to reduce the amount of work needed.

Here's our main Application Class - SimpleCopyingOfObjects.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      backgroundColor="#CDCDCD"
      creationComplete="creationCompleteEvent(event)">
 <fx:Script>
  <![CDATA[
   import flash.net.*;
   import flash.utils.*;
   
   import mx.collections.ArrayCollection;
   import mx.events.FlexEvent;
   import mx.utils.ObjectUtil;
   
   import view.CustomComponent;
   
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    //Creating 2 new CustomComponent and assign some
    //values to it.
    var tempObj:CustomComponent = new CustomComponent();
    var tempObj1:CustomComponent = new CustomComponent();
    tempObj.message = "I'm am a new View.";
    tempObj1.message = "I'm am a new View1.";
    tempObj.myContent.addItem(tempObj1);
    
    //Now let's create some clones.
    var tempClone1 = cloneObject(tempObj);
    var tempClone2 = cloneCustomObject(tempObj);
    
    txtMsg.appendText("tempObj is: " + 
     getQualifiedClassName(tempObj));
    txtMsg.appendText("\ntempClone1 is: " + 
     getQualifiedClassName(tempClone1));
    txtMsg.appendText("\ntempClone2 is: " + 
     getQualifiedClassName(tempClone2));
    
    txtMsg.appendText("\n\nValue of tempObj.message is: " + 
     tempObj.message);
    if(tempClone1.hasOwnProperty("message"))
    {
     txtMsg.appendText("\nValue of tempClone1.message is: "+ 
      tempClone1.message);
    }else{
     txtMsg.appendText("\nValue of tempClone1.message is " + 
      "unaccessible.");
    }
    if(tempClone2.hasOwnProperty("message"))
    {
     txtMsg.appendText("\nValue of tempClone2.message is: "+ 
      tempClone2.message);
    }else{
     txtMsg.appendText("\nValue of tempClone2.message is " + 
      "unaccessible.");
    }
    
    tempObj.message = "I'm am a old View.";
    txtMsg.appendText("\n\nChange Value of tempObj.message "+ 
     "to 'I'm am a old View.'");
    txtMsg.appendText("\nValue of tempObj.message is: "+ 
     tempObj.message);
    if(tempClone1.hasOwnProperty("message"))
    {
     txtMsg.appendText("\nValue of tempClone1.message is: "+ 
      tempClone1.message);
    }else{
     txtMsg.appendText("\nValue of tempClone1.message is " + 
      "unaccessible.");
    }
    if(tempClone2.hasOwnProperty("message"))
    {
     txtMsg.appendText("\nValue of tempClone2.message is: "+ 
      tempClone2.message);
    }else{
     txtMsg.appendText("\nValue of tempClone2.message is " + 
      "unaccessible.");
    }
    
    tempObj1.message = "I'm am a old View1.";
    txtMsg.appendText("\n\nChange Value of tempObj1.message "+ 
     "to 'I'm am a old View1.'");
    
    var tempObject:CustomComponent = CustomComponent(
     tempObj.myContent.getItemAt(0));
    txtMsg.appendText("\nValue of tempObj.myContent." +
     "getItemAt(0).message is: " + tempObject.message);
    if(tempClone1.myContent is ArrayCollection)
    {
     if(tempClone1.myContent.getItemAt(0) is CustomComponent)
     {
      tempObject = CustomComponent(
       tempClone1.myContent.getItemAt(0));
      txtMsg.appendText("\nValue of tempClone1.myContent." +
       "getItemAt(0).message is: " + tempObject.message);
     }else{
      txtMsg.appendText("\nValue of tempClone1.myContent." +
       "getItemAt(0) is not a CustomComponent.");
     }
    }else{
     txtMsg.appendText("\nValue of tempClone1.myContent" +
      "getItemAt(0) is unaccessible.");
    }
    if(tempClone2.myContent is ArrayCollection)
    {
     if(tempClone2.myContent.getItemAt(0) is CustomComponent)
     {
      tempObject = CustomComponent(
       tempClone2.myContent.getItemAt(0));
      txtMsg.appendText("\nValue of tempClone2.myContent." +
       "getItemAt(0).message is: " + tempObject.message);
     }else{
      txtMsg.appendText("\nValue of tempClone2.myContent." +
       "getItemAt(0) is not a CustomComponent.");
     }
    }else{
     txtMsg.appendText("\nValue of tempClone2.myContent" +
      "getItemAt(0) is unaccessible.");
    }
   }
   
   //This function will copy an custom object/component
   //into an Object.
   private function cloneObject(CustomObject:*):*
   {
    var ba:ByteArray = new ByteArray();
    ba.writeObject(CustomObject);
    ba.position = 0;
    return ba.readObject();
   }
   
   //This function will clone an custom object/component.
   //based on the given type of the original object.
   private function cloneCustomObject(CustomObject:*):*
   {
    //Grab the Class Name of the object that we are copying
    var className:String = getQualifiedClassName(CustomObject);
    //Register it first before we clone it
    registerClassAlias(className, 
     getDefinitionByName(className) as Class);
    //While copying the object, the Class Type will be register
    //because of the registerClassAlias call previously
    return ObjectUtil.copy(CustomObject);
   }
  ]]>
 </fx:Script>
 <s:VGroup width="100%" 
     height="100%"
     verticalAlign="middle"
     horizontalAlign="center">
  <s:Label textAlign="center" 
     text="Output:"/>
  <s:TextArea width="90%" 
     height="90%"
     id="txtMsg"/>
 </s:VGroup>
</s:Application>
And here's our custom component Class - CustomComponent.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" 
   xmlns:s="library://ns.adobe.com/flex/spark" 
   xmlns:mx="library://ns.adobe.com/flex/mx" >
 <fx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;
   private var _message:String = "";

   public function get message():String
   {
    return _message;
   }

   public function set message(value:String):void
   {
    _message = value;
   }
   
   private var _myContent:ArrayCollection = 
    new ArrayCollection();

   public function get myContent():ArrayCollection
   {
    return _myContent;
   }

   public function set myContent(value:ArrayCollection):void
   {
    _myContent = value;
   }
   
  ]]>
 </fx:Script>
</s:Group>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Friday, October 5, 2012

Flex: Showing/Hiding Chart Data

As time goes by, your chart presentation tend to becomes more and more complex and presenting all the chart data at once can be a hassle. Therefore wouldn't it be better off if the user can choose what the data that he is interested and what are the data that should be hidden.

AS usual source codes - SimpleChartVisibility.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      backgroundColor="#CDCDCD"
      creationComplete="creationCompleteEvent(event)"> 
 <fx:Declarations>
  <!-- 
  We need to specify the types of animations over here
  -->
  <s:Parallel id="parallelEffect">
   <s:Fade duration="1000" alphaFrom="0" alphaTo="1"/>
   <mx:SeriesSlide duration="1000" direction="up"/>
  </s:Parallel>
  <s:Parallel id="parallelCarEffect">
   <s:Fade duration="1000" alphaFrom="0" alphaTo="1"/>
  </s:Parallel>
 </fx:Declarations>
 <fx:Script>
  <![CDATA[
   import flashx.textLayout.conversion.TextConverter;
   
   import mx.charts.HitData;
   import mx.charts.chartClasses.IChartElement2;
   import mx.charts.chartClasses.Series;
   import mx.charts.series.ColumnSeries;
   import mx.charts.series.LineSeries;
   import mx.collections.ArrayCollection;
   import mx.collections.Sort;
   import mx.collections.SortField;
   import mx.events.FlexEvent;
   import mx.formatters.DateFormatter;
   import mx.graphics.Stroke;
   
   import spark.events.IndexChangeEvent;
   
   //Records used in the chart
   [Bindable]
   private var myData:XML = 
    <records> 
     <record>
      <date>01/09/2013</date>
      <people>63</people>
      <car>23</car>
     </record>
     <record>
      <date>02/09/2013</date>
      <people>61</people>
      <car>81</car>
     </record>
     <record>
      <date>03/09/2013</date>
      <people>67</people>
      <car>47</car>
     </record>
     <record>
      <date>04/09/2013</date>
      <people>75</people>
      <car>95</car>
     </record>
     <record>
      <date>05/09/2013</date>
      <people>65</people>
      <car>45</car>
     </record>
     <record>
      <date>06/09/2013</date>
      <people>32</people>
      <car>52</car>
     </record>
     <record>
      <date>07/09/2013</date>
      <people>66</people>
      <car>46</car>
     </record>
     <record>
      <date>08/09/2013</date>
      <people>85</people>
      <car>105</car>
     </record>
     <record>
      <date>09/09/2013</date>
      <people>37</people>
      <car>57</car>
     </record>
     <record>
      <date>10/09/2013</date>
      <people>80</people>
      <car>100</car>
     </record>
    </records>;
   
   private var localSeries:ColumnSeries = new ColumnSeries();
   private var localCarSeries:LineSeries = new LineSeries();
   
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    //Create the SortField object for the "time" field in 
    //the ArrayCollection object, and make sure we do a 
    //numeric sort.
    var dataSortField:SortField = new SortField();
    dataSortField.name = "time";
    dataSortField.numeric = true;
    
    //Create the Sort object and add the SortField object 
    //created earlier to the array of fields to sort on.
    var numericDataSort:Sort = new Sort();
    numericDataSort.fields = [dataSortField];
    
    // Parsing the xml data into ArrayCollection
    var objArray:ArrayCollection = new ArrayCollection();
    var tempObj:Object;
    var dateArray:Array;
    var tempDate:Date;
    for(var i:int = 0; i < myData.record.length(); i ++)
    {
     tempObj = new Object();
     dateArray = String(myData.record[i].date).split("/");
     //Convert the date data into a Date Object
     tempDate = new Date(dateArray[2], 
      Number(dateArray[1]) - 1, 
      dateArray[0]);
     tempObj.date = tempDate;
     tempObj.time = tempDate.time;
     tempObj.people = myData.record[i].people;
     tempObj.label = dateFormatter(tempDate);
     objArray.addItem(tempObj);
    }
    
    objArray.sort = numericDataSort;
    objArray.refresh();
    
    //Create the new series and set its properties.
    localSeries.dataProvider = objArray;
    localSeries.yField = "people";
    localSeries.xField = "date";
    //Create alternate colors for the columns
    localSeries.setStyle("fills", [0xCDFFCD, 0xCDCDFF]);
    //Create the strokes for the columns
    localSeries.setStyle("stroke", 
     new Stroke(0xFFFFFF, 0.1, 0.5));
    localSeries.displayName = "col_people"
    
    objArray = new ArrayCollection();
    for(i = 0; i < myData.record.length(); i ++)
    {
     tempObj = new Object();
     dateArray = String(myData.record[i].date).split("/");
     //Convert the date data into a Date Object
     tempDate = new Date(dateArray[2], 
      Number(dateArray[1]) - 1, 
      dateArray[0]);
     tempObj.date = tempDate;
     tempObj.time = tempDate.time;
     tempObj.car = myData.record[i].car;
     tempObj.label = dateFormatter(tempDate);
     objArray.addItem(tempObj);
    } 
    
    objArray.sort = numericDataSort;
    objArray.refresh();
    
    //Create the new series and set its properties.
    localCarSeries.dataProvider = objArray;
    localCarSeries.yField = "car";
    localCarSeries.xField = "date";
    //Create alternate colors for the columns
    //Create the strokes for the columns
    localCarSeries.setStyle("lineStroke", 
     new Stroke(0x00DD00, 1, 0.5));
    localCarSeries.displayName = "line_car";
    
    //We will remove all the series attach to the chart
    //first
    chart.series = null;
    
    //End all the effects first, else some glich will
    //appear.
    parallelEffect.end();
    parallelCarEffect.end();
    
    //Base on the type of animation selected, attach 
    //the effect to the column
    localSeries.setStyle("creationCompleteEffect", 
     parallelEffect);
    localCarSeries.setStyle("creationCompleteEffect", 
     parallelCarEffect);
    
    // Back up the current series on the chart.
    var currentSeries:Array = chart.series;
    // Add the new series to the current Array of series.
    currentSeries.push(localCarSeries);
    currentSeries.push(localSeries);
    // Add the new Array of series to the chart.
    chart.series = currentSeries;
   }
   
   //This function will return a string based on the
   //Date format DD/MM/YYYY.
   private function dateFormatter(tempDate:Date):String
   {
    var fmt:DateFormatter = new DateFormatter();
    fmt.formatString = "DD/MM/YYYY";
    return fmt.format(tempDate);
   }
   
   //We are customizing the datatip / tool tip of the
   //chart data.
   public function myDataTipFunction(e:HitData):String {
    var s:String = "";
    var tempDate:Date = e.item.date as Date;
    s += "Date: " + dateFormatter(tempDate) + "<br>";
    if(Series(e.element).displayName == "col_people")
    {
     s += "No. of People: " + e.item.people;
    }else{
     s += "No. of Cars: " + e.item.car;
    }
    return s;
   }
   
   //This function will be used to change the date labels of
   //the chart to match the data.
   public function createDate(s:Date):Date {    
    var newDate:Date = new Date();
    newDate.time = s.time;
    //We need to increase a day to the labels.
    newDate.date += 1;
    return newDate;
   }  
   
   //This function will toggle the visibility of the chart
   //Data based on the values of the respective check boxes.
   protected function chkChartChangeEvent(event:Event):void
   {
    var showCarData:Boolean = chkCar.selected;
    var showPeopleData:Boolean = chkPeople.selected;
    for(var i:int = 0; i < chart.series.length; i ++)
    {
     if(chart.series[i].displayName == "line_car")
     {
      chart.series[i].visible = showCarData;
     }
     if(chart.series[i].displayName == "col_people")
     {
      chart.series[i].visible = showPeopleData;
     }
    }
   }
   
  ]]>
 </fx:Script>
 <s:VGroup width="100%" 
     height="100%"
     verticalAlign="middle"
     horizontalAlign="center">
  <s:BorderContainer width="100%"
         backgroundAlpha="0"
         borderVisible="false">
   <s:HGroup verticalAlign="middle" horizontalAlign="center"
       width="100%"
       height="100%">
    <!-- Need to set the gutterLeft and 
    gutterTop of the chart -->
    <mx:CartesianChart id="chart"
        gutterTop="0"
        gutterLeft="50"
        showDataTips="true" 
        width="80%"
        height="80%"
        dataTipFunction="myDataTipFunction">
     <mx:horizontalAxis>
      <mx:DateTimeAxis dataUnits="days" id="dateAxis" 
           alignLabelsToUnits="false" 
           parseFunction="createDate"/> 
     </mx:horizontalAxis>
    </mx:CartesianChart>
   </s:HGroup>
  </s:BorderContainer>
  <s:HGroup width="100%" horizontalAlign="center" 
      verticalAlign="middle">
   <s:CheckBox id="chkCar"
      change="chkChartChangeEvent(event)" 
      selected="true"
      label="Show Car Data"/>
   <s:CheckBox id="chkPeople"
      change="chkChartChangeEvent(event)" 
      selected="true"
       label="Show People Data"/>
  </s:HGroup>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Friday, September 28, 2012

Flex: Simple Chart Animation

I was playing with the column charts, a bit of styling, animation, etc... And all of us out there love animations and beautiful stuff therefore here you go. XD

The main source file - SimpleChartAnimation.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      backgroundColor="#CDCDCD"
      creationComplete="creationCompleteEvent(event)"> 
 <fx:Declarations>
  <s:RadioButtonGroup change="animateChangeEvent(event)" 
       id="rdoGrpAnimate"/>
  <!-- 
   We need to specify the types of animations over here
   1) For parallel animations, meaning all the effects
      will be executed together.
   2) For sequence animations, the effects will be running
      one by one.
  -->
  <s:Parallel id="parallelEffect">
   <s:Fade duration="1000" alphaFrom="0" alphaTo="1"/>
   <mx:SeriesSlide duration="1000" direction="up"/>
  </s:Parallel>
  <s:Sequence id="sequenceEffect">
   <mx:SeriesSlide duration="500" direction="up"/>
   <s:Fade duration="500" alphaFrom="0.5" alphaTo="1"/>
  </s:Sequence>
 </fx:Declarations>
 <fx:Script>
  <![CDATA[
   import flashx.textLayout.conversion.TextConverter;
   
   import mx.charts.HitData;
   import mx.charts.chartClasses.IChartElement2;
   import mx.charts.series.ColumnSeries;
   import mx.collections.ArrayCollection;
   import mx.collections.Sort;
   import mx.collections.SortField;
   import mx.events.FlexEvent;
   import mx.formatters.DateFormatter;
   import mx.graphics.Stroke;
   
   import spark.events.IndexChangeEvent;
   
   //Records used in the chart
   [Bindable]
   private var myData:XML = 
    <records> 
     <record>
      <date>01/09/2013</date>
      <people>63</people>
     </record>
     <record>
      <date>02/09/2013</date>
      <people>61</people>
     </record>
     <record>
      <date>03/09/2013</date>
      <people>67</people>
     </record>
     <record>
      <date>04/09/2013</date>
      <people>75</people>
     </record>
     <record>
      <date>05/09/2013</date>
      <people>65</people>
     </record>
     <record>
      <date>06/09/2013</date>
      <people>32</people>
     </record>
     <record>
      <date>07/09/2013</date>
      <people>66</people>
     </record>
     <record>
      <date>08/09/2013</date>
      <people>85</people>
     </record>
     <record>
      <date>09/09/2013</date>
      <people>37</people>
     </record>
     <record>
      <date>10/09/2013</date>
      <people>80</people>
     </record>
    </records>;
   
   private var localSeries:ColumnSeries = new ColumnSeries();
   private var firstRun:Boolean = false;
   
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    // Parsing the xml data into ArrayCollection
    var objArray:ArrayCollection = new ArrayCollection();
    var tempObj:Object;
    var dateArray:Array;
    var tempDate:Date;
    for(var i:int = 0; i < myData.record.length(); i ++)
    {
     tempObj = new Object();
     dateArray = String(myData.record[i].date).split("/");
     //Convert the date data into a Date Object
     tempDate = new Date(dateArray[2], 
      Number(dateArray[1]) - 1, 
      dateArray[0]);
     tempObj.date = tempDate;
     tempObj.time = tempDate.time;
     tempObj.people = myData.record[i].people;
     tempObj.label = dateFormatter(tempDate);
     objArray.addItem(tempObj);
    }
    
    //Create the SortField object for the "time" field in 
    //the ArrayCollection object, and make sure we do a 
    //numeric sort.
    var dataSortField:SortField = new SortField();
    dataSortField.name = "time";
    dataSortField.numeric = true;
    
    //Create the Sort object and add the SortField object 
    //created earlier to the array of fields to sort on.
    var numericDataSort:Sort = new Sort();
    numericDataSort.fields = [dataSortField];
    
    objArray.sort = numericDataSort;
    objArray.refresh();
    
    //Create the new series and set its properties.
    localSeries.dataProvider = objArray;
    localSeries.yField = "people";
    localSeries.xField = "date";
    //Create alternate colors for the columns
    localSeries.setStyle("fills", [0xCDFFCD, 0xCDCDFF]);
    //Create the strokes for the columns
    localSeries.setStyle("stroke", 
     new Stroke(0xFFFFFF, 0.1, 0.5));

    //Animation the Column Charts
    animateChangeEvent(null);
   }
   
   //This function will return a string based on the
   //Date format DD/MM/YYYY.
   private function dateFormatter(tempDate:Date):String
   {
    var fmt:DateFormatter = new DateFormatter();
    fmt.formatString = "DD/MM/YYYY";
    return fmt.format(tempDate);
   }
   
   //We are customizing the datatip / tool tip of the
   //chart data.
   public function myDataTipFunction(e:HitData):String {
    var s:String = "";
    var tempDate:Date = e.item.date as Date;
    s += "Date: " + dateFormatter(tempDate) + "<br>";
    s += "No. of People: " + e.item.people;
    return s;
   }
   
   //This function will be used to change the date labels of
   //the chart to match the data.
   public function createDate(s:Date):Date {    
    var newDate:Date = new Date();
    newDate.time = s.time;
    //We need to increase a day to the labels.
    newDate.date += 1;
    return newDate;
   }  
   
   protected function animateChangeEvent(event:Event):void
   {
    //We will remove all the series attach to the chart
    //first
    columnChart.series = null;
    
    //End all the effects first, else some glich will
    //appear.
    parallelEffect.end();
    sequenceEffect.end();
    
    //Base on the type of animation selected, attach 
    //the effect to the column
    if(rdoGrpAnimate.selectedValue == "parallel")
    {
     localSeries.alpha = 0;
     if(!firstRun)
     {
      localSeries.setStyle("creationCompleteEffect", 
       parallelEffect);
     }else{
      localSeries.setStyle("addedEffect", 
       parallelEffect);
     }
    }else{
     localSeries.alpha = 0.5;
     if(!firstRun)
     {
      localSeries.setStyle("creationCompleteEffect", 
       sequenceEffect);
     }else{
      localSeries.setStyle("addedEffect", 
       sequenceEffect);
     }
    }
    
    firstRun = true;
    
    // Back up the current series on the chart.
    var currentSeries:Array = columnChart.series;
    // Add the new series to the current Array of series.
    currentSeries.push(localSeries);
    // Add the new Array of series to the chart.
    columnChart.series = currentSeries;
   } 
   
  ]]>
 </fx:Script>
 <s:VGroup width="100%" 
     height="100%"
     verticalAlign="middle"
     horizontalAlign="center">
  <s:BorderContainer width="100%"
         backgroundAlpha="0"
         borderVisible="false">
   <s:HGroup verticalAlign="middle" horizontalAlign="center"
       width="100%"
       height="100%">
    <!-- Need to set the gutterLeft and 
    gutterTop of the chart -->
    <mx:ColumnChart id="columnChart"
         gutterTop="0"
         gutterLeft="50"
         showDataTips="true" 
         width="80%"
         height="80%"
         dataTipFunction="myDataTipFunction">
     <mx:horizontalAxis>
      <mx:DateTimeAxis dataUnits="days" id="dateAxis" 
           alignLabelsToUnits="false" 
           parseFunction="createDate"/> 
     </mx:horizontalAxis>
    </mx:ColumnChart>
   </s:HGroup>
  </s:BorderContainer>
  <s:HGroup width="100%" horizontalAlign="center" 
      verticalAlign="middle">
   <s:VGroup gap="0">
    <s:Spacer height="4"/>
    <s:Label text="Show Column Animation:"/>
   </s:VGroup>
   <s:RadioButton value="parallel"
         group="{rdoGrpAnimate}"
         selected="true"
         label="Parallel Animation"/>
   <s:RadioButton value="sequence"
         group="{rdoGrpAnimate}"
         label="Sequence Animation"/>
  </s:HGroup>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Friday, September 21, 2012

FB + Adobe AIR: Logout Bug

A question was thrown at me the other day, regarding the issue of successfully logging out of FB on a Adobe AIR application. It seems that the Facebook Graph Desktop API has a bug. If a user has successfully log in to FB through the Adobe AIR application, the username and the password will be stored somewhere. Even after the user has log out of FB, the username and the password isn't cleared by the application and when another user tries to log in through the same application on the same machine, rather than prompting the user to enter his username and password, the application will log in automatically using the last set of username and password that are working perfectly. After spending a bit of time playing with it, I managed to find a workaround for this bug.

Rather than doing the following,
 //Rather than using the logout function of the API,
 //you need to add some more codes to it.
 FacebookDesktop.logout(handleLogout, APP_ORIGIN);

You need to add a few more lines to log out properly.
 /*
  All the following liners are required to logout successfully.
 */
 var uri:String = APP_ORIGIN;
 var params:URLVariables = new URLVariables();
 params.next = uri;
 params.access_token = FacebookDesktop.getSession().accessToken;
     
 var req:URLRequest = new URLRequest("https://www.facebook.com/logout.php");
 req.method = URLRequestMethod.GET;
 req.data = params;
     
 var netLoader:URLLoader = new URLLoader();
 netLoader.load(req);
     
 FacebookDesktop.logout(handleLogout, APP_ORIGIN);
* Click here to play with the Adobe AIR application.
^ Click here to take a look at the source files that I'm playing with.
~ Click here to find out more about the Facebook Graph Desktop API.

Sunday, September 16, 2012

Flash: Playing with Tabbing Sequence

You have created a simple form and rather than left to right, you wanted to change the tab sequence a bit, but how to do that?

Source code of the main application file - SimpleTabSequence.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      creationComplete="creationCompleteEvent(event)">
 <fx:Script>
  <![CDATA[
   import mx.events.FlexEvent;
   
   /*
    When the laste checkbox has been selected, we
    will change the tab sequence from top to bottom,
    else it will be bottom to top. 
   */
   protected function rdoChangeHandler(event:Event):void
   {
    if(chk1.selected)
    {
     setupSeq();
    }else{
     setupSeq(false);
    }
   }
   
   // Upon CreationComplete, setup the tab sequence from 
   // top to bottom.
   protected function creationCompleteEvent(e:FlexEvent):void
   {
    setupSeq();
   }
   
   /*
    Most of the work will be done here. It will assign 
    the tabIndex of all the components and it will also
    remove one of the checkbox from the tab sequence
    upon selection.
   */
   private function setupSeq(isTopdown:Boolean = true):void
   {
    if(isTopdown)
    {
     txtTxt0.tabIndex = 1;
     txtTxt1.tabIndex = 2;
     btn0.tabIndex = 3;
     txtTxt2.tabIndex = 4;
     btn1.tabIndex = 5;
     if(!chk0.selected)
     {
      chk0.tabIndex = 6;
      chk1.tabIndex = 7;
     }else{
      chk1.tabIndex = 6;
     }
    }else{
     if(!chk0.selected)
     {
      txtTxt0.tabIndex = 7;
      txtTxt1.tabIndex = 6;
      btn0.tabIndex = 5;
      txtTxt2.tabIndex = 4;
      btn1.tabIndex = 3;
      chk0.tabIndex = 2;
     }else{
      txtTxt0.tabIndex = 6;
      txtTxt1.tabIndex = 5;
      btn0.tabIndex = 4;
      txtTxt2.tabIndex = 3;
      btn1.tabIndex = 2;
     }
     chk1.tabIndex = 1;
    }
    chk0.tabFocusEnabled = !chk0.selected;
    lblTxt0.text = "Textfield(" + txtTxt0.tabIndex + ")";
    lblTxt1.text = "Textfield(" + txtTxt1.tabIndex + ")";
    btn0.label = "Does Nothing(" + btn0.tabIndex + ")";
    lblTxt2.text = "Textfield(" + txtTxt2.tabIndex + ")";
    btn1.label = "Not going to do anything(" + 
     btn1.tabIndex + ")";
    if(!chk0.selected)
    {
     chk0.label = "Checking will remove this " +
      "from tabbing(" + chk0.tabIndex + ")";
    }else{
     chk0.label = "Checking will remove this " +
      "from tabbing";
    }
    chk1.label = "Tabbing Sequence Is Top To " +
     "Bottom(" + chk1.tabIndex + ")";
   }
   
  ]]>
 </fx:Script>
 <!-- Just a list of components -->
 <s:VGroup width="100%" height="100%"
     verticalAlign="middle" horizontalAlign="center">
  <s:VGroup width="300" height="100%"
      verticalAlign="middle" horizontalAlign="center">
   <s:HGroup width="100%"
       horizontalAlign="center"
       verticalAlign="middle">
    <s:Label id="lblTxt0"/>
    <s:TextInput id="txtTxt0"/>
    <s:Spacer width="100%"/>
    <s:Label id="lblTxt1"
       text=""/>
    <s:TextInput id="txtTxt1"/>
   </s:HGroup>
   <s:HGroup width="100%" 
       horizontalAlign="center">
    <s:Button id="btn0" />
   </s:HGroup>
   <s:HGroup width="100%"
       horizontalAlign="center"
       verticalAlign="middle">
    <s:Label id="lblTxt2"/>
    <s:TextInput id="txtTxt2"/>
   </s:HGroup>
   <s:HGroup width="100%"
       horizontalAlign="center">
    <s:Button id="btn1"/>
   </s:HGroup>
   <s:HGroup width="100%"
       horizontalAlign="center">
    <s:CheckBox id="chk0" 
       change="rdoChangeHandler(event)" />
   </s:HGroup>
   <s:HGroup width="100%"
       horizontalAlign="center">
    <s:CheckBox id="chk1"  
       selected="true"
       change="rdoChangeHandler(event)" />
   </s:HGroup>
  </s:VGroup>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Friday, September 7, 2012

AS3: In Search of the missing ReleaseOutside

In the older flash, or during the AS2 era, there's a onReleaseOutside that allows you to click on a button and release it outside. However, in AS3, one could easily find MouseEvents like MOUSE_DOWN, MOUSE_UP, CLICK, etc... but there isn't a RELEASE_OUTSIDE action, so how do work around it?

Time for some source files - SimpleMouseReleaseOutside.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      creationComplete="creationCompleteEvent(event)">
 <fx:Script>
  <![CDATA[
   import mx.events.FlexEvent;
   
   //Upon creation complete, setup the button event of the button
   //and setup the check box
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    btn1.buttonMode = true;
    btn1.label = "Select Me Please";
    btn1.addEventListener(MouseEvent.MOUSE_DOWN, btnMouseDownEvent);
    
    chk1.buttonMode = true;
    chk1.label = "Click me to enable 'ReleaseOutside' on the btn.";
    chk1.selected = false;
   }
   
   //Upon performing a MOUSE DOWN action on the button, add the 
   //listeners for MOUSE UP and if the checkbox is selected, we
   //will be adding a MOUSE UP listener for the stage (This
   //will perform like a ReleaseOutside event in as2.
   private function btnMouseDownEvent(event:Event):void
   {
    btn1.label = "I'm selected";
    btn1.addEventListener(
     MouseEvent.MOUSE_UP, btnMouseUpEvent);
    if(chk1.selected)
    {
     stage.addEventListener(
      MouseEvent.MOUSE_UP, btnMouseUpEvent);
    }
   }
   
   //Upon performing a MOUSE UP action on the button or anywhere
   //in the file, we will be removing the listeners for the button
   //away.
   private function btnMouseUpEvent(event:Event):void
   {
    btn1.label = "Select Me Please";
    btn1.removeEventListener(
     MouseEvent.MOUSE_UP, btnMouseUpEvent);
    if(chk1.selected)
    {
     stage.removeEventListener(
      MouseEvent.MOUSE_UP, btnMouseUpEvent);
    }
   }
   
  ]]>
 </fx:Script>
 <s:VGroup width="100%"
     height="100%"
     horizontalAlign="center">
  <s:Spacer height="100%"/>
  <s:Button id="btn1" 
      width="200"/>
  <s:Spacer height="100%"/>
  <s:CheckBox id="chk1"/>
  <s:Spacer height="100%"/>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Saturday, September 1, 2012

Flex: Locating a point

Probably there' a requirement that requires you to place an icon for both the highest and lowest y value of a chart, but how do you do it?

And here's my answer to the above question. :P Time for some coding... Here's my main application file - "SimpleChartPositionOfAPoint.mxml"
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      backgroundColor="#CDCDCD"
      creationComplete="creationCompleteEvent(event)">
 <fx:Script>
  <![CDATA[
   import flashx.textLayout.conversion.TextConverter;
   
   import mx.charts.HitData;
   import mx.charts.chartClasses.IChartElement2;
   import mx.charts.series.LineSeries;
   import mx.collections.ArrayCollection;
   import mx.collections.Sort;
   import mx.collections.SortField;
   import mx.events.FlexEvent;
   import mx.formatters.DateFormatter;
   
   import spark.events.IndexChangeEvent;
   
   //Records used in the chart
   [Bindable]
   private var myData:XML = 
    <records>
     <record>
      <date>16/11/2012</date>
      <people>74</people>
     </record>
     <record>
      <date>24/07/2013</date>
      <people>99</people>
     </record>
     <record>
      <date>05/04/2013</date>
      <people>79</people>
     </record>
     <record>
      <date>08/07/2012</date>
      <people>54</people>
     </record>
     <record>
      <date>22/11/2012</date>
      <people>42</people>
     </record>
     <record>
      <date>10/04/2012</date>
      <people>42</people>
     </record>
     <record>
      <date>17/03/2012</date>
      <people>57</people>
     </record>
     <record>
      <date>16/10/2011</date>
      <people>63</people>
     </record>
     <record>
      <date>06/02/2012</date>
      <people>80</people>
     </record>
     <record>
      <date>09/01/2012</date>
      <people>2</people>
     </record>
     <record>
      <date>18/11/2011</date>
      <people>100</people>
     </record>
     <record>
      <date>25/11/2011</date>
      <people>88</people>
     </record>
     <record>
      <date>29/05/2012</date>
      <people>94</people>
     </record>
     <record>
      <date>30/03/2013</date>
      <people>99</people>
     </record>
     <record>
      <date>08/09/2012</date>
      <people>33</people>
     </record>
     <record>
      <date>26/01/2012</date>
      <people>33</people>
     </record>
     <record>
      <date>12/11/2012</date>
      <people>48</people>
     </record>
     <record>
      <date>17/05/2013</date>
      <people>57</people>
     </record>
     <record>
      <date>13/04/2012</date>
      <people>57</people>
     </record>
     <record>
      <date>18/07/2013</date>
      <people>81</people>
     </record>
     <record>
      <date>09/08/2012</date>
      <people>60</people>
     </record>
     <record>
      <date>04/09/2012</date>
      <people>49</people>
     </record>
     <record>
      <date>29/12/2012</date>
      <people>15</people>
     </record>
     <record>
      <date>17/08/2011</date>
      <people>1</people>
     </record>
     <record>
      <date>09/10/2012</date>
      <people>76</people>
     </record>
     <record>
      <date>20/02/2012</date>
      <people>62</people>
     </record>
     <record>
      <date>24/06/2013</date>
      <people>70</people>
     </record>
     <record>
      <date>17/07/2013</date>
      <people>17</people>
     </record>
     <record>
      <date>21/04/2012</date>
      <people>32</people>
     </record>
     <record>
      <date>11/04/2013</date>
      <people>92</people>
     </record>
     <record>
      <date>02/08/2013</date>
      <people>97</people>
     </record>
     <record>
      <date>27/12/2011</date>
      <people>5</people>
     </record>
     <record>
      <date>01/05/2012</date>
      <people>90</people>
     </record>
     <record>
      <date>20/04/2013</date>
      <people>75</people>
     </record>
     <record>
      <date>26/10/2012</date>
      <people>60</people>
     </record>
     <record>
      <date>02/01/2012</date>
      <people>31</people>
     </record>
     <record>
      <date>04/02/2013</date>
      <people>17</people>
     </record>
     <record>
      <date>17/04/2013</date>
      <people>16</people>
     </record>
     <record>
      <date>22/03/2013</date>
      <people>94</people>
     </record>
     <record>
      <date>10/09/2011</date>
      <people>97</people>
     </record>
     <record>
      <date>27/11/2012</date>
      <people>18</people>
     </record>
     <record>
      <date>15/03/2013</date>
      <people>94</people>
     </record>
     <record>
      <date>17/10/2011</date>
      <people>85</people>
     </record>
     <record>
      <date>18/03/2012</date>
      <people>12</people>
     </record>
     <record>
      <date>09/10/2011</date>
      <people>92</people>
     </record>
     <record>
      <date>08/09/2012</date>
      <people>17</people>
     </record>
     <record>
      <date>22/10/2012</date>
      <people>45</people>
     </record>
     <record>
      <date>09/11/2012</date>
      <people>40</people>
     </record>
     <record>
      <date>06/09/2012</date>
      <people>75</people>
     </record>
     <record>
      <date>13/08/2012</date>
      <people>2</people>
     </record>
     <record>
      <date>04/03/2012</date>
      <people>20</people>
     </record>
     <record>
      <date>17/11/2012</date>
      <people>34</people>
     </record>
     <record>
      <date>01/02/2012</date>
      <people>51</people>
     </record>
     <record>
      <date>29/08/2011</date>
      <people>35</people>
     </record>
     <record>
      <date>11/02/2012</date>
      <people>35</people>
     </record>
     <record>
      <date>02/06/2012</date>
      <people>26</people>
     </record>
     <record>
      <date>20/01/2013</date>
      <people>98</people>
     </record>
     <record>
      <date>07/02/2013</date>
      <people>4</people>
     </record>
     <record>
      <date>27/08/2012</date>
      <people>44</people>
     </record>
     <record>
      <date>12/12/2012</date>
      <people>29</people>
     </record>
     <record>
      <date>18/03/2012</date>
      <people>96</people>
     </record>
     <record>
      <date>01/09/2012</date>
      <people>40</people>
     </record>
     <record>
      <date>09/02/2013</date>
      <people>34</people>
     </record>
     <record>
      <date>22/09/2012</date>
      <people>86</people>
     </record>
     <record>
      <date>28/02/2012</date>
      <people>14</people>
     </record>
     <record>
      <date>02/02/2012</date>
      <people>94</people>
     </record>
     <record>
      <date>08/06/2013</date>
      <people>16</people>
     </record>
     <record>
      <date>23/03/2012</date>
      <people>32</people>
     </record>
     <record>
      <date>21/03/2013</date>
      <people>10</people>
     </record>
     <record>
      <date>07/04/2013</date>
      <people>10</people>
     </record>
     <record>
      <date>11/03/2013</date>
      <people>28</people>
     </record>
     <record>
      <date>02/11/2012</date>
      <people>28</people>
     </record>
     <record>
      <date>14/11/2011</date>
      <people>3</people>
     </record>
     <record>
      <date>05/02/2013</date>
      <people>12</people>
     </record>
     <record>
      <date>21/03/2013</date>
      <people>41</people>
     </record>
     <record>
      <date>30/09/2012</date>
      <people>96</people>
     </record>
     <record>
      <date>19/02/2013</date>
      <people>29</people>
     </record>
     <record>
      <date>09/07/2013</date>
      <people>86</people>
     </record>
     <record>
      <date>30/04/2013</date>
      <people>35</people>
     </record>
     <record>
      <date>21/01/2012</date>
      <people>4</people>
     </record>
     <record>
      <date>26/06/2013</date>
      <people>88</people>
     </record>
     <record>
      <date>17/04/2013</date>
      <people>55</people>
     </record>
     <record>
      <date>04/10/2011</date>
      <people>38</people>
     </record>
     <record>
      <date>20/03/2013</date>
      <people>38</people>
     </record>
     <record>
      <date>19/05/2013</date>
      <people>91</people>
     </record>
     <record>
      <date>28/08/2011</date>
      <people>73</people>
     </record>
     <record>
      <date>31/08/2011</date>
      <people>65</people>
     </record>
     <record>
      <date>11/03/2013</date>
      <people>88</people>
     </record>
     <record>
      <date>10/06/2013</date>
      <people>78</people>
     </record>
     <record>
      <date>03/07/2013</date>
      <people>8</people>
     </record>
     <record>
      <date>08/10/2012</date>
      <people>16</people>
     </record>
     <record>
      <date>19/03/2012</date>
      <people>74</people>
     </record>
     <record>
      <date>28/09/2012</date>
      <people>49</people>
     </record>
     <record>
      <date>29/07/2013</date>
      <people>51</people>
     </record>
     <record>
      <date>05/10/2012</date>
      <people>60</people>
     </record>
     <record>
      <date>18/02/2012</date>
      <people>63</people>
     </record>
     <record>
      <date>03/09/2011</date>
      <people>45</people>
     </record>
     <record>
      <date>30/12/2011</date>
      <people>76</people>
     </record>
     <record>
      <date>11/04/2013</date>
      <people>96</people>
     </record>
     <record>
      <date>15/06/2012</date>
      <people>55</people>
     </record>
    </records>;
   
   private var localSeries:LineSeries = new LineSeries();
   
   //This function will show a text box which is positioned
   //above the selected point
   protected function cb1ChangeEvent(event:IndexChangeEvent):void
   {
    //We will need the date, the date in numerical format
    //and the number of people during that date
    var tempDate:Date = event.currentTarget.selectedItem.date;
    var xPos:Number = tempDate.time;
    var yPos:Number = event.currentTarget.selectedItem.people;
    
    //Next, create a point withe the date in numerical format
    //and the number of people, which is used to create the
    //x and y axis of the chart
    var myPoint:Point = new Point(xPos,yPos);
    
    //Next, check it against the line series to find the 
    //and y postion of the point on the chart
    //Note: dataToLocal didn't calculate the gutters around
    //the chart, you need to calculate yrself.
    myPoint = localSeries.dataToLocal(xPos, yPos);
    
    //Next, offset the gutters on the left and top of the point
    myPoint.x += lineChart.getStyle("gutterLeft");
    myPoint.y += lineChart.getStyle("gutterTop");
    
    //find the global position of the point
    myPoint = lineChart.localToGlobal(myPoint);
    
    //followed by the step of mapping the point into the
    //layer abouve the chart 
    myPoint = canvas.globalToLocal(myPoint);
    
    //We will position the textfield above the selected point
    txtResult.visible = true;
    txtResult.x = myPoint.x - txtResult.width / 2;
    txtResult.y = myPoint.y - txtResult.height - 5;
    
    //Let's populate the textfield with some data.
    var tempStr:String = "";
    tempStr += "Date: " + dateFormatter(tempDate) + "<br>";
    tempStr += "No. of People: " + yPos;
    txtResult.textFlow = TextConverter.importToFlow(tempStr,
     TextConverter.TEXT_FIELD_HTML_FORMAT);
   }
   
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    // Parsing the xml data into ArrayCollection
    var objArray:ArrayCollection = new ArrayCollection();
    var tempObj:Object;
    var dateArray:Array;
    var tempDate:Date;
    for(var i:int = 0; i < myData.record.length(); i ++)
    {
     tempObj = new Object();
     dateArray = String(myData.record[i].date).split("/");
     //Convert the date data into a Date Object
     tempDate = new Date(dateArray[2], 
      Number(dateArray[1]) - 1, 
      dateArray[0]);
     tempObj.date = tempDate;
     tempObj.time = tempDate.time;
     tempObj.people = myData.record[i].people;
     tempObj.label = dateFormatter(tempDate);
     objArray.addItem(tempObj);
    }
    
    //Create the SortField object for the "time" field 
    //in the ArrayCollection object, and make sure we
    //do a numeric sort.
    var dataSortField:SortField = new SortField();
    dataSortField.name = "time";
    dataSortField.numeric = true;
    
    //Create the Sort object and add the SortField 
    //object created earlier to the array of fields
    //to sort on.
    var numericDataSort:Sort = new Sort();
    numericDataSort.fields = [dataSortField];
    
    objArray.sort = numericDataSort;
    objArray.refresh();
    
    //Create the new series and set its properties.
    localSeries.dataProvider = objArray;
    localSeries.yField = "people";
    localSeries.xField = "date";
    
    //Back up the current series on the chart.
    var currentSeries:Array = lineChart.series;
    //Add the new series to the current Array of 
    //series.
    currentSeries.push(localSeries);
    //Add the new Array of series to the chart.
    lineChart.series = currentSeries;
    
    cb1.dataProvider = objArray;
   }
   
   //This function will return a string based on the
   //Date format DD/MM/YYYY.
   private function dateFormatter(tempDate:Date):String
   {
    var fmt:DateFormatter = new DateFormatter();
    fmt.formatString = "DD/MM/YYYY";
    return fmt.format(tempDate);
   }
   
   //We are customizing the datatip / tool tip of the
   //chart data.
   public function myDataTipFunction(e:HitData):String {
    var s:String = "";
    var tempDate:Date = e.item.date as Date;
    s += "Date: " + dateFormatter(tempDate) + "<br>";
    s += "No. of People: " + e.item.people;
    return s;
   }
   
  ]]>
 </fx:Script>
 <s:VGroup width="100%" 
     height="100%"
     verticalAlign="middle"
     horizontalAlign="center">
  <s:BorderContainer width="100%"
         height="100%"
         backgroundAlpha="0"
         borderVisible="false">
   <s:HGroup verticalAlign="middle" horizontalAlign="center"
       width="100%"
       height="100%">
    <!-- Need to set the gutterLeft and 
     gutterTop of the chart -->
    <mx:LineChart id="lineChart"
         gutterTop="0"
         gutterLeft="50"
         showDataTips="true" 
         width="80%"
         height="80%"
         dataTipFunction="myDataTipFunction">
     <mx:horizontalAxis>
      <mx:DateTimeAxis dataUnits="days" id="dateAxis" 
           alignLabelsToUnits="true"/> 
     </mx:horizontalAxis>
    </mx:LineChart>
   </s:HGroup>
   <s:BorderContainer width="100%" 
          height="100%"
          id="canvas"
          mouseEnabled="false"
          backgroundAlpha="0"
          borderVisible="false"
          mouseChildren="false">
    <s:TextArea width="150"
       height="50" 
        visible="false"
       id="txtResult"/>
   </s:BorderContainer>
  </s:BorderContainer>
  <s:HGroup width="100%" 
      verticalAlign="middle"
      horizontalAlign="center">
   <s:Label text="Select a Date:"/>
   <s:ComboBox id="cb1" 
       change="cb1ChangeEvent(event)"/>
  </s:HGroup>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Friday, August 24, 2012

Flex: The not so global ToolTipManager...

I was playing around the ToolTipManager class today and I realised that the ToolTipManager class isn't so global after all. Although you can use ToolTipManager.enabled to show/hide all the tooltips but...

Here the source codes of my main application - NotReallyGlobalTooltip.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      creationComplete="creationCompleteHandler(event)"> 
 <fx:Script>
  <![CDATA[
   import mx.charts.series.ColumnSeries;
   import mx.collections.ArrayCollection;
   import mx.events.FlexEvent;
   import mx.managers.ToolTipManager;
   
   
   //Records used in the chart
   [Bindable]
   private var myData:XML = 
    <records>
     <record>
      <month>Jan</month>
      <car>73</car>
     </record>
     <record>
      <month>Feb</month>
      <car>72</car>
     </record>
     <record>
      <month>Mar</month>
      <car>79</car>
     </record>
     <record>
      <month>Apr</month>
      <car>80</car>
     </record>
     <record>
      <month>May</month>
      <car>51</car>
     </record>
    </records>;
   
   protected function btnGlobalToolTipEvent(event:MouseEvent):void
   {
    //Toggle the visibility of the Tooltips for all the 
    //components except the charts 
    ToolTipManager.enabled = !ToolTipManager.enabled;
    updateBtns();
   }
   
   protected function btnChartToolTipEvent(event:MouseEvent):void
   {
    //Toggle the visibility of the Tooltips for all the 
    //items in the charts
    chartMain.showDataTips = !chartMain.showDataTips;
    updateBtns();
   }
   
   protected function btnSuperToolTipEvent(event:MouseEvent):void
   {
    //Toggle the visibility of the Tooltips for all the 
    //components and the charts and sync them
    if(ToolTipManager.enabled == chartMain.showDataTips)
    {
     ToolTipManager.enabled = !ToolTipManager.enabled;
     chartMain.showDataTips = !chartMain.showDataTips;
    }else{
     ToolTipManager.enabled = false;
     chartMain.showDataTips = false;
    }
    updateBtns();
   }
   
   protected function creationCompleteHandler(event:FlexEvent):void
   {
    updateBtns();
   }
   
   //This function will updates all the labels of the buttons.
   private function updateBtns():void
   {
    if(ToolTipManager.enabled == chartMain.showDataTips)
    {
     if(ToolTipManager.enabled)
     {
      btnSuperToolTip.label = 
       "All ToolTips Enabled";
     }else{
      btnSuperToolTip.label = 
       "All ToolTips Disabled";
     }
    }else{
     btnSuperToolTip.label = 
      "All ToolTips Mixed";
    }
    
    if(ToolTipManager.enabled)
    {
     btnGlobalToolTip.label = 
      "Global ToolTip Enabled";
    }else{
     btnGlobalToolTip.label = 
      "Global ToolTip Disabled";
    }
    
    if(chartMain.showDataTips)
    {
     btnChartToolTip.label = 
      "Chart ToolTip Enabled";
    }else{
     btnChartToolTip.label = 
      "Chart ToolTip Disabled"; 
    }
   }
  ]]>
 </fx:Script>
 <s:VGroup width="100%" height="100%" gap="10" 
     horizontalAlign="center" verticalAlign="middle">
  <mx:ColumnChart id="chartMain" dataProvider="{myData.record}">
   <mx:horizontalAxis>
    <mx:CategoryAxis 
     categoryField="month"/>
   </mx:horizontalAxis>
   <mx:verticalAxis>
    <mx:LinearAxis 
     minorInterval="10"
      maximum="100"/>
   </mx:verticalAxis>
   <mx:series>
    <mx:ColumnSeries 
     yField="car" 
     displayName="No Of Cars."/>
   </mx:series>  
  </mx:ColumnChart>
  <s:HGroup gap="10">
   <mx:Text text="Some Dummy Text with Tooltip" 
      toolTip="A empty tooltip"/>
   <s:Button label="Button"
       toolTip="that does nothing"/>
  </s:HGroup>
  <s:Spacer height="10"/>
  <mx:Text htmlText="Click on the buttons below and rollover
     <br>the components in the above to see the differences."
     textAlign="center"/>
  <s:HGroup gap="10">
   <s:Button id="btnGlobalToolTip"
       width="200" 
       click="btnGlobalToolTipEvent(event)"/>
   <s:Button id="btnChartToolTip"
       width="200" 
       click="btnChartToolTipEvent(event)"/>
   <s:Button id="btnSuperToolTip"
       width="200" 
       click="btnSuperToolTipEvent(event)"/>
  </s:HGroup>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

Saturday, August 4, 2012

Flex: Mac OS X, Mouse Wheel Detection

For some reason, mouse wheel scrolling doesn't really work on a Mac. Therefore after doing a bit of Googling or searching on the Internet, I finally found an ideal solution to my troubles.

* Click here for the demo that was created by pixelbreaker.
^ Click here for the post on Mac OS X, Mouse Wheel Detection by pixelbreaker.

Saturday, July 21, 2012

AS3: Fading Sound

Music playing, something that is very important to an interactive website, therefore I'm going to show you how to create a pretty simple fade in and fade out effect for your background music today.

Main class file - Main.as
package com.zcs.main 
{ 
 import flash.display.MovieClip;
 import flash.events.Event;
 import flash.events.MouseEvent;
 import com.greensock.TweenLite;
 import flash.media.SoundChannel;
 import flash.media.SoundTransform;
 
 public class Main extends MovieClip 
 {
  private var _btn_mc:MovieClip;
  
  private var bgSound:Sound1;
  
  //Variable to toggle the playing state
  private var isPlaying:Boolean = false;
  
  private var soundChannel:SoundChannel;
  private var soundT:SoundTransform;
  
  public function Main() 
  {
   this.addEventListener(Event.ADDED_TO_STAGE, stageEvent);
  }
  
  private function stageEvent(event:Event):void
  {
   //Setup up the listener for the play stop button.
   _btn_mc = this.getChildByName("btn_mc") as MovieClip;
   _btn_mc.buttonMode = true;
   _btn_mc.addEventListener(MouseEvent.CLICK, clickEvent);
   
   //Create an instance for the Sound Object
   bgSound = new Sound1();
   
   //Create a Sound Transform instance that will be used
   //to modify the volume of the Sound Object 'bgSound'.
   soundT = new SoundTransform();
   soundT.volume = 0;
   
   //In Flash, we need to assign the audio that we want
   //to play to a Sound Channel
   soundChannel = new SoundChannel();
   soundChannel = bgSound.play(0,9999);
   soundChannel.soundTransform = soundT;
   
   //Call the function toggleSound()
   toggleSound();
  }
  
  /*
   If the Sound Object isn't 'playing', we need to slowly
   increase the volume of the sound channel to the max.
   If it's 'playing', slowly decrease the volume to 0.
  */
  private function toggleSound():void
  {
   if(isPlaying)
   {
    _btn_mc.gotoAndStop("stopping");
    TweenLite.to(soundT, 1, {volume:0, onUpdate:updateSoundEvent, 
        onComplete:updateSoundEvent});
   }else{
    _btn_mc.gotoAndStop("playing");
    TweenLite.to(soundT, 1, {volume:1, onUpdate:updateSoundEvent, 
        onComplete:updateSoundEvent});
   }
   isPlaying = !isPlaying;
  }
  
  //When the volume changes, we need to update the sound transform
  //of the sound channel.
  private function updateSoundEvent():void
  {
   soundChannel.soundTransform = soundT;
  }
  
  //Upon clicking the button, run the function toggleSound().
  private function clickEvent(event:Event):void
  {
   toggleSound();
  }
 }
}
* Click here for the demo.
^ Click here for the source files of the demo.

Friday, July 13, 2012

AS3 : Toggle Fullscreen

The other day, friends were asking me how to do full screen popup for flash. So here I am with a simple demo for creating a full screen swf popup. (Note: the full screen popup can only be trigger by a generic mouse click only.)

Time for some coding... The main as file for the swf - Main.as
package com.zcs 
{ 
 import flash.events.Event;
 import flash.display.MovieClip;
 import flash.events.MouseEvent;
 import flash.display.StageDisplayState;

 public class Main extends InterfaceSetup implements IInterfaceSetup
 {
  private var _top_mc:MovieClip;
  private var _bottom_mc:MovieClip;
  private var _left_mc:MovieClip;
  private var _right_mc:MovieClip;
  
  private var _cover_mc:MovieClip;
  
  public function Main():void 
  {
   // constructor code
  }
  
  //Override the stage variables
  override public function setupStageEvent(event:Event):void
  {
   super.setupStageEvent(event);
   stageWidthF = 600;
   stageHeightF = 400;
   
   _top_mc = this.getChildByName("top_mc") as MovieClip;
   _bottom_mc = this.getChildByName("bottom_mc") as MovieClip;
   _left_mc = this.getChildByName("left_mc") as MovieClip;
   _right_mc = this.getChildByName("right_mc") as MovieClip;
   
   _cover_mc = this.getChildByName("cover_mc") as MovieClip;
   _cover_mc.buttonMode = true;
   _cover_mc.addEventListener(MouseEvent.CLICK, clickEvent);
   
   resizeEvent();
  }
  
  //Upon Screen Resize....
  override public function resizeEvent(event:Event = null):void
  {
   //Scale and position the MovieClip accordingly
   resizeSubFunc(_top_mc, "50%", "l", "0", "t", 1);
   resizeSubFunc(_bottom_mc, "50%", "l", "0", "b", 1);
   resizeSubFunc(_left_mc, "0", "l", "50%", "t", 1);
   resizeSubFunc(_right_mc, "0", "r", "50%", "t", 1);
   resizeSubFunc(_cover_mc, "0", "l", "0", "t", 1);
  }
  
  private function clickEvent(event:Event):void
  {
   toggleFullScreen();
  }
  
  private function toggleFullScreen():void
  {
   //if normal size, go to fullscreen, else go to normal size
   if(stage.displayState==StageDisplayState.NORMAL){
    stage.displayState=StageDisplayState.FULL_SCREEN;
   }else{
    stage.displayState=StageDisplayState.NORMAL;
   }
  }  
 }
}
This is an simple as file that I have been using for reusing some screen resize functions - InterfaceSetup.as
package com.zcs
{
 import flash.display.MovieClip;
 import flash.events.*;
 import flash.system.*;
 import flash.external.*;
 
 public class InterfaceSetup extends MovieClip implements IInterfaceSetup
 {
  public var stageWidthF;
  public var stageHeightF;
  
  public function InterfaceSetup():void
  {
   this.addEventListener(Event.ADDED_TO_STAGE, setupStageEvent);
  }
  
  //setup the stage after this is added to stage and listen for resize of flash file
  public function setupStageEvent(event:Event):void
  {
   this.removeEventListener(Event.ADDED_TO_STAGE, setupStageEvent);
   stageWidthF = 1024;
   stageHeightF = 768;
   
   stage.addEventListener(Event.RESIZE, resizeEvent);
  }
  
  //resize event
  public function resizeEvent(event:Event = null):void
  {
   
  }
  
  //sub resize function that requires the following variables
  //tempMC = the movieclip lo :P
  //tempPosX, the x position (ex: 0 || 50%)
  //Hdir, from l (left) or r (right)
  //tempPosY, the y position (ex: 0 || 50%)
  //Vdir, from t (top) or b (bottom)
  public function resizeSubFunc(tempMC,tempPosX,Hdir,tempPosY,Vdir,scaleByScreen = 0):void
  {
   var strArray;
   
   if(scaleByScreen == 1){
    tempMC.scaleX = (stage.stageWidth/stageWidthF) * 1;
    tempMC.scaleY = (stage.stageHeight/stageHeightF) * 1;
   }else if(scaleByScreen == -1){
    tempMC.scaleX = (stage.stageHeight/stageHeightF) * 1;
    tempMC.scaleY = (stage.stageWidth/stageWidthF) * 1;
   }
   
   if(Hdir == "l"){
    tempMC.x = -1 * ((stage.stageWidth - stageWidthF) / 2);
    if(String(tempPosX).indexOf("%") != -1)
    {
     strArray = String(tempPosX).split("%");
     tempMC.x += (int(strArray[0])/100) * stage.stageWidth;
    }else{
     tempMC.x += Number(tempPosX);
    }
   }else{
    tempMC.x = stageWidthF + ((stage.stageWidth - stageWidthF) / 2);
    if(String(tempPosX).indexOf("%") != -1)
    {
     strArray = String(tempPosX).split("%");
     tempMC.x -= (int(strArray[0])/100) * stage.stageWidth;
    }else{
     tempMC.x -= Number(tempPosX);
    }
   }
   
   if(Vdir == "t"){
    tempMC.y = -1 * ((stage.stageHeight - stageHeightF) / 2);
    if(String(tempPosY).indexOf("%") != -1)
    {
     strArray = String(tempPosY).split("%");
     tempMC.y += (int(strArray[0])/100) * stage.stageHeight;
    }else{
     tempMC.y += Number(tempPosY);
    }
   }else{
    tempMC.y = stageHeightF + ((stage.stageHeight - stageHeightF) / 2);
    if(String(tempPosY).indexOf("%") != -1)
    {
     strArray = String(tempPosY).split("%");
     tempMC.y -= (int(strArray[0])/100) * stage.stageHeight;
    }else{
     tempMC.y -= Number(tempPosY);
    }
   } 
   tempMC.x = Math.ceil(tempMC.x);
   tempMC.y = Math.ceil(tempMC.y);
  }
 }
}
A simple Interface file - IInterfaceSetup.as
package com.zcs
{
 import flash.events.Event;

 public interface IInterfaceSetup 
 {
  // Interface methods:
  function setupStageEvent(event:Event):void;
  function resizeEvent(event:Event = null):void;
 }
}
Main HTML file - main.html
<html>
 <head>
<style type="text/css">
<!--
html{height:100%}
body {
 margin-left: 0px;
 margin-top: 0px;
 margin-right: 0px;
 margin-bottom: 0px;
 background-color: #000;
}
#flashContent {width:100%;height:100%;}
-->
</style>
    
   <!-- Include support librarys first -->  
  <script type="text/javascript" src="jsScript/swfobject.js"></script>
  <script type="text/javascript" src="jsScript/swfforcesize.js"></script>      
  <script type="text/javascript" src="jsScript/swfaddress.js?tracker=null"></script>    


    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>
    <table cellpadding="0" cellspacing="0" border="0" width="100%" height="100%">
    <tr><td align="center">
    <div id="flashContent">
         <h1>You need at least Flash Player 10.0 to view this page.</h1>
                <p><a href="http://www.adobe.com/go/getflashplayer"><img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" /></a></p>
    </div>
  <script type="text/javascript">
   function createSWF(){
    var now=new Date();
    now = Date.UTC(now.getYear(),now.getMonth(),now.getDate(),now.getHours(),now.getMinutes(),now.getSeconds());
    var swfURL = "swf/main.swf?date=" + now;
    var flashvars = {};
    var params = {bgcolor:"#FFFFFF"};
    //Need to set allowfullscreen to "true"
    params.allowfullscreen = "true";
    //By changing scale to "exactFit" will make the swf
    //fill up all the spaces in the browser window
    params.scale = "exactFit";

    var attributes = {};
    attributes.name = "flashContent";
    swfobject.embedSWF(swfURL, "flashContent", "100%", "100%", "10.0.2", null, flashvars, params, attributes);   
   }
   createSWF();
  </script>    
     </td></tr></table>  
</body>
</html>
* Click here for the demo.
(Click on any part of the flash file to show the full screen popup.)
^ Click here for the source files of the demo.