Flex: Determine if a component is showing - apache-flex

What is the best way to determine if a component in Flex/Flash is showing on the user's screen? I'm looking for an analog to Java's Component.isShowing() method.
The show and hide events fire for visibility, and this seems to work for the first descendant of a ViewStack component, but not further down the display tree.

... or avoiding recursion:
public static function isVisible(obj:DisplayObject):Boolean
{
while (obj && obj.visible && obj !== Application.application)
{
obj = obj.parent;
}
return obj && obj.visible;
}

You want to check if the component property visible is true and this is for all the parents of your component in the DisplayList, am I correct?
public static function isVisible(c : UIComponent) : Boolean {
if (c == null) return false;
if (c is Application) return c.visible;
return c.visible && isVisible(c.parent);
}

UIComponent.visible is not necessarily valid for children of an object where visible=false. From the docs:
"In either case the children of the object will not emit a show or hide event unless the object has specifically written an implementation to do so."
I wrote a sample application that confirms this to be true. What you can do is walk up the display list checking for visible to be false on a parent. Basically "visible" gives false positives but shouldn't give false negatives. Here is a quick utility I put together:
package
{
import flash.display.DisplayObject;
import mx.core.Application;
public class VisibilityUtils
{
public static function isDisplayObjectVisible(obj : DisplayObject) : Boolean {
if (!obj.visible) return false;
return checkDisplayObjectVisible(obj);
}
private static function checkDisplayObjectVisible(obj : DisplayObject) : Boolean {
if (!obj.parent.visible) return false;
if (obj.parent != null && !(obj.parent is Application))
return checkDisplayObjectVisible(obj.parent);
else
return true;
}
}
}
I haven't done anything more than trivial tests on this but it should get you started.

Strange as it seems, now that you mention it, I don't believe there is a simple test to determine whether a component is actually visible onscreen in the sense Component.isShowing() implies.
It's also true the show and hide events don't bubble by default, so if you want to be notified of visibility changes in a descendant of a ViewStack container, you'll need to listen for them explicitly. The implementation details would vary depending on what sort of behavior you were after, but to take the simple example:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:VBox>
<mx:HBox>
<mx:Button id="btn1" click="vs.selectedIndex = 0" label="Show 1" />
<mx:Button id="btn2" click="vs.selectedIndex = 1" label="Show 2" />
</mx:HBox>
<mx:ViewStack id="vs" selectedIndex="0">
<mx:Panel id="panel1">
<mx:Label id="label1" text="Label 1" show="trace('showing label 1')" hide="trace('hiding label 1')" visible="{panel1.visible}" />
</mx:Panel>
<mx:Panel id="panel2">
<mx:Label id="label2" text="Label 2" show="trace('showing label 2')" hide="trace('hiding label 2')" visible="{panel2.visible}" />
</mx:Panel>
</mx:ViewStack>
</mx:VBox>
</mx:Application>
... you'll see the show and hide events for each label fire once their visible properties have been bound to their parent panels'. Hopefully that illustrates the point; you can extend it however best suits your application. Good luck!

I was trying to obtain the same in a reusable manner.. I almost found out a way using getObjectsUnderPoint() - this returns the object under a particolar point, z-ordered (even if they are not siblings, e.g. ViewStack, Popups, ecc.).
Basically, I get the topmost display object on under a particular point of the stage, then go up un the display object hierarchy to find the tested object. If I find it, the object is visible (not visible objects in the hierarchy should be already filtered out by the getObjectsUnderPoint call).
The problem here is that you must use a non-transparent point of your object (in my case, I used an offset of 5 pixel due to rounder borders), otherwise it will not be picked up by this function.
Any ideas to improve it?
Cosma
public static function isVisible(object:DisplayObject):Boolean {
var point:Point = object.localToGlobal(new Point(5, 5));
var objects:Array = object.stage.getObjectsUnderPoint(point);
if (objects.length > 0) {
if (isDescendantOf(object, objects[objects.length - 1] as DisplayObject)) {
return true;
}
}
return false;
}
public static function isDescendantOf(parent:DisplayObject, child:DisplayObject):Boolean {
while (child.parent != null) {
if (child.parent === parent) {
return true;
} else {
child = child.parent;
}
}
return false;
}

This is all you really need. The "Application.application" check is futile.
/**
* Returns `true` if this component is actually shown on screen currently. This could be false even with
* "visible" set to `true`, because one or more parents could have "visible" set to `false`.
*/
public static function isShowing (c : DisplayObject) : Boolean {
while (c && c.visible && c.parent) {
c = c.parent;
}
return c.visible;
}

Related

Custom Composite Control not rendering correctly for only 0.5-1 sec after being added back into a VGROUP

I am moving away from MXML and have built a custom component control within ActionScript.
I have the control displaying correctly. The problem comes after I remove it from the display list and add it back in again with the .addElement(control) method.
Here is the code that adds it back in again.
private function displayParameters(parameters:ArrayCollection):void{
for(var index:int = 0; index<parameters.length; index++){
if(parameters[index] is ReportControl){
var control:ReportControl = parameters[index] as ReportControl;
control.percentWidth = 100;
vgParameters.addElement(control);
}
}
}
ReportControl is the base class for comboBoxMultiSelect which is shown below. There is nothing graphically special about ReportControl, it only serves as a programmatic interface for its concrete implementations (polymorphic).
public class comboBoxMultiSelect extends ReportControl{
[Embed("../Assets/Icons/plus-16.png")]
private var plusIcon:Class;
[Embed("../Assets/Icons/minus-16.png")]
private var minusIcon:Class;
private var expanded:Boolean = false;
private var buttonIconChanged:Boolean = false;
private var _drp:ComboBox;
private var _btnMultiple:Button;
private var _horizontalGroup:HGroup;
private var _multiSelector:ReportGridSelector;
private var _multiSelection:Boolean = true;
private var bMultiSelectionChanged:Boolean = false;
public function ToggleExpanded():void{
expanded = !_expanded;
buttonIconChanged = true;
invalidateSize();
invalidateProperties();
invalidateDisplayList();
}
public function comboBoxMultiSelect(){
super();
}
override protected function createChildren():void{
super.createChildren();
if(!_horizontalGroup){
_horizontalGroup = new HGroup();
_horizontalGroup.gap = 0;
_horizontalGroup.percentWidth = 100;
_horizontalGroup.height = ReportControl.SIZE_DEFAULT_HEIGHT;
addChild(_horizontalGroup);
}
if(!_drp){
_drp = new ComboBox();
_drp.text = GuiText;
_drp.percentWidth = 100;
_drp.height = ReportControl.SIZE_DEFAULT_HEIGHT;
_horizontalGroup.addElement(_drp);
}
if(!_btnMultiple && _multiSelection){
_btnMultiple = new Button;
_btnMultiple.setStyle("icon", plusIcon);
_btnMultiple.width = 20;
_btnMultiple.height = ReportControl.SIZE_DEFAULT_HEIGHT;
_btnMultiple.visible = true;
_btnMultiple.addEventListener(MouseEvent.CLICK,
function(event:MouseEvent):void{
ToggleExpanded();
});
_horizontalGroup.addElement(_btnMultiple);
}
}
override protected function commitProperties():void{
super.commitProperties();
if(buttonIconChanged){
if(_expanded==true){
_btnMultiple.setStyle("icon", minusIcon);
}
else{
_btnMultiple.setStyle("icon", plusIcon);
}
buttonIconChanged = false;
}
}
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void{
super.updateDisplayList(unscaledWidth, unscaledHeight);
_horizontalGroup.width = unscaledWidth;
_horizontalGroup.height = unscaledHeight;
}
override protected function measure():void{
super.measure();
measuredMinWidth = measuredWidth = ReportControl.SIZE_DEFAULT_WIDTH;
//minimum size //default size
if(_expanded==true)
measuredMinHeight= measuredHeight = 200;
else
measuredMinHeight= measuredHeight =
ReportControl.SIZE_DEFAULT_HEIGHT;
}
}
When I add the control back in using vgParameters.addElement(control), the comboBoxMultiSelect is not rendering properly. The plusIcon inside the button _btnMultiple is not postioned correctly at first, but then quickly corrects itself about 0.5-1 secs later.
I pretty sure the problem lies within comboBoxMultiSelect, just not sure how to force the icon to stay in the same place.
This is highly annoying after all my hard work, anyone have ideas as to what I am doing wrong?
Thanks :)
UPDATE -----> Here is the ReportControl code
[Event (name= "controlChanged", type="Reporting.ReportControls.ReportControlEvent")]
[Event (name= "controlIsNowValid", type="Reporting.ReportControls.ReportControlEvent")]
public class ReportControl extends UIComponent
{
private var _guiText:String;
private var _amfPHPArgumentName:String;
private var _reportResult:ReportResult;
private var _sequence:int;
private var _reportId:int;
private var _controlConfiguration:ReportParameterVO;
private var _isValid:Boolean = false;
internal var _selection:Object;
/**
* SIZE_DEFAULT_HEIGHT = 22
*/
internal static const SIZE_DEFAULT_HEIGHT:int = 22;
/**
* SIZE_DEFAULT_WIDTH = 150
*/
internal static const SIZE_DEFAULT_WIDTH:int = 150;
public function get ControlConfiguration():ReportParameterVO{
return _controlConfiguration;
}
public function set ControlConfiguration(value:ReportParameterVO):void{
_controlConfiguration = value;
_guiText = (value ? value.GuiText:"");
_amfPHPArgumentName = (value ? value.AMFPHP_ArgumentName: "");
_sequence = (value ? value.Sequence : null);
_reportId = (value ? value.ReportId : null);
}
public function get IsValid():Boolean{
return _isValid;
}
public function get ReportID():int{
return _reportId;
}
public function get Sequence():int{
return _sequence;
}
public function get ControlRepResult():ReportResult{
return _reportResult;
}
public function set ControlRepResult(value:ReportResult):void{
_reportResult = value;
}
internal function set Selection(value:Object):void{
_selection = value;
}
internal function get Selection():Object{
return _selection;
}
public function get ParameterSelection():Object{
return _selection;
}
public function get GuiText():String{
return _guiText;
}
public function get AmfPHPArgumentName():String{
return _amfPHPArgumentName;
}
public function ReportControl(){
//TODO: implement function
super();
}
public function dispatchControlChanged():void{
this.dispatchEvent(new ReportControlEvent(ReportControlEvent.CONTROL_CHANGED, this, true));
}
public function dispatchControlIsNowValid():void{
this.dispatchEvent(new ReportControlEvent(ReportControlEvent.CONTROL_IS_NOW_VALID, this, true));
}
public function addSelfToValueObject(valueObject:Object):Object{
valueObject[AmfPHPArgumentName] = _selection;
return valueObject;
}
}
I'll try to give you an example of what I mean with the Spark skinning architecture we've discussed in the comments above. It's not directly an answer to your question, but I thought you might find it interesting. I will have to make it somewhat simpler than your component for brevity's sake and because you seem to have stripped out some of the code for your question so I can't know exactly what it's supposed to do.
This will be a component that will let you toggle between a normal and an expanded state through the click of a Button. First we'll create the skin class. Normally you'd create the host component first, but it'll be easier to explain this way.
<!-- my.skins.ComboBoxMultiSelectSkin -->
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
height.normal="25" height.expanded="200">
<fx:Metadata>
[HostComponent("my.components.ComboBoxMultiSelect")]
</fx:Metadata>
<s:states>
<s:State name="normal" />
<s:State name="expanded" />
</s:states>
<s:layout>
<s:HorizontalLayout gap="0" />
</s:layout>
<s:ComboBox id="comboBox" width="100%" />
<s:Button id="toggleButton" width="20"
icon.normal="#Embed('../Assets/Icons/plus-16.png')"
icon.expanded="#Embed('../Assets/Icons/minus-16.png')"/>
</s:Skin>
Thus we've set up completely how your component will look and how it will lay out. Do you feel your headaches dissipating? I for one find this quite elegant. We have the two states and the height of the component will adjust to the currently selected state as will the icon of the Button. How and when the state is toggled is component behaviour and will be defined in the host component.
Now let's create that host component in plain ActionScript. For this we'll extend SkinnableComponent (note that it could also extend your ReportControl if that would extend SkinnableComponent instead of UIComponent).
[SkinState("normal")]
[SkinState("expanded")]
public class ComboBoxMultiSelect extends SkinnableComponent {
[SkinPart(required="true")]
public var toggleButton:IEventDispatcher;
[SkinPart(required="true")]
public var comboBox:ComboBox;
private var expanded:Boolean;
override protected function partAdded(partName:String, instance:Object):void {
super.partAdded(partName, instance);
switch (instance) {
case toggleButton:
toggleButton.addEventListener(MouseEvent.CLICK, handleToggleButtonClick);
break;
case comboBox:
comboBox.addEventListener(IndexChangeEvent.CHANGE, handleComboSelection);
break;
}
}
private function handleToggleButtonClick(event:MouseEvent):void {
toggleExpanded();
}
private function handleComboSelection(event:IndexChangeEvent):void {
//handle comboBox selection
}
protected function toggleExpanded():void {
expanded = !expanded;
invalidateSkinState();
}
override protected function getCurrentSkinState():String {
return expanded ? "expanded" : "normal";
}
}
Allright, there's a lot more going on here.
First look at the SkinState metadata declarations: when a skin class is assigned to the component, the compiler will check whether that skin has the required states implemented.
Then the SkinPart declarations: the name of the property on the host component must exactly match the id of the tag in the skin class. As required is set to true the compiler will check whether these components do really exist in the skin. If you want optional skin parts, you set it to false.
Note that the type of toggleButton is IEventDispatcher: from the host component's point of view, all toggleButton has to do, is dispatching CLICK events. This means that we could now create a skin with <s:Image id="toggleButton" source="..." /> and the whole thing would keep working the same way. See how powerful this is?
Because the skinpart properties are not assigned immediately, we override the partAdded() method which will be executed whenever a component becomes available. In most cases this is the place where you hook up your event listeners.
In the toggleExpanded() method, we toggle the boolean just like the component in your question, however we only invalidate the skin state. This will cause the skin to call the getCurrentSkinState() method and update its state to whatever value is returned.
Et voilà! You have a working component with the behaviour nicely separated into an actionscript class and you didn't have to worry about the layout intricacies. And if you ever wish to create a component with the same behaviour, but it should expand horizontally instead of vertically: just create a new skin that adjusts the width instead of the height and assign that to the same host component.
Oh wait! I nearly forgot to tell you how to assign the skin to the components. You can do it either inline:
<c:ComboBoxMultiSelect skinClass="my.skins.ComboBoxMultiSelectSkin" />
or through styling:
#namespace c "my.components.*";
c|ComboBoxMultiSelect {
skinClass: ClassReference("my.skins.ComboBoxMultiSelectSkin")
}
One thing that stands out is in your implementation of updateDisplayList(). As you know, this is where your component should size and position it's child objects (and/or do any programatic drawing).
But rather than set the child object's width/height directly, you should use one of the Flex lifecycle methods: setActualSize() or setLayoutBoundsSize(). Use setLayoutBoundsSize() with spark components.
When you set a Flex component's width/height, the component will invalidate itself so that on the next update cycle it can be re-rendered. But since you are trying to render the component in updateDisplayList() you should be careful to not invalidate your child objects inside this method.
The setActualSize() and setLayoutBoundsSize() methods set the width/height on a Flex component, but do not invalidate the component.
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
_horizontalGroup.setLayoutBoundsSize(unscaledWidth, unscaledHeight);
// if you wanted to position objects, you would set their x/y coordinates
// here with the move() or setLayoutBoundsPosition() methods
}
Note, it looks like some child objects are being sized in createChildren() as well ... and it's not really clear what the base Flex component is in this case (what class does ReportControl extend?
Doing it this way may get rid of that rendering glitch. It will most certainly execute less code than if you set width/height properties directly.
[Edit]
It may be an interaction with the HGroup which is kind of unnecessary in this component. While I think making components this way is fun, it can be more tedious... which is why #RIAStar is wisely pointing out another approach.
Some further ideas, if you want to continue down this path:
1) Take a look at the sizing you are doing in createChildren() - for example, the HGroup is given a percentWidth, but in updateDisplayList() it is given a fixed width (this may be a red herring, but I would not set the percentWidth).
2) You might be able to trick the component into validating itself after you remove it or before you re-add it. A hacky hunch that may be a waste of time.
3) Remove the 'HGroup' from your component. It's kind of unnecessary: the layout requirements are simple enough to do w/a few lines of Actionscript. Your mileage will vary as the layout requirements get more complex!
In createChildren() add the combo box and button directly to the UIComponent. Then size and position them in updateDisplayList(), something like this:
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var padding:Number = 10;
var gap:Number = 0;
// make the ComboBox consume all of the width execpt for 20px and gap + padding
var availableWidth:Number = unscaledWidth - 20 - gap - (2*padding);
_drp.setLayoutBoundsSize(availableWidth, unscaledHeight); // combo box 100% width
_btnMultiple.setLayoutBoundsSize(20, unscaledHeight); // button is 20px wide
// now position them ...
// probably should not use 0, rather calculate a Y coordinate that centers them
// in the unscaledHeight
_drp.setLayoutBoundsPosition(padding, 0);
_btnMultiple.setLayoutBoundsPosition(unscaledWidth - padding - 20, 0);
}
Thank you both so much for your answers! The consideration and attention to detail in explaining the concepts is awesome! Très bien!
#RIAstar However due to the amount of code already in place, changing my architecture (separating visual element from behavioural) would force to large a re-factor of the code and would cost to much for a feature that hasn't been explicitly requested. (visual representation of the control being able to change at runtime) It certainly is interesting and I will be adding that into a future version.
That said, I think I've been able to find a solution to my problem. I decided to build off of #SunilD.'s suggestion of validating the control before it's added back in. A hack I know, but humans aren't perfect and thus the code aint either. ;-)
When looking at the control, I noticed it was only the button that was having issues with rendering its image. So to test, I added and removed JUST a button instance with an icon, and I saw the same behaviour! (regardless of how comboBoxMultiSelect was implemented) I ALSO noticed that I didn't see the button do this when it was first created. So why not just reconstruct the button when it gets removed from the display list?
I ended up wiring comboBoxMultiSelect to the FlexEvent.REMOVE event, destroy the button reference, create a new one, and add it back in with AddChild(). Below is an explanation of the event.
"Dispatched when the component is removed from a container as a content
child by using the removeChild(), removeChildAt(), removeElement(), or
removeElementAt() method. If the component is removed from the
container as a noncontent child by using the rawChildren.removeChild()
or rawChildren.removeChildAt() method, the event is not dispatched.
This event only dispatched when there are one or more relevant
listeners attached to the dispatching object."
Sure enough, this fixed the icon from displaying incorrectly and explains what happening. For some reason the button is taking more than one render event to apply its style when it's added back in. Anyone else able to replicate this behaviour?
I guess the real question now is "what is the best way to remove and add-back-in a button to the display list, so that its embedded icon is unaffected?"

Does Spark List honour the IDropInItemRenderer interfaces?

It appears as though the new spark List component does not honour the IDropInItemRenderer interface.
Ie - if I implement IDropInItemRenderer on my renderer, the setter of listData is never called.
Am I missing something, or is this interface now deprecated?
If so, What is the suggested approach for providing similar dataProvider context information to the renderer?
For example, I want the renderer for the last item in a collection to behave slightly differently.
I see that IItemRenderer now defines a listIndex property, however this approach doesn't work without knowing the count of the source dataProvider.
Here's the workaround I've ended up using.
In it's own way, the DataGroup is dripping Spark's compositional goodness, in that it exposes a rendererUpdateDelegate property, which you can set with your own class to provide whatever custom functionliaty you're after.
While it's frustrating that the interface got dropped without really being advertised, this approach is much more powerful.
Here's an example class. In my example, I want the last renderer to have it's collapsable property set to false:
/**
* Decorates another IItemRendererOwner (eg., a DataGroup) and augments the updateRenderer method
* to set the isCollapsable property */
public class ThreadMessageRendererUpdateDelegate implements IItemRendererOwner
{
private var _dataGroup:DataGroup;
public function get dataGroup():DataGroup
{
return _dataGroup;
}
public function set dataGroup(value:DataGroup):void
{
_dataGroup = value;
if (dataGroup)
{
dataGroup.rendererUpdateDelegate = this;
}
}
public var dataProvider:ArrayCollection;
public function ThreadMessageRendererUpdateDelegate(owner:DataGroup=null)
{
this.dataGroup = owner;
}
public function itemToLabel(item:Object):String
{
return dataGroup.itemToLabel(item);
}
public function updateRenderer(renderer:IVisualElement, itemIndex:int, data:Object):void
{
dataGroup.updateRenderer(renderer,itemIndex,data);
if (renderer is ThreadMessageRenderer)
{
ThreadMessageRenderer(renderer).collapsable = itemIndex < dataProvider.length - 1;
}
}
}
And here's it's example usage:
<fx:Declarations>
<viewer:ThreadMessageRendererUpdateDelegate dataProvider="{dataProvider}" dataGroup="{threadList}" />
</fx:Declarations>
<fx:Script>
<![CDATA[
[Bindable]
public var dataProvider:ArrayCollection
]]>
</fx:Script>
<s:DataGroup height="100%"
width="100%"
dataProvider="{dataProvider}"
itemRenderer="ThreadMessageRenderer"
id="threadList"
>
</s:DataGroup>
Man! Just spent ages trying to find DataGroup.rendererUpdateDelegate(...), eventually discovering why I couldn't, courtesy of this SO post.
Anyway, thinking about the (disappearance of) rendererUpdateDelegate property and your offering a little bit more, I realise neither are really necessary.
DataGroup has the rendererAdd event which gives you enough info, at the right time, to do what you want; for example:
...
<s:DataGroup id="dg"
dataProvider="{model.dataProvider}"
itemRenderer="{model.itemRendererFactory}"
rendererAdd="model.updateRenderer(event.data, event.index, event.renderer)">
...
...and in the model we have:
public function updateRenderer(data:Object, index:int, renderer:IVisualElement):void
{
if (renderer is ICollapsable)
{
ICollapsable(renderer).collapse = index < dataProvider.length - 1;
}
}
Fewer lines of code and clearer intent

Changing properties of several objects in Flex

In my example below I have several objects. I want to change the label of all objects in one go, without calling each element by id. I know how to do this in HTML, but not in Flex.
// HTML
<div class="text" id="text1">SomeText</div>
<div class="text" id="text2">SomeText</div>
<div class="text" id="text3">SomeText</div>
// jQuery
$(".text").css("color", "#333333");
This is how I would usually set the color of 3 objects to grey in one line.
// Flex
<s:Button id="button1" label="Button 1"/>
<s:Button id="button2" label="Button 2"/>
<s:Button id="button3" label="Button 3"/>
// AS3
button1.label = 'Something else';
button2.label = 'Something else';
button3.label = 'Something else';
Is there any way I can change the labels of all 3 buttons with a single line of code similar to the jQuery example? Thanks in advance.
I'm pretty sure the answer is no, with a caveat.
Keep in mind that JQuery is a framework that hides the complexity of what it is doing. ( A lot of frameworks do that including the flex Framework ). In Flex, I can create a DataGrid in one line of code. However, there are thousands of lines of code, and multiple classes already written that allow me to do that. I suspect the same is true for a lot of JQuery functionality.
there is no reason you can't encapsulate that functionality to make the change and then call it with one line of code.
As #www.Flextras.com pointed out - you can write a class to do this.
I'd encourage you to consider an alternative approach however, as looping through the children looking for a specific property is quite slow. That said - it does make for an interesting coding challenge.
Here's a class & example that should acheive what you're after.
package com.mangofactory.util
{
import flash.display.DisplayObject;
import mx.core.UIComponent;
/**
* Utility class to set a given property of all matching children on a target.
* Named to act as an operator, rather than a class (hence no captial letter)
*
* eg., on(someTarget).of(SomeClass).someProperty = someValue;
* */
public class on
{
private var root:UIComponent;
private var requiredPropertyName:String;
private var requiredType:Class;
public function on(root:UIComponent)
{
this.root = root;
}
/**
* Returns a list of targets which match the defined criteria.
* Note - the root component is also evaluated
* */
private function get targets():void
{
var result:Array = [];
if (matches(root))
{
result.push(root);
}
for (var i:int = 0; i < root.numChildren; i++)
{
var child:DisplayObject = root.getChildAt(i);
if (matches(child))
result.push(child);
}
}
/**
* Returns true if the target param matches the defined criteria.
* If a propertyName has been given (by calling 'having') that is checked first.
* Otherwise, the type is checked against the value passed calling ('of')
* */
private function matches(target:Object):Boolean
{
if (requiredPropertyName && target.hasOwnProperty(requiredPropertyName))
return true;
if (requiredType && target is requiredType)
return true;
return false;
}
public function having(propertyName:String):PropertyCatcher
{
this.requiredPropertyName = propertyName;
}
public function setOnTargets(propertyName:*,value:*):void
{
for each (var matchedTarget:Object in targets)
{
if (matchedTarget.hasOwnProperty(propertyName))
matchedTarget[propertyName] = value;
}
}
public function of(type:Class):PropertyCatcher
{
this.requiredType = type;
}
}
}
import com.mangofactory.util.on;
import flash.utils.Proxy;
import flash.utils.flash_proxy;
use namespace flash_proxy;
dynamic class PropertyCatcher() extends Proxy
{
private var callbackTarget:on;
public function PropertyCatcher(callbackTarget:on)
{
this.callbackTarget = callbackTarget;
}
override flash_proxy function setProperty(name:*, value:*):void {
callbackTarget.setOnTargets(name,value);
}
}
And an example:
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<s:Button />
<s:Button />
<s:Button />
<mx:Canvas />
<fx:Script>
<![CDATA[
public function doTest():void
{
// Sets the label of all Buttons to "Hello World"
on(this).of(Button).label = "Hello World";
// Sets the visible property of all children which declare a "alpha" property to false.
on(this).having("alpha").visible = false;
}
]]>
</fx:Script>
</mx:Canvas>
Note - I haven't tested this, but in theory it should work.
I don't know of any selector of the css3/jquery type for flex. But a workaround will be to use an array of buttons instead of many buttons variables and then just iterate through all of them (button[i] instead of buttoni)

While loading data make all the components disabled in adobe flex

I have a vertical tab-bar with 4 items with different sub tabs. All the four items use data from a single xml file, which is a big file. When the user clicks on a tab, while the data is being loaded this error is shown.
TypeError: Error #1009: Cannot access a property or method of a null object reference.
I want to disable all the tabs while the xml file is being loaded.
You can set the main application to disabled; which should disable all children. Use the enabled property and set it to false. IF you want to stop mouse interaction too, be sure to set the mouseEnabled property to false.
you can display an overlay component which will block all events. and that overlay can be removed once all data is loaded.
to make it more cool overlay can have background partially transparent.
Control the enabled/disabled property by binding to getters and setters on the data you're working with. For example:
<mx:Script>
<![CDATA[
[Bindable]
public var myParsedXML:Object;
private var _myData:Object;
public function set myData(value:Object):void
{
//Check for null
if(value)
{
myTabs.enabled = true;
}
else
{
myTabs.enabled = false;
}
_myData = value;
}
public function get myData(value:Object):void
{
return _myData;
}
]]>
</mx:Script>
<mx:Binding source="myParsedXML" destination="myData" />
<mx:TabNavigator id="myTabs">
<mx:VBox label="tab 1">
</mx:VBox>
<mx:VBox label="tab 2">
</mx:VBox>
</mx:TabNavigator>
Once you've created this property, you can bind to it and be sure that it will be notified when the data is set.

Flex conditional data binding

I have got two labels in my flex mxml component.
first one shows playheadtime of videodisplay and another is also used for same purpose. the difference is that when we are in add mode(decided from a flag variable) both should show current playheadtime using binding. but when we are in edit mode(again decided from flag) the latter label should remain static, to be more specific, the value retrived from database.
how can I do that using actionscript. I tried ChangeWathcer but I found it a bit tricky. Is there any other simpler way or am I missing something.
following is my code.
private function init():void
{
if (queFlag == 'a')
{
// timeLbl.text = currentTimeLbl.text using some binding mechanism
}
else if(queFlag == 'e')
{
// timeLbl.text = 'value retrived from database' ;
}
}
here currentTimeLbl shows videoDisplay playheadtime so it changes dynamically as video plays.
please help me out.
You could do it in something like the following:
<Label id="timeLbl" text="{timeLabelText}"/>
<Label id="currentTimeLbl" change="dispatchEvent('currentTimeLblChanged')"/>
[Bindable(event = "queFlagChanged")]
[Bindable(event = "currentTimeLblChanged")]
private function get timeLabelText():String
{
if(_queFlag == 'a')
{
return currentTimeLbl.text;
}
else
{
return 'value retrived from database';
}
}
public function set queFlag(value:String):void
{
_queFlag = value;
dispatchEvent(new Event("queFlagChanged"));
}
Here is a very short way of conditional binding in Flex. If you code the conditions into MXML curly-bracket-bindings they will be transformed by the MXML compiler to listeners on all objects participating in this expression.
Here is a working example:
<mx:CheckBox id="flagBox"/>
<mx:ComboBox dataProvider="{['a','e']}" id="flagBox2"/>
<mx:TextInput id="txtBox"/>
<mx:Label text="default: {txtBox.text}"/>
<mx:Label text="conditional (bool): { (flagBox.selected)? txtBox.text: 'default' }"/>
<mx:Label text="conditional (value): { (flagBox2.selectedItem == 'a')? txtBox.text: 'default' }"/>
Checking flagBox will result in label #2 displaying "default" otherwise the text from the txtBox is displayed.
Selecting "a" in flagBox2 will result in label #3 displaying "default" otherwise the text from the txtBox is displayed.
I regularly use this for reducing my lines of code in my UI-logic and it works quite well for me. A problem of this techniques is that you can't use all logic symbols in curly-braket-bindings, such as < or &&, but i usually could life with that.

Resources