flex: How to respond to change of data inside a component - apache-flex

I've created a custom component based on Image component. I want to teach it to respond to change of binded variable. E.g. if main class has a variable balance, I want the component to change image in case balance = 100 one image, in case balance = 50 to another.
Can somebody help to understand how to do that
I am using flex3 so don't see propertyWatcner there. Also I am using component2 from main mxml file this way
<MyComp:MyIcon left="15" top="20" width="60" height="60"
id="tower" price="100" accountState="{accountMoney}"
click="drawBuildingShadow(event)" />
And inside component MyIcon I want to be able to react changes of binded accountState variable.

Without any code to go by (please include a sample of both components if you can), there are a number of ways you could approach this.
You could add a ChangeWatcher which is bound to the variable in question, and invokes a method when the property changes. This method could change the image, based on whatever conditions apply to the property.
It would look something like this:
Component 1:
[Bindable]
public var yourVariable:Number;
Component 2:
private var propertyWatcher:ChangeWatcher;
//In some initialization method -- this is JUST an example method for how to create the property watcher
public function init(): void {
...
propertyWatcher = ChangeWatcher.watch(component1, "yourVariable", onVariableUpdate);
}
//Define this as a new method to handle when the property changes
private function onVariableUpdate(event:PropertyChangeEvent):void {
if(event.newValue == 50) {
yourImage.source = newSource;
}
else if(event.newValue == 100) {
yourImage.source = otherSource;
}
}
Obviously, this is very truncated and shorthand, but hopefully it will help get you started.
Edit: ChangeWatchers do exist in Flex 3, but it sounds like you should go in a different direction. Since the code snippet you posted is a bit small, I'm going to make a few assumptions on how you might do this :)
As alxx mentioned in his comment, you can change the property accountState in your component from an actual property, to a setter/getter. This will allow you to do more extensive processing when accountState gets updated.
It should look something like this:
MyComp:
//Inside your script tag:
private var _accountState:Number;
[Bindable]
public function get accountState():Number {
return _accountState;
}
public function set accountState(state:Number):void {
_accountState = state;
switch(state) {
case 50:
yourIcon.source = "blahblahblah";
break;
case 100:
yourIcon.source = "blahblahblah2";
break;
//And so on
}
}
This won't change the code you posted: it should still work as you've written it. I haven't tested this, so it's possible I'm missing something, but hopefully this will help :)

Related

how to find height of dynamic flex component used as a popup

I've created a custom component with several inline item renderers that I use as a tooltip. The height of the component is unknown, as the data contents of the component are not known until runtime.
However, when displaying the tooltip, occasionally it extends beyond the boundaries of the flash application, thus, I'd like to be able to detect this occurrence and reposition the tip.
The problem is that the height and width of the component are, apparently, not available until after being rendered by the popup manager. (i.e. they are always 0)
But, I do not know any way of finding out when the popup is actually rendered and, therefore, the height/width values available.
I tried adding a resize event listener to the component, but it doesn't appear to work, though I most certainly could be doing something wrong since it seems to me that the resize event only gives you the "oldWidth" and "oldHeight" of the object, which, at first display, would be 0...and useless to me.
Any ideas about how to proceed?
-----Edit-----
I have a base class like this:
public class TTComponent extends Canvas
{
var _parentC:UIComponent;
var popped:Boolean = false;
var timer:Timer;
var _comp:UIComponent;
public function set parentComponent(pC:UIComponent):void
{
_parentC = pc;
_parentC.addEventListener(MouseEvent.MOUSE_OUT, mouseOut);
_parentC.addEventListener(MouseEvent.MOUSE_OVER, mouseOver);
}
public function mouseOver(evt:MouseEvent):void
{
if (_parentC != null)
{
timer = new Timer(150,1);
_comp = this;
timer.addEventListener(TimerEvent.TIMER_COMPLETE, function( tevt:TimerEvent ):void
{
this.move( somex, somey);
if (popped != true)
{
PopUpManager.addPopUp(_comp, parentComponent );
popped = true;
});
timer.start();
}
}
public function mouseOut(evt:MouseEvent ):void
{
if ( timer )
{
timer.stop();
timer = null;
}
//If we popped up, remove the popup
if ( popped )
{
PopUpManager.removePopUp( _comp );
popped = false;
parentC .removeEventListener(MouseEvent.MOUSE_OUT, mouseOut);
parentC .removeEventListener(MouseEvent.MOUSE_OVER, mouseOver);
}
}
}
Then, an extended renderer like this:
<c:TTComponent name="T" xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:c="components.*">
<s:BorderContainer>
...about 30 labels grouped in various manners
...2 lists with inline item renderers
</s:BorderContainer>
</c:TTComponent>
Now, the code is called like this:
var w = new TTComponent();
w.data = data;
win.parentComponent = this;
This will add listeners to the mouse over and mouse out events on the parent, whatever it is, and then show or hide the tooltip accordingly.
------Edit------
Using a portion of what a commenter below suggested, this is the solution I came up with:
Inside the TTComponent class:
import flash.events.Event;
import mx.binding.utils.ChangeWatcher;
private var heightWatcher:ChangeWatcher;
public function set parentComponent
{
...
heightWatcher = ChangeWatcher.watch(this,'height',onSizeChange);
}
public function onSizeChange(evt:Event):void
{
if (this.height != 0)
{
....calculate the new component coords
this.move(newx, newy);
}
}
Note that this additional code doesn't bind to any component variable, it just adds a watcher on the component property.
You could also try binding your width and height. If these are made bindable in your class, flex will automatically adjust your popup's width and height.
When using mxml for your binding, you can just do something like this
<mx:YourComponent height="{HeightOfYourTooltip}" width="{WidthOfYourTooltip}"></mx:YourComponent>
You can also add a eventListener that listens to the change event if you want to reposition you component, like so
<mx:YourComponent height="{HeightOfYourTooltip}" width="{WidthOfYourTooltip}" change="yourComponentResizeHandler()"></mx:YourComponent>
If you are using a programmed approach, you should should use the changewatcher. Below is shown how you can use that.
ChangeWatcher.watch(YourComponent, "width", repositionHandler);
ChangeWatcher.watch(YourComponent, "height", repositionHandler);
If you want to watch for other variables or properties to change, be sure to add the [Bindable]-tag above your variables in your class, like this
[Bindable]
var myVariable:SomeVariable;
I hope this helps.
For displaying toolTip which controls you are using in itemRenderer? Text or Label?
Try to Listen update complete Event of that component. May this Help you. :)
This might be messy, but on the pop up component, you could add an event listener after complete is fired, if the height or width == 0 then you setTimeout() to a function after say 100ms until you get valid data.
Yes, I know it is a bit of a hack, but those will eventually report correctly measured values so it's not going to call that many times.
Just an idea if you are against a deadline or something like this isn't critical. :)

Help with containers

I am using view stack...so when view change like when we move from one page to another hide event is dispatched.So i am saving the information of last page in hide event before i go to next page.but thing is that if i change nothing still change on view hide event is invoked nd call go to backend...i just want do call only if sumthing change in the view..like sum text value...So i have two options
use event listener on each component if sumthing change its make the flag true...nd hide event check, if flag is true send call to backend.
event listener at container level ..if sumthing change in child componenet through bubbling container knows if sum event is dispatched.nd makes the flag true.
I have doubt with container...
Can i use container, and how?
Reason why I can't use container?
What are the pros and cons either way?
I would recommend using a dataProvider with the ability to compare them. For instance, if you are changing things with textinputs, you could basically do something like this:
[Bindable]
private var myDataProvider:Object = new Object();
private function creationCompleteHandler():void {
myDataProvider.updated = false;
myDataProvider.defaultValue = 'default';
myDataProvider.defaultValueTwo = 'default';
}
etc.
Then, in your mxml, you can have something like this:
<mx:TextInput id="myText" text="{myDataProvider.defaultValue}" change="myDataProvider.defaultValue=myText.text; myDataProvider.updated=true;" />
Lastly, in your hide event, you can do the following:
private function hideEventHandler( event:Event ):void {
if( myDataProvider.updated ){
// Call your RemoteServices (or w/e) to update the information
}
}
This way, when anything changes, you can update your dataProvider and have access to the new information each time.
Hope this helps!
I've used an approach similar to your first option in a couple of my past projects. In the change event for each of my form's controls I make a call to a small function that just sets a changesMade flag to true in my model. When the user tries to navigate away from my form, I check the changesMade flag to see if I need to save the info.
Data models are your friend!
If you get in the habit of creating strongly typed data models out of your loaded data, questions like this become very basic.
I always have a key binding set to generate a code snipit similar to this...
private var _foo:String;
public function get foo():String
{
return _foo;
}
public function set foo(value:String):void
{
if(_foo == value)
return;
var oldVal:String = _foo;
_foo = value;
this.invalidateProperty("foo", oldVal, value);
}
If your data used getters/setters like this, it would be very easy to validate a change on the model level, cutting the view out of the process entirely.

Adobe Flex3: Keyboard shortcuts when a view is visible?

I have a quite large Flex application with a large set of views and I ceratain views I'd like to add shortcuts.
And i'm looking for something like:
<mx:Vbox>
<foo:Shortcut keys="ctrl+s" action="{bar();}"/>
....
</mx:VBox>
Is there any framwork or component already done that does something like this? I guess it should be too difficult to build? When building this I only want the shortcuts to be active when the view is visible. How do I detect this? What base class is best to inherit from when working with non visual components?
I don't know of any framework component that does that already, but the examples above should get you started if you try to build your own.
There's no need to inherit from any component for a non-visual component like the one you've described here (your "foo" class needs no parents.) There's nothing in the Flex framework you need to inherit from for this.
However you architect it, your foo class is going to have to take in and parse keyboard codes to listen for and accept one or more methods to call. All you have to do is figure out when to add and remove the event listeners that will call the passed-in methods.
To handle turning your keyboard events on and off based on visibility, just have your foo component bind to the "visible" property of it's parent and add/remove event listeners accordingly.
You might also consider having the listeners added when the component that foo is nested in is on the display list rather than just visible. To do this, simply added and remove your event listeners in one of the component lifecycle methods - probably commitProperties is the most appropriate.
I don't think this solution answer your question directly but anyway, to help solve your problem here is an example.
For instance, I've extended the TextArea component like so. This is the best I can do so far, it can definitely be improved upon. Like, I don't know how to make the cursor go to the end after the next shortcut is pressed.
public class TextArea extends mx.controls.TextArea
{
// the keysmap is an example dictionary holding keycodes
private var keysmap:*={
112 = "some text for F1"
,113 = "the text for F2!"
//etc, etc
}
public var handleKeyDown:Boolean =false;
public function TextArea(){
if(handleKeyDown ==true){
this.addEventListener(KeyboardEvent.KEY_DOWN,this.keydownHandler);
}
}
public function keydownHandler(e:KeyboardEvent):void{
if(e.keyCode >= 112 && e.keyCode <= 123){
e.currentTarget["text"] += String(keysmap[e.keyCode]) +" ";
}//focusManager.setFocus(this);
}
}
I can't give you a solution using MXML, however my first thought would involve a singleton static class with a Dictionary that contains a list of objects as its keys and dynamically created dictionaries as the value pairing that contain keys denoting the desired key press with a function reference as the value.
So, say you had a Sprite and you wanted to capture ctrl+s for save when focus is on that object, I would get the instance of that Singleton, and call a function such as registerKeyBinding passing in the Sprite, the keyCode you want, and your pre-defined callback:
private var registeredObjects:Dictionary = new Dictionary(true);
public function registerKeyBinding(targetObject:Object, keyCode:int, callback:Function) {
if (registeredObjects[targetObject]) {
Dictionary(registeredObjects[targetObject])[keyCode] = callback;
}
else {
registeredObjects[targetObject] = new Dictionary();
Dictionary(registeredObjects[targetObject])[keyCode] = callback;
targetObject.addEventListener(KeyboardEvent.KEY_DOWN, keyDownListener);
}
}
private function keyDownListener(e:KeyboardEvent):void {
if (e.ctrlKey == true) {
//calls the function if that key exists.
Dictionary(registeredObjects[e.target])[e.keyCode];
}
}
Can't say I've tested this, but it was just the first thing that popped into my head. You could then setup functions to deregister and delete keys from the dictionaries, check states of the objects in addition to the keyCodes, remove old listeners, and delete entire dictionaries when there is no longer a need for them. Hopefully this is at least a tiny bit helpful.

Flex: Special-casing an item in a list or menu?

I've found it's often useful to special case the first item in a drop-down menu (ie, an instance of Menu). For example, if I want to pick a color from the list provided by a web service:
<mx:PopUpMenuButton id="colorSelelector"
dataProvider="{colorsService.lastResult}" />
I might also want a special-case, which is "enter a new color", allowing the user to enter the RGB values for a new color which isn't in the list. For example:
var newColor = { label: "Enter a new color", rgb: null };
Then used with:
<mx:PopUpMenuButton id="colorSelelector"
dataProvider="{colorsService.lastResult}"
lastOption="{newColor}" />
So, apart from changing the list I get back from the service, is there any better way to do this?
(and just a preemptive comment: this is a simplification… I'm not actually trying to make a color-picking-list)
When you bind to the dataProvider, call a function that adds your special case. For instance:
<mx:PopUpMenuButton id="colorSelector"
dataProvider="{addSpecialCases(colorsService.lastResult)}"/>
So, apart from changing the list I get
back from the service, is there any
better way to do this?
This approach is going to be the cleanest, without extending HTTPService, which would work well (but is really just altering your result ;) ):
package
{
import mx.rpc.http.HTTPService;
public class MyHTTPService extends HTTPService
{
public var appendToResult:Object;
public function MyHTTPService(rootURL:String=null, destination:String=null)
{
super(rootURL, destination);
}
[Bindable("resultForBinding")]
override public function get lastResult():Object
{
//I know what my type is, Array as an example
var myResult:Array = operation.lastResult;
myResult.push( this.appendToResult )
return myResult;
}
}
}

unable to get focus on canvas

I am creating a canvas in actionscript like :
private var cvs_preview:Canvas = null;
private function show_preview():void
{
this.cvs_preview = new Canvas();
this.cvs_preview.id = "cvs_preview_1";
this.cvs_preview.setStyle('backgroundColor', 0x000000);
this.cvs_preview.setStyle('backgroundAlpha', 1);
this.cvs_preview.setStyle('borderColor', 0x417FDD);
this.cvs_preview.setStyle('cornerRadius', 10);
this.cvs_preview.setStyle('borderStyle', 'solid');
this.cvs_preview.setStyle('dropShadowEnabled', true);
var pt:Point = image.localToGlobal(new Point(image.x, image.y));
this.cvs_preview.x = pt.x - 50;
this.cvs_preview.y = pt.y - 50;
this.cvs_preview.height = 200;
this.cvs_preview.width = 250;
//this.cvs_preview.addEventListener(FlexEvent.CREATION_COMPLETE, get_focus_on_canvas);
//this.cvs_preview.focusManager.setFocus(
//this.cvs_preview.addEventListener(MouseEvent.CLICK, end_preview_on_focus_change);
this.cvs_preview.addEventListener(FocusEvent.MOUSE_FOCUS_CHANGE, end_preview_on_focus_change);
Application.application.addChild(this.cvs_preview); //add as top-most visible container
btn_mini_preview.enabled = false;
}
So on the focus change i want to run the "end_preview_on_focus_change()"
but this is not working.
As per my understanding, i think the canvas not getting any focus in the first place. I was trying to use focusManager.setFocus to do that after the canvas's creation complete. but even that is giving me an error.
the code i was trying on Creation.Complete is :
private function get_focus_on_canvas(e:FlexEvent)
{
focusManager.setFocus(e.target);
//Alert.show("testing img complete");
}
this is giving me an error "1118: Implicit coercion of a value with static type Object to a possibly unrelated type mx.managers:IFocusManagerComponent."
basically i just want to use the focus out event of the canvas.
Can someone help me with this...
I have been on this issue since a long time.
Regards
Zeeshan
The error is correct. You have an object of type Object which you are trying to use as an IFocusManagerComponent. This will not work. To accomplish that line of code, you need to do something like
focusManager.setFocus( IFocusManagerComponent( e.target ) );
This, of course, assumes that the target implements IFocusManagerComponent. It will give you an error otherwise (and likely will in this case because Canvas is not listed as an IFocusManagerComponent). The good news is that Canvas does have a drawFocus method which will accomplish the same thing.
As to your MOUSE_FOCUS_CHANGE event, that will only be fired if an object already HAS focus and then loses it. I think you are better off using FlexEvent.CREATION_COMPLETE. This will ensure that the component has registered itself with all of the appropriate classes in the Flex SDK so that the FocusManager can even be aware of the new object. Whatever you do, do not try to set focus on something which has not been added to the stage (ie: Event.ADDED has been called).
As another piece of advice -- Event.ADDED bubbles, make sure that event.currentTarget == event.target to make sure that you are listening to the correct object. Otherwise, you might be calling the same function multiple times erroneously.
Only a few classes implement IFocusManagerComponent as others mentioned and Canvas is not one of them. If you really must call FocusManager.setFocus() you will have to extend the canvas class to implement this interface and use that class instead. You don't have to write any methods to implement this interface, all methods have already been implemented by UIComponent itself
//FocusableCanvas.as (include appropriate package and import statements)
public class FocusableCanvas extends Canvas implements IFocusManagerComponent
{
public function FocusableCanvas()
{
super();
}
}
//Now use this class instead of Canvas
this.cvs_preview = new FocusableCanvas();
//setFocus in creation complete handler
FocusManager.setFocus(IFocusManagerComponent(e.target));
But if all you want to do is to set focus on the canvas upon it's creation, you can call canvas.setFocus() from the creationComplete handler instead.
private function get_focus_on_canvas(e:FlexEvent)
{
Canvas(e.currentTarget).setFocus();
trace("done");
}
I see two problems, and no perfect solutions. With any luck, this can help you out.
First of all, e.target returns an object typecast with type Object. This explains your implict coercion error, because Object does not implement IFocusManagerComponent.
Second, iFocusManagerComponent is only implemented by Accordion, AdvancedListBase, Button, ButtonBar, ChartBase, ComboBase, DateChooser, DateField, HTML, ListBase, MenuBar, NumericStepper, TabNavigator, TextArea, TextInput, UIMovieClip as per this entry in the Flex 3.4 AS3 Reference.
This leads me to believe that a Canvas element cannot take focus and has simply inherited access to the FocusManager through inheritance of UIComponent.
The only solutions I can see are to utilize something other than Canvas to handle your focus related concerns, or subclass Canvas and implement iFocusManagerComponent, though that looks fairly complex.
Edit
Apologies for missing drawFocus in the above solution.
Please try;
private function get_focus_on_canvas(e:FlexEvent)
{
this.cvs_preview.setFocus();
}

Resources