Flex : Adding a click handler on SkinnableDataContainer's items - apache-flex

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...

Related

Flex - Propagating event from a built-in component to a custom event

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.

Spark List with Buttons

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.

popup editor for datagrid items - hangs the browser

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.

detect change in Flex Form elements (textinput, textarea, combobox, checkbox..)

I have various (read lots of..) flex forms in my app, and I now want to create a system by which the user is notified that he hasn't saved if he modifies anything in the form...
now.. I don't want to rewrite every form I have.. is there some nice way to implement this?
Extending the TextInput (and others..) classes somehow?
thanks
This is not really thought through but should work.
You could create a custom component, let's call it FormWatcher which you would than put next to your Form. What the form watcher would do is wait for the CreationComplete event from the form.
So now, as we have the Form ready you can use the getChildren() method of the form to get all the FormItems in it. Than look inside each of them, and you will get TextInputs, Comboboxes, etc. to which you can add event listeners (as individual components), eg.
// THIS IS WHERE THE COMPONENT SHOULD START
protected function changeHandler(event:Event):void
{
trace ("something is dirty");
}
protected function startWatching(passTheFormHere:Form):void
{
for each (var O:Object in passTheFormHere.getChildren())
{
if (O is FormItem)
{
// Let's assume you only have a single child in one FormItem
// and always one child for simplicity
addChangeHandlerFor((O as FormItem).getChildAt(0));
}
}
}
protected function addChangeHandlerFor(someComponent:Object):void
{
// Most regular flex components send a Event.CHANGE event
// when their value is changing
// keep in mind you should check stuff, this is a simple example
(someComponent).addEventListener(Event.CHANGE,changeHandler);
}
// THIS IS WHERE THE COMPONENT SHOULD END
Just paste this code next to some form, and call the startWatching(nameOfYourForm), you should see the changeHandler is being called.
A few more notes:
1) You should clean up the event listeners once you're done.
2) I would create a component out of it so that you would use it like this:
<mx:Form id="form1" >
[...]
</mx:Form>
<FormWatcher form="{form1}" />
Where FormWatcher would have a Boolean var called "clean" or something.
3) The example is very simple, so it will only work for forms similiar to this one:
<mx:Form id="myForm" >
<mx:FormItem>
<mx:TextInput id="someComponent1" />
</mx:FormItem>
<mx:FormItem>
<mx:CheckBox id="someComponent2" />
</mx:FormItem>
<mx:FormItem>
<mx:TextArea id="someComponent3" />
</mx:FormItem>
</mx:Form>
You could go into the TextInput class (and others) and add that event listener and function, but then you would be changing the SDK itself, which is kind of a bad idea. I would create custom classes extending the ones your using and do a find/replace to make it faster.

In Flex, is there something like a 'this' reference for an MXML component?

I could code what I want to achieve like this:
<mx:Button id="someButton" click="doRememberButton(someButton)" ... />
but would find it very helpful (I am putting together a rather large UI) if I could write:
<mx:Button click="doRememberButton(this)" ... />
Now, the obvious problem is that 'this' does not point to the Button, but to the main component defined by the file the code is in (e.g. VBox), yet it would be a great help if I had some reference to the 'current' MXML component..
Would anybody have a solution for this? Thanks!
Tom
Inline event handlers is really just wrapped code, so you may use the event object to get details of the dispatcher and other event information. Like so:
<mx:Button click="trace(event.target)" />
In your case, you'd have to change the signature of your event handler, e.g.:
private function doRememberButton(event:Event):void
{
...
}
And in the MXML code:
<mx:Button click="doRememberButton(event)" />
The target property of the event class is the original dispatcher of the event. There is also a currentTarget property which is the current target in the event chain. This relates to event bubbling. There is more information on this in Adobe LiveDocs
private function doRememberButton(ev: Event) {
//this gives your button
ev.currentTarget;
}
here is a solution more precisely the way u needed
<mx:Button id="someButton" click="doRememberButton(event.currentTarget as Button)" />
at the function:
private function doRememberButton(thisBtn:Button):void
{
...
}
that's it! :)

Resources