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!
Related
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;
I'm just getting started with custom events in a custom component. And I don't quite have the hang of it, yet.
I've got a component with a button in it. When it's clicked, I want to call a function in the main app.
Custom Component:
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" >
<mx:Metadata>
[Event(name="goClick", type="mx.events.Event")]
</mx:Metadata>
<mx:Script>
<![CDATA[
private function onButtonClickHandler(event:MouseEvent):void {
dispatchEvent(new Event("goClick"));
}
]]>
</mx:Script>
<mx:Button id="myGoButton" label="Go"
click="onButtonClickHandler(MouseEvent)" />
</mx:Panel>
Main App:
<myFolder:MyComponent
goClick="MyCoolFunction()">
Unfortunately, I'm doing something wrong. It says that the Event type is unavailable. What do I need to change or add? My guess is that I need to declare goClick in some way?
Thank you!
-Laxmidi
No such class as mx.events.Event unless you created one. Change that to flash.events.Event and you'll be good to go.
If you did create an 'mx.events.Event" class, make sure that your dispatch event is create an instance of your custom class and not the flash.events.Event.
i've got a pretty straightforward thing: a datagrid which renders some items. clicking on an item would bring up a popup editor (as the item has lots of properties and may not be edited right in the datagrid).
the popup contains just a form and a [Bindable] reference to the item it edits (which is passed from itemClick handler of the datagrid). form's default values are taken by binding to corresponding item properties with {} notion, while form values are bound back to the item using mx:Binding tags.
and now the problem. when the popup is brought up for the first time, everything is fine. however, when after being closed the popup is brought up again by clicking on the same item, the browser hangs (afaik because of change watchers being endlessly fired resulting in stackoverflow or something similar).
we have same behaviour in Safari, IE and Chrome, so i guess it's not to do with something browser-related. removing either [Bindable] from the item reference in the popup or mx:Binding tags from editors suppresses the problem, but of course the editing no longer works.
i'm banging my head against the wall for several days already, but still can't make it work. does it ring a bell to someone, what can be wrong in here (what can be damn easier that this)?
here's the code of the popup:
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" title="Details"
showCloseButton="true" close="PopUpManager.removePopUp(this);" creationComplete="PopUpManager.centerPopUp(this)">
<mx:Script>
<![CDATA[
import mx.managers.PopUpManager;
import my.Detail;
[Bindable] private var _documentDetail:Detail;
public function set documentDetail(value:Detail):void {
this._documentDetail = value;
}
public function set readOnly(value:Boolean):void {
if (value) {
this.currentState = "read-only";
}
}
]]>
</mx:Script>
<mx:states>
<mx:State name="read-only">
<mx:SetProperty target="{startDate}" name="enabled" value="false"/>
<mx:SetProperty target="{comments}" name="enabled" value="false"/>
</mx:State>
</mx:states>
<!--
<mx:Binding source="this.startDate.selectedDate" destination="_documentDetail.startDate"/>
<mx:Binding source="this.comments.text" destination="_documentDetail.comment"/>
-->
<mx:VBox width="100%" height="100%">
<mx:FormItem label="{resourceManager.getString('eRequestAppli','startdate')}:" labelWidth="160" width="100%">
<mx:DateField id="startDate" width="100%" selectedDate="{_documentDetail.startDate}" formatString="{resourceManager.getString('eRequestAppli', 'dateformat')}" editable="false"/>
</mx:FormItem>
<mx:FormItem label="{resourceManager.getString('eRequestAppli','comments')}:" labelWidth="160" width="100%" height="79">
<mx:TextArea id="comments" width="100%" height="100%" text="{_documentDetail.comment}" editable="false"/>
</mx:FormItem>
</mx:VBox>
</mx:TitleWindow>
here's how i call it:
private function show(detail:Detail, readOnly:Boolean=false):void {
var popup:fxc_ProposalDetail =
fxc_ProposalDetail(PopUpManager.createPopUp(UIComponent(Application.application), fxc_ProposalDetail, true));
popup.documentDetail = detail;
popup.readOnly = readOnly;
}
Thanks for posting the code. Now I might be able to help.
Where are you handling the close event of the popup? Besure to use something like this:
private function handleCloseEvent():void {
PopUpManager.removePopUp(this);
}
Besides that it appears your problem has to do with the following:
Because a module is loaded into a child domain, it owns class definitions that are not in the main application’s domain. For example, the first module to load the PopUpManager class becomes the owner of the PopUpManager class for the entire application because it registers the manager with the SingletonManager. If another module later tries to use the PopUpManager, Adobe ® Flash® Player throws an exception.
The solution is to ensure that managers such as PopUpManager and any other shared services are defined by the main application (or loaded late into the shell’s application domain). When you promote one of those classes to the shell, the class can then be used by all modules. Typically, this is done by adding the following to a script block:
import mx.managers.PopUpManager;
private var popUpManager:PopUpManager;
The module that first uses the component owns that component’s class definition in its domain. As a result, if another module tries to use a component that has already been used by another module, its definition will not match the existing definition. To avoid a mismatch of component definitions, create an instance of the component in the main application. The result is that the definition of the component is owned by the main application and can be used by modules in any child domain.
see: http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c0bf69084-799a.html
for a better understanding of modules.
as suggested before, reusing the popup instead of creating a new one each time seems to have solved the issue.
I need some help with sending info from a component. I'm not sure how to proceed.
I'm using Alex Uhlmann's flip card class (Distortion Effects). I've got a card that has 3 faces. When the user clicks the button, it fires a change event, and in the main application, the change event calls a function, flipTo, that flips the card. The component is below:
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel
xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
width="500"
height="400">
<mx:Metadata>
[Event("change", type="mx.events.Event")]
</mx:Metadata>
<mx:Script>
<![CDATA[
[Bindable]
public var backCaption:String;
]]>
</mx:Script>
<mx:Text id="myAnswer" htmlText="{backCaption}" width="100%" />
<mx:ControlBar height="40" width="100%" >
<mx:Button
x="20" y="400"
label="Flip"
click="dispatchEvent( new Event( Event.CHANGE ) );" />
</mx:ControlBar>
</mx:Panel>
The main application looks like:
<mx:Canvas id="homeStack" >
<mx:ViewStack id="flipViewStack2" x="200" y="150" >
<sides:FlipFace
id="frontFace2"
title="Newport"
change="flipTo(frontFace2, backFace2, DistortionConstants.LEFT, DistortionConstants.RIGHT);" />
<sides:FlipReverse
id="backFace2"
title="Newport: Answer"
change="flipTo(backFace2, anotherFace2, DistortionConstants.LEFT, DistortionConstants.LEFT);" />
<sides:FlipAnotherSide
id="anotherFace2"
title="Other Stuff"
change="flipTo(anotherFace2, frontFace2, DistortionConstants.RIGHT, DistortionConstants.LEFT);"/>
</mx:ViewStack>
</mx:Canvas>
<mx:Canvas id="OtherStack" >
(more code)
</mx:Canvas>
The flipTo function in the main application takes 4 parameters: the starting side, the ending side, and then two parameters that determine the direction of the flip.
Everything works great. If I hit the button, I can flip through all of the sides. But, I'd like to add a comboBox, so that the user can flip directly to the side that they want instead of having to cycle through all of the sides. (This is important as I plan to add more sides).
In the main application, please note that the sides have the number 2 in their ids. For example, frontFace2. I've got multiple sets of cards each with a different number, frontFace3, frontFace4, etc. The number determines which data is pulled from the database. (I've simplified the code for brevity).
How can I add a comboBox in the component that causes the card to flip to the selected side?
Do I need a custom event? (Unfortunately, I don't know anything about custom events). Is there a way to have the comboBox set a public variable and then somehow access that variable in the main application and call flipTo with the comboBox's chosen side? Other possibilities?
Any suggestions?
Thank you.
-Laxmidi
oh, yeah, you will need a custom event.
the custom event is your inheritance to the Event class which on it you can load more data, params, views and more.
in your case, things are a bit more simple but you will need to see what "face" the user selected and flip to it.
steps
1. create a custom event (inherit from event)
2. add a var to the event class that's called face
3. when the user selects the value from the combo, fill the face var with value and dispatch the event
4. listen to the event in the application level and then flipTo the relevant face.
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(..)