Hiding a tab in a Spark TabBar - apache-flex

I have a spark TabBar and I want to hide and show some elements of it from an external user input (namely a checkbox check)
I am having trouble changing the tabs visibility. They are currently always shown.
Does anyone have any idea? I have seen a getTabAt on the mx TabBar but the look of the tab is important and the requirement is for it to look like a tab bar rather than a button bar.
My code for the tabs and for hiding and showing is below:
<fx:Script>
<![CDATA[
import mx.containers.VBox;
import mx.controls.Label;
private function onCreationComplete():void {
var vbox1:VBox = new VBox();
vbox1.label = "Tab 1";
var lbl1:Label = new Label()
lbl1.text = "Panel1";
vbox1.addChild(lbl1);
dp.addChild(vbox1);
var vbox2:VBox = new VBox();
vbox2.label = "Tab 2";
var lbl2:Label = new Label()
lbl2.text = "Panel 2";
vbox2.addChild(lbl2);
dp.addChild(vbox2);
}
private function showTab(event:MouseEvent):void {
makeVisible(true);
}
private function hideTab(event:MouseEvent):void {
makeVisible(false);
}
private function makeVisible(vis:Boolean):void {
VBox(dp.getChildAt(0)).visible = vis;
VBox(dp.getChildAt(0)).enabled = vis;
VBox(dp.getChildAt(0)).includeInLayout = vis;
}
]]>
</fx:Script>
<s:VGroup>
<s:TabBar id="tabNavigator" width="100%" height="100%" dataProvider="{dp}"/>
<mx:ViewStack width="100%" height="100%" id="dp" borderStyle="solid"/>
<s:Button click="showTab(event)" label="show Tab"/>
<s:Button click="hideTab(event)" label="hide Tab"/>
</s:VGroup>
Any advice greatly received
Thanks

Yea, it's really nasty to not be documented such a trivial task. I come upon this post but i'm on Flex builder 4.6 and targeting mobile application (flex mobile). There is Spark TabbedViewNavigatorApplication which has TabbedViewNavigator as its child. The adobe forums and help show only how to hide the entire tabBar, which is really obvious, but not how to hide distinct options inside the tabBar.
Some places i visited suggested to remove items from the TabbedViewNavigator when you want to hide them, and then put them back again with removeItemAt, addItemAt combination ... but you really don't want to do that.
The first reason is that with removing items from tabBar you are removing ViewNavigators which form the View stack of certain section.
With removing one of the navigators, you are messing with this stack, and if your application tends to be on the complex side, or tends to grow that way, you will find yourself in trouble writing code that manages all those removal and adding processes, keeping in mind that your indexes in the navigators Vector in TabbedViewNavigator don't get messed.
Furthermore, if you do some caching, or custom handle navigator properties, restoring them to the state where they been in the moment you removed them from the tab stack will give you a lot of headache.
Following the solution from the original post, and with little experimenting the solution is quite simple:
// let say that the instance of Tabbed view navigator look like this:
// appRef is reference to TabbedViewNavigatorApplication (if you are in the main mxml, just put "this" as reference)
....
var myTabbedViewNavigator : TabbedViewNavigator = appRef.tabbedNavigator;
var index : int = 0; // we take item at index 0 for example (1st option in tabBar)
var dg : DataGroup = myTabbedViewNavigator.tabBar.dataGroup;
dg.getElementAt(index).visible = false;
dg.getElementAt(index).includeInLayout = false;
....
To show the tab again put true, and that's it, your navigators will still be there inside your TabbedViewNavigator but their visual representation in the tabBar will be invisible.

This function will hide a tab at a particular index. If you do not have the includeInLayout then the tab disappears and leaves a hole.
private function setTabEnabled(index:int, enabled:Boolean):void {
var theTab:UIComponent = tabNavigator.dataGroup.getElementAt(index) as UIComponent;
if (theTab)
theTab.visible = enabled;
theTab.includeInLayout = enabled;
}
}

Related

How do you create a top margin when printing a PrintAdvancedDataGrid

My datagrid prints starting at the very top of the page. I can't figure out how to move the datagrid down. I don't see anything in FlexPrintJob or PrintAdvancedDataGrid that will do this. Do I have to create a blank object to add to the top of my FlexPrintJob object?
Any help or a link that will help.
Thanks,
John
After trying a lot of examples form adobe and others that did not work at all for me, I figured out that I had to put the datagrid in a vBox where I could put a blank header above the datagrid. As all of the examples showed, this was best done with a vBox component.
Below is a working example of what I came up with, which is a bit different than the examples I found. In the vBox component you can do a lot of things to enhance the print job. In my case I added a title and date.
Hope this helps someone out there,
John
I am tusing a generic function to print any AdvancedDataGrid I might have in my application...
public function printAdvancedDataGridContents(advDG:AdvancedDataGrid, xmlListCollection:XMLListCollection, headerText:String):void
{
const printJob:FlexPrintJob = new FlexPrintJob();
if ( printJob.start() ) {
//create an instance of the FormPrintView_ADG component containing the datagrid
var thePrintView:FormPrintView_ADG = new FormPrintView_ADG();
//add the component to my application
FlexGlobals.topLevelApplication.addChild(thePrintView);
//load the datagrid
thePrintView.printDataGrid.dataProvider = xmlListCollection.copy();
//format the datagrid
thePrintView.printDataGrid.width = printJob.pageWidth-45; //set a left margin for the dg in a right justiified vBox
thePrintView.printDataGrid.height = printJob.pageHeight-73; //page adjusted for header title and date
thePrintView.printDataGrid.setStyle("fontSize", 8);
thePrintView.printDataGrid.columns = advDG.columns;
thePrintView.printDataGrid.setStyle("fontFamily", 'Times');
thePrintView.printDataGrid.setStyle("color", 000000);
//set the header text
thePrintView.headerText.height = 45;
thePrintView.headerText.width = printJob.pageWidth-20;
thePrintView.headerText.text = "\r"+headerText;
//add the first page to the print job
printJob.addObject(thePrintView, FlexPrintJobScaleType.NONE);
while (thePrintView.printDataGrid.validNextPage) {
// Move the next page of data to the top of the PrintDataGrid and add it to the printjob
thePrintView.printDataGrid.nextPage();
printJob.addObject(thePrintView, FlexPrintJobScaleType.NONE);
}
//print it and remove the component from my application
printJob.send();
FlexGlobals.topLevelApplication.removeChild(thePrintView);
}
}
Here is the vBox component I am using...
<?xml version="1.0"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
horizontalAlign="right"
creationComplete="init();">
<mx:Script>
<![CDATA[
[Bindable] private var date:String;
private function init():void{
date = new Date().toString();
date = df.format(date);
}
]]>
</mx:Script>
<mx:DateFormatter id="df" formatString="EEEE, MMMM D, YYYY"/>
<!-- this header can contain a title or be left blank to create a top margin -->
<mx:TextArea id="headerText" borderThickness="0" color="#000000" fontWeight="bold"
textAlign="center" textDecoration="none"/>
<!-- date label. set visible to false in the calling function if not needed -->
<mx:Label color="#000000" fontSize="8" fontStyle="normal" fontWeight="normal" text="{date}"/>
<!-- the data grid -->
<mx:PrintAdvancedDataGrid id="printDataGrid"/>
</mx:VBox>

SpriteVisualElement doesn't take mouse input

I'm trying to use one great example of using SpriteVisualElement for item renderers from here:
The issue i have is it's impossible to detect the mouse click event when click points to the area of the renderer which doesn't have any child components. For example: if I click on the textfield, then it works and i see the mouse even dispatched. If I click on an empty spot on the renderer then no mouse event is dispatched. I've tried mouseEnabled=true (which is true by default any way) with no luck. I see from the Flex doc:
the click event is inherited from InteractiveObject. So maybe this has something to do with the focus (see the tread at the and of the page). Looking for an explanation why InteractiveObject behaves that way. Thanks!
What is happening is that you do not have anything in the renderer to click on so click will fall through your renderer, by adding and image or graphic you are creating a clickable area.
The best thing to do is to tell the render that it does not have any mouseChildren which will then make it respond to any click on it.
change this method
public function TweetRenderer()
{
this.mouseChildren = false;
percentWidth = 100;
}
I think is getting a bit clear now. The mouseChildren is a property on DisplayObjectContainer. And as the following example shows DisplayObjectContainer doesn't dispatch any mouse click events, when click occur on the area which is not taken by any of it's children. This is unintuitive because DisplayObjectContainer has a click event inherited from InteractiveObject, so one (a newbe like me) would expect it to dispatch an event if i click on the container. Setting mouseChildren=false kind of flattens the DisplayObjectContainer, so the click event on any of the children will be dispatched having target as a container. But!!! This still assumes that you click on the child, not on the empty area. There is no way to dispatch it when click is done inside the area which is not taken by the child. This example shows this: If you click on either TextField or on fill, then even is dispatched with SpriteVisualElement as target. If you click elsewhere the event is not dispatched. I'm still unclear on why this is an intended behavior, taking into account the presence of click event on the DisplayObjectContainer. Maybe because containers don't meant to detect the mouse clicks at all, but rather their children are? This is a bit unintuitive to me.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
creationComplete="creationCompleteHandler(event)" >
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
protected function creationCompleteHandler(event:FlexEvent):void {
var tf:TextField = new TextField();
tf.text = "I'm inside SpriteVisualElement!";
tf.background = true;
tf.backgroundColor = 0xff0000;
tf.alpha = 0.75;
tf.selectable = false;
tf.width = 150;
tf.height = 25;
uic.addChild(tf);
uic.addEventListener(MouseEvent.CLICK, clickHandler);
uic.mouseChildren = false;
uic.mouseEnabled = true;
uic.graphics.lineStyle(1, 0xFF0000);
uic.graphics.moveTo(0,0);
uic.graphics.lineTo(uic.width,0);
uic.graphics.lineTo(uic.width,uic.height);
uic.graphics.lineTo(0,uic.height);
uic.graphics.lineTo(0,0);
uic.graphics.beginFill(0x00FF00,1);
uic.graphics.drawRect(12, 12, 178, 28);
uic.graphics.endFill();
}
protected function clickHandler(e:MouseEvent):void {
trace("click detected, target:",e.target);
}
]]>
</fx:Script>
<s:SpriteVisualElement id="uic" horizontalCenter="0" verticalCenter="0" width="200" height="50" />
</s:Application>

Flex 3 - Issues with textArea "editable" property

I'm having issues with the property "editable" of textArea control.
I have a component: OrderView.mxml and it's associated data class OrderViewData.as.
Orderview.mxml is inside a viewStack to enable navigation from a component to another.
In this particular case, OrderView.mxml is called by another component: SearchResult.mxml. I can thus navigate from SearchResult.mxml to OrderView.mxml, and back to SearchResult.mxml...
OrderView.mxml has textArea and textInput control, that have to be editable or nonEditable depending on the property var isEditable:Boolean from OrderViewData.as.
When the application is launched, isEditable = true. So, all textInput and textArea controls are editable the first time the user gets to OrderView.mxml. When the user clicks on the button order from OrderView.mxml, isEditable = false. When the user goes back to SearchResult.mxml, isEditable = true (again) --> Until here, everything works fine.
The thing is: when the user goes back to OrderView.mxml for the second time (and beyond), even if the property isEditable = true, textArea controls are still non editable... But the textInput controls are editable!
Here is some code for your comprehension:
OrderView.mxml
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#F3EDEC">
<mx:TextArea
id="contentTA"
text="{OrderViewData.instance.contentTA}"
enabled="{OrderViewData.instance.isEnabled}"
width="100%" height="51"
maxChars="18" styleName="ORTextInput"
focusIn="if(OrderViewData.instance.isEditable) contentTA.setSelection(0, contentTA.length)"
editable="{OrderViewData.instance.isEditable}"/>
<mx:TextInput id="contentTI"
text="{OrderViewData.instance.contentTI}"
width="40" height="18" maxChars="4"
styleName="ORTextInput"
change="contentTI_change()"
focusIn="if(OrderViewData.instance.isEditable) contentTI.setSelection(0, contentTI.length)"
editable="{OrderViewData.instance.isEditable}"/>
</mx:Canvas>
Am I missing something?
Did you make your isEditable variable [Bindable]?
Well, looks like anybody has more ideas...
I came up with a not really clean solution. But it works...
I used a init function in the show event of the component, where I create the control and add it to the proper parent. Thus, the mxml code written before has been deleted =)
This init function looks like this:
private function init():void
{
// contentTA
if(contentTA != null && parentBox.contains(contentTA))
parentBox.removeChild(contentTA);
contentTA = new TextArea;
contentTA.text = OrderViewData.instance.contentTA;
contentTA.enabled = OrderViewData.instance.isEnabled;
contentTA.percentWidth = 100;
contentTA.height = 51;
contentTA.maxChars = 50;
contentTA.styleName = "ORTextInput";
contentTA.editable = OrderViewData.instance.isEditable;
contentTA.addEventListener(FocusEvent.FOCUS_IN, focusIn);
parentBox.addChild(contentTA);
// same thing for all the other textAreas of my component
...
}

How to get Flex 3 ComboBox width to adjust based on bound dataProvider contents having changed?

In Flex 3, I've created a ComboBox within an MXML component similar to the following:
<mx:ComboBox id="comboBox" dataProvider="{_choices}" />
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
// etc...
public function get choices():ArrayCollection { return _choices; }
[Bindable]
private var _choices:ArrayCollection =
new ArrayCollection( [ { data: "ALL", label: "All" } ] );
// etc...
]]>
</mx:Script>
</mx:HBox>
In the parent MXML application, I'm modifying the contents of the choices property:
myComponentId.choices.removeAll();
myComponentId.choices.addItem({data: "NY", label: "New York"});
myComponentId.choices.addItem({data: "CA", label: "California"});
// etc...
The binding is working in that the ComboBox is automatically picking up the new contents added at runtime, however it is not adjusting its width. The initial width of the ComboBox is wide enough only to show the initial item "All" declared in the component. However, I want and would have expected the ComboBox to re-size automatically during binding to be able to show "California", but it isn't.
How can I get the ComboBox to update its width after I have added new wider labels to its dataProvider? Thank you!
You probably just need to call invalidateProperties(), invalidateDisplayList(), invalidateSize(), or some combination of the three (I'm something of a flex newbie myself), to force an update to the component's measurements after changing the data provider or its contents.
myComponentId.invalidateSize();
myComponentId.invalidateDisplayList();
myComponentId.invalidateProperties();
I would add a setter for choices and call validateNow() on the ComboBox at the end of the setter:
public function set choices(value:ArrayCollection):void
{
_choices = value;
comboBox.validateNow();
}
I encountered the same problem and neither of these worked for me. I managed to solve this by creating a new event handler
menuComboBox.addEventListener(ResizeEvent.RESIZE, updateListWidth);
The method called in this event simply resizes the dropdown.width property.
private function updateListWidth(event:ResizeEvent):void {
menuComboBox.dropdown.width = menuComboBox.width;
}

Flex - Problem with auto resizing datagrid

I'm trying to create a datagrid which will resize vertically to ensure all the renderers are displayed in full. Additionally,
Renderers are of variable height
Renderers can resize themselves
Generally speaking, the flow of events is as follows :
One of the item renderers resizes itself (normally in response to a user click etc)
It dispatches a bubbling event which the parent datagrid picks up
The DataGrid attempts to resize to ensure that all renderers remain visible in full.
I'm currently using this code within the datagrid to calculate the height:
height = measureHeightOfItems(0, dataProvider.length ) + headerHeight;
This appears to get an incorrect height. I've tried a number of variations including callLater ( to ensure the resize has completed so measure can work correctly), and overriding meausre() and calling invalidateSize() / validateSize(), but neither works.
Below are 3 classes which will illustrate the problem. Clicking the button in the item renderers resizes the renderer. The grid should also expand so that all of the 3 renderers are shown in their entirety.
Any suggestions would be greatly appreciated.
Regards
Marty
DataGridProblem.mxml (Application file)
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
xmlns:view="view.*">
<mx:ArrayCollection id="dataProvider">
<mx:String>Item A</mx:String>
<mx:String>Item B</mx:String>
<mx:String>Item C</mx:String>
</mx:ArrayCollection>
<view:TestDataGrid
id="dg"
dataProvider="{ dataProvider }"
width="400">
<view:columns>
<mx:DataGridColumn dataField="text" />
<mx:DataGridColumn itemRenderer="view.RendererButton" />
</view:columns>
</view:TestDataGrid>
</mx:Application>
view.TestDataGrid.as
package view
{
import flash.events.Event;
import mx.controls.DataGrid;
import mx.core.ScrollPolicy;
public class TestDataGrid extends DataGrid
{
public function TestDataGrid()
{
this.verticalScrollPolicy = ScrollPolicy.OFF;
this.variableRowHeight = true;
this.addEventListener( RendererButton.RENDERER_RESIZE , onRendererResize );
}
private function onRendererResize( event : Event ) : void
{
resizeDatagrid();
}
private function resizeDatagrid():void
{
height = measureHeightOfItems(0, dataProvider.length ) + headerHeight;
}
}
}
view.RendererButton.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Button width="50" height="50"
click="onClick()" />
<mx:Script>
<![CDATA[
public static const RENDERER_RESIZE : String = "resizeRenderer";
private function onClick() : void
{
this.height += 20;
dispatchEvent( new Event( RENDERER_RESIZE , true ) );
}
]]>
</mx:Script>
</mx:HBox>
You can achieve this goal with the AdvancedDataGrid using the following code. If you remove the this.headerHeight, it works for List as well, which makes me believe it should work for the regular old DataGrid.
override protected function measure():void
{
super.measure();
if ( this.dataProvider )
{
var newHeight : int = measureHeightOfItems( 0, this.dataProvider.length ) + this.headerHeight;
this.minHeight = newHeight;
this.height = newHeight;
}
}
To re size the Data-grid at runtime....use rowcount property and bind it with the dataprovider length. As the dataprovider is updated so will be the rowcount.
You can see the example how to use rowcount in flex here
To do things such as variable width and height cells/rows/column, as well as other "advanced" features - try extending the AdvancedDataGrid rather than the older, more boring DataGrid
For what it's worth, I never managed to resolve this issue. Code quickly became obsessed with dealing with edge cases.
I ended up throwing out the dataGrid approach, and wrote a solution using VBox & HBox to facilitate resizing.
for what its worth; you were supposed to use the variableRowHeight="true" property of the datagrid / advanced datagrid.
if it makes you feel better i created the custom VBox,HBox solution then after that discovered that its already done!! (snap!!)
good luck!
There's an underlying problem to your approach. ItemRenderers are intended to render the data item in a consistent manner, and if you throw out the current renderer and create a new one and set the data property of the new renderer, it should look identical to the previous one. You're not supposed to make transient changes to the renderer outside of the data member, and the renderer is going to behave in odd ways when you do that.
I suspect that if you changed your data model objects to contain a count property and data bound your renderer's height to the expression "{50 + data.count * 20}", and then made the button's click handler increment data.count by 1, that this would work properly. (My experience is that the DataGrid will redraw itself with the proper size for each row as long as the changes get done as a result of the data property, before it calls makeRowsAndColumns(). ) I haven't tried that approach for your example, so I can't say for sure that it actually works, but certainly that's the proper mindset for working with item renderers.
I have a little bit Dirty solution for such problem Try this one
private function ResizeGrid():void{
if ( this.dg.maxVerticalScrollPosition > 0 ){
this.dg.height +=5;
setTimeout(this.ResizeGrid,100);
}
}
And Call your function with some delay like follows
setTimeout(this.ResizeGrid,100);
This is dirty approach but it works for me :)
The following code in gridItemRenderer helped me:
protected function resize():void
{
column.grid.validateSize();
column.grid.validateDisplayList();
}
I have a solution for this problem, renderer changes need to sync with your dataprovider.
Then only measureHeightOfItems method will give accurate results.

Resources