I am trying to understand event propagation in Flex framework and have a following doubt on it:
Scenario: I have a built-in component DropDownList on change of which I want to create a custom event refreshPreview and want to propagate it to a custom component PictureComponent.
In the custom component's mxml, I have added the Metadata directive as follows to register a refreshPreview event with the compiler
<fx:Metadata>
[Event(name="refreshPreview", type="flash.events.Event")]
</fx:Metadata>
Layout Details: In my main mxml application I have laid out DropDownList and the custom component such that both are siblings (i.e. have a common indirect parent)
e.g.
<s:Group id="contentGroup">
<s:Group id="formGroup">
<s:Form x="11"
y="86">
<s:FormItem label="Employee:">
<!-- Built-in component -->
<s:DropDownList id="dropDownList"
dataProvider="{employeesCollection}"
labelField="LASTNAME">
</s:DropDownList>
</s:FormItem>
</s:Form>
</s:Group>
<s:Group id="pictureGroup">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<!-- Custom Component -->
<p:PictureComponent x="360"
y="2"
employee="{new Employee(dropDownList.selectedItem.ID,firstName.text, lastName.text)}"
refreshPreview="picturecomponent1_refreshPreviewHandler(event)">
</p:PictureComponent>
</s:Group>
</s:Group>
Also I have added event listeners on the built-in component in the init() method called on creationComplete event:
// Initializes this component
private function init():void
{
//add event listeners for dropDownList
dropDownList.addEventListener(IndexChangeEvent.CHANGE, employeeChangeEventHandler);
}
And here is the event handler for handling the built-in component event and preparing the custom event and dispatching it:
private function employeeChangeEventHandler(event:IndexChangeEvent):void
{
var eventObject:Event=new Event("refreshPreview");
dispatchEvent(eventObject);
}
I am seeing that the refreshPreview event is not getting propagated to the custom component.
I doubt this is because the built-in component is a sibling of the target (where the event got generated) and not a parent. If that's the reason could you please help me know how to make refreshPreview event propagate to custom component?
Inside your DropDownList MXML, add this:
<s:change>
dispatchEvent(new Event('refreshPreview', true));//true allows it to bubble out of your View
</s:change>
Note that a Flash event with a string that is not one of the Event Class constants is not a custom Event. It is a Flash Event with a different type parameter. A custom Event would be writing your own Event Class, which you do not need to do.
Note that shaunhusain is correct...the way you have this structured, PictureComponent is not going to respond to an Event in the parent View, but in itself. I wouldn't get in the habit of dispatching events on sibling Views. Instead, I'd have the parent View set some property on the PictureComponent or bind a property in PictureComponent to the selected item in dropDownList.
Related
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 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.
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 am new to Flex.
What I am looking for here is adding a click handler on all the items created by a SkinnableDataContainer. I tried several things that didn't work, and I have no idea what is the right way to do it.
<s:SkinnableDataContainer id="teamList"
itemRenderer="TeamSummaryRenderer">
<s:dataProvider>
<s:ArrayList>
<fx:Object teamName="A super team 1"/>
<fx:Object teamName="A super team 2"/>
<fx:Object teamName="A super team 3"/>
</s:ArrayList>
</s:dataProvider>
</s:SkinnableDataContainer>
Furthermore, I don't want to declare the handler in my custom TeamSummaryRenderer component. I would prefer that the handler code stays at application level.
Is there a simple 'Flex-ish' to achieve this ?
No.
<s:SkinnableDataContainer
Properties
autoLayout="true"
clipAndEnableScrolling="false"
dataProvider="null"
horizontalScrollPosition="null"
itemRenderer="null"
itemRendererFunction="null"
layout="VerticalLayout"
typicalItem="null"
verticalScrollPosition="null"
Events
rendererAdd="No default"
rendererRemove="No default"
/>
http://opensource.adobe.com/wiki/display/flexsdk/Spark+SkinnableDataContainer
I think you have to keep your handler in the itemRenderer as the document says. They don't have any properties to achieve it directly.
Ok ... I found the answer myself :
<s:SkinnableDataContainer
rendererAdd="my_handler(event)"/>
private function my_handler(event:RendererExistenceEvent):void{
event.renderer.addEventListener(flash.events.MouseEvent.CLICK, clickhandler);
}
The rendererAdd event is triggered every time a new renderer is added to the container, and it has a property renderer which is the renderer object itself. So here is the place for adding a click handler on every one of the renderers that are created.
You could also subclass the SkinnableDataContainer and handle all the renderer-listening there. Then, when your eventhandler is triggered, your custom SkinnableDataContainer will dispatch a change event or some other event. Next, in your application, you set a listener on that specific event and there you go...
Let me start off by saying that I'm pretty new to flex and action script. Anyways, I'm trying to create a custom event to take a selected employee and populate a form. The event is being dispatched (the dispatchEvent(event) is returning true) but the breakpoints set inside of the handler are never being hit.
Here is some snippets of my code (Logically Arranged for better understanding):
Employee Form Custom Component
<mx:Metadata>
[Event(name="selectEmployeeEvent", type="events.EmployeeEvent")]
<mx:Metadata>
<mx:Script>
[Bindable]
public var selectedEmployee:Employee;
</mx:Script>
...Form Fields
Application
<mx:DataGrid id="grdEmployees" width="160" height="268"
itemClick="EmployeeClicked(event);">
<custom:EmployeeForm
selectEmployeeEvent="SelectEmployeeEventHandler(event)"
selectedEmployee="{selectedEmployee}" />
<mx:Script>
[Bindable]
private var selectedEmployee:Employee;
private function EmployeeClicked(event:ListEvent):void
{
var employeeData:Employee;
employeeData = event.itemRenderer.data as Employee;
var employeeEventObject:EmployeeEvent =
new EmployeeEvent( "selectEmployeeEvent", employeeData);
var test:Boolean = dispatchEvent(employeeEventObject);
//test == true in debugger
}
private function SelectEmployeeEventHandler(event:EmployeeEvent):void
{
selectedEmployee = event.Employee; //This event never fires
}
</mx:Script>
There are a few things conspiring to cause trouble here. :)
First, this declaration in your custom component:
<mx:Metadata>
[Event(name="selectEmployeeEvent", type="events.EmployeeEvent")]
<mx:Metadata>
... announces to the compiler that your custom component dispatches this particular event, but you're actually dispatching it with your containing document, rather than your custom component -- so your event listener never gets called, because there's nothing set up to call it.
Another thing is that, in list-item-selection situations like this one, the more typical behavior is to extract the object from the selectedItem property of the currentTarget of the ListEvent (i.e., your grid -- and currentTarget rather than target, because of the way event bubbling works). In your case, the selectedItem property of the grid should contain an Employee object (assuming your grid's dataProvider is an ArrayCollection of Employee objects), so you'd reference it directly this way:
private function employeeClicked(event:ListEvent):void
{
var employee:Employee = event.currentTarget.selectedItem as Employee;
dispatchEvent(new EmployeeEvent("selectEmployeeEvent"), employee);
}
Using the itemRenderer as a source of data is sort of notoriously unreliable, since item renderers are visual elements that get reused by the framework in ways you can't always predict. It's much safer, instead, to pull the item from the event directly as I've demonstrated above -- in which case you wouldn't need an "employeeData" object at all, since you'd already have the Employee object.
Perhaps, though, it might be better to suggest an approach that's more in line with the way the framework operates -- one in which no event dispatching is necessary because of the data-binding features you get out-of-the-box with the framework. For example, in your containing document:
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
[Bindable]
private var selectedEmployee:Employee;
private function dgEmployees_itemClick(event:ListEvent):void
{
selectedEmployee = event.currentTarget.selectedItem as Employee;
}
]]>
</mx:Script>
<mx:DataGrid id="dgEmployees" dataProvider="{someArrayCollectionOfEmployees}" itemClick="dgEmployees_itemClick(event)" />
<custom:EmployeeForm selectedEmployee="{selectedEmployee}" />
... you'd define a selectedEmployee member, marked as Bindable, which you set in the DataGrid's itemClick handler. That way, the binding expression you've specified on the EmployeeForm will make sure the form always has a reference to the selected employee. Then, in the form itself:
<mx:Script>
<![CDATA[
[Bindable]
[Inspectable]
public var selectedEmployee:Employee;
]]>
</mx:Script>
<mx:Form>
<mx:FormItem label="Name">
<mx:TextInput text="{selectedEmployee.name}" />
</mx:FormItem>
</mx:Form>
... you simply accept the selected employee (which is marked bindable again, to ensure the form fields always have the most recent information about that selected employee).
Does this make sense? I might be oversimplifying given that I've no familiarity with the requirements of your application, but it's such a common scenario that I figured I'd throw an alternative out there just for illustration, to give you a better understanding of the conventional way of handling it.
If for some reason you do need to dispatch a custom event, though, in addition to simply setting the selected item -- e.g., if there are other components listening for that event -- then the dispatchEvent call you've specified should be fine, so long as you understand it's the containing document that's dispatching the event, so anyone wishing to be notified of it would need to attach a specific listener to that document:
yourContainingDoc.addEventListener("selectEmployeeEvent", yourOtherComponentsSelectionHandler);
Hope it helps -- feel free to post comments and I'll keep an eye out.
You're dispatching the event from the class in the lower code sample (DataGrid, I guess), but the event is defined in the EmployeeForm class, and the event handler is likewise attached to the EmployeeForm instance. To the best of my knowledge, events propagate up in the hierarchy, not down, so when you fire the event from the form's parent, it will never trigger handlers in the form itself. Either fire the event from the form or attach the event handler to the parent component.