Selecting MXML siblings with actionscript, like javascript? - apache-flex

I'm trying to get the sibling of an mxml tag similar to the way siblings are selected in javascript. Is this possible in Actionscript?
For example, when I click the TextArea with id textarea1, I need it to tell me that the sibling has an id of rect1 so I can do further processing to it.
<s:Group>
<s:TextArea id="textarea1" click="getSibling(event)" />
<s:Rect id="rect1" />
</s:Group>

Assuming Group, TextArea and Rect are UIComponents, I think this should work:
private function getSibling(e:Event):void {
var parent:UIComponent = e.currentTarget.parent;
if(parent) {
var len:int = parent.numChildren;
var child:UIComponent;
for(var i:int = 0; i < len; i++) {
child = parent.getChildAt(i) as UIComponent;
if(child && child != e.currentTarget) {
trace(child.id);
}
}
}
}

My initial thought here is to access the parent and then retrieve a list of children within it.
function getSibling(e:Event):void {
//get an array of children from the parent.
var children:Array = e.target.parent.getChildren();
//process children as you wish...
}
This was discussed with respect to Javascript here.
Hope this helps.
Nick
nickgs.com

As far as I know there is no way to do this. However, both textarea1 and rect1 are children of the Group. If you give the group an ID you should be able to loop over all the children to find all the siblings of the TextArea.
In Flex 3, you'd use a for loop, numChildren, and getChildAt. I suspect in Flex 4 it would be similar.

Related

flex 4.6 add or change element name when using addElement()

flex 4.6
I am using addElement as below. I note however that when inspecting the element, the name of the element is suffixed with a number, so the loaded element name of newMod becomes newMod10 (number is variable). If I want to then removeElement() I have no idea of what the correct getChildByName() would be, so getChildByName("newMod") fails.
So my Q's are
how do i addElement() with a unique name
how do find the name of the element I just added so I can reference by the nameXX
thx
Art
/* load module */
/* creationComplete="loadNewMod('modToLoad','A' )
public function loadNewMod(modName,evtTyp):void {
info = ModuleManager.getModule(modName);
var self:Object = this;
var meh = "modEventHandler"+(evtTyp);
info.addEventListener(ModuleEvent.READY, function(e:ModuleEvent){
self[meh](e)
});
info.load(null, null, null, moduleFactory);
}
private function modEventHandlerA(e:ModuleEvent):void {
vg1.addElement(info.factory.create() as IVisualElement);
}
<s:Group id="vg1" horizontalCenter="0" verticalCenter="0">
<s:Label id="newLabel" />
</s:Group>
[EDIT]
by breaking out the function I have added an ID that seems to work
private function modEventHandlerA(e:ModuleEvent,fcall):void {
var newID = info.factory.create();
newID.name = "myElem";
vg1.addElement(hh as IVisualElement);
}
Best way is likely to attach a name property to the object and do a for loop. It's inefficient, but the ID label doesn't seem to work properly when setting it in AS3.
info.name = "info1";
for ( var i:Number = 0; i < vg1.numElements; i++ ) {
if ( ( vg1.getElementAt(i) as UIComponent).name == "info1" ) {
vg1.removeElementAt(i);
break;
}
}
I didn't check to see if info would have the name property, but that would do the trick. Alternatively, you could use the exact same method with the id tag, except use indexOf("info1") >= 0, instead of .name == "info1"

Scroll inside an item renderer result in an empty item

I'm developing a mobile app. I have a view with an horizontal list, each item has a long description so I would need a vertical scroll for that independent information. I'm trying to add an scroll as a Child of an itemRenderer. And I dont get it. Anyone knows what I'm doing bad?
I have a class with inheritance of ItemRenderer (I tried BaseRenderer from AsFusion too, that it seems to have more performance for mobile apps, with the same result).
And here is the part of the scroll from my code:
override protected function createChildren():void
{
super.createChildren();
scroller = new VScrollBar();
scroller.percentHeight = 100;
scroller.setStyle('right', 0);
scrollGroup = new Group();
scrollGroup.percentHeight = 100;
scrollGroup.percentWidth = 100;
super.addElement(scroller);
super.addElement(scrollGroup);
scroller.viewport = scrollGroup;
}
I also tried
override protected function createChildren():void
{
super.createChildren();
scroller = new Scroller();
scroller.percentHeight = 100;
scroller.percentWidth = 100;
scrollGroup = new Group();
scrollGroup.percentHeight = 100;
scrollGroup.percentWidth = 100;
super.addElement(scroller);
super.addElement(scrollGroup);
scroller.viewport = scrollGroup;
}
And the result is the same. An empty item in the list. I can change page (pagesnapping of the list horizontal scroll) and the next item is also empty. If I delete addElement(scroller) I can see the items perfectly, but without the vertical scroll that I really need. So the problem is in the scroller. Any idea what I'm doing so bad?? Please? I need the solution in actionscript, I have more itemrenderers done and I will make inheritance, and the performance for the mobile is better in actionscript. Thank you in advance.
I've never used scroll bars in an item renderer... But you might check out the Scroller component? Something like this:
<s:ItemRenderer>
<s:Scroller width="100%" height="100%">
<s:Group>
<Your_components_here />
</s:Group>
</s:Scroller>
</s:ItemRenderer>
Not sure if it would behave any different though.
In order to work, scrolls need an actual width and height. It seems that the groups you're passing are actually empty, although they do have a set percentWidth. Add content into them.
If you're scrolling text, it might be more viable to use the built in scroll of the TextArea.
I solved, with the guidance of Grigorash Vasilij I noticed that the content of the scroller was not showing because in the group the content size was 0 and private visible variable was false. So the percent sizes of the scroller was not working, I updated it in the method updateDisplayList.
override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number ):void
{
super.updateDisplayList( unscaledWidth, unscaledHeight );
scroller.width = unscaledWidth;
scroller.height = unscaledHeight;
...
}
After that, the scroller was horizontal, then the horizontal scroll wasn't work, I wanted a verticalScroll if needed in the item Renderer, so after the constructor of Scroller I set the horizontalPolicy of the scroll equal to off. The result is the next:
override protected function createChildren():void
{
super.createChildren();
scroller = new Scroller();
scroller.percentHeight = 100;
scroller.percentWidth = 100;
scroller.setStyle("horizontalScrollPolicy", "off");
scrollGroup = new Group();
scrollGroup.percentHeight = 100;
scrollGroup.percentWidth = 100;
addChild(scrollGroup);
scroller.viewport = scrollGroup;
addChild(scroller);
}
My class is inheriting of BaseRenderer from Asfusion If you inherit of itemrenderer use addElement instead of addChild.

dynamic add component

I want to dynamically add component in Container like Canvas(TileList constraints each child has the same size, GridList is poor in performance), for example
<mx:Canvas id="myHolder" width="600" height="550">
</mx:Canvas>
<mx:Button label="Add Button" click="addButton()"/>
when I click the button, I hope add a component(whatever the component is, and maybe each component has different size), and if the total width of all added child is greater than myHolder, I hope the new child can begin in new line, and stretch the height of myHolder at the same time.(layout with custom code is better)
On Canvas you have complete freedom to lay components anywhere using their x and y properties, so there's a lot of ways to skin this cat. Since you need rows, one of the methods may be (not tested):
//inside of your Canvas-based component
private function updateChildrenPositions():void
{
var rowY:Number = 0;
var rowWidth:Number = 0;
var rowHeight:Number = 0;
for (var i:int = 0, total:int = numChildren; i < total; i++)
{
var child:DisplayObject = getChildAt(i);
if (rowWidth + child.width > width)
{
//child will cause overflow, start next row
rowY += rowHeight;
rowWidth = 0;
rowHeight = 0;
}
rowWidth += child.width;
child.x = rowWidth;
child.y = rowY;
if (child.height > rowHeight) rowHeight = child.height; //accumulating max height
}
height = rowY + rowHeight;
}
This assumes Canvas has fixed width and set height depending on layout. You can add paddings and gaps later, it's a good exercise :)
To get the functionality you want, I wouldn't use an HBox. As alxx suggested, a TileList would be a better fit in this situation.
Here are some examples using a TileList to get you started:
http://blog.flexexamples.com/category/halo/tilelist/
http://learn.adobe.com/wiki/display/Flex/TileList

Is there a multiline text workaround for Flex

Is there a workaround for displaying multiline text in Flex 3? The two controls I have tried so far are mx:Text, and mx:TextArea. Each control has its own bug associated with it. For reference: mx:Text bug - http://bugs.adobe.com/jira/browse/SDK-9819 mx:TextArea bug - http://bugs.adobe.com/jira/browse/SDK-12616. Basically, neither control handles scrolling correctly if you do not specify a height and the text wraps onto the next line (height is determined dynamically by Flex, based on the wrapping). Does anybody have a workaround that might be helpful?
Thanks.
Update: One of the methods I have tried in the past has been to manually calculate the height of a mx:Text element. I can do this by using the following:
var textItem:Text = new Text();
var len:int = value.length;
var lines:int = int(len/115) + 1;
var height:int = lines * 20;
textItem.height = height;
While this seems to get around the problem in mx:Text, there is one big fault. The calculation relies heavily on font-size, letter-spacing, and the width of textItem. I can use this method, and move on with my project. However, maintenance on this is inevitable, and with code like this, it will a gigantic PITA.
I've had to deal with this a few times myself. The best way I've found to get dynamic height sizing of <mx:Text> is to leave the height out of the text and then specify a percent height of 100% on the enclosing VBox, HBox, etc. Something like the following should work for you:
<mx:VBox width="100%" height="100%">
<mx:Text text="Your really long text goes here." width="100%"/>
</mx:VBox>
As this is a bit of a hack itself, your milage may vary.
Edit
If you want to extend your above example so that maintenance on the code is easier, you should look into the TextLineMetrics class. This will allow you to measure the width and height of your text, taking into account font, size, etc. The docs for TextLineMetrics can be found here. To use your above example, you'd want to do something like the following:
var textItem:Text = new Text();
var metrics:TextLineMetrics = textItem.measureText( value );
var len:int = metrics.width;
var lines:int = int(len/textItem.width) + 1;
var height:int = lines * metrics.height;
textItem.height = height;
I use a variable height text area class that works very well for me:
package
{
import mx.controls.TextArea;
/**
* TextArea that xpands to the height of the content contained
* within.
* #author joel
*
*/
public class VariableHeightTextArea extends TextArea
{
public function VariableHeightTextArea()
{
super();
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if(this.height != int(this.textField.measuredHeight) + 5 )
{
this.height = this.textField.measuredHeight + 5;
}
}
}
}

Flex: Custom Item Renderer For Combobox controls truncates text

I've implemented a custom item renderer that I'm using with a combobox on a flex project I'm working on. It displays and icon and some text for each item. The only problem is that when the text is long the width of the menu is not being adjusted properly and the text is being truncated when displayed. I've tried tweaking all of the obvious properties to alleviate this problem but have not had any success. Does anyone know how to make the combobox menu width scale appropriately to whatever data it's rendering?
My custom item renderer implementation is:
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml"
styleName="plain" horizontalScrollPolicy="off">
<mx:Image source="{data.icon}" />
<mx:Label text="{data.label}" fontSize="11" fontWeight="bold" truncateToFit="false"/>
</mx:HBox>
And my combobox uses it like so:
<mx:ComboBox id="quicklinksMenu" change="quicklinkHandler(quicklinksMenu.selectedItem.data);" click="event.stopImmediatePropagation();" itemRenderer="renderers.QuickLinkItemRenderer" width="100%"/>
EDIT:
I should clarify on thing: I can set the dropdownWidth property on the combobox to some arbitrarily large value - this will make everything fit, but it will be too wide. Since the data being displayed in this combobox is generic, I want it to automatically size itself to the largest element in the dataprovider (the flex documentation says it will do this, but I have the feeling my custom item renderer is somehow breaking that behavior)
Just a random thought (no clue if this will help):
Try setting the parent HBox and the Label's widths to 100%. That's generally fixed any problems I've run into that were similar.
Have you tried using the calculatePreferredSizeFromData() method?
protected override function calculatePreferredSizeFromData(count:int):Object
This answer is probably too late, but I had a very similar problem with the DataGrid's column widths.
After much noodling, I decided to pre-render my text in a private TextField, get the width of the rendered text from that, and explicitly set the width of the column on all of the appropriate resize type events. A little hack-y but works well enough if you haven't got a lot of changing data.
You would need to do two things:
for the text, use mx.controls.Text (that supports text wrapping) instead of mx.controls.Label
set comboBox's dropdownFactory.variableRowHeight=true -- this dropdownFactory is normally a subclass of List, and the itemRenderer you are setting on ComboBox is what will be used to render each item in the list
And, do not explicitly set comboBox.dropdownWidth -- let the default value of comboBox.width be used as dropdown width.
If you look at the measure method of mx.controls.ComboBase, you'll see that the the comboBox calculates it's measuredMinWidth as a sum of the width of the text and the width of the comboBox button.
// Text fields have 4 pixels of white space added to each side
// by the player, so fudge this amount.
// If we don't have any data, measure a single space char for defaults
if (collection && collection.length > 0)
{
var prefSize:Object = calculatePreferredSizeFromData(collection.length);
var bm:EdgeMetrics = borderMetrics;
var textWidth:Number = prefSize.width + bm.left + bm.right + 8;
var textHeight:Number = prefSize.height + bm.top + bm.bottom
+ UITextField.TEXT_HEIGHT_PADDING;
measuredMinWidth = measuredWidth = textWidth + buttonWidth;
measuredMinHeight = measuredHeight = Math.max(textHeight, buttonHeight);
}
The calculatePreferredSizeFromData method mentioned by #defmeta (implemented in mx.controls.ComboBox) assumes that the data renderer is just a text field, and uses flash.text.lineMetrics to calculate the text width from label field in the data object. If you want to add an additional visual element to the item renderer and have the ComboBox take it's size into account when calculating it's own size, you will have to extend the mx.controls.ComboBox class and override the calculatePreferredSizeFromData method like so:
override protected function calculatePreferredSizeFromData(count:int):Object
{
var prefSize:Object = super.calculatePrefferedSizeFromData(count);
var maxW:Number = 0;
var maxH:Number = 0;
var bookmark:CursorBookmark = iterator ? iterator.bookmark : null;
var more:Boolean = iterator != null;
for ( var i:int = 0 ; i < count ; i++)
{
var data:Object;
if (more) data = iterator ? iterator.current : null;
else data = null;
if(data)
{
var imgH:Number;
var imgW:Number;
//calculate the image height and width using the data object here
maxH = Math.max(maxH, prefSize.height + imgH);
maxW = Math.max(maxW, prefSize.width + imgW);
}
if(iterator) iterator.moveNext();
}
if(iterator) iterator.seek(bookmark, 0);
return {width: maxW, height: maxH};
}
If possible store the image dimensions in the data object and use those values as imgH and imgW, that will make sizing much easier.
EDIT:
If you are adding elements to the render besides an image, like a label, you will also have to calculate their size as well when you iterate through the data elements and take those dimensions into account when calculating maxH and maxW.

Resources