Home > Component, Flex, code > Adding colspan to a datagrid

Adding colspan to a datagrid

Recently at my job, our User Interface guru came up with the design and interface that she wanted for our users. However, the design she came up with required adding a colspan to a datagrid. As many know who have tried it, this is not really a possibility right out of the box with a Flex DataGrid. It is apparently a possibility with the AdvancedDataGrid, but I have just never really liked the visuals of the datagrid, especially with how it outputs the rows of data…it seems very cluttered to me when grouping is added. Plus, I have a PagedArrayCollection that I wrote and use quite regularly, that (currently) does not play nicely with the AdvancedDataGrid (I think it has something to do with the grouping), and currently just works with the regular DataGrid. So, in order to accommodate the business and my PagedArrayCollection, rather than just have multiple rows of data in multiple columns, I came up with a solution to allow for the spanning of multiple columns, as many as is necessary.

I tried to figure out, visually and programmatically, I could mimic the HTML table colspan. My solution actually involves 2 datagrids + single datagrids as itemrenderers within the rows. As Flex recycles its itemrenderers, this appeared to be a pretty practical solution. My first datagrid is used solely for the headers. I made the height of the datagrid match the height of the headers of the first datagrid, so all that is visible is the headers themselves.

DataGrid Header

DataGrid Header

The code for this is as follows (notice the small height of the datagrid):

<mx:DataGrid id="headerDataGrid" x="70" y="40" width="780" height="23" dataProvider="{ dataProvider }">
	<mx:columns>
		<mx:DataGridColumn headerText="Name" dataField="name"/>
		<mx:DataGridColumn headerText="Location" dataField="location"/>
		<mx:DataGridColumn headerText="Exp" dataField="exp"/>
	</mx:columns>
</mx:DataGrid>

From here I created another datagrid, but this one contains only a single column. I also removed the headers from this datagrid as I will be using the datagrid headers from the previously created datagrid (headerDataGrid), so this one just looks like the rows from a datagrid.

DataGrid Body

DataGrid Body

Once I had these 2 datagrids, I put them right next to each other so it looks like they’re actually the same datagrid, but without vertical lines between the columns.

The code for the 2nd grid is:

<mx:DataGrid x="70" y="61" id="dgMain" width="780" height="444" showHeaders="false" dataProvider="{ dataProvider }" paddingTop="0" paddingBottom="0"
	variableRowHeight="true" selectable="true">
	<mx:columns>
		<mx:DataGridColumn headerText="All data" dataField="col1" itemRenderer="com.flexoop.utility.renderer.ColSpanRowRenderer" />
	</mx:columns>
</mx:DataGrid>

Both of these datagrids are bound to the same dataprovider. This makes manipulation of the arraycollection extremely simple. I had thought I would be writing my own sort and filter functions, overriding the headers, but I forgot that once these datagrids are bound to the same arraycollection, Flex automatically handles all of that and everything just works. Simple! :) The headertext and dataField are irrelevant in this datagrid as they will not be used. I set the variableRowHeight=”true” so the itemrenderer will show the multiple rows of data correctly.

As you can see from the above code, I created a ColSpanRowRenderer itemRenderer. This handles the final part of the colspanned datagrid…the actual colspan.

ColSpan Row Renderer

ColSpan Row Renderer

The code for this looks like:

<mx:DataGrid id="dgLocal" x="0" y="0" width="100%" showHeaders="false" height="23" dataProvider="{ data }" backgroundAlpha="0" selectable="false"
	borderSides="{ parentDocument.detailed ? 'bottom top' : 'bottom' }">
	<mx:columns>
		<mx:DataGridColumn headerText="Name" dataField="name" />
		<mx:DataGridColumn headerText="Location" dataField="location" />
		<mx:DataGridColumn headerText="Exp" dataField="exp" />
	</mx:columns>
</mx:DataGrid>
<mx:Text visible="{ parentDocument.detailed }" includeInLayout="{ parentDocument.detailed }"
	htmlText="This is where the detailed text will go.&lt;br /&gt;This is not formatted now but can be once it goes live" y="22" width="100%" />

This is all contained within a VBox with all padding set to zero to aid in visual layout, and to make it look like it is a row in the dgMainBody datagrid. The height of the datagrid forces it to just show the single row of data, with showHeaders=”false” again. I bind it to the “data” value that is passed in to the itemrenderer, then output the properties of that data object within the single row datagrid. I have a variable named “detailed” in the parentDocument (the page containing my header and main body datagrids). This allows me to show or hide the extra colspanned row of data. This was part of the requirement from the business, so I added it in. As the header, main body, and colspanrowrenderers all need to be tightly coupled, I was not so worried about referring to the “parentDocument”. Had this been something I was going to be moving around to other components, I would have tried to think of another solution. The “includeInLayout” attribute will completely remove the “Text” box from the view, so as not to take up any space when it is not visible. In order to prevent the user from selecting the single row datagrid item, I set ‘selectable=”false”‘, then in the outer datagrid (dgMainBody), I set ‘selectable=”true”‘. This will then allow the user to select the complete itemrenderer (both datagrid row and colspanned text field).

I had found that when detailed=’false’, the single datagrid rows did not alternate colors, which made sense as they were all the first row. In order to fix this problem, I set backgroundAlpha=”0″ in the colspanrowrenderer. This then used the colors of the dgMainBody datagrid for coloring the itemrenderer. The final visual part was setting the borderSides=”bottom” or “bottom top” depending on whether the view was detailed or not.

The final part of getting this to look and act like a regular datagrid was to adjust the column widths of all of the ColSpanRowRenderer rows when the header items were moved, as the rows are not actually linked directly back to the dgHeader. To catch these changes, I added a creationComplete=”init()” to the vbox in my ColSpanRowRenderer. Then in my init() function (along with a setColumnWidth function):

private function init():void {
	setColumnWidth();
	parentDocument.headerDataGrid.addEventListener( DataGridEvent.COLUMN_STRETCH, setColumnWidth );
}
private function setColumnWidth( event:DataGridEvent=null ):void {
	var _i:uint = 0;
	var _length:uint = parentDocument.headerDataGrid.columns.length;
	while ( _i < _length ) {
		dgLocal.columns[ _i ].width = parentDocument.headerDataGrid.columns[ _i ].width;
		_i++;
	}
}

At first, I was only changing the width of the column that was being stretched, but I found that the flash player must do some other computations on the other columns, so I just decided to loop over all of the columns and resize each one to match the header columns. I found that I had to add a ‘render=”setColumnWidth()”‘ also as some of my columns were a little screwy at times. This adds a slight adjustment after the page renders, but is not all that noticeable and fixes any column width issues I was having.

So the final product looks like this:

DataGrid With ColSpan

DataGrid With ColSpan

And code is DataGridColSpan.mxml and ColSpanRowRenderer.mxml

Hopefully I haven’t bored anyone to tears with my explanation, but I wanted to be sure that everyone understands my reasoning for doing everything that I did to the datagrids. Enjoy!

Categories: Component, Flex, code Tags: , , ,
  1. Alexander
    April 2nd, 2009 at 09:49 | #1

    Nice article !
    2 issues,however :
    1. have you tried to replace columns ? (Drag and drop 1 column upon another one) :-)
    2. What about expanding only single row ?

  2. Gareth
    April 2nd, 2009 at 11:12 | #2

    Geez! Cause me more grief, why dontcha :)

    1. You are correct. This is something I’ll need to fix. Shouldn’t be too bad though…just need to match up the column order “on column move”.

    2. Expanding a single row should work OK. Right now they are all tied to the external data value, but as they’re all itemRenderers internally, it should just be expanding based upon something internal (like a button click, etc.)

    Thanks for thinking of some other possibilities that need to be thought through.

  3. bruce
    April 8th, 2009 at 16:00 | #3

    This was an awesome jumping off point for me.

    I used 1 DataGrid, 1 List, and 1 smart ItemRenderer that has contained areas, 1 for the normal columns which are dynamically generated from the first Header DataGrid. Makes it easy for resize and moving of the columns.

  4. Gareth
    April 8th, 2009 at 18:33 | #4

    @bruce,
    Glad to be of assistance. Any improvements you have, feel free to send them along. I’m sure there are some things I could change.

  5. KR
    April 29th, 2009 at 07:57 | #5

    Hi, what about vertical scroll bar effect for this All-MixedDataGrid (Though there should be some collective name for your intelligent work)? Would the header remain in top when you scroll down just like the actual data grid?

    Thanks for sharing your ideas…

  6. Gareth
    April 29th, 2009 at 08:30 | #6

    Hey KR,
    Not sure I understand about the “vertical scroll bar effect”. A scroll bar will appear in certain rows if you do not make the row “tall” enough for the content within it (I just encountered this recently while testing). However, if you know roughly the height of the content, it will not have the scroll bar if you set the height with enough room. If you do a percentage height, I’ve found that it sets the height correctly on initial load, but has trouble maintaining that full 100% and will occasionally show the scroll bars when scrolling up and down the datagrid. I think this is a refresh issue with the way Flex calculates the height, so setting the value explicitly seems to work the best.

    And yes, the header remains at the top during the scroll.

    Thanks for reading.

  7. Vince
    September 15th, 2009 at 11:36 | #7

    Hi, how to bind “htmlText” with dgLocal’s dataProvider?

    if my dataProvider is like:

    I want htmlText’value bind with “text”.
    Thanks for your article.

  8. Vince
    September 15th, 2009 at 11:38 | #8

    Seems it can not show tag…

    ArrayCollection #
    [0] = Object name=”1″ location=”loc1″ exp=”333″ text=”123″
    [1] = Object name=”2″ location=”loc2″ exp=”333″ text=”456″

  9. Gareth
    September 15th, 2009 at 11:55 | #9

    Should just be (using my example) htmlText=”{ parentDocument.text }”, as long as the value is bindable.

  10. Oluwaseun
    December 8th, 2009 at 06:53 | #10

    Okay, I’m presently in need of help desperately. I need a user to double-clcik a datagrid row and the row is repalced with a vbox with the detail data of the row. e.g. if the row had first name, last name, age, sex, and so on. When the user clicks that row, the row is replaced with a vbox which is supposed to be the detail view. Anyone with any clue as to how to progress on this?

  11. Gareth
    December 8th, 2009 at 09:10 | #11

    Out of the box, you can’t replace the whole row with a vbox. You can set the datagrid to editable=”true” and it will allow the user to click or tab into each cell and make edits, but not the whole row.

  1. No trackbacks yet.