Custom Event not bubbling (or not heard!) - actionscript-3

I've been having some trauma with an event being dispatched, but I can't detect it in the main app. I've knocked up an example of the code, removing redundant stuff, so it looks a little unusual!
I have a main app, (Air) which has a canvas with a single button. This single button opens a PopUp titlewindow which has a datagrid in it. On clicking the datagrid a really simple custom event is dispatched which is heard at the lowest level, but not calling canvas or main app. I assume its how i define the listener at the upper levels, but it's really driving me mad! Code Below:
mainApp.mxml..........
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
creationComplete="{onCreationComplete()}"
xmlns:components="components.*"
>
<mx:Script>
<![CDATA[
import components.lowestLevel;
private var lowest:lowestLevel;
private function onCreationComplete():void
{
lowest = new lowestLevel;
lowest.addEventListener('myEventType',mainAppListenerHandler);
}
private function mainAppListenerHandler(event:Event):void
{
trace("ive been heard in main app")
}
]]>
</mx:Script>
<components:middleLevel/>
</mx:WindowedApplication>
middleLevel.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
width="900"
height="50"
creationComplete="{ addEventListener('myEventType',listenerHandler);}"
>
<mx:Script>
<![CDATA[
import mx.core.Application;
import mx.managers.PopUpManager;
import actionScript.sql;
private function launchViewAllLoads(event:MouseEvent):void
{
sql.getSourceFilesToView()
var win:lowestLevel = new lowestLevel();
PopUpManager.addPopUp(win,parent,true);
PopUpManager.centerPopUp(win);
}
private function listenerHandler(event:Event):void
{
trace("im heard at the Middle level" )
}
]]>
</mx:Script>
<mx:Button id="btnViewAllLoads"
label="View Current"
click="{launchViewAllLoads(event)};"
/>
</mx:Canvas>
lowestLevel.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical" width="800" height="550"
title="View All Loads" horizontalAlign="center"
creationComplete="{ addEventListener('myEventType',listenerHandler);}"
>
<mx:Script>
<![CDATA[
import mx.core.IFlexDisplayObject;
import mx.events.CloseEvent;
import mx.managers.PopUpManager;
private function listenerHandler(event:Event):void
{
trace("im heard at the lowest level" )
}
private function sourceFilesToViewClickedHandler():void
{
PopUpManager.removePopUp(this as IFlexDisplayObject);
var myEvent:Event = new Event('myEventType' ,true,true);
dispatchEvent(myEvent);
}
]]>
</mx:Script>
<mx:Canvas id="cSourceFiles" width = "100%" height="100%">
<mx:DataGrid id="dgSourceFiles"
click="{sourceFilesToViewClickedHandler()}">
<mx:columns>
<mx:DataGridColumn dataField="Batch" headerText="Batch" width="80"/>
<mx:DataGridColumn dataField="LastUpdated" headerText="Last Updated" width="85"/>
</mx:columns>
</mx:DataGrid>
</mx:Canvas>
</mx:TitleWindow>

I see some issues here. First is this in your mainApp.mxml:
private function onCreationComplete():void
{
lowest = new lowestLevel;
lowest.addEventListener('myEventType',mainAppListenerHandler);
}
You are creating an instance of lowestLevel and adding an event listener to this. But, this is not the same instance created in middleLevel. If you have two independent instances of a component; dispatching an event in one instance will not cause event listeners on the other instance to fire.
Since your mainApp's lowestLevel instance is never added to the stage in any manner; the user can never interact with it firing off the event.
You can try this:
private function onCreationComplete():void
{
this.addEventListener('myEventType',mainAppListenerHandler);
}
This will add the event listener on the mainApp file. As the event bubbles; the event is dispatched on every class in the display hierarchy. So, this should work.
The second issue I see is that the PopUpManager creates its windows as a child of the SystemManager component; which is independent of the current display hierarchy. To quote the docs:
all popups are parented by the SystemManager.
So, dispatching an event from the lowestLevel will not bubble to middleLevel or mainApp because it is not actually the "lowest level". It is almost as it exists in a parallel hierarchy.
The solution would be to listen for the event directly on the lowestLevel instance in middleLevel and redispatch it.

What if you redispatch the same event:
private function listenerHandler(event:Event):void {
trace("im heard at the Middle level" )
dispatchEvent(event);
}
By using a callback:
var callback = function():void {
// do some stuff here
}
lowest = new lowestLevel(callback);
And later, instead of dispatching the event:
private var _callback:Function;
public function lowestLevel(callback:Function):void {
_callback = callback;
}
private function sourceFilesToViewClickedHandler():void {
PopUpManager.removePopUp(this as IFlexDisplayObject);
_callback();
}

Related

Spark.Components.List with variable content: Flex 4, AS3

I coded the following in Flex 4/AS3 and it doesn't worked as expected.
So, I would like to know if there is something wrong with my code... I'll explain it:
TileTest.mxml
<s:Application minWidth="955" minHeight="600" creationComplete="createButtons()"
<fx:Script>
<![CDATA[
import Object.TestButton;
import mx.collections.ArrayList;
private var _names:Array = ["one", "two", "three"];
public function createButtons():void
{
var buttons:ArrayList = new ArrayList();
for(var a:int = 0; a < _names.length; a++)
{
var testButton:TestButton = new TestButton();
testButton.customName = _names[a];
buttons.addItem(testButton);
}
myList.dataProvider = buttons;
}
]]>
</fx:Script>
<s:VGroup gap="12" width="100%">
<s:Label text="Options" fontSize="18" fontWeight="bold" color="#333333" />
<s:List width="100%" height="100%" id="myList" itemRenderer="Object.TestButton" borderVisible="false">
<s:layout>
<s:TileLayout orientation="rows" columnWidth="290" rowHeight="90" columnAlign="left" horizontalGap="0" verticalGap="0" />
</s:layout>
</s:List>
</s:VGroup>
</s:Application>
This is my Application. Here, I have a List called myList, where I load some TestButtons. I set a name for each button inside the loop.
TestButton
<s:Group width="300" height="90" click="{ Alert.show(_name); }"
<fx:Script>
<![CDATA[
import mx.controls.Alert;
private var _name:String = "just a test...";
public function set customName(newName:String):void
{
_name = newName;
Alert.show(_name);
this.addEventListener(MouseEvent.MOUSE_OVER, function():void{ Alert.show(_name); });
}
]]>
</fx:Script>
<s:BorderContainer accentColor="#000000" width="100%" height="100%" />
</s:Group>
As you can see, I have three Alerts in this component... I did so we can understand the problem.
The first Alert occurs when I set the customName, which occurs in the Application, as already shown.
The second one should occur on Mouse_Over, as the event listener been added to the Group element.
And the third Alert should occur on Click in the Group element.
Now, if I run the resulting swf, I see all the three buttons in the screen and three Alerts, one for each set customName by the Application and it alerts the right name.
If I put the mouse over any button, it doesn't alert any message.
If I click any button, it alerts the default message set to the _name property, which is "just a test..."
I can't understand what is the problem, as I was expecting it to always alert the name set by the Application to each button. Also, I don't want to change the components... what I'm saying is that I would like to have a List and TestButtons with a private String inside.
If the problem is in these specific implementation, so I'll have no other way than change it...
Thank you all!
The problem is that you have a list of TestButtons in your data provider, instead of just plain data. Those buttons get created, and that is why you see the correct Alert messages.
So, then Flex sees a list with three items and therefore it creates three additional TestButtons to render the data in your list. But those TestButtons are completely new ones, with default values for its properties.
To fix this, it would be better if you had data only in your data provider ArrayList, and you would access it through the data property of your item renderer.

"Flexicious" : not able to change the search functionality dynamically in a data Grid

"Flexicious" a third party component library built for handling very larg data set in DataGrid for flex, The issue is, i am not able to change the search functionality dynamically in a data Grid.
<flxs:FlexDataGridColumn id="multiselect" dataField="Name" headerText="Name"
filterControl="NumericTextInput" headerAlign="center">
<flxs:headerRenderer>
<fx:Component>
<controls:ComboBox change="changeSel(event)" width="10" height="41" dataProvider="outerDocument.searchArray}">
<fx:Script>
<![CDATA[
import com.flexicious.controls.ComboBox;
import mx.controls.Alert;
public function changeSel(event:Event):void{
var cbox:ComboBox = event.currentTarget as ComboBox;
if(cbox.selectedItem=="Less Than"){
outerDocument.multiselect.filterOperation="LessThanEquals";
//Alert.show(""+outerDocument.multiselect.filterOperation);
}else if(cbox.selectedItem=="Greator Than"){
outerDocument.multiselect.filterOperation="GreaterThanEquals";
//Alert.show(""+outerDocument.multiselect.filterOperation);
}else if(cbox.selectedItem=="Equal To"){
outerDocument.multiselect.filterOperation="Equals";
//Alert.show(""+outerDocument.multiselect.filterOperation);
}else if(cbox.selectedItem=="Begins With"){
outerDocument.multiselect.filterOperation="BeginsWith";
//Alert.show(""+outerDocument.multiselect.filterOperation);
}
}
]]>
</fx:Script>
</controls:ComboBox>
</fx:Component>
</flxs:headerRenderer>
</flxs:FlexDataGridColumn>
Now when i select any option from the rendered combobox i am not able to change filteroption, however when i alter the filteroperation it dose show me the changed operatioin but in functionality it doesn't change.
You should call grid.rebuildFilter() after changing the filterOperation

View Navigator Hide Specific Tab Not TabBar

I'm trying to figure out whether it is possible to define view navigators and selectively hide some depending on a particular user state?
For example I have two navigator tabs one which is a sign in tab and the other shows a users policy. I only want the policy tab to be visible if the user has signed in:
<s:ViewNavigator id="policyTab" width="100%" height="100%" firstView="views.policy.PoliciesView">
<s:navigationContent>
<s:Button id="policyTabButton" label="Policies" click="tabButton_clickHandler(event)" />
</s:navigationContent>
</s:ViewNavigator>
Sign in tab is navigator:
<s:ViewNavigator id="signInTab" width="100%" height="100%" firstView="views.SignInView">
<s:navigationContent>
<s:Button id="signInTabButton" icon="#Embed('images/lockSmall.png')" click="tabButton_clickHandler(event)" />
</s:navigationContent>
</s:ViewNavigator>
Everything that I've researched points me to hiding the entire tab bar which I don't want to do. I've tried simply calling signInTab.visible = false; but is doesn't work.
Any help would be appreciated.
It is true that you can't hide the contents of a TabbedViewNavigator, but there is another way to adjust the content to hide tabs. Basically you can remove the tab from the TabbedViewNavigator to hide it and re-add it to show it again. I've come up with a very simple example which seems to do what you are asking.
TabbedViewNavTest.mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:TabbedViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
applicationDPI="160"
preinitialize="preinitializeHandler(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
import views.Tab2View;
private static var app:TabbedViewNavigatorApplication;
public static function adjustTabBar():void {
if(app.navigators.length > 1) {
removeTab2();
} else {
addTab2();
}
}
private static function removeTab2():void {
app.tabbedNavigator.removeItemAt(1);
}
private static function addTab2():void {
var tab2:ViewNavigator = new ViewNavigator();
tab2.label = "Tab2";
tab2.percentWidth = 100;
tab2.percentHeight = 100;
tab2.firstView = Tab2View;
app.tabbedNavigator.addItemAt(tab2, 1);
}
protected function preinitializeHandler(event:FlexEvent):void {
app = this;
}
]]>
</fx:Script>
<s:ViewNavigator id="tab1" label="Tab1" width="100%" height="100%" firstView="views.Tab1View"/>
<s:ViewNavigator id="tab2" label="Tab2" width="100%" height="100%" firstView="views.Tab2View"/>
</s:TabbedViewNavigatorApplication>
Tab1View.mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
title="Tab1">
<fx:Script>
<![CDATA[
protected function showHideButton_clickHandler(event:MouseEvent):void {
TabbedViewNavTest.adjustTabBar();
}
]]>
</fx:Script>
<s:Button id="showHideButton" label="Click Me!" click="showHideButton_clickHandler(event)" />
</s:View>
Tab2View is just an empty view that was created when I created the project.
While this should do what you need it to do, I'm wondering if there is a better way to achieve what you are attempting to do. For instance, in the case you originally presented of a login tab which disappears when the user logs in you could have created your application as a generic application with 2 states: notLoggedIn and loggedIn. In the notLoggedIn state you only have a view show that presents the login screen, or have a tabbedViewNavigator show which has the login and policy tabs. In the logged in state, you have a separate tabbedViewNavigator which has only the policy tab or perhaps the other tabs available when a user is logged in. If you want me to create an example of what I mean, let me know and I can do that.

Make a tab blink in Spark (Flex)

In a Flex 4 app (with Spark components) I have a ViewStack with various screens, and a TabBar to navigate between them. I'd like the screens to be able to "blink" their tab when something happens in them (like Windows task bar buttons).
How can I do this? My idea is to hack the blinking state into the screen's label (inherited from NavigatorContent) by putting a * in it when blinking, and somehow reading that in a custom tab bar skin.
Is there an easier way? If now, how exactly can I implement mine?
This is a bit hard to explain since it isn't the easiest thing to do, but I'll give it my best. I would create a <s:TabBar /> with a dataProvider of an array of all views in your viewstack and create a custom item renderer for your TabBar which then contains a custom component that extends ButtonBarButton that has a blinking property that's 2-way binded and a custom skin to actually show it blinking, like this:
(man that was a mouthful)
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:local="*">
<fx:Script>
<![CDATA[
import mx.collections.ArrayList;
]]>
</fx:Script>
<s:TabBar dataProvider="{new ArrayList([view1,view2])}">
<s:itemRenderer>
<fx:Component>
<local:BlinkingTab label="{data.label}" blink="#{data.isBlinking}" skinClass="BlinkingTabSkin" />
</fx:Component>
</s:itemRenderer>
</s:TabBar>
<mx:ViewStack>
<local:Foo id="view1" label="View 1" />
<local:Foo id="view2" label="View 2" />
</mx:ViewStack>
</s:Application>
In this case, my views extends 'NavigatorContent', however, we need to be able to express a boolean flag to say that the tab needs to blink, like so:
<s:NavigatorContent xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
implements="ITabView">
<fx:Script>
<![CDATA[
private var _blink:Boolean = false;
[Bindable]
public function get isBlinking():Boolean
{
return this._blink;
}
public function set isBlinking(value:Boolean):void
{
this._blink = value;
}
]]>
</fx:Script>
</s:NavigatorContent>
You'll notice that the view is implementing ITabView. That's only there for typesafing the 'isBlinking' property, but it's optional. When you want your tab to blink, you just need to set this to 'true'. But now we need to get the tab to actually blink. In the custom component 'BlinkingTab' we created for the TabBar, we need to take in the blink property and change the skin state appropriately like so:
<s:ButtonBarButton 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 _blink:Boolean;
[Bindable]
public function get blink():Boolean
{
return this._blink;
}
public function set blink(value:Boolean):void
{
this._blink = value;
}
override protected function getCurrentSkinState():String
{
if(!selected && enabled && this._blink)
{
return super.getCurrentSkinState()+'Blinking';
}else{
return super.getCurrentSkinState();
}
}
override protected function mouseEventHandler(event:Event):void
{
super.mouseEventHandler(event);
if(event.type == MouseEvent.CLICK)
{
blink = false;
}
}
]]>
</fx:Script>
</s:ButtonBarButton>
You'll notice that the skin state will only have the 'blinking' string on it if it's enabled and not selected. If it is selected, it won't blink; and if the user clicks on the tab, it will remove the blinking flag which should propagate back to the view (I'm not certain about this part, could always override the 'selected' property or something). And the last part is the skin; you need to create a custom skin so that you can add a blinking animation to your tab. Just create a new skin with a ButtonBarButton host component that uses the TabBarButtonSkin and add these new states:
<s:State name="upBlinking" basedOn="up" stateGroups="blinking" />
<s:State name="overBlinking" basedOn="over" stateGroups="blinking" />
<s:State name="downBlinking" basedOn="down" stateGroups="blinking" />
From here, you need to create your own state based blinking. This is not fully tested, but I think I helped you get 95% of the way. Hope this helps.
BTW, this method is 100% legit. No hacking and you can reuse every single part of the code for somewhere else :)

I call Alert.Show in a function and want to get the result from there (Flex, ActionScript)

I'm using the Alert.show in a function and I want to get the user answer from there. So how can I achieve this. The problem is the function that call Alert.show will return a true or false value depend the user answer.
but It seem that in Alert.show it only allow to pass in a CloseHandler for this. that is a new function. and since that I can get the user answer from where it is call to return the user answer.
Really thanks for help
Yuan
Try this code
<?xml version="1.0" encoding="utf-8"?>
<!-- Simple example to demonstrate the Halo Alert control. -->
<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">
<fx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.events.CloseEvent;
// Event handler function uses a static method to show
// a pop-up window with the title, message, and requested buttons.
private function clickHandler(evt:Event):void {
Alert.show("Do you want to save your changes?", "Save Changes", Alert.YES|Alert.NO, this, alertClickHandler);
}
// Event handler function for displaying the selected Alert button.
private function alertClickHandler(evt:CloseEvent):void {
if (evt.detail == Alert.YES) {
status.text = "You answered Yes";
} else {
status.text = "You answered No";
}
}
// Event handler function changes the default Button labels and sets the
// Button widths. If you later use an Alert with the default Buttons,
// you must reset these values.
private function secondClickHandler(evt:Event):void {
Alert.buttonWidth = 100;
Alert.yesLabel = "Magenta";
Alert.noLabel = "Blue";
Alert.cancelLabel = "Green";
Alert.show("Select a color:", "Color Selection", Alert.YES|Alert.NO|Alert.CANCEL, this);
// Set the labels back to normal:
Alert.yesLabel = "Yes";
Alert.noLabel = "No";
}
]]>
</fx:Script>
<s:Panel title="Halo Alert Control Example"
width="75%"
horizontalCenter="0" verticalCenter="0">
<s:VGroup left="10" right="10" top="10" bottom="10">
<s:Label color="blue"
text="Click the button below to display a simple Alert window."/>
<s:Button label="Click Me" click="Alert.show('Hello World!', 'Message');"/>
<mx:HRule width="100%" />
<s:Label color="blue"
text="Click the button below to display an Alert window and capture the button pressed by the user."/>
<s:Button label="Click Me" click="clickHandler(event);"/>
<s:Label id="status" fontWeight="bold"/>
<mx:HRule width="100%" />
<s:Label color="blue"
text="Click the button below to display an Alert window that uses custom Button labels."/>
<s:Button label="Click Me" click="secondClickHandler(event);"/>
</s:VGroup>
</s:Panel>
</s:Application>
You can do this.
here's how:
private function deleteItem_Confirmation_Handler(event:CloseEvent):void
{
if(event.detail == Alert.OK)
{
//Your code here
}
}
public function deleteValue():void
{
Alert.show("Are you sure you want to delete this item?", "confimation", Alert.OK | Alert.CANCEL, null, deleteItem_Confirmation_Handler, null, Alert.OK);
}
change
evt.detail == Alert.YES
to
evt.detail == 1
the yes button returns an int value of 1, the no button would return an int value of 2