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;
Related
Is there a way to listen for this? I can easily listen for click which selects a RadioButton, but I can't seem to find a way to listen for when the RadioButton has been deselected.
Any ideas?
Thanks!
Back in the Flex 2 days, the "change" event triggered when a radio button was deselected. However, this little convenience disappeared in Flex 3, for some reason, and I don't believe that we were provided with any sort of replacement event.
Handling your events at the RadioButtonGroup level is all fine and well, except that there are times when you really want to handle the events on the radio button level -- particularly if you were hoping to interact with a data provider entry via an itemRenderer that is drawing the radio button.
Conveniently, I have a little bit of boilerplate code you can use as drop-in replacements for RadioButton and RadioButtonGroup that provide a "unselect" event at the radio button level. Here is the SmartRadioButton, first of all:
<?xml version="1.0" encoding="utf-8"?>
<s:RadioButton 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 events.SmartRadioButtonEvent;
public function notifyDeselect():void {
dispatchEvent(new SmartRadioButtonEvent('deselect'));
}
]]>
</fx:Script>
<fx:Metadata>
[Event(name="deselect", type="events.SmartRadioButtonEvent")]
</fx:Metadata>
</s:RadioButton>
And here is the SmartRadioButtonGroup:
<?xml version="1.0" encoding="utf-8"?>
<s:RadioButtonGroup xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
change="selectionChanged();"
>
<fx:Script>
<![CDATA[
import spark.components.RadioButton;
import components.SmartRadioButton;
protected var oldSelection:SmartRadioButton = null;
// Notify our old selection that it has been deselected, and update
// the reference to the new selection.
public function selectionChanged():void {
var newSelection:SmartRadioButton = this.selection as SmartRadioButton;
if (oldSelection == newSelection) return;
if (oldSelection != null) {
oldSelection.notifyDeselect();
}
oldSelection = newSelection;
}
// Override some properties to make sure that we update oldSelection correctly,
// in the event of a programmatic selection change.
override public function set selectedValue(value:Object):void {
super.selectedValue = value;
oldSelection = super.selection as SmartRadioButton;
}
override public function set selection(value:RadioButton):void {
super.selection = value;
oldSelection = super.selection as SmartRadioButton;
}
]]>
</fx:Script>
</s:RadioButtonGroup>
The two property overrides are in there to make sure that we correctly update the oldSelection, in the event of a programmatic change to the group's selection.
SmartRadioButtonEvent isn't anything fancy or important. It could probably just be a plain Event, since there isn't a special payload.
I have tested the above code, and it all works, but there are surely edge conditions, and other oddities that should be addressed, if it's used in a larger system.
I have this App.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">
<s:Button id="bt" label="click me"/>
</s:Application>
And this test case:
package flexUnitTests
{
import flexunit.framework.Assert;
public class AppTest
{
[Test]
public function testApp():void
{
var app:App = new App();
Assert.assertNotNull(app.bt);
}
}
}
But app.bt is null. I want to access the button :(
Short Answer:
The life cycle methods have not run on app; so no createChildren() method was executed in order to create the child component of bt.
Longer Answer:
Things get slightly more complicated with the main application file, as it there is no higher level Flex component in the display hierarchy. I'm unclear on all specifics, but..
I think the Flex Compiler does some magic to set up this component--and the Flex Framework--that help makes the whole app work. You are, in essence, bypassing that work by creating your own instance of the component.
I have a Spark List with a data provider consisting of a list of filled out form applications. What is the best way to add a button to each List Item (form application)? This button will be named Open and will navigate to the specified form application.
Thanks in advance for any advice!
This is similar to what #www.Flextras.com said, so I'm not going to repeat that. However, I'll add an example and one or two things.
Your custom ItemRenderer might look like this:
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Script>
<![CDATA[
import mx.events.ItemClickEvent;
private function requestForm():void {
var event:ItemClickEvent = new ItemClickEvent(ItemClickEvent.ITEM_CLICK);
event.index = itemIndex;
event.item = data;
owner.dispatchEvent(event);
}
]]>
</fx:Script>
<s:Label id="labelDisplay" verticalCenter="0" />
<s:Button right="0" label="open" verticalCenter="0" click="requestForm()" />
</s:ItemRenderer>
Two things that differ from Flextras' answer:
I use the built-in ItemClickEvent instead of a custom event > less
coding
I dispatch the event on the owner of the ItemRenderer, which
is in fact the List that contains this ItemRenderer. Because of this,
you don't need to bubble the Event.
Now to open the form when the Button is clicked, do something like this:
myList.addEventListener(ItemClickEvent.ITEM_CLICK, openForm);
private function openForm(event:ItemClickEvent):void {
trace("open " + event.item.toString());
}
Use a custom itemRenderer which displays the button along w/ your itemRenderer data (form application).
When the button is clicked; dispatch a custom event which bubbles. You may have to include some identifier for the form application this button click represents.
Listen to the event on the list class using the addEventListener() method. You can't use MXML since you'll be using a custom event undefined in the List's default metadata.
In your listener, perform the relevant UI changes to display your form application.
I'm working on a template for dynamic questionnaires in Flex. More detailed description in my previous thread HERE
To create the questionnaire, I use nested repeaters - one for the questions and one for the answers (as their amount may vary).
<mx:Repeater id="r" dataProvider="{questions}">
<mx:Label text="{r.currentItem.question}" width="200"/>
<mx:Repeater id="r2" dataProvider="{r.currentItem.answers}">
<mx:RadioButton label="{r2.currentItem.text}" width="200"
click="answerHandler(event, event.currentTarget.getRepeaterItem())"/>
</mx:Repeater>
</mx:Repeater>
To understand my data providers, it's probably best to check the answer for my previous thread - easier than if I try to explain it here.
The question here is... As you can see, I created click event handler for each radio button and my plan was to do playerScore++ every time the user chose correctly (which can be achieved by checking the Boolean property "correct" of sent RepeaterItem).
However, I see now that even if the button is selected already, I can still click on it more times, and even though it's not changing anything in the view, it increments the score every time.. I would also have to handle situation in which the user changes his mind (I could give + points for each good answer and - points for wrong, but this would mean, that if the user chose more wrong answers, my score will be negative and I don't want it).
So it would be way way easier to just have a Submit button and check the states of all my buttons and if they are correct only after the user clicks "submit". Is it possible?
I recommend you to refer to the following sample and add RadioButtonGroup to your repeater groups and listen to change events instead of click. You can listen change event right in RadioButtonGroup and check if (radioGroup.selectedValue.correct) for correctness of new selection. See corresponding documentation.
To have possibility to assign radiogroups unique name you have to extract inner repeater with answers into separate component. Breaking your application into smaller components can make your application more clear and readable. You can treat every MXML file as a class (as in OOP) but in declarative form. And to tell the true every MXML component is a class which inherited from root-node-class.
Lets look at the code.
First, our inner component which serves answers:
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Metadata>
[Event(name="rightAnswerEvent", type="AnswerEvent")]
</mx:Metadata>
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var answers:ArrayCollection;
protected function answerGroup_changeHandler(event:Event):void
{
if (answerGroup.selectedValue.correct)
dispatchEvent(new AnswerEvent(AnswerEvent.RIGHT_ANSWER_EVENT));
}
]]>
</mx:Script>
<mx:RadioButtonGroup change="answerGroup_changeHandler(event)" id="answerGroup" />
<mx:Repeater dataProvider="{answers}" id="answersRepeater">
<mx:RadioButton group="{answerGroup}" label="{answersRepeater.currentItem.text}"
value="{answersRepeater.currentItem}" />
</mx:Repeater>
</mx:VBox>
It gets answers collection as input and fires our custom event to inform some components about right answer (see Metadata tag).
Our custom event is pretty simple an looks like the following:
package
{
import flash.events.Event;
public class AnswerEvent extends Event
{
public static const RIGHT_ANSWER_EVENT:String = "rightAnswerEvent";
public function AnswerEvent(type:String)
{
super(type);
}
}
}
So now our top level component:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application layout="vertical" xmlns:local="*"
xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var questions:ArrayCollection = new ArrayCollection();
[Bindable]
private var scoreCounter:int;
]]>
</mx:Script>
<mx:Label text="{'Score ' + scoreCounter}" />
<mx:Repeater dataProvider="{questions}" id="questionRepeater">
<mx:Label text="{questionRepeater.currentItem.question}" />
<local:AnswerGroup answers="{questionRepeater.currentItem.answers}" rightAnswerEvent="scoreCounter++" />
</mx:Repeater>
</mx:Application>
I omitted initialization code to populate our Question and Answer domain objects with data from XML (see previous thread).
So now we have compact modularized code where every part solves its own task.
Hope this helps!
I have a Flex3 project with 2 HorizontalList controls; one of which has drag & drop enabled. Both controls will always have the same number of items, and are related... index 0 from the 1st control matches index 0 from the 2nd control, and so on.
Naturally, when using drag and drop to reorder the 2nd control, I want the items in the 1st control to reflect the same reordering. I think I can handle the actual updating of the control, but I'm having trouble finding the from and to indexes of the item that has been moved via drag & drop.
Using the dragDrop method on the control that allows drag and drop, and perusing the contents of event.currentTarget (instead of event.target because the docs say so) in the debugger shows a public property named selectedIndex which does seem to hold the from index, but I don't see any properties that will tell me the to index.
Am I overlooking the property? Do I have to infer it based on something else? Is there a better way to do this?
update: An acceptable solution might also be to iterate over the contents of the HorizontalList and save the internal index value to an array that could then be used to re-sort the dataProvider for the other list; but I can't figure out how to iterate over the list contents, so I'm just as stuck. Any help there?
Here's my code:
main.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:com="components.*"
layout="absolute"
>
<mx:Script>
<![CDATA[
import mx.events.DragEvent;
import mx.controls.Alert;
import mx.collections.ArrayCollection;
//these two variables are junk data to represent
//what we will get back from our data services...
[Bindable]
private var btnData:ArrayCollection = new ArrayCollection(
[
{title:'title1', index:0},
{title:'title2', index:1},
{title:'title3', index:2},
{title:'title4', index:3}
]
);
[Bindable]
private var chainData:ArrayCollection = new ArrayCollection(
[
{tmp:'testing 1'},
{tmp:'testing 2'},
{tmp:'testing 3'},
{tmp:'testing 4'}
]
);
//handle button re-ordering
private function handleBtnReorder(event:DragEvent):void{
Alert.show(event.action.toString());
}
]]>
</mx:Script>
<mx:HorizontalList
id="ChainView"
dataProvider="{chainData}"
itemRenderer="components.ItemRenderers.ChainLinkRenderer"
left="20"
right="20"
top="20"
height="300"
rowHeight="300"
columnWidth="500"
rollOverColor="#ffffff"
selectionColor="#eeeeee"
/>
<mx:HorizontalList
id="btnList"
dataProvider="{btnData}"
dragEnabled="true"
dropEnabled="true"
dragMoveEnabled="true"
dragDrop="handleBtnReorder(event)"
itemRenderer="components.ItemRenderers.BtnListRenderer"
horizontalCenter="true"
left="20"
right="20"
top="320"
height="50"
rowHeight="35"
rollOverColor="#ffffff"
selectionColor="#ffffff"
/>
</mx:Application>
The Alert.show(...) isn't there to show anything useful, I've set a breakpoint on that line so that I can inspect the event argument.
I don't think the custom itemRenderer's are important (there's not much code to them, yet), so I'll leave them out for brevity's sake. If you think they are important, let me know and I'll edit them in here.
It turns out the solution was to switch from using the dragDrop event to the dragComplete event; at which point, the binded dataProvider has been updated to reflect the reordering.
Thats why the Model Locator pattern is made for!
You must move your dataProvider to a singleton class (Model for instance) than your item renderers can easily access to it. There is no point that your item has the property Index since its already given by a [ArrayCollection].getItemIndex(..)