flex event triggered on component re-render? - apache-flex

What's the call analogous to creationComplete that happens every time a component is rendered? I want to rerun a function every time the component gets rendered (it has to make an HTTP request, but the url it calls changes each time) and I can't figure out how to do it.
For context:
<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" width="100%" height="100%"
creationComplete="loadGallery()">
private function loadGallery():void {
var src:String = "http://localhost:3000/car_type/" + UserPreference.getInstance().carType.toString() + ".xml";
Alert.show(src);
httpService.url = src;
httpService.send();
}

Instead of creationComplete use updateComplete.

I would override the updateDisplayList method for the component and add a call to your loadGallery method there.
Hope that helps.

http://docs.huihoo.com/flex/4/flash/display/DisplayObject.html#event:render maybe?

Related

Flex 4 Custom Component - How to notify skin of property changes?

I have a custom Flex 4+ component that I am trying to make and have the skin be aware of changes to a custom property. This property will determine the graphic on the button (and some other visual changes) but the data will change constantly as it will be updated by a timer.
I've looked at untold examples and still seem unable to get the syntax correct or discover how things should be separated. I've looked at overriding commitProperties and the PropertyChangeEvent without success. So I have two questions.
1) How can I get a skin to be notified of a bound property when it changes?
2) If the data for a bound property of the component is an object, will binding work properly if a property of the object changes (or would it be better to pass each property separately)?
Here is a stripped down example of what I'm trying to achieve.
The component looks like this:
<s:ButtonBase 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[
private var _iconData:String;
[Bindable]
public function get iconData():String
{
return _iconData;
}
public function set iconData(value:String):void
{
_iconData = value;
}
]]>
</fx:Script>
I'm calling it like this:
<components:MyButton id="myButton" iconData="{myData.curIconTag}" skinClass="skins.MyButtonSkin" />
I have a lot of different images I could be loading and so I'm afraid the number of states (with the combinations of up/down/over/disabled, etc. may get out of hand so the SetIconDisplay is setting the icon, but the real key is that I have other code in that function that needs to execute when the iconData property changes every X minutes or so. So the skin is something like this:
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
creationComplete="init()">
<fx:Metadata>
[HostComponent("components.MyButton")]
</fx:Metadata>
<s:states>
<s:State name="default" />
<s:State name="down"/>
<s:State name="up"/>
<s:State name="over"/>
<s:State name="disabled" />
</s:states>
<fx:Script>
<![CDATA[
import components.MyButton;
[Embed(source="images/image1.png")]
private var icon1:Class;
[Embed(source="images/image2.png")]
private var icon2:Class;
[Embed(source="images/image3.png")]
private var icon3:Class;
[Bindable]
public var hostComponent:MyButton;
[Bindable]
private var iconClass:Class;
private function init():void
{
iconClass = new Class();
}
// how do I get this called when the iconData property on my custom component is changed?
private function SetIconDisplay():void
{
switch (hostComponent.iconData)
{
case "apple":
iconClass=icon1;
break;
case "orange":
iconClass=icon2;
break;
case "grape":
iconClass=icon3;
break;
}
}
]]>
</fx:Script>
<s:BitmapImage source="{iconClass}" x="0" y="0" width="180" height="108"/>
Again, don't worry as much about how the skin is actually doing what it is doing as that will probably change (not using states). I'm just trying to figure out how to call a specific function when the bound property is changed.
Thank You!
I ended up dispatching a custom event when the data is updated and listen for it in the skin.
The component:
<s:ButtonBase 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 classes.CustomEvent;
private var _iconData:String;
[Bindable]
public function get iconData():String
{
return _iconData;
}
public function set iconData(value:String):void
{
_iconData = value;
dispatchEvent(new CustomEvent("iconDataUpdated"));
}
]]>
</fx:Script>
The skin adds this:
protected function skin_preinitializeHandler(event:FlexEvent):void
{
hostComponent.addEventListener(CustomEvent.ICON_DATA_UPDATED,SetIconDisplay);
}
Having the base class call a function on one particular skin can get awkward, as it means that the base class is dependent on the skin class, which makes it difficult to swap out skins. There are two good ways to get around this:
Option 1: Move the iconClass up into the component. The skin class can bind directly to the property, and the logic for deciding which icon to use can be handled by the component instead of the skin. This keeps logic out of the skin, and keeps the amount of skinning code you have to work with down.
Option 2: Add an iconData property to the skin, and bind it to the iconData property of the host component. In the setter function, call SetIconDisplay when you have a valid value. This keeps the icons encapsulated in the skin, which may help if you want to use a very different skin for the same component.
Edit: If you're planning on creating several other skins that don't use the icons, #2 is the way to go. Create a property on the skin like so:
private var _iconData:String;
public function get iconData():String
{
return _iconData;
}
public function set iconData(value:String):void
{
_iconData = value;
SetIconDisplay()
}
Then use a binding to connect it to the hostComponent:
<fx:Binding source="hostComponent.iconData" destination="iconData" />
Another solution to the general question, though maybe not ideal in this situation, is to call skin.invalidateDisplayList() whenever a property changes. Then, in the skin, override the updateDisplayList function and from there call a function that reacts to the changed properties, as well as calling the function on the parent class obviously.
See here: https://forums.adobe.com/thread/797247
<s:BitmapImage source="{hostComponent.iconClass}" />
should work
you don't need to declare public var hostComponent:MyButton;
it's part of SparkSkin

measure various phases of code execution

Is there a way to measure the average time it takes my code to run and each frame to render for my Flex app? More specifically, I know how to use getTimer() but I'm not sure about which events I should listen to in order to do this. I was reading this post and am not sure how you'd figure out how long the actual rendering took (it would seem like it may be the time between the RENDER event fires and the next ENTER_FRAME event fires, but I'm not sure). Also, not exactly sure where the user code happens, or whether I should care about EXIT_FRAME and FRAME_CONSTRUCTED.
Any help much appreciated!
EDIT ----
here's a snippet of code showing the main events for each repetition of the second frame in a super simple flex app. What I'm trying to figure out is whether there is a clear relationship between the "user code" and "rendering" parts of the classic Flex racetrack and the intervals between the four main signals that I'm tracing from.
The code:
<?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" minWidth="955" minHeight="600">
<fx:Script>
<![CDATA[
import flash.utils.getTimer;
public var t:Timer;
protected function button1_clickHandler(event:MouseEvent):void
{
t = new Timer(40, 50);
t.addEventListener(TimerEvent.TIMER, handleTimeTick);
t.addEventListener(TimerEvent.TIMER_COMPLETE, timerDone);
addEventListener(Event.RENDER, application1_renderHandler);
addEventListener(Event.ENTER_FRAME, application1_enterFrameHandler);
addEventListener(Event.EXIT_FRAME, application1_exitFrameHandler);
addEventListener(Event.FRAME_CONSTRUCTED, application1_frameConstructedHandler);
t.start();
}
protected function handleTimeTick(e:TimerEvent):void
{
shape.x += 5;
}
protected function timerDone(e:TimerEvent):void
{
t.stop();
t.removeEventListener(TimerEvent.TIMER, handleTimeTick);
t.removeEventListener(TimerEvent.TIMER_COMPLETE, timerDone);
removeEventListener(Event.RENDER, application1_renderHandler);
removeEventListener(Event.ENTER_FRAME, application1_enterFrameHandler);
removeEventListener(Event.EXIT_FRAME, application1_exitFrameHandler);
removeEventListener(Event.FRAME_CONSTRUCTED, application1_frameConstructedHandler);
}
protected function application1_renderHandler(event:Event):void
{
trace("render fire", getTimer());
}
protected function application1_enterFrameHandler(event:Event):void
{
trace("enter frame fire", getTimer());
}
protected function application1_exitFrameHandler(event:Event):void
{
trace("exit frame fire", getTimer());
}
protected function application1_frameConstructedHandler(event:Event):void
{
trace("frame constructed fire", getTimer());
}
]]>
</fx:Script>
<s:Rect id="shape" x="0" y="0" height="20" width="20">
<s:fill>
<s:SolidColor color="0xff0000"/>
</s:fill>
</s:Rect>
<s:Button x="10" y="100" click="button1_clickHandler(event)" label="go"/>
</s:Application>
Easy way to test out the time to takes for a "frame" to run. Here's a quick example. This is untested but you get the idea. You should also look at Grant Skinners talk about performance and framerates.
<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"
xmlns:parsley="http://www.spicefactory.org/parsley"
creationComplete="onCreationComplete()">
<fx:Script>
<![CDATA[
private var _timer:Timer = new Timer(1000);
private var _previousTime:int;
private var _avgTime:int;
private var _times:Array = [];
private function onCreationComplete():void
{
// Add event listener for enter frame
this.addEventListener(Event.ENTER_FRAME, onEnterFrame);
// Listen to timer
this._timer.addEventListener(TimerEvent.TIMER, onTimer);
// start timer
this._timer.start();
}
private function onEnterFrame(e:Event):void
{
var time:int = getTimer() - this._previousTime;
trace("timer frame took "+ time +"ms");
this._previousTime = getTimer();
this._times.push(time);
}
private function onTimer(e:Event):void
{
var total:int = 0;
for(var i:uint = 0, len:uint = this._times.length; i < len ; i++)
{
total += this._times[i];
}
this._avgTime = total/len;
this._times = [];
trace("Average frame time is "+ this._avgTime +"ms");
}
]]>
</fx:Script>
</s:Application>
Any Flex app is only two frames. The first frame is the startup/initialization of the framework and the second frame is your app.
The framerate is set on the Flex Application tag and the default is 24 frames per second. So, therefore it takes Flex 1/24 of a second to render the frames of your application.
However, it is entirely probably that your code takes more than one frame to execute, and I believe that is what you want to measure.
For some background reading, you should investigate the Flex Elastic Racetrack about how Flex divides each frame for different types of processing.
Then read up, if you haven't already, about the Flex Component LifeCycle. ( Flex 3 Version ) .
You already mentioned the getTimer() method. The gist is to use getTimer() at two points and compare the two values. What those two points are depends entirely on what you want to measure.
If you want to measure the time it takes a Flex component to go through the startup process, use getTimer() before you create it (AKA new Component() ) and then in a listener for that component's creationComplete event.
If you want to time the full application setup, you're best bet is to probably get the value in a preinitialize event handler of the main Application tag and on the applicationComplete handler of the same tag.
Does that help?
Ok, after reading plenty around the web I think this presentation gives the best info about how to measure elastic race track times (see for instance this piece of code from the presentation).

Get variable from ItemRenderer to Main Application

I have a List with TextInput as item renderer. I want to get the value entered in the TextInput (form the TextInputItemRenderer) and pass it the main application to do some checks(upon tapping enter on the textInput -- see code below).
I know that we can do it thru dispatching event but I still don't understand how to pass a variable from the ItemRenderer to the main app.
Help Pls.
Thanks
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="true" xmlns:components="components.*" width="100%"
>
<s:layout>
<s:HorizontalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
override public function set data( value:Object ) : void {
super.data = value;
}
protected function myTextInput_enterHandler(event:FlexEvent):void
{
trace(myTextInput.text);
What Next??
}
]]>
</fx:Script>
<components:ClearableTextInput text="{data.label}" id="myTextInput" enter="myTextInput_enterHandler(event)"/>
</s:ItemRenderer>
i'm not sure if I got your question correctly but would this help?
http://www.ajibanda.blogspot.com/2011/02/changing-currentstate-of-main-and.html
Instead of trying to access from MainApp to itemRenderer, i think you can do backward. Follow one of two solutions below:
In itemRenderer, assign value you want to check later to a public global variable on the MainApp. The limitation is you then only enable to check it on MainApp, not any where esle (other itemRenderer, component, module etc.)
Use EvenBus to put the value to a global container. Create a static eventBus instance in AppUtils, for example. In itemRenderer, AppUtils.eventBus.dispatch() an event with the value attached to it each time the value changed. And then use AppUtils.eventBus again to addEventListener() to retrieve the value and check wherever you want. Google AS3Commons for EventBus.

Event bubbling weirdness

I'm seeing some weirdness in event bubbling, which suggests I don't really understand how this is supposed to work.
I have a component which extends DataGroup, and it's item renderer dispatches an event (which bubbles).
// MyRenderer.mxml
<s:ItemRenderer>
<s:Button click='dispatchEvent(new Event('customEvent',true))' />
</s:ItemRenderer>
The DataGroup adds a listener for the event to itself.
// MyDataGroup.mxml
<s:DataGroup itemRenderer="MyRenderer" creationComplete='onCreationComplete()'>
<fx:Metadata>
[Event(name='customEvent',type='flash.events.Event')]
</fx:Metadata>
<fx:Script>
private function onCreationComplete():void
{
addEventListener('customEvent',onCustomEvent);
}
private function onCustomEvent(event:Event):void
{
}
</fx:Script>
</s:DataGroup>
The Parent of the datagroup is also adding a listener for the event.
// MyComponent.mxml
<s:Group>
<MyDataGroup customEvent='onCustomEventHandler()' />
</s:Group>
I'd have expected that the handler registered in MyDataGroup should catch the event first, then the handler in MyComponent.
However, I'm seeing the reverse - ie., caught in MyComponent, then in MyDataGroup. When caught, event.phase == EventPhase.BUBBLING.
What's going on here? Why am I seeing this behaviour?
I'm using Flex 4.0.
I'm pretty sure the problem is that both of your event listeners are listening to the same instance on the MyDataGroup component.
If you add the an event listener to MyComponent instead of MyDataGroup, you'll get expected behavior:
<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" width="400" height="300"
initialize="group1_initializeHandler(event)">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
public function onCustomEventHandler(event:Event):void{
trace('my Component handler');
}
protected function group1_initializeHandler(event:FlexEvent):void
{
// add the event listener to 'this'
this.addEventListener('customEvent',onCustomEventHandler);
}
]]>
</fx:Script>
<martyPitt:MyDataGroup id="dataGroup" />
<!-- The event listener here was listening on the myDataGroup instance, not on the MyComponent instance customEvent="onCustomEventHandler(event)" -->
</s:Group>
I suspect the event listeners--even though not in the same component--were firing based on the order they were added. You'd have to examine the generated ActionScript with the compiler argument '-keep' to figure that out specifically. I suspect your in-line listener (MyComponent) is added in the MyDataGroup constructor. Since the other listener is added in the MyDataGroup a creationComplete handler, the MyComponent listener fires first.
Strange behaviour indeed.
Actually, when you add an event listener inline (in your example :
<MyDataGroup
customEvent='onCustomEventHandler()'
/>
), the framework add an event listener on the capture phase. So it's an expected behaviour it enters in your parent handler first. What I don't undestand is that the eventPhase is equals to EventPhase.BUBBLE_PHASE although it should display EventPhase.CAPTURE_PHASE.

A question on capturing the button click event in ANOTHER mxml file

this seems to be an interesting question to be discovered in Flex.
I registered a very simple button event listener in A.mxml:
<mx:Script><![CDATA[
import mx.controls.Alert;
public function Handler():void
{
Alert.show('click event occured');
}
]]></mx:Script>
<mx:Button label="{resourceManager.getString('resources', 'button.startLab')}"
id="nextStepButton" click="Handler()" />
It works fine when clicking the button everytime.
Now I want to have something interesting,that is,I want to capture this buttonClick Event in another mxml file,say B.mxml and do something in B.mxml instead of A.
I am bit stuck on this,hope you could give me some hint and help,thanks a lot.
There are a number of approaches to this problem. The simplest (and least object-oriented) is to have A be aware of B, or vice versa. In that case you can just add a listener. In B you could say a.nextStepButton.addEventListener(MouseEvent.CLICK, myHandler), or in A you could do this.nextStepButton.addEventListener(MouseEvent.CLICK, b.myHandler). (When one component is instantiated, you have to set a reference to it on the other component.)
One step better would be to dispatch a custom event that bubbles, with one of the components still aware of the other. In B: a.addEventListener(CustomNavigationEvent.NEXT_CLICK, myHandler), or in A: b.addEventListener(CustomNavigationEvent.NEXT_CLICK, myHandler).
Taking it further, you could just let the event bubble to the top (the SystemManager) and add your listener to the SystemManager. This way B is not aware of A at all. In B: this.systemManager.addEventListener(CustomNavigationEvent.NEXT_CLICK, myHandler).
Taking it even further, you can implement your own version of an event broadcaster, which is just a third object that is accessible by any component, usually implemented as a singleton, that takes listener registrations and accepts event dispatches, then broadcasts that event to registered listeners.
Hope that helps.
EDIT: Here's some code for doing it the first way:
In A.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" creationComplete="onCreationComplete(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
public var b:B;
private function onCreationComplete(e:FlexEvent):void {
// Note that you have to have a public onClick handler in B
this.myButton.addEventListener(MouseEvent.CLICK, b.onClick);
}
]]>
</fx:Script>
<s:Button id="myButton"/>
</s:Group>
You need to make A aware of B in the container that declares instances of both A and B:
MXML:
<mypackage:A id="aComponent" b="bComponent"/>
<mypackage:B id="bComponent"/>
ActionScript equivalent:
var aComponent:A = new A();
var bComponent:B = new B();
aComponent.b = bComponent;

Resources