Spinner class
package util
{
import flash.events.Event;
import flash.geom.Point;
import mx.controls.SWFLoader;
import mx.core.FlexGlobals;
import mx.core.UIComponent;
import mx.events.ResizeEvent;
import mx.managers.PopUpManager;
import spark.components.Application;
import spark.components.SkinnableContainer;
public class Spinner extends SkinnableContainer
{
private var _spinnerSWF:SWFLoader;
private var _target:UIComponent;
/**
* if true indicate the target is in a popup window, otherwise not.
*/
private var isTargetPopUp:Boolean;
/**
* if user send another request before the first rquest complete, and both of them are spinning the same target,
* for the second request we will not use a new spinner, but only increase the counter of the spinner, when request finished, we will check the counter, if counter is not
* 0, which means another request has not been finished yet, in this case the spinner is not hidden, we only decrease the counter,
* only all requests having spinner on the same target are finished, the spinner will be hidden.
*/
public var counter:int=0;
public function Spinner()
{
this.setStyle("backgroundColor","0xfafafc");
this.alpha=0.5;
_spinnerSWF=new SpinnerSWF();
_spinnerSWF.width=0;
_spinnerSWF.height=0;
width=0;
height=0;
x=-10;
y=-10;
addElement(_spinnerSWF);
}
private function measurePosition():void{
if(!_target){
throw new Error("Target cannot be null");
}
if(_target.parent){
var globalPosition:Point=_target.parent.localToGlobal(new Point(_target.x,_target.y));
move(globalPosition.x,globalPosition.y);
}
}
/**
* Make sure this method is invoked after measureSize()
*/
private function measureSpinnerSWFSize():void{
var min:int = _target.width < _target.height? _target.width: _target.height;
if( min> 0 && min<70 ){
_spinnerSWF.width = min;
_spinnerSWF.height = min;
}else{
_spinnerSWF.width = 70;
_spinnerSWF.height = 70;
}
}
private function measureSpinnerSWFPosition():void{
_spinnerSWF.x = (_target.width - _spinnerSWF.width)/2;
_spinnerSWF.y = (_target.height - _spinnerSWF.height)/2;
}
private function measureSize():void{
width=_target.width;
height=_target.height;
}
public function showSpinner(trgt:UIComponent):void{
if(!trgt){
throw new Error("Target cannot be null");
}
_target=trgt;
measureSize();
measurePosition();
measureSpinnerSWFSize();
measureSpinnerSWFPosition();
//why we add movement listeners to target.parentDocument if target is in a popup window, because "xChanged" and "yChanged" event is not
//triggered on the target, only triggered on target.parentDocument when we move the popup window.
//why we don't care whether target itself is a popup window, because if the target itself is a popup window, user
//actually is not able to move the target, so we don't care
if(_target.parentDocument!=null && (_target.parentDocument as UIComponent).isPopUp){
isTargetPopUp=true;
}else{
isTargetPopUp=false;
}
if(isTargetPopUp){
_target.parentDocument.addEventListener(ResizeEvent.RESIZE,targetResizeHandler);
_target.parentDocument.addEventListener("xChanged",targetXChangedHandler);
_target.parentDocument.addEventListener("yChanged",targetYChangedHandler);
PopUpManager.addPopUp(this,_target,false);
}else{
_target.addEventListener(ResizeEvent.RESIZE,targetResizeHandler);
_target.addEventListener("xChanged",targetXChangedHandler);
_target.addEventListener("yChanged",targetYChangedHandler);
(FlexGlobals.topLevelApplication as SkinnableContainer).addElement(this);
}
}
private function targetXChangedHandler(e:Event):void{
measurePosition();
measureSpinnerSWFPosition();
}
private function targetYChangedHandler(e:Event):void{
measurePosition();
measureSpinnerSWFPosition();
}
private function targetResizeHandler(event:ResizeEvent):void{
measureSize();
measurePosition();
measureSpinnerSWFSize();
measureSpinnerSWFPosition();
}
public function hideSpinner():void{
if(_spinnerSWF){
_spinnerSWF.width=0;
_spinnerSWF.height=0;
}
width=0;
height=0;
x=-10;
y=-10;
removeListeners();
removeSpinner();
}
private function removeSpinner():void{
if(isTargetPopUp){
PopUpManager.removePopUp(this);
}else{
(FlexGlobals.topLevelApplication as SkinnableContainer).removeElement(this);
}
}
public function destroySpinner():void{
removeListeners();
removeElement(_spinnerSWF);
_spinnerSWF=null;
removeSpinner();
}
private function removeListeners():void{
if(_target.hasEventListener(ResizeEvent.RESIZE))
_target.removeEventListener(ResizeEvent.RESIZE,targetResizeHandler);
if(_target.hasEventListener("xChanged"))
_target.removeEventListener("xChanged",targetResizeHandler);
if(_target.hasEventListener("yChanged"))
_target.removeEventListener("yChanged",targetYChangedHandler);
}
}
}
SpinnerFactory class
package util
{
import flash.utils.Dictionary;
import mx.collections.ArrayCollection;
import mx.core.UIComponent;
public class SpinnerFactory
{
private static var _instance:SpinnerFactory;
private var _targetsHasSpinner:Dictionary;
private var _spinnerPool:ArrayCollection;
public function SpinnerFactory()
{
if(_instance){
throw new Error("SpinnerFactory singleton instance has already constructed");
}
_targetsHasSpinner=new Dictionary();
initSpinnerPool();
}
private function initSpinnerPool():void{
_spinnerPool=new ArrayCollection();
for(var i:int=0;i<5;i++){
_spinnerPool.addItem(new Spinner());
}
}
private function ensureCapacity():void{
for(var i:int=0;i<5;i++){
_spinnerPool.addItem(new Spinner());
}
}
public static function getInstance():SpinnerFactory{
if(!_instance){
_instance=new SpinnerFactory();
}
return _instance;
}
public function spinOneTarget(target:UIComponent):void{
if(target==null){
return;
}
var spinner:Spinner=_targetsHasSpinner[target];
if(spinner!=null){
spinner.counter++;
return;
}
if(_spinnerPool.length>0){
spinner=_spinnerPool.getItemAt(0) as Spinner;
}else{
ensureCapacity();
spinner=_spinnerPool.getItemAt(0) as Spinner;
}
if(spinner){
_spinnerPool.removeItemAt(0);
_targetsHasSpinner[target]=spinner;
spinner.showSpinner(target);
}
}
/**
* spin multiple UIComponents
*/
public function spinTargets(targets:Array):void{
if(targets==null||targets.length==0)
return;
for each(var item:Object in targets){
if(item!=null)
spinOneTarget(item as UIComponent);
}
}
public function unspinOneTarget(target:UIComponent):void{
if(target==null){
return;
}
var spinner:Spinner = _targetsHasSpinner[target];
if(spinner!=null){
if(spinner.counter!=0){
spinner.counter--;
}else{
spinner.hideSpinner();
_targetsHasSpinner[target]=null;
_spinnerPool.addItem(spinner);
}
}
}
/**
* unspin multiple UIComponents
*/
public function unspinTargets(targets:Array):void{
if(targets==null||targets.length==0)
return;
for each(var item:Object in targets){
if(item!=null)
unspinOneTarget(item as UIComponent);
}
}
}
}
swf
<s:SWFLoader xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="100" height="100" source="@Embed(source='assets/loading.swf')">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
</s:SWFLoader>