How does focus works in Flex? - apache-flex

I try to figure out how does focus mechanism work in Flex. Here comes the example of what I mean:
Let's assume that we have a simple web application, which contains custom component that extends Canvas and implements mx.managers.IFocusManagerComponent. This component overrides focusInHandler and focusOutHandler methods and shows some feedback on how they are called (thinner or thicker border). This custom component also contains some Text.
The source of the component is:
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100" height="100" creationComplete="cc();" implements="mx.managers.IFocusManagerComponent">
<mx:Script>
<![CDATA[
import mx.containers.Canvas;
import mx.controls.Text;
import mx.controls.TextArea;
import mx.core.UIComponent;
import mx.managers.IFocusManagerComponent;
public function cc():void
{
text = new Text;
text.text = "123";
addChild(text);
setStyle("backgroundColor", "0xddddff");
setStyle("borderColor", "0x000000");
setStyle("borderThickness", 1);
setStyle("borderStyle", "solid");
}
private var text:Text;
override protected function focusInHandler(e:FocusEvent):void {
trace("focusInHandler, currFocus: " + focusManager.getFocus());
setStyle("borderThickness", 4);
}
override protected function focusOutHandler(e:FocusEvent):void {
trace("focusOutHandler, currFocus: " + focusManager.getFocus());
setStyle("borderThickness", 1);
}
]]>
</mx:Script>
</mx:Canvas>
Here is the live version (with source view): http://rafalrybacki.com/lab/focus_question/. In the app there is also a TextArea below the Canvas - to ease the focus manipulation when testing.
The questions:
If you click once on a violet canvas it receives focus (focusInHandler is called), then if you click again the focus is lost (focusOutHandler called) - why?
Of you click on a Text the Canvas receives focus (focusInHandler called) and keeps it when being clicked wherever on the area (focusOutHandler nevet called) - why?
Maybe my understanding of the whole focus issue is wrong? Thank you for any suggestions.
With respect,
Rafal

Hey Rafalrybacki. [I'm in a meeting, and can't really spend time on the question but thought I could help with a pointer or two:]
First, the intent of a Canvas is to interact with FocusManager differently than a component that implements IFocusManagerComponent. Canvas implements IFocusManagerContainer and while you can accomplish what you're trying to accomplish by making the container an IFocusManagerComponent, I'd avoid it, simply because I try to do that which I think the flex sdk team intended when using internal components.
What I think they'd intend is for you to listen to the FocusEvent in your container. FocusEvents bubble by default, so you can accomplish most everything you need to with a simple event listener.
Note: Listening to a focusOut can get confusing with components that have multiple uicomponents as children --- i.e. combobox has a UITextField and a Button, so the component has multiple FocusOut and FocusIn events happening from whithin the same component. Your savior will be (I would guess) doing a container.contains() on a focusManger.getFocus() item (casting it etc.) to accurately set your style.
I just blab, so if you need some help beyond this, or would like to know more about why a focusIn or focusOut evt is being dispatched when it is being dispatched -- I'd be happy to slap some code together for you and explain why the type of event is being caught. Your best bet tho' (to fit within flex sdk guidelines) is to use an event listener from within the container, and not fight with a component that is both an IFocusManagerComponent AND an IFocusManagerContainer. Could get messy.
Hope that helps. Best of luck,
Jeremy

Related

How can i refer to an external function in an actionscript file - Flashbuilder

I hava a problem with flashbuilder:
I have a list with an itemrenderer that renders an image that (should be) draggable.
the rendered image refers to a function that is declared in an actionscript file: dragDrop.as in the folder AS.
the list:
<s:List id="imageList" width="139" height="438"
dataProvider="{xmlListColl}"
itemRenderer="itemRenderer.ImageRenderer"
dragEnabled="true">
</s:List>
the itemrenderer renders this image and refers to the function doDrag:
<mx:Image width="100" height="70" maintainAspectRatio="true"
MouseDownEffect="AS.dragDrop.doDrag(event)"
source="{data.#thumbnailImage}"/>
the function in dragDrop.as:
public function doDrag(event:MouseEvent):void
{
var img:mx.controls.Image = event.currentTarget as mx.controls.Image;
var dragImg:mx.controls.Image = new mx.controls.Image();
dragImg.source = img.source;
var dsource:DragSource = new DragSource();
dsource.addData(img, 'img');
DragManager.doDrag(img, dsource, event, dragImg);
}
but it seems the function is never called...
also parentdocument and outerdocument don't seem to work (if i put the function in the document where the itemrenderer is called)
Please Help!
There's a few issues here, but ultimately, you're not seeing a reference to that method, which means your dragDrop.as file is not including.
Here are a few suggestions:
Replace MouseDownEffect with mouseDown. Instead of causing an effect to occur, you're now listening for the "MouseEvent.MOUSE_DOWN" event to fire. Differences between effects and responding to events are described here and, to quote, "Behaviors let you add animation and motion to your application when some user or programmatic action occurs, where a behavior is a combination of a trigger paired with an effect. A trigger is an action, such as a mouse click on a component ... An effect is a visible or audible change to the target component that occurs over a period of time, measured in milliseconds."
Make sure you're including your dragDrop.as file. Flex 3 vs. Flex 4 handle script tags differently. If you're not including or importing your code, then of course it won't fire.
include vs import is a good question. You can "include" code that's a definition of methods, instances, constants, etc. But you would "import" defined classes for use. Since your method is a public function, does it live within a class? Or is it meant to just live in the script file, in which case you should remove the accessor "public"
If you're looking to implement Drag-and-Drop, I highly recommend NOT re-inventing the wheel and checking out what Adobe has already implemented for components, including dragEnabled='true' and dragMoveEnabled='true'. Check them out here: http://livedocs.adobe.com/flex/3/html/help.html?content=dragdrop_4.html
http://livedocs.adobe.com/flex/3/html/help.html?content=dragdrop_1.html
Here is an example of a Flex 3 script tag:
<mx:Script source="AS/dragDrop.as"/>
Here is an example of a Flex 4 script tag:
<fx:Script source="AS/dragDrop.as"/>
This is an link to the documentation on how to include directly into a <fx:Script> tag the code you'd like: http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c0bf61c8a-7ff4.html

Flex easy set buttonmode to all buttons

is there an easy way to set the buttonMode to true to all my buttons in my application?
I thought to create a custom component which extends the s:Button and set there buttonMode to true but I wonder if there is an easier way like with CSS.
Thanks in advance
As Wade said, the skin is a good way to go. While setting buttomMode="true" on the skin won't work the way you want, you can set the host button's buttonMode from the skin like so:
<fx:Script>
<![CDATA[
import spark.components.Button;
override protected function commitProperties():void
{
super.commitProperties();
hostComponent.buttonMode = true;
}
]]>
</fx:Script>
You may be able to do this with a skin and css. Copy the default ButtonSkin to a custom skin class and set buttonMode="true" on the skin. (If that doesn't work you can try to set hostComponent.buttonMode = true on creation complete.) Then set the skin for s:Button to your custom skin in your css.
You could either try writing a crawler which would walk through all your object at runtime, or something like this:
stage.addEventListener(Event.ADDED_TO_STAGE, parseItem, true);
and then:
function parseItem(e:Event):void{
if (e.target is Button)
e.target.buttonMode = true;
}
Each item which is added to the stage should go through this event (even if only its parent is added to the display list).
Of course you'll have to add this event listener before anything is added to the stage actually!
Unfortunately, I don't think you have any other option that creating a custom Button.
You could monkey patch the original Button component but I wouldn't recommand this pratice.
I would also suggest Maurycy's approach or you could just monkey patch the Button class in your project. That might even be easier and it would not cause a performance hit.

Instantiating a button using AS3 in Flex 4?

I used this code on Flex 3.5 SDK with Flash Builder 4 inside a function that is being called on creationComplete of the MXML app.
var myButton:Button = new Button;
myButton.label = "test";
addChild(myButton);
Alert.show("Button Created");
It works fine however, when I use it on the same Flash Builder 4 only this time, under Flex 4.0 SDK, nothing's happening. The Alert.show() isn't even showing which means it doesn't even get to that point.
So my question is, what's wrong? Am I missing something?
P.S.
I need to be able to create / remove MXML components on the fly (while the app is running). This is just a test script and I'm failing miserably at achieving what I need.
In Spark you need to use addElement instead of addChild
var b:Button = new Button();
addElement(b);
I'm not sure why your alert isn't working :\
Usually you create the UI in MXML, but in some cases you do need to create UI elements on the fly, and as the other poster mentioned, addElement() is the key with Flex 4 Spark containers.
When adding components to MX containers (from Flex 3), you still use addChild() in Flex 4. You only need to use addElement() when adding to Spark containers.
Of course, Adobe recommends you use the Spark containers when there is a somewhat comparable MX container.
Its better that you do it in the flex way.
<fx:Script>
<![CDATA[
import mx.controls.Alert;
private function alert():void
{
Alert.show("Button added to stage");
}
]]>
</fx:Script>
<s:Button id="myButton" label="test" addedToStage="alert()"/>
You can't add a button using mere addChild in flex. First you need to create an UIComponent and then add the button to the UIComponent. Its a bit different from the flash way.

Flex cancel a change event on a Tree

Brief details: I'm using Flex 3.5.
I have a Tree component that's used as a navigation menu between different 'pages'.
When the user clicks a certain option in the menu, I switch the 'page' by switching between State components in my application.
The thing is that when the user indeed clicks an option in the menu, I want to perform a validation of some of the information in a certain component. If the validation fails, I show an alert, and I'd like to prevent the navigation to the other page. One part of this is simply not changing the currentState of the document, but the tree component still goes on with the change event, and the result is page A still being shown on the screen, whereas the selected option in the tree is page B (to which the user wanted to navigate, but failed since some of the information wasn't valid).
I tried to figure out how I can cancel the change event on the tree component itself.
The thoughts I had didn't quite fit nicely:
I searched for a slightly different event (such as 'changing' or 'startChange') on which I can call the stopPropagation() method (since the regular 'change' event is not cancelable), but none exists for the Tree component.
I also thought about always saving the current option that's selected in the Tree component by myself, and when the validation fails, I will set the Tree's selectedItem to that saved option. That's also ugly because such an action will raise another change event on the Tree, thus another change to the States components, and another population of the page in which I'm already at. That's something I really don't want to do.
I also though about using a different component, such as Menu (and I also found an implementation of a vertical Menu), but that doesn't even seem to help. The same problem will exist there.
Is there a proper way to do this?
There must be a best-practice for preventing a change process to commit!
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.events.ListEvent;
private function tree_changeHandler(event:ListEvent):void
{
trace("Change, selectedItem.label is: " + tree.selectedItem.label);
}
protected function tree_itemClickHandler(event:ListEvent):void
{
var data:Object = event.itemRenderer.data;
if (!tree.isItemSelectable(data))
Alert.show("Item \"" + data.label + "\" is not selectable");
}
]]>
</mx:Script>
<local:MyTree id="tree" change="tree_changeHandler(event)" itemClick="tree_itemClickHandler(event)">
<local:dataProvider>
<mx:ArrayCollection>
<mx:Object label="Label 1"/>
<mx:Object label="Label 2"/>
<mx:Object label="Label 3 (non-selectable)"/>
<mx:Object label="Label 4"/>
</mx:ArrayCollection>
</local:dataProvider>
</local:MyTree>
</mx:Application>
Source for MyTree.as:
package
{
import mx.controls.Tree;
public class MyTree extends Tree
{
override public function isItemSelectable(data:Object):Boolean
{
if (!super.isItemSelectable(data))
return false;
var label:String = data.label;
if (label.indexOf("non-selectable") >= 0)
return false;
return true;
}
}
}
Eventually I found the place to put the code that determines each item's selectability: when the information that should be validated is changed, I perform the validation, and according to its result I set a property to all of the items in the Tree component, indicating whether they can be navigated to or not. If the validation was successful, the property is set to allow navigation, and if unsuccessful, it is set not to allow navigation.
Like Maxim, I extend the Tree component and overrode the isItemSelectable() method to check this property of the specified item, this way preventing the change process.
The access between the view that holds the information to-be-validated, and the view that holds the Tree component (they are not necessarily the same view) is done via a presentor class that holds both views (I use the MVP mechanism). This is not the most elegant design, but it is much better than anything else I could have thought of. The alleged problem with the design is the coupling between the views and the complexity of the presentor, that has to deal with more than one view and have methods that are related to the interaction between the views (instead of methods that represent actions of a specific view). The thing is that business-wise, the two views are coupled (since the information in one affects the navigation tree in the other), thus the presentor couples between them. The coupling is also done through the interface of the presentor, so that each view doesn't really "know" the other view.
I hope it might help other people.
Thanks,
Daniel

Moving children of a container (defined in MXML) inside an "inner container"

I'm currently working on a custom component which extends Canvas (let's call it SuperCanvas) ; it's basically a container that let you zoom & pan its contents.
It would be too long to explain why, but I can't use scrollRect, so I was forced to declare a Canvas object (called innerCanvas)... inside my SuperCanvas (I know, not very nice =/)
I would like to know if there's a proper way to "redirect" the creation of my component's children in this canvas.
Let me explain:
<comp:SuperCanvas id="superCanvas">
<mx:Image id="img" source="image.jpg"/>
<mx:Label id="lbl" text="Sample"/>
</comp:SuperCanvas>
With this, img and lbl are added to my SuperCanvas. I want them to be added to superCanvas.innerCanvas instead.
I can't override the add/removeChild methods to do the "redirection", since I won't be able to add this innerCanvas...
So I tried this :
<comp:SuperCanvas>
<comp:innerCanvas>
<mx:Image id="img" source="image.jpg"/>
<mx:Label id="lbl" text="Sample"/>
</comp:innerCanvas>
</comp:SuperCanvas>
But Flex complains that "In initializer for 'contents': type mx.controls.Image is not assignable to target type mx.containers.Canvas". I read I could use an array of UIComponents with a [ArrayElementType] metatag, and manually instanciate objects, but I I'm looking for a simplier (and probably proper) solution.
I also saw the childDescriptor property (which contains descriptions for every child defined in the MXML file), but it's read-only, so I can't pass it to my innerCanvas.
If I'm not clear enough, do not hesitate to ask me precisions, english isn't my native tongue, so it's pretty hard to explain things well =/
Any help would be greatly appreciated, I'm totally stuck.
EDIT:
My SuperCanvas class (minus the imports and the zoom & pan logic that doesn't matter here) :
public class SuperCanvas extends Canvas
{
public innerCanvas:Canvas = new Canvas();
public function SuperCanvas()
{
super();
addChild( innerCanvas );
}
}
This blog entry details an approach where you add components to the SuperCanvas, but then move them all to the inner canvas after creation. So that's one workaround.
Alternatively, you could set the DefaultProperty to be a dataProvider-type object, and then add things to the inner canvas from there, rather than making them children of the SuperCanvas first.
Addition:
I ran across this blog entry which, among other things, talks about the Panel component and how it handles this problem. You might look at it and at the Panel source code.

Resources