Flex cancel a change event on a Tree - apache-flex

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

Related

How do I access a public function outside of the view it is in using Flex?

Hi, I have been working on a Flex Mobile application using Flash Builder 4.6.
I have 2 mxml 'views' in my project. In one mxml file, i have a function that grabs xml data.
In my other mxml file, I have a refresh button that when depressed is suppsosed to call the function in the first mxml file in order to once again grab the xml data.
I dont know how to call that function from outside the mxml file it is housed in.
I appreciate any help given. Thank you!
[UPDATE #2]*
I thought I should share some more details about my issue.
It is a reddit client mobile app. It fetches the feeds, etc.
In my main view called RedditReaderHomeView.mxml, I am using a splitViewNavigator spark component to house two other views like so:
RedditReaderHomeView.mxml
<s:SplitViewNavigator width="100%" height="100%" id="splitViewNavigator" autoHideFirstViewNavigator="true">
<s:ViewNavigator id="redditList" firstView="views.subredditList" width="300" height="100%"/>
<s:ViewNavigator id="redditFeed" firstView="views.redditFeed" width="100%" height="100%">
<s:actionContent.landscape>
<s:Button id="refreshButtonlLandscape" icon="#Embed('assets/refresh160.png')" click="refreshRSS()" />
</s:actionContent.landscape>
<s:actionContent.portrait>
<s:Button id="refreshButton" icon="#Embed('assets/refresh160.png')" />
<s:Button id="navigatorButton" label="Search" click="splitViewNavigator.showFirstViewNavigatorInPopUp(navigatorButton)" />
</s:actionContent.portrait>
</s:ViewNavigator>
</s:SplitViewNavigator>
As you can see in the code above, in my main view I have a button with the id "refreshButton." When I click this button, I want the reddit data to refresh. In other words I want to call a function to refresh the data, that is housed in the view, 'redditFeed'.
This is the function which is in a separate view named 'redditFeed.mxml', that I want to call using the refresh button in the main view shown above.
redditFeed.mxml
protected function myList_creationCompleteHandler(url:String):void
{
getRedditFeedResult.token = redditFeedGrabber.getRedditFeed(url);
getRedditFeedResult.addEventListener(ResultEvent.RESULT,busyOff);
}
I hope this helped clear out confusion as to what I was trying to do. Im assuming that the solution is quite simple, but alas, I am a novice programmer and new to Flex, so Im learning the ropes. Any help is appreciated. Thank you!
IF you have an instance of the view, then just do:
myViewInstance.myPublicFunction();
In MXML, the id element of the MXML tag is used to reference the view in ActionScript. Since you didnt' describe your architecture; it is unclear how one view can call the other.
If the view that needs to trigger the call is a parent of the view that has the function to make the call, then you could use the approach described above.
If the view that need to trigger the call is a child of the view that has the function to make the call, then you should dispatch an event from the "child" which the parent can listen to. In the event handler you would trigger the call.
If the view that needs to trigger and the view that has the function to make the call are both children of the same parent; then you should dispatch an event from the "Trigger" view, listen for it in the parent, and then use that event listener to make the call (Using similar code to what I explained above).
If you have a more complicated architecture of these two views; then you should look into some method to encapsulate the "remote call" functionality, such as into a service class. Many frameworks offer approaches to share that service class and/or results across multiple classes. ( MXML Files are classes).
There are two ways you can do this without getting into bad architecture by having the child view explicitly know about its parent:
Your child view can generate an event, which the parent is listening for. The parent will then call the function
The child view can have a public property of type Function. The parent view passes a reference to that function by setting the variable. The child view then calls the function (after checking to make sure it is not null).

Flex List item renderer

I have created an item renderer spark list in flex , but i want to call a function on addition of new row in list and not afterwards. I am getting a data object in rendered list in it i am getting the type of data to be displayed in list ie. either text or image. So on addition of new data in list i want a function to be called up in rendered list that checks the type of data received and then it will either create and add an image element or a text element. So the main problem is how i get a function called on addition of data. I have tried events like datachange and added but they keep on calling the function over and over again when we scroll the list but i want the function to be called once only on addition of data and not after wards. Below is the renderer list code , maybe you will get a better idea of what i am trying to do:
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="true" dataChange="test_add()">
<fx:Script>
<![CDATA[
import mx.controls.Alert;
public function test_add() : void {
Alert.show("type="+data.msg_type);
if(data.msg_type=="text"){
//code to create and add new text element to list_row//
}
if(data.msg_type=="image"){
//code to create and add new image element to list_row//
}
}
]]>
</fx:Script>
<s:Group id="list_row" width="100%" verticalAlign="middle" verticalCenter="0">
</s:Group>
</s:ItemRenderer>
Any help will be highly appreciated.
Thanks
As far as I can tell from the code you show, the easiest solution to your problem would be to work with two separate ItemRenderers: one that renders text and the other that renders images. You can do this using the SkinnableDataContainer#itemRendererFunction property instead of itemRenderer.
The List with the new property:
<s:List id="myList" dataProvider="{dp}"
itemRendererFunction="getItemRenderer" />
The function that returns a factory for the right ItemRenderer.
private function getItemRenderer(item:Object):IFactory {
if (item.msg_type == "text")
return new ClassFactory(MyTextItemRenderer);
if (item.msg_type == "image")
return new ClassFactory(MyImageItemRenderer);
}
In these two different ItemRenderers you can then display your data as you wish.
Edit: why it's OK that the dataChange event fires every time you scroll.
There is in fact nothing wrong with your approach as you describe it, although I would argue that the itemRendererFunction approach allows for better separation of concerns. I could tell you that you can turn the unwanted behavior off, simply by setting the List#useVirtualLayout property to false.
<s:List id="myList" dataProvider="{dp}"
itemRenderer="myItemRenderer" useVirtualLayout="false" />
Though this will do what you ask for (i.e. create the ItemRenderers only once), that would not be good advice. There is a good reason this property is set to true by default.
When virtual layout is used, item renderers are created only as they are needed, i.e. when they come into view and need to be displayed to the user. This allows you to load thousands of items without performance loss.
Let's say you load 1000 value objects: that doesn't take up much memory or CPU. But now you want to render them. If you don't use virtual layout an item renderer will be created for all of them up front, which means thousands of graphic elements and thousands of event listeners (how many exactly depends on your setup). Now that is going to hurt performance on a slow computer.
If you do use virtual layout only - say - 10 item renderers will be created at once. If the user scrolls down, the next 10 will be created and the ones that just disappeared from the view are removed and eventually garbage collected. So you see: what you may have perceived as something that was bad for performance at first, is actually a very good thing.
So I would advise you not to do what I just told you. Unless perhaps you would have a situation where you knew there would never be more than a very limited number of items in your List. Then you may consider not using virtual layout.

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

How does focus works in 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

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