« Multiline Buttons | Main | Icons in ComboBox »

DataGrid Footers

Ever notice that the DataGrid has headers but no footers? That's because footers have to be computed if they are totals or averages or whatever and we wanted to leave that for advanced datagrids in a future release.

Lately, due to some other work we're doing for 3.0, I've been wondering whether footers, headers and locked columns and rows should be part of the chrome instead of the main set of cells. If so, it is possible to create footers in 2.0.1 today by subclassing DataGrid and making a special Border that can put renderers in the border.

Here's an example that computes the average of the price column. Usual caveats apply.

Download source

Run demo

You can see how one could do column headers that way, or add headers to Tree for a TreeDataGrid.

Now, if the renderers are in the chrome, they are not part of the selection model, and they should probably not renderer data from the dataprovider per-se. So I'd be interested in knowing:

1) If you have used lockedColumns or lockedRows today, what have you used them for?
2) Did you want the lockedColumns or lockedRows to be selectable? Editable?
3) Did you do this to get more header rows/columns or to display data from the dataprovider?

Comments

Your post is very timely. I'm actually in the process of building a custom component that allows for fixed, hierarchical headings (such as those you'd see in a pivot table). For example, the column headings could show quarters grouped by years and the rows might be products grouped by categories. So far, I've got the row and column header hierarchy rendered in two separate mx:Grids (not DataGrid), taking advantage of rowSpan and colSpan (similar to Excel's merged cells). The guts of the table uses an mx:DataGrid with some specialized ItemRenderers that delegate rendering based on the data passed in (think: charts and aggregations).
I'm in the process of synching up the header GridItem dimensions with the corresponding data table cells and listening for scroll events on the main table to move the headers accordingly for the "fixed" effect.
Any suggestions or further code samples would be much appreciated.

-------------

I wish I had more time to keep pumping out examples, but we're in a busy period right now. Remember that the scrollEvent is propagated from the DataGrid so you can catch it and see what it did. Good luck.

Hi Alex,
Its a really nice custom control. Is there any way by which, footer columns can also be resized and re-ordered based on the header? What is the best approach to do this?

Regards,
Sumanth

------------

I think if you call invalidateDisplayLIst on the border it should reset to the new sizes/order.

-Alex

I really hope that locked columns, subheads/summary rows,and parent child relationships are considered in a future version of the datagrid. I've seen crazy implementations of datagrids within lists, single column rows with custom renderers that use different component depending on data and other crazy things, but they are universally UGLY. Seems to me these items are now minimum requirements for a grid component.

----------------

Well, I wouldn't say minimum, but yes, we are hard at work on those features.

-Alex

For some reason the vertical lines are not drawing in the footer for me. They work in your example, but not when I integrate the ideas into my code.

My hunch is that the overlay object is being obscured by something else, leaving a plain white area.

I see the labels though in the footer, which leaves me confused.

I realize it's hard to answer without looking at how I've used your ideas, but do you have any thoughts on what would make the overlay not show the vertical lines?

--------------------

These kinds of problems can be hard to debug. I normally break on a mouse event and use the debugger to walk down through the variables and introspect the display objects. For instance, the overlay variable should show how big it is and how many children it has. You can also turn off the visibility of the renderers in case they are hiding the overlay.

In the example, the line color comes from styles. You might want to debug through or trace out that you got the right color and boolean as to whether to draw them. Good luck.

I was unable to find out why the vertical lines were not drawing in the footer. I had already done some of the things that you suggested (line color/style, steping through to make sure it draws, etc), but not all of them.

Ultimately I decided not to use this solution for column aggregates because when the DataGrid has a horizontal scrollbar, the scrollbar appears between the content and the footer.

Instead I opted for putting the aggregates in a locked first row. Doing so was simple, modulo small behaviors like having to customize the sort functions to keep the aggregate row on top.

Ultimately, it would be very nice if the DataGrid supported the concept of locked aggregate rows that appeared either on the top or bottom of the grid.

--------------

Yeah, it won't work with horizontal scrollbar. It turns out that we're refactoring DataGrid for the next release to do something like this, but instead of adding them to the border, we'll be adding them to sub-"containers" in the DG.

Good luck,
-Alex

When can we expect feature of locked initial mutiple rows in Flex. Right now, lockedrow feature is locking the chrome but not the data. Therefore, when we sort new data appear in the locked rows.

-----------------------

I don't think we've planned for that. It sounds like you want the sort not to affect some number of initial rows? To me, that sounds like the initial rows aren't really part of the data, but other header like things. One way to handle that is to wrap the original data in another collection that prepends the initial rows. Maybe I'll get around to an example of that.

In the next major release, it might be easier to set such a thing up, but it won't be "automatic"

Hello,

I tried to adapt you method to a datagrid I am currently developping, however I have problems when setting a custom renderer for the footer.
The application goes into an infinite loop on the updateDislplayList method of the border object.
Did you also encounter that problem ?

---------------

Gee, it's been so long I don't remember. However this is a common problem where the renderer causes the parent to think its size changed. Check your measure functions to make sure they don't change their mind or set an explicit width. You may also need to trap invalidation calls on the renderer so it doesn't dirty the parent.

Well i'm using this component, but frecuently I get this error message when run the application.
TypeError: Error #1009: No se puede acceder a una propiedad o a un método de una referencia a un objeto nulo.
at ArenaComponents.FootDataGrid::FooterBorder/ArenaComponents.FootDataGrid:FooterBorder::updateDisplayList()
at mx.core::UIComponent/validateDisplayList()
at mx.managers::LayoutManager/validateClient()
at mx.core::UIComponent/validateNow()
at mx.controls.dataGridClasses::DataGridBase/mx.controls.dataGridClasses:DataGridBase::drawItem()
at mx.controls.dataGridClasses::DataGridBase/mx.controls.dataGridClasses:DataGridBase::makeRowsAndColumns()
at mx.controls::DataGrid/mx.controls:DataGrid::makeRowsAndColumns()
at mx.controls.listClasses::ListBase/mx.controls.listClasses:ListBase::updateDisplayList()
at mx.controls::DataGrid/mx.controls:DataGrid::updateDisplayList()
at mx.core::UIComponent/validateDisplayList()
at mx.managers::LayoutManager/::validateDisplayList()
at mx.managers::LayoutManager/::doPhasedInstantiation()
at Function/http://adobe.com/AS3/2006/builtin::apply()
at mx.core::UIComponent/::callLaterDispatcher2()
at mx.core::UIComponent/::callLaterDispatcher()

------------

Like I said, there could be bugs. You'll have to debug into it and see what went wrong and add code to protect against this error. Good luck,

Hi Alex,

Thanks for posting your code. I have used it and, as a Flex beginner, have a simple (I guess) question.

I have set the datagrid to be editable. Now I would like to have the average value to be updated each time any cell is changed by the user. I caught the itemEditEdit(event) method but don't then what to do :-(

Any help/comment would be appreciated if you have a couple of seconds to reply.

Thanks a lot,
Fabrice.

------------------------------

In theory, if you call invalidateDisplayLIst on the footer it should redraw and compute your new values.

Alex,

I have the same problem as Fabrice and would like to trigger the labelfunction to refresh itself on the itemEditEnd method. I called the invalidateDisplayList but it does not call the averageFunction. Any other thoughts?

-------------

Are you sure you called invalidateDisplayList on the footer? Calling it on the DataGrid won't have any effect. I don't have time to try it right now, but that should do it.

Hi Alex,
Thank you very much for this extremely useful code! But may I ask you to be a little more specific on your previous comment? How do we call invalidateDisplayList on the footer? My datagrid displays information which changes depending on the state of a ComboBox, and the footer should be updated accordingly.
Thank you very much for your answer!

--------------------

it should be border.invalidateDisplayList()

Border is protected so you have to be in the FooterDataGrid code, or add a method to FooterDataGrid so you can call it from the outside.

Hi Alex,

Thanks for all your great inputs.
I'm trying to crate 2 DataGrids that affect each other’s selectedItems.
The dataProvider is the same and presorted. The user select lines in one datagrid and this affects the selected items in the second DataGrid.
I've tried to use data binding on selectedItems, and on change (or click) event change the binding Array. This worked only in one direction. Any thoughts?

------------------

I would not use binding. Simply listen for change events from each DG and update the selectedIndex of the other DG.

Do you know of any way of having row headers in the same way there are column headers? Without putting the value in the dataprovider....

---------------

In the next release this is supported so you may want to get the beta and try it out.

For now, adding a dummy column with a labelFunction should allow you to have row headers.

The FooterDataGrid produces the error message "Property col1 not found on FooterDataGridColumn and there is no default value" when complex components such as checkboxes are used in an editable datagrid. The error message is being generated in SPSFooterBorder:updateDisplayList() method.













-----------------

CheckBox and lots of other components are not valid header renderers without some subclassing. You can see how I modified ComboBox in another blog post to be a header renderer and work from there.

Let me rephrase my question with regard to complex components in a FooterDataGrid.

I would like to create a DataGrid that has two columns:

1. An editable column with CheckBoxes.
2. A not editable column consisting of numbers.

I would like to add a Footer that does not have any complex components. The first column would be blank and the second column has the total of all the numbers.

Please advise on how I could tweak the FooterDataGrid.

------------

I would put a labelFunction that returns " " for the checkbox column.

Hi,

I'd like to see a locked row that can be editable.

It would be nice to add a row of filters.

How can I do that? Thanks.

--------------------

Are you saying the footer should be editable? You can certainly make the header be a ComboBox or TextInput by borrowing from the CheckBoxHeader example.

If you want the footer to be editable, it is doable but much harder. I'd go with headers.

Dear Alex,
I came across what seems to be a well hidden bug in your code. My Flex application takes the whole window and when I was resizing it, it used to crash with the following error:

TypeError: Error #1009: Cannot access a property or method of a null object reference.
at FooterBorder/FooterBorder::updateDisplayList()[C:\dev\spg\tamsDashboard\dev\flex\Dashboard;;FooterBorder.as:113]

After some time, I figured out that this was due to a rounding error: in the while statement of line 83, xx was 424.999999999994 and w - wv.right 425. At that point, the while loop should actually have stopped, but it wouldn't because of the rounding error. I fixed it by writing:

while (int(xx + 0.5)

instead.

Regards

Cedric

------------------

Like I said, these things are not guaranteed. Thanks for working through it.

I like what you have done.

I tried a number of things prior to posting, but haven't solved the problem. The grid I have is populated from an external data set like so;

grdVendorSummary.dataProvider = acVendorSummary;

The user has the ability to select a new set of records, and re-execute this line. The totals in the footer do not change, I am using the a labelFunction to populate them.

What would you recommend to resolve this?

Alex:

I must be dumb as dirt cause I can't figure out where/how to put the "border.invalidateDisplayList();" code into my app. I have tried putting it directly in the app, adding a function called refereshBorder() to FooterDataGrid, and calling it from my app, etc.

Based on your previous comments, I assumed the latter way would be correct, but I get a compile time error 1061 on invalidateDisplayList through IFlexObject.

If you would revisit this one more time I would appreciate it.

I need to be able to refresh some totals when they are calculated.

Paul

Alex:

Well, perhaps I am at least as smart as a good sandy loam. I was able to solve the problem, but I would like to believe there is a better approach. I created a function;

public function refreshBorder():void
{
this.updateDisplayList(0,0);
}

and put it into the FooterDataGrid class, and I call it whenever I need to refresh the data.

I don't really like the "this.updateDisplayList(0,0);" bit, and hope someone can tell me the right way of doing it.

Paul

After flailing about for a while longer, I realized that the code I posted just previously, while synchronizing the footer charmingly, seems to make the header disappear. I think I have resolved that problem as well, but it still looks nasty to me. I present it here hoping that someone will show me the right way to do it;

public function refreshBorder():void
{
if (this.measuredWidth + this.measuredHeight > 0)
this.updateDisplayList(this.measuredWidth,this.measuredHeight);
}
Paul

----------------

I think it should just be:

public function refreshBorder():void
{
this.invalidateDisplayList();
}

I thought I had tried that during my flailing about, but just to be sure, I tried again. I commented out my two lines;

if (this.measuredWidth + this.measuredHeight > 0)
this.updateDisplayList(this.measuredWidth,this.measuredHeight);

and replace it with;

this.invalidateDisplayList();

And while it did not give any errors, it didn't seem to work either.

I put my code back and it seems to work every time.

One question, is there a way to force this function to execute whenever the datagrid's data is updated?

Paul

------------

strange. A call to invalidateDisplayList should result in a call to updateDisplayList.

I would try hooking up a call to the border refresh in an override of invalidateDisplayList on the DataGrid

Worked like a charm. Keep up the great work.

Thanks!

Hello,

i use actionscript to generate a DataGrid:

var tableMain:DataGrid = new DataGrid();
var fieldsDataGrid:Array = new Array();
for ( var j:uint = 0; j fieldsDataGrid[j] = new DataGridColumn();
fieldsDataGrid[j].headerText = dataXML.child('table').child('position')[j].text;
fieldsDataGrid[j].dataField = dataXML.child('table').child('position')[j].feld;
fieldsDataGrid[j].itemRenderer = new ClassFactory(rendererButton);
fieldsDataGrid[j].sortCompareFunction = sortField;
}
tableMain.columns = felderDataGrid;
[...]

But i have problems to generate the FooterDataGrid with Actionscript! I've used these two examples, but it doesn't work:

var tableMain:FooterDataGrid = new FooterDataGrid();
var fieldsDataGrid:Array = new Array();
for ( var j:uint = 0; j fieldsDataGrid[j] = new FooterDataGridColumn();
fieldsDataGrid[j].headerText = dataXML.child('table').child('position')[j].text;
fieldsDataGrid[j].dataField = dataXML.child('table').child('position')[j].feld;
fieldsDataGrid[j].itemRenderer = new ClassFactory(rendererButton);
fieldsDataGrid[j].sortCompareFunction = sortField;
}
tableMain.columns = felderDataGrid;
[...]

var tableMain:FooterDataGrid = new FooterDataGrid();
var fieldsDataGrid:Array = new Array();
var fieldsFooterDataGrid:Array = new Array();
for ( var j:uint = 0; j fieldsDataGrid[j] = new DataGridColumn();
fieldsDataGrid[j].headerText = dataXML.child('table').child('position')[j].text;
fieldsDataGrid[j].dataField = dataXML.child('table').child('position')[j].feld;
fieldsDataGrid[j].itemRenderer = new ClassFactory(rendererButton);
fieldsDataGrid[j].sortCompareFunction = sortField;
fieldsFooterDataGrid[j] = new FooterDataGridColumn();
fieldsFooterDataGrid[j].footerColumn = fieldsDataGrid[j];
}
tableMain.columns = fieldsFooterDataGrid;
[...]

Have anyone a idea to solve my problem!

-----------------

What didn't work?

Following code doesn't work?

[...]
fieldsDataGrid[j] = new DataGridColumn();
[...]
fieldsFooterDataGrid[j] = new FooterDataGridColumn();
fieldsFooterDataGrid[j].footerColumn = fieldsDataGrid[j];
[...]
tableMain.columns = fieldsFooterDataGrid;

But I don't know why?
Thank you for your help

-----------

It's hard to tell from this w/o knowing what error you are getting. Please post your questions on FlexCoders as a few others have used this successfully and may be able to help as well. Post more code and what error you are getting.

You mentioned in response to an earlier post that Row Headers in a dataGrid are supported in Flex 3. I have downloaded the beta, but can not find how to enable Row Headers in a dataGrid. Can you please explain?

---------------

It should be in AdvancedDataGrid

There's been a couple of comments relating to problems dynamically using FooterDataGrid with AS. As far as I can see, the problem is that in your example you've wrapped DataGridColumns in FooterDataGridColumns - something they haven't replicated in the AS, and indeed cannot (? - note I'm fairly new to Flex) because we can't do .addChild on the FooterDataGridColumn.

My solution involved adding a footerLabelFunction property to the FooterDataGridColumn and editing FooterBorder to utilise this. Then the DataGridColumn's can be left out.

--------------

You can do anything in AS that you can do in MXML, so that can't be the issue. I think it just isn't clear how to get the footer to update. It should just be a call to border.invalidateDisplayList().

- actually I left footerColumn in so as to not have to play around with the renderer.

public class FooterDataGridColumn extends DataGridColumn
{

public function FooterDataGridColumn()
{
super();

footerColumn = new DataGridColumn();
footerColumn.headerText = this.headerText;
footerColumn.dataField = this.dataField;
footerColumn.labelFunction = footerLabelFunction;
}

public var footerColumn:DataGridColumn;

public var footerLabelFunction:Function;
}

Alex, thanks for this nice example. Unfortunately, I have a datagrid that needs to be scrolled horizontally.

I saw that Greg mentioned another approach that sounds like the way to go for me right now. But I don't know how to get in touch with Greg. Do you?

I am also currently looking into the AdvancedDataGrid in Flex3Beta2 and are disappointed that the summary row can only be used on hierarchical data grouping. I have a straight forward flat datagrid that needs to display aggregates for some columns.

I hope that the simple case (flat data) with aggregates will be implemented as well..

Thanks for any help you might be able to provide me .

----------------------

In theory, you can add a footer ListBaseContentHolder to a subclass of DG in 3.0, but I haven't proven it out.

the datagrid footer headertext can't be dynamically change after first initialized, even using data binding also wont be work!

-----------------

It should be a matter of calling invalidateDisplayList on the border

Thanks for the code!
I belive i can contribute with something too: Footer resizing.

Apply this changes to FooterDataGrid class

import mx.core.UIComponent;

...

private function footerColumnStretch(ev:DataGridEvent):void
{
(border as UIComponent).invalidateDisplayList();
}

public function FooterDataGrid()
{
super();

this.addEventListener(DataGridEvent.COLUMN_STRETCH, footerColumnStretch);
}

BTW: Not quite shure if the cast to UIComponent is the best choice but it works for me.

Regards, António Inácio

i modified it and used it onto a AdvancedDataGrid (flex3), everything is fine but except i attempts to refresh the footer border, althought i have call invalidateDisplayList to refresh it. doesn't it worked fine on ADG?

-------------

Haven't tried it on ADG. I would implement something completely different in Flex 3. I haven't had time to create the example yet.

i had one DropDown on form.On it's selectedIndexchanged i have to fill the dropdown in footer of the grid control
if any one had the solution for it then told me

------------------

Find a way so the dropdown in the footer knows about the one in the form. A subclass could have an extra property that gets filled in by the property bag on the classFactory that references the dropdown in the form.

i have used the footer. it really nice and help me lot. but when ever i define any renderer in datagridcolumn then code seem not to be working. Niether it give error message nor screen. it seem that code goes in an infinite loop. what is reason for that.

----------------

Not sure. You'll have to debug into it and see what is going on.

I have applied the footer to the ADG and it works great, besides some minor issues. (resize the columns so that you see only part of the last column, the vertical lines or the footer text are rendered outside the datagrid boundaries)

But i am curious about your comment "I would implement something completely different in Flex 3. I haven't had time to create the example yet."

Why and how would you do this ?

---------------

In Flex 3, we refactored DG to have separate containers for the locked columns, rows, etc. This should make it easier to add a footer container which would then show up correctly when the horizontalScrollBar is used.

I tried this great work with my own customized ItemRenderer. I set the ItemRenderer for each column of the DataGrid. Unfortunately it doesn't work. It seems that the value passed to the ItemRenderer is not set correctly in my case. I am sure that the ItemRenderer I use is working correctly before I use the FooterDataGrid.

So I am wondering has anybody been making the footer work successfully with customized ItemRenderer? Do we need to change any code?

Thanks. :)

---------------------

A custom renderer in the footer has to understand that the .data property is the FooterDataGridColumn and use that to display the correct information

I'm looking to extend the ADG to support a footer row at the bottom that behaves like the headers; I don't necessarily need to access data from the dataProvider (but I think this sort of functionality might be desirable in many cases). I'd rather not hijack the border if possible. You mention that you'd use a different approach for the ADG in Flex 3, perhaps using ListBaseContentHolder. Any chance you could give some more insight to point me in the right direction?

Many thanks,
Bryan

-------------------------
Alex responds:

I just posted a way to do this here: http://blogs.adobe.com/aharui/2008/03/flex_3_datagrid_footers.html

Hi Alex ,
nice article
i am a flex newbie can u tell me how to determine the boundaries of a cell in advance datagrid
i have been looking for it for quite some time now
i guess we shud be using local X Y coordinates but am not really sure how to use it
any help on it will be appreciated a lot
thanks & Regards,
Lisa

--------------------------------
Alex responds:

It is rare that you need to know the bounds of a cell from outside the cell. You might want to think about your design and approach to the problem. Normally renderers handle any special interaction and they know their bounds. If a subclass needs to help with interaction, usually that is handled in mouseEvent handlers and the event.target is used to determine the bounds.

You might have better luck posting your question on FlexCoders.

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)