Thursday, October 25, 2012

Flex 4: Simple walkthrough in adding GA to your flex site

As an individual started creating a new website, he/she will be interested in finding out how did the website perform. He/she will be interested in tracking down the actions of the user and which is the most popular page throughout the whole website. Therefore I'm creating a simple demo on integrating the Google Analytics services into your flex website.

Time for some coding.
The main application file source code - SimpleGATracking.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)"
      addedToStage="addedToStageEvent(event)"
      xmlns:local="*">
 <fx:Script>
  <![CDATA[
   import com.util.GATracker.GATrackerObj;
   
   import mx.events.FlexEvent;
   
   private var _isCreationComplete:Boolean = false;
   private var _isAddedToStage:Boolean = false;
   
   protected function creationCompleteEvent(event:FlexEvent):void
   {
    _isCreationComplete = true;
    createContent();    
   }
   
   protected function addedToStageEvent(event:Event):void
   {
    _isAddedToStage = true;
    createContent();
   }
   
   private function createContent():void
   {
    if(_isCreationComplete && _isAddedToStage)
    {
     //let's setup the GATrackerObj
     GATrackerObj.getInstance().setup(this,"UA-35825942-1",true);
    }
   }
   
   /* 
    Upon clicking on one of the buttons,
    we will track the button click.
   */
   protected function clickEvent(event:MouseEvent):void
   {
    if(event.currentTarget == btn1)
    {
     GATrackerObj.getInstance().
      track("main/button_1_click");
    }else if(event.currentTarget == btn2){
     GATrackerObj.getInstance().
      track("main/button_2_click");
    }
   }
   
  ]]>
 </fx:Script>
 <s:VGroup width="100%"
     height="100%"
     verticalAlign="middle"
     horizontalAlign="center">
  <s:VGroup width="50%"
      height="50%">
   <s:HGroup horizontalAlign="center"
       verticalAlign="middle"
       width="100%"
       height="50%">
    <s:Button id="btn1"
        label="Button 1"
        click="clickEvent(event)"/>
    <s:Button id="btn2"
        label="Button 2"
        click="clickEvent(event)"/>
   </s:HGroup>
   <local:SimpleCanvasView width="100%"
      height="50%"
       backgroundColor="#DEDEDE"/>
  </s:VGroup>
 </s:VGroup>
</s:Application>

The view file source code - SimpleCanvasView.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:BorderContainer xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">
 <s:layout>
  <s:HorizontalLayout horizontalAlign="center"
        verticalAlign="middle"/>
 </s:layout>
 <fx:Script>
  <![CDATA[
   import com.util.GATracker.GATrackerObj;
   /* 
    Upon clicking on one of the buttons,
    we will track the button click.
   */
   protected function clickEvent(event:MouseEvent):void
   {
    if(event.currentTarget == btn1)
    {
     GATrackerObj.getInstance().
      track("inner_view/button_1_click");
    }else if(event.currentTarget == btn2){
     GATrackerObj.getInstance().
      track("inner_view/button_2_click");
    }
   }
  ]]>
 </fx:Script>
 
 <s:Button id="btn1"
     label="Button 1"
     click="clickEvent(event)"/>
 <s:Button id="btn2"
     label="Button 2"
     click="clickEvent(event)"/> 
</s:BorderContainer>

The file that I have created to communicate with Google Analytics - GATrackerObj.as
package com.util.GATracker
{
 import com.google.analytics.AnalyticsTracker;
 import com.google.analytics.GATracker;
 
 import mx.core.UIComponent;
 
 public class GATrackerObj extends UIComponent
 {
  private static var _instance:GATrackerObj=null;
  public var tracker:AnalyticsTracker;
  
  //You need the following functions to create a 
  //singleton Object. SingletonObject(e:SingletonEnforcer)
  //and getInstance():SingletonObject
  //Rather than using new SingletonObject to create a new
  //object of the class, you need to use
  //SingletonObject.getInstance() to point to the Singleton
  //class.
  public function GATrackerObj(e:SingletonEnforcer)
  {
   trace("new singleton object created");
  }
  
  public static function getInstance():GATrackerObj{
   if(_instance==null){
    _instance=new GATrackerObj(new SingletonEnforcer);
   }
   return _instance;
  }
  
  /**
   * In order to track(value) your pages, 
   * you need to run setup first
   * 
   * @param view - the view that you are executing the setup()
   * @param id - the GA Account ID
   * @param debug - do you want to show the visual debug console 
   */
  public function setup(view:*,
         id:String, 
         debug:Boolean = false):void
  {
   view.addElement(GATrackerObj.getInstance());
   tracker = new GATracker(this, id, "AS3", debug);
  }
  
  /**
   * In order to track(value) your pages, 
   * you need to run setup first
   * 
   * @param value - the page you want to track 
   */
  public function track(value:String):void
  {
   tracker.trackPageview("/web/" + value);
  }
 }
}

//This class is needed in creating a singleton class.
class SingletonEnforcer{
 
}

* Click here for the demo shown in this post.
^ Click here for the source files for the demo.
~ Click here for the in-depth contents of Google Analytics.
** Click here for the Google Analytics Component for Flash/Flex.

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.

Sunday, October 14, 2012

Playing with open source web stuff

Probably around 8 or 9 years ago, ever since I knew about the existence of an open source server side scripting language (PHP), I would spend a bit of time playing with it whenever I'm free. But I still remember how annoying it was if you are trying to setup a web server environment on your local machine. You need to find separate installers for Apache, PHP, mySQL, etc... Just by finding all these installers and downloading all of them can be pretty annoying. Luckily...

We have such tools that will help us to reduce all the crazy installation issues. Introducing...

'WampServer'
Taken from the website, 'WampServer' is a Windows web development environment.
It allows you to create web applications with Apache2, PHP and a MySQL database.
Alongside, PhpMyAdmin allows you to manage easily your database.
Congratulations, upon a successful installation of 'WampServer', you can skip
the step of downloading and installing the individual applications. Isn't that great?


'MAMP'
As for 'MAMP', which is pretty much similar to 'WampServer' except for the fact
that it is meant for 'Mac' Users only. I was using this application when I was
using a 'Mac' computer in the office back then. The performance of this app
is pretty good and it's pretty useful too.

While browsing through the web, it seems that Linux have it's own set of applications that can accomplish the task of installing and configuring an open source web development environment for Apache, MySQL and PHP too. But you probably need to spend some time searching. (I have never used Linux before therefore...)

* Click here to find out more about 'WampServer'.
^ Click here to find out more about 'MAMP'

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.