I have a data grid that has a checkbox item renderer in a cloumn to allow row selections:
Main application:
<mx:DataGrid id="dg">
<mx:columns>
<mx:DataGridColumn id="ir" itemRenderer="renderers.RowCheckbox" />
<mx:DataGridColumn dataField="Name" headerText="Name" />
</mx:columns>
</mx:DataGrid>
Item renderer:
<-- RowCheckbox -->
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" horizontalAlign="center">
<mx:CheckBox id="chk"/>
</mx:HBox>
How can I get a handle to the item renderer / checkbox so that I may determine which rows are checked?
Just a word of advice: We had a similar problem in our application and we solved it by adding a "selected" property to the entities in the dataprovider of the datagrid. The selected property of the checkBox was then bound to the selected property of our entity. To know which ones were selected, we just looped over the entities in the dataprovider instead of the item renderers. After a lot of different approaches, this really was the best option.
If I remember correctly, the problem was that the itemrenderers did not remember the selected state correctly and the datagrid was completely messed up when you scrolled up and down. The wrong rows were selected after scrolling.
Another option would be to dispatch an event in the item renderer which bubbles up all the way to the control hosting the datagrid. You could then listen for these events and update your model to reflect the changes.
I ran into similar issues with the DataGrid and multiple item renderers and the reuse of item renderers when scrolling. In order to access DataGrid item renderers I extended the DataGrid. My first thought was to use the indicesToIndex() followed by indexToItemRenderer(). Unfortunately these methods didn't do what I expected so I added the indicesToItemRenderer() method:
package com.whatever.controls
{
import mx.controls.DataGrid;
import mx.controls.listClasses.IListItemRenderer;
public class CustomDataGrid extends DataGrid
{
public function CustomDataGrid()
{
super();
}
public function indicesToItemRenderer(rowIndex:int, colIndex:int):IListItemRenderer
{
var firstItemIndex:int = verticalScrollPosition - offscreenExtraRowsTop;
if (rowIndex < firstItemIndex ||
rowIndex >= firstItemIndex + listItems.length
)
{
return null;
}
return listItems[rowIndex - firstItemIndex][colIndex];
}
}
To resolve the reused item renderers when scrolling issue, refer to this article:
http://www.adobe.com/devnet/flex/articles/itemrenderers_pt1.html
It boils down to overriding the data setter and storing properties in data. For example, I had one column using a CheckBox itemRenderer and another column using ComboBox. For both I listen for the change event and store selected, selectedIndex, etc in data whenever properties change and override the data setter to set those properties:
override public function set data(value:Object):void
{
if (value != null)
{
super.data = value;
if (data.hasOwnProperty('selected') && data.selected)
{
selected = data.selected;
}
else
{
selected = false;
}
}
}
You can use the indexToItemRenderer() method exposed by all subclasses of ListBase.
For example:
private function someFunction(index:int):void
{
var rowCheckbox:RowCheckbox = dg.indexToItemRenderer(index) as RowCheckbox;
trace(rowCheckbox.chk.selected.toString());
}
... where index represents the index of the DataGrid item whose "chk" property you want test.
In the ItemRenderer, try putting Checkbox Component in a VBox..resolves the scrolling issue.
Related
As I am somewhat new to Flex I may be missing something fundamental here. I have a Spark List container whose dataProvider is bound to a result set coming back from a RemoteObject call. Pretty standard stuff.
<s:List id="list" dataProvider="{model.stuff}" width="100%" height="100%"
selectedIndex="#{selectedSlider.value}"
itemRenderer="{stuffRenderer}">
</s:List>
The selectedIndex is associated with an HSlider, but that is not the problem. My issue is that I would like to automatically select a certain "preferred" element from the list (only initially...to guide the user).
I tried to do that in a creationComplete event but my data hadn't shown up yet...setting selectedIndex didn't work...it was too early.
What's the right way to do this?
private function findAllUsers_resultHandler(e:ResultEvent):void
{
list.dataProvider = new ArrayCollection(e.result as Array);
if(firstTry)
{
list.selectedIndex = 0;
firstTry = false;
}
}
spark.components.List has spark.components.SkinnableDataContainer in its class hierarchy which dispatches a dataProviderChanged event whenever the dataProvider changes. Unfortunatly there is no [Event] metadata in SkinnableDataContainer that allows using this event in MXML. So, you'll need to create your own custom component that extends List.
package
{
import spark.components.List;
[Event(name="dataProviderChanged", type="flash.events.Event")]
public class MyList extends List
{
public function MyList()
{
super();
}
}
}
By using your custom component you can add an event listener for dataProviderChanged and update your selectedIndex accordingly.
<ns1:MyList id="list" dataProvider="{model.stuff}" width="100%" height="100%"
dataProviderChanged="selectedIndex = selectedSlider.value"
selectedIndex="#{selectedSlider.value}"
itemRenderer="{stuffRenderer}">
</ns1:MyList>
BTW: This works with other List-based components (like DropDownList) too.
I believe it should work if you just set the initial value of the slider to the index you want to be selected at the beginning.
Something like this:
<s:List dataProvider="{yourData}" selectedIndex="{hSlider.value}" /> <s:HSlider id="hSlider" minimum="0" maximum="{yourData.length - 1}" stepSize="1" value="theIndexYouWantAsInitial" liveDragging="true" />
That should work.
HTH
FTQuest
In my Flex app I am using a repeater to show a report on my database data. On this report the user can "drill-down" on the data to show more detail. In order to make this easier on the eye I have a header label and then a datagrid within the repeater.
Whilst this works perfectly, because the dataprovider for the datagrid comes from an array in the repeaters dataprovider, it is causing the following warning:
Data binding will not be able to detect assignments to "report"
The warning is for this line:
<mx:DataGrid id="dgReport" dataProvider="{rptReport.currentItem.report}" rowCount="{rptReport.currentItem.report.length}">
Below is my code, if anyone has any suggestions for how I can get rid of the warning/do this properly they will be most welcome!
<mx:Script>
<![CDATA[
[Bindable] private var reportProvider;
private function report_Handler(event:ResultEvent):void {
// Temp variables
var currentHeader:String = "";
var previousHeader:String = "";
// Retrieve PHP array
var reportPHP:Array = ArrayUtil.toArray(event.result);
// Create Flex array
var reportFlex:Array = [];
var reportFlex_dataGrid:Array = [];
// Loop through PHP array
for(var i:int = 0; i < reportPHP.length; i++) {
// Retrieve current header
currentHeader = reportPHP[i].header;
// Clear array
if (currentHeader != previousHeader) {
reportFlex_dataGrid = [];
}
reportFlex_dataGrid.push({column1:reportPHP[i].column1, column2:reportPHP[i].column2, column3:reportPHP[i].column3});
}
// Add to repeater array
if (currentHeader != previousHeader) {
// Add to array
reportFlex.push({header:reportPHP[i].header, report:reportFlex_dataGrid});
}
// Store previous headers
previousHeader = reportPHP[i].header;
// Add to combobox data provider
reportProvider = new ArrayCollection(reportFlex);
}
]]>
</mx:Script>
<mx:Repeater id="rptReport" dataProvider="{reportProvider}">
<mx:VBox>
<mx:Spacer height="5"/>
<mx:Label id="lblHeader" text="{rptReport.currentItem.header}"/>
<mx:DataGrid id="dgReport" dataProvider="{rptReport.currentItem.report}" rowCount="{rptReport.currentItem.report.length}">
<mx:columns>
<mx:DataGridColumn headerText="Column1" dataField="column1"/>
<mx:DataGridColumn headerText="Column2" dataField="column2"/>
<mx:DataGridColumn headerText="Column3" dataField="column3"/>
</mx:columns>
</mx:DataGrid>
</mx:VBox>
</mx:Repeater>
Data binding will not be able to detect assignments to "report"
Your dataProvider is rptReport.currentItem.report. Of this, rptReport, being an mxml element, is Bindable. The currentItem property of the Repeater component is also declared to be Bindable. The report property of the current item is not bindable - current item itself is just an object. Through this warning Flex is saying that if you alter the report of an already assigned object to something else, it won't be automatically reflected in the data grid.
In most cases you can safely ignore this type of warnings.
When you say x="{a.b.c.d}" in mxml, the guarantee is that flex will detect changes made to any of the four items in the chain (a, b, c and d) and update the value of x. In other words, x will change when a or a.b or b.c or c.d is changed. For this to work, Flex expects that all those four are declared bindable. If it finds any of these items to be not bindable, it will throw a warning. A property is bindable if it was declared using mxml or if it was declared with the [Bindable] metadata tag in ActionScript.
In most cases, one would be interested only in the changes to a or a.b. In your example, changes happen only when HTTPService is resend, in which case the dataProvider itself will change.
Dude, a little off-topic, but having a grid in a repeater sounds really busy. If you want to have a drill-down, pop it up or put it in a pane that's only visible in that mode.
Visually, the repeater is a pattern which the user can internalize. A grid inside that pattern is a lot harder to deal with. Scrolling the grid vs. scrolling the repeater will likely be frustrating, let alone Tab navigation.
Logistically, you are creating a lot of in-memory UI. I would worry about performance.
Consider using a List with a custom Item renderer instead of a repeater. I still would not put a grid in there, but it's worth the effort.
Cheers
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;
}
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.
When I am editing a cell in a dataGrid, the changes are not applied to the dataProvider until I finish editing. Is there a way that I can make the changes appear in the dataProvider whilst editing?
I would assume that the way of doing this would be to subclass the editor I am using, in this case NumericStepper, but I don't know how I would go about it.
Is there some sort of event that I need to trigger?
if you create your own itemEditor/itemRenderer you can do something like:
<mx:TextInput xmlns:mx="..." change="onChange(event)"
implements="mx.controls.listClasses.IDropInListItemRenderer">
<mx:Script>
<![CDATA[
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
[Bindable("dataChange")] private var _listData : BaseListData;
public function get listData():BaseListData
{
return _listData;
}
public function set listData( value : BaseListData ) : void
{
_listData = value;
}
private function onChange(event:Event):void
{
this.data[ (listData as DataGridListData).dataField ] = this.text;
}
]]>
</mx:Script>
</mx:TextInput>
hope this helps.
If you need to reference anything outside of an itemeditor the way I have done this is through outerDocument.somePublicVar.
So if you need to reference the dataprovider of the datagrid you are editing you can update the var you have binded to the datagrid but it must be public (i think) or you can edit the datagrids dataprovider directly.
Within the item editor you can just catch the change event and update the value in the dataprovider from there. But remember that the end edit item event will be thrown and if your doing any processing in there that might mess with your data provider as well.