воскресенье, 20 сентября 2009 г.

Smooth integration Google Analytics in flex project

I propose a method for bloodless integration tracking in a finished project. Ideology is borrowing from the implementation of the automation logic in flex framework. For each class added to the stage (or concrete list of instances) defined the relevant tracking class.
The tracking classes can be dynamically loaded at runtime for reducing costs or implementation "by request" logic.


source

So, the main class responsible for tracking is GATrackingManager
package ua.org.enginer.managers
{
import com.google.analytics.AnalyticsTracker;
import com.google.analytics.GATracker;
import com.google.analytics.debug.VisualDebugMode;

import flash.display.DisplayObject;
import flash.events.Event;
import flash.utils.getQualifiedClassName;

import mx.core.Container;
import mx.core.UIComponent;
import mx.managers.SystemManager;

import ua.org.enginer.tracking.ButtonTrackingImpl;
import ua.org.enginer.tracking.ComboBoxTrackingImpl;
import ua.org.enginer.tracking.ContainerTrackingImpl;
import ua.org.enginer.tracking.ITrackingObject;
import ua.org.enginer.tracking.ListTrackingImpl;
import ua.org.enginer.tracking.UIComponentTrackingImpl;
import ua.org.enginer.tracking.ViewStackTrackingImpl;
import ua.org.enginer.utils.ClassUtils;

public class GATrackingManager {

private var account:String
private var tracker:AnalyticsTracker

function GATrackingManager(account:String) {
this.account = account
}

public function init(obj:Object):void {
// listen for popUps and etc.
SystemManager.getSWFRoot(obj).addEventListener(Event.ADDED, onAdded)

tracker = new GATracker(SystemManager.getSWFRoot(obj), account, "AS3", true);
tracker.debug.verbose = true;
tracker.debug.mode = VisualDebugMode.advanced;
tracker.debug.traceOutput = true;
//tracker.debug.GIFRequests = true;
}

public function accociateComponent(component:UIComponent):void {
if (!component) return;

var implementationClass:Class = findTrackingImplementation(component);
if (implementationClass) {
var implementation:ITrackingObject = new implementationClass(component);
implementation.tracker = tracker
implementationInstances.push(implementation);
} else {
trace("***no implementation:"+component+"=>"+getQualifiedClassName(component)+" <<"+implementationClass);
}
}

private function findTrackingImplementation(component:UIComponent):Class {
var componentName:String = getQualifiedClassName(component);

var componentClassName:String = componentName.split("::")[1]+"TrackingImpl";
var clazz:Class = findTrackingImplementationClassByName(componentClassName);
if (clazz) return clazz; // found tracking implementation

// That's it, let's not go higher in the chain.
if ("mx.core::UIComponent" == componentClassName) return null;


trace("\t search parent:"+component.className);
for each (componentName in ClassUtils.inheritance(component)) {
trace("\t\t :"+componentName);
componentClassName = componentName.split("::")[1]+"TrackingImpl";
clazz = findTrackingImplementationClassByName(componentClassName);
if (clazz) return clazz; // found tracking implementation
}
// not found
return null;
}

private function findTrackingImplementationClassByName(componentClassName:String):Class {
for each (var item:Class in implementationClasses) {
var implementationClassName:String = getQualifiedClassName(item);
if (implementationClassName.indexOf("::"+componentClassName) > 0) return item;
}
return null;
}

private function onAdded(event:Event):void {
if (event.target is UIComponent) {
accociateComponent(UIComponent(event.target))
if (event.target is Container) {
for each(var child:DisplayObject in Container(event.target).getChildren())
if (child is UIComponent) accociateComponent(UIComponent(child))
}

}
}

private var implementationInstances:Array = [];

private var implementationClasses:Array = [
UIComponentTrackingImpl,
ButtonTrackingImpl,
ContainerTrackingImpl,
ViewStackTrackingImpl,
ComboBoxTrackingImpl,
ListTrackingImpl
]
}
}
The private var implementationClasses:Array it is a storage for tracking implementation classes. ListTrackingImpl for example:
package ua.org.enginer.tracking
{
import flash.events.Event;

import mx.controls.List;

public class ListTrackingImpl extends UIComponentTrackingImpl
{
public function ListTrackingImpl(target:List) {
super(target)
List(target).addEventListener(Event.CHANGE, onChange)
}

protected function onChange(event:Event):void {
var item:Object = List(target).selectedItem
var value:String

if (item) {
if (item.hasOwnProperty('label')) {
value = item['label']
}else {
value = item.toString()
}
}

tracker.trackEvent(trackingName, "change", value)
}
}
}

extends UIComponentTrackingImpl

package ua.org.enginer.tracking
{
import com.google.analytics.AnalyticsTracker;

import flash.events.KeyboardEvent;
import flash.events.MouseEvent;

import mx.core.EventPriority;
import mx.core.UIComponent;

public class UIComponentTrackingImpl implements ITrackingObject
{
public var target:Object
private var _tracker:AnalyticsTracker

public function set tracker(value:AnalyticsTracker):void {
_tracker = value
}

public function get tracker():AnalyticsTracker {
return _tracker
}

public function UIComponentTrackingImpl(target:UIComponent)
{
this.target = target

target.addEventListener(MouseEvent.CLICK, mouseClickHandler, false, EventPriority.DEFAULT+1, true);
target.addEventListener(MouseEvent.DOUBLE_CLICK, mouseClickHandler, false, EventPriority.DEFAULT+1, true);
target.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler, false, EventPriority.DEFAULT+1, true);
}

protected function mouseClickHandler(event:MouseEvent):void
{
if (trackingName && tracker) tracker.trackEvent(trackingName, "MouseEvent."+event.type)
}

protected function keyDownHandler(event:KeyboardEvent):void
{
if (trackingName && tracker) tracker.trackEvent(trackingName, "KeyboardEvent."+event.type)
}

public function get trackingName():String
{
if (target.hasOwnProperty('label'))
return target['label']

return target.automationName || target.toolTip || target.name || target.id
}
}
}
For starting tracking insert:
new GATrackingManager('UA-111-222').init(this)
there 'UA-111-222' - your Google Analytics account id.

Required library:
Another solutions:

суббота, 23 мая 2009 г.

Links for flex diver

Some jokers who call themselves flexinmotion have released a ridiculously overpriced Flex component which “will automatically track all user navigation clicks, button click, check boxes, radio buttons and a number of other controls within your app automatically” using Google Analytics. Let me show you how to save those 149 bucks.
http://blog.iconara.net/2008/04/18/how-to-save-149/

Deploying patches in your Flex application
Every time you need to make minor changes in your production application, just put them in patches.swc and upload it to the production machine.
http://flexblog.faratasystems.com/?p=279

Manifests, Namespaces and Flex Builder
http://blog.flashgen.com/2007/07/04/manifests-namespaces-and-flex-builder-2/

SSL, crossdomains.xml
flash.system.Security.loadPolicyFile("{Url to my crossdomain.xml file on the SSL virtual root}"); With these changes in place, I’m able to easily integrate Google Accounts with my Flash app.
http://www.voiceoftech.com/swhitley/?p=117

Singletons are Pathological Liars
The problem is that the APIs are pathological liars
http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/

The following example shows how you can view a Flex application’s generated source code by adding the -keep compiler argument in Flex Builder. The generated source code will appear in a /generated/ folder within the Flex Builder project’s /src/ folder. If your MXML file was named main.mxml, the generated file could be found at /generated/main-generated.as.
http://blog.flexexamples.com/2008/08/02/viewing-a-flex-applications-generated-source-code/

Adobe Flex Component Lifecycle
http://www.slideshare.net/rjowen/adobe-flex-component-lifecycle-presentation

How are you doing global exception handling in Flex/Flash/AS3
http://dougmccune.com/blog/2009/02/10/how-are-you-doing-global-exception-handling-in-flexflashas3/

DataGrid with selection by checkboxes

The DataGrid what change selection by clicking on checkboxes and accordingly change state of checkboxes by changing DataGrid selection. In addition select all by key "a" pressed.


view source enabled

CheckBoxSelectionDataGrid
package ua.org.enginer.controls {

import flash.events.Event;
import flash.events.KeyboardEvent;

import mx.collections.ArrayCollection;
import mx.controls.DataGrid;
import mx.controls.dataGridClasses.DataGridColumn;
import mx.core.ClassFactory;
import mx.events.ListEvent;

import ua.org.enginer.controls.renderers.DataGridSelectionIndicator;
import ua.org.enginer.controls.renderers.DataGridSelectionIndicatorHeader;


/**
* [a] key for select all (like [Ctrl+a])
*
* Indicate selection by CheckBoxes
*/
public class CheckBoxSelectionDataGrid extends DataGrid {

public function CheckBoxSelectionDataGrid() {
super();
addEventListener(KeyboardEvent.KEY_DOWN, onKetDown)

function onKetDown(event:KeyboardEvent):void {
//if (event.ctrlKey && event.keyCode == 65) {
// My lovely IE do not pass [Ctrl+A]... so simply [a]
if (event.keyCode == 65) {
selectAll()
}
}
}

public function selectAll(value:Boolean=true):void {
selectedItems = value ? ArrayCollection(collection).source : []
}

// storage for seted columns
private var rawColumns:Array

override public function set columns(value:Array):void {
super.columns = value
rawColumns = value
}

private var _indicatorAlign:String = "right";

public function set indicatorAlign(value:String):void {
if (value != _indicatorAlign) {
_indicatorAlign = value;
invalidateProperties()
dispatchEvent (new Event("indicatorAlignChange"));
}
}

[Bindable(event="indicatorAlignChange")]
[Inspectable(category="General", enumeration="left,right", defaultValue="right")]
public function get indicatorAlign():String {
return _indicatorAlign;
}

private var _indicatorsColumn:DataGridColumn

public function get indicatorsColumn():DataGridColumn {
if (_indicatorsColumn) return _indicatorsColumn

var selectionIndicators:DataGridColumn = new DataGridColumn()
selectionIndicators.itemRenderer = new ClassFactory(DataGridSelectionIndicator)
selectionIndicators.headerRenderer = new ClassFactory(DataGridSelectionIndicatorHeader)
selectionIndicators.width = 20
selectionIndicators.sortable = false
selectionIndicators.resizable = false

return _indicatorsColumn = selectionIndicators
}

override protected function commitProperties():void {

// clone array
var newColumns:Array = rawColumns.concat()
indicatorAlign == "right" ? newColumns.push(indicatorsColumn) :
newColumns.unshift(indicatorsColumn)
super.columns = newColumns

super.commitProperties()
}

override public function set selectedItems(items:Array):void {
super.selectedItems = items
// for recalculation
dispatchEvent(new ListEvent(ListEvent.CHANGE))
}

override public function set selectedIndices(indices:Array):void {
super.selectedIndices = indices
dispatchEvent(new ListEvent(ListEvent.CHANGE))
}

override public function set dataProvider(value:Object):void {
super.dataProvider = value
dispatchEvent(new ListEvent(ListEvent.CHANGE))
}
}
}



DataGridSelectionIndicator
package ua.org.enginer.controls.renderers {

import flash.events.Event;

import mx.controls.CheckBox;
import mx.controls.DataGrid;
import mx.controls.listClasses.BaseListData;
import mx.events.FlexEvent;
import mx.events.ListEvent;

public class DataGridSelectionIndicator extends CheckBox {

public function DataGridSelectionIndicator() {
super();
addEventListener(Event.CHANGE, onChange)
setStyle("paddingLeft", 3)
}


private function onChange(event:Event):void {
var grid:DataGrid = DataGrid(listData.owner)
var myIndex:int = grid.itemRendererToIndex(this)

if (selected) {
if (grid.selectedIndices.indexOf(myIndex)>=0) return;
var indices:Array = grid.selectedIndices
indices.push(myIndex)
grid.selectedIndices = indices
}
else grid.selectedIndices = grid.selectedIndices.filter(function (...args):Boolean {
if (args[0] == myIndex) return false;
return true
})
}

private function onItemClick(event:Event):void {
var grid:DataGrid = DataGrid(listData.owner)
var myIndex:int = grid.itemRendererToIndex(this)
selected = grid.selectedIndices.indexOf(myIndex)>=0
}

override public function set listData(value:BaseListData):void {
super.listData = value

var grid:DataGrid = DataGrid(value.owner)

grid.addEventListener(FlexEvent.VALUE_COMMIT, onItemClick)
grid.addEventListener(ListEvent.ITEM_CLICK, onItemClick)
//grid.addEventListener(ListEvent.CHANGE, onItemClick)
selected = false
}

override public function set data(value:Object):void {
// prevent default behavior
}
}
}




DataGridSelectionIndicatorHeader
package ua.org.enginer.controls.renderers {

import flash.events.Event;

import mx.controls.CheckBox;
import mx.controls.DataGrid;
import mx.controls.listClasses.BaseListData;
import mx.events.FlexEvent;
import mx.events.ListEvent;

import ua.org.enginer.controls.CheckBoxSelectionDataGrid;

// fixme: do not change state on click
public class DataGridSelectionIndicatorHeader extends CheckBox {

private var grid:CheckBoxSelectionDataGrid

public function DataGridSelectionIndicatorHeader() {
super();
addEventListener(Event.CHANGE, onChange)
setStyle("paddingLeft", 3)
toolTip = "Отметить все"
}


private function onChange(event:Event):void {
grid.selectAll(selected)
}

override public function set listData(value:BaseListData):void {
//super.listData = value
grid = CheckBoxSelectionDataGrid(value.owner)
}

override public function set data(value:Object):void {
// prevent default behavior
}

}
}


четверг, 14 мая 2009 г.

Что изображено на логотипе?

Как вы думаете что изображено на рисунке сверху поцентру?