CheckBox header renderer with HierarchicalCollectionView - apache-flex

I've gotten a checkbox header renderer to work well with flat DPs, but a
hierarchical collection view is another story. On click, I want it to select all
checkboxes in a given column. Here is my code:
var dp:HierarchicalCollectionView = _dataGrid.dataProvider as
HierarchicalCollectionView;
var testDp:GroupingCollection = dp.source as GroupingCollection;
var rawDp:ArrayCollection = testDp.source as ArrayCollection;
for(var i:int=0 ; i < rawDp.length ; i++){
rawDp[i][_dataField] = cb.selected;
}
It selects all checkboxes on the 2nd level of data, but doesn't select the top
level of data. What am I missing here? I can't seem to find it.
Any tips are greatly appreciated. Thank you.

For hierarchical data you have to use a cursor which iterates over all levels of the hierarchical data.
var dp:IHierarchicalCollectionView = _dataGrid.hierarchicalCollectionView;
var cursor:IViewCursor= dp.createCursor();
while (!cursor.afterLast)
{
cursor.current[_dataField] = cb.selected;
cursor.moveNext();
}
Howerver, this works only with nodes that have previously been opened. So either expand all nodes with _dataGrid.expandAll() (you can collapse them afterwards since the nodes only have to be opened once) or iterate your hierarchical data manually:
function setCheckBoxValue(children:ArrayCollection, value:Boolean):void
{
for each (var child:Object in children)
{
if (child.hasOwnProperty("children") && child["children"])
setCheckBoxValue(child["children"], value);
child[_dataField] = value;
}
}
var myDataProvider:HierarchicalData = /* your data provider */;
// Call it like this...
setCheckBoxValue(myDataProvider.source, cb.selected);
Update: To answer your second question...
Create a new CheckBoxColumn which extends AdvancedDataGridColumn. You can use it to preconfigure your headerRenderer and itemRenderer.
In your custom item renderer you get hold of your column like this:grid = AdvancedDataGrid(listData.owner);
column = grid.columns[listData.columnIndex] as CheckBoxColumn;
Do the same in your header renderer.
Whenever the CheckBox value in one of your item renderers changes dispatch a event through your column. Something like: column.dispatchEvent(new Event("checkBoxValueChanged"));
Your header render should add an event listener to the column for the "checkBoxValueChanged" event (or whatever you call it). Whenever that event is fired loop through your data provider and update the headers CheckBox accordingly.
In theory that should work. HTH

Related

Flex 3: dynamically created checkbox column in datagrid - data population issues and event listener

I have a datagrid control in my mxml file:
Now in my AS file, in the result function when obtaining data from DB, I can create columns dynamically. Let's say I create 1 column (client name):
private function GetDebtors_Result(event:ResultEvent):void
{
var arrayCol:Array = new Array();
var xmlSrc:XML = new XML("<main></main>");
var xmlTmp:XML;
var colClientname:DataGridColumn;
//Build an XML from DB data received (could as well use "event.result" directly to act as dataprovider for the datagrid, but I needed to break it down here)
for each(var o:Object in event.result)
{
xmlTmp = <row>
<CLIENTNAME>{o.CLIENTNAME}</CLIENTNAME>
</row>;
xmlSrc.appendChild(xmlTmp);
}
//Create the column CLIENTNAME
colClientname = new DataGridColumn("CLIENTNAME");
colClientname.headerText = "Client Name";
//Add the newly created column in the "Column" array.
arrayCol.push(colClientname);
//Use the "Column" array to set the columns of the datagrid.
dgSearch.columns = arrayCol;
//Populate the datagrid with the XML data.
dgSearch.dataProvider = xmlSrc.row;
}
This works well.
Now comes the issue: I need to add a second column which will contain checkboxes. They will be selected or deselected depending on the data from database. I'll show how I've done it by updating the same "GetDebtors_Result" function as above (added lines are commented as "// ADDED"):
private function GetDebtors_Result(event:ResultEvent):void
{
var arrayCol:Array = new Array();
var xmlSrc:XML = new XML("<main></main>");
var xmlTmp:XML;
var colClientname:DataGridColumn;
var colSel:DataGridColumn; // **ADDED**
//Build an XML from DB data received (could as well use "event.result" directly to act as dataprovider for the datagrid, but I needed to break it down here)
for each(var o:Object in event.result)
{
xmlTmp = <row>
<CLIENTNAME>{o.CLIENTNAME}</CLIENTNAME>
<SELECTED>{(o.SELECTED == 1)?true:false}</SELECTED> //**ADDED**
</row>;
xmlSrc.appendChild(xmlTmp);
}
//Create the column CLIENTNAME
colClientname = new DataGridColumn("CLIENTNAME");
colClientname.headerText = "Client Name";
//Create the column SELECTED
colSel = new DataGridColumn("SELECTED"); // **ADDED**
colSel.headerText = ""; // **ADDED**
colSel.itemRenderer = new ClassFactory(mx.controls.CheckBox); // **ADDED**
colSel.dataField = "SELECTED"; // **ADDED**
//Add the newly created column in the "Column" array.
arrayCol.push(colClientname);
//Add the "selection" column in the "Column" array.
arrayCol.push(colSel); // **ADDED**
//Use the "Column" array to set the columns of the datagrid.
dgSearch.columns = arrayCol;
//Populate the datagrid with the XML data.
dgSearch.dataProvider = xmlSrc.row;
}
Problem #1: The checkbox column appears, I can check and uncheck the checkboxes, but they are not checked/unchecked respective to DB data when loaded.
Problem #2: How do I associate a function to the checkboxes, for instance one which will update the XML so that I can save the new data to the DB?
Anybody got the solution? Thank you in advance.
Seems to be a very old question that I saw today.
Hopefully you would have found out the solution by now, just in-case if anyone has same problem:
While adding a checkbox to column- just instantiate it 1st:
var chkTempCheck: Checkbox = new CheckBox();
Then set all the properties required:
chkTempCheck.selected = o.dBColumnToDecideCheckUnCheck
here 'o' is the Object you are using from event.result.
This will work for sure!
The initial scenario was: all columns were defined in the mxml file. The checkbox column used itemrenderer and was working properly. I was using the same datagrid in 3 different cases - only thing was that some columns were set visible/invisible depending on the 'views'. The problem was when shifting 'views' and populate the grid and shift 'views' again, the column widths kept increasing exponentially. I excluded the checkbox column and everything worked fine; columns widths were ok. I included the checkbox column back and tried setting the column widths in AS file and the column-increasing-exponentially problem was fixed but the column widths were never the same when populating grid in view A and when populating grid in view B. ...So I ventured out in trying to set the columns in AS file just after obtaining DB data. Hope you can find yourself in those situations. Thanks for helping.

Ensure Spark DataGrid item is visible

Is there a way to make sure the selected item is visible in the Spark DataGrid?
.
Context
I have a data grid bound to an array collection. I receive remotely a service that gives me a ID (string) of an object that is in the collection. Using just the string I loop through the collection to find the item that matches the string. I find the object by it's ID. Now I have the object I want to select in the datagrid. I can set the
dataGrid.selectedItem = object;
Now I need to make sure it's visible. I do not have the row or column index.
.
Update
Using the answer below I've complimented it with this function:
/**
* Ensures the item is visible (for spark data grid)
**/
public function ensureItemIsVisibleInSparkDataGrid(datagrid:spark.components.DataGrid, item:Object):void {
var list:IList = datagrid.dataProvider;
var length:int = list.length;
var itemFound:Boolean;
var object:Object;
var index:int;
for (var i:int;i<length;i++) {
object = list.getItemAt(i);
if (object==item) {
itemFound = true;
index = i;
break;
}
}
if (itemFound) {
datagrid.ensureCellIsVisible(index);
}
}
Yes, it's called ensureCellIsVisible(). You need to know that row and column of the item in question. To get this to work you'd need to listen for the selectionChange event then calculate the row and column of the currently selected item.

How to update content in Row after selection in dojo datagrid

I have created one dojo datagrid. Every column has a formatter attached to it. When grid is generated the formatter is called. Now I want it so that if a user selects any row the formatter will be called and some strings should be attached to the selected row's column element.
Like grid is like this :
COLUMN
-------
a
b
c
and now user selects the 2nd row, the grid should change to :
COLUMN
-------
a
b SELECTED
c
Currently I implemented it like this :
if(this.grid.selection.selectedIndex !== -1){
retrun value + "SELECTED";
}else{
return value;
}
Can you please suggest a some good way of doing this? Please note that "SELECTED" string should not be added to the grid store.
The formatted is not hooked into clicking / selection of rows. It is solely performed when the contents (value) of a cell is set. Instead you'd want to move focus over to onRowClicked - an event on the grid component. It works like this:
grid.onRowClick = onRowClickHandler;
I wouldnt know which of following samples would put you closest to your goal but onRowClickHandler could be setup as such:
function onRowClickHandler(evt) {
var rows = this.selection.getSelected();
// perform cell rendering here
dojo.forEach(rows, function(row) {
// this row is an item though.. you will have row._O as its index
});
}
OR
function onRowClickHandler(e) {
var cellClicked = this.focus.cell
cellClicked.formatter();
}
However you may find that there are not much references to the viewable data anywhere in the grid component.. You could use following query selectors to find cell data and update the viewed html by calling formatter on each value. You would need to capture a previous selection for 'teardown' of your custom setting of values though.
var prevSelectedRows = [];
function onRowClickHandler(evt) {
var idx = this.selection.selectedIndex,
rawRow = dojo.query(".dojoxGridRow:nth-child("+(idx+1)+")", this.domNode)[0],
self = this;
// perform resetting of viewable values
dojo.forEach(prevSelectedRows, function(raw) {
dojo.query('.dojoxGridCell', raw).forEach(function(cellDOM, i) {
cellDOM.innerHTML = cellDOM.innerHTML.replace("SELECTED", "");
});
});
prevSelectedRows = []; // reset prev selection
// look into grid.view.content for methods on this
// perform setting of viewable values (SELECTED)
dojo.query('.dojoxGridCell', rawRow).forEach(function(cellDOM, i) {
// this function might be of interest, lets see how it looks in console
console.log(self.layout.cells[i].formatter);
cellDOM.innerHTML = cellDOM.innerHTML + "SELECTED"
});
prevSelectedRows.push(rawRow);
}

How to sort AdvancedDataGrid with hierarchical data?

I have an AdvancedDataGrid with a HierarchicalCollectionView as its dataProvider. When I view the grid with the dataset I'm working with, and click the header of the column I wish to sort on, everything works perfectly. It sorts it hierarchically exactly how I would expect it to.
What I want to do now is have the grid already be sorted when it is shown to the user. Is there a way to do this programatically? I can't for the life of me figure out how to do this, and clearly it's possible since the AdvancedDataGrid has this built in.
Edit - BTW, I've tried this:
var myData:HierarchicalCollectionView = new HierarchicalCollectionView(theDataSource);
// Works fine using only the line above and clicking the header to sort.
// This is the part that I tried adding:
var sort:Sort = new Sort();
sort.fields = [new SortField("startDate")];
myData.sort = sort;
myData.refresh();
This appears to do something as far as sorting goes, but it doesn't sort it in the same way as clicking the column header. "startDate" is a property of an object in theDataSource by the way.
Looks like you want to sort dates. Sort can't do that out of the box. You have to use a compareFunction.
If your objects are of type Date it's quite easy:
var sortField:SortField = new SortField("startDate");
sortField.compareFunction = ObjectUtil.dateCompare;
In case your column contains dates as strings you'll have to parse them first (code example from http://blog.flexexamples.com/2007/08/12/sorting-date-columns-in-a-datagrid/):
private function date_sortCompareFunc(itemA:Object, itemB:Object):int
{
/* Date.parse() returns an int, but
ObjectUtil.dateCompare() expects two
Date objects, so convert String to
int to Date. */
var dateA:Date = new Date(Date.parse(itemA));
var dateB:Date = new Date(Date.parse(itemB));
return ObjectUtil.dateCompare(dateA, dateB);
}
var sortField:SortField = new SortField("startDate");
sortField.compareFunction = date_sortCompareFunc;
Then just use the sortField like you did in your example. That should work fine.
You can create a new advanced data grid sort event and dispatch it on the grid after the hierarchical data is set on it (unfortunately I've had to use a callLater to give the grid time to deal with the collection internally it seems assignments to the dataProvider of the ADG are sometimes asynchronous)
var advancedDataGridEvent : AdvancedDataGridEvent = new AdvancedDataGridEvent(AdvancedDataGridEvent.SORT, false, true);
advancedDataGridEvent.columnIndex = columnIndex;
advancedDataGridEvent.dataField = dataField;
dispatchEvent(advancedDataGridEvent);
This code is from an extension of ADG so you would want the dispatchEvent to actually be on your instance of the grid if you're not creating an extension.
Also a note from the code:
//setting sortDescending=true on a column does not work as expected. so, until a solution
//is found, this works just as well. the event that is dispatch just tells the column
//to reset. so, one resorts ascending (the default), while a second resorts descending.
//however, this second is only dispatched if defaultSortDesc is true on the grid.
if (defaultSortDesc)
{
dispatchEvent(advancedDataGridEvent);
}
It dispatches the event twice to flip the sort.

How do I programmatically associate a RadioButton with a RadioButtonGroup in ActionScript3?

I have a UI component that, for various reasons, I have to construct programatically. The component is a table of radio buttons grouped by column.
Right now, I'm constructing the column groups like so:
private function createGroupsForItemList(items: XMLList): void {
for each (var item: XML in items) {
var rbGroup: RadioButtonGroup = new RadioButtonGroup();
groups[item.#level.toString()] = rbGroup;
}
}
I'm trying to associate the RadioButton instances with the column groups like so:
private function createValueControl(item: XML): UIComponent {
var control: RadioButton = new RadioButton();
control.label = "";
control.group = groups[item.#level.toString()];
control.addEventListener(Event.SELECT, updateSelection);
return control;
}
I can see in the debugger that the control has an association to the group:
control.group == groups[item.#level.toString()]
However, I can see equally that the group does not know anything about the control:
group.radioButtons.length == 0
I imagine that this is because the setter for group in RadioButton is a dumb setter; all it does is copy to the variable, which doesn't do the magic that groupName does. However, I can't seem to find the value I should use to set the RadioButton.groupName property correctly.
So, in short, I'm stumped on how to get these bits to talk to each other. How do I do this?
-- EDIT --
It turns out that I can have the groups created and associated simply by setting the groupName property, but I can't get at the group to set up a selection listener; the group is NULL immediately after the setting process, which means that the second line below throws the Flex equivalent of an NPE:
control.groupName = groupNameForLevel(item);
control.group.addEventListener(Event.SELECT, updateSelection);
First instinct is that this issue has to do with invalidateDisplayList and when and how that is called. Of course, since issues related to that function are behind a number of Flex's quirks, I may just be scapegoating.
This is not the answer to your question per se, but it seems like it might actually work as an alternate solution.
RadioButtonGroups will initialize based on a IFlexDisplayObject. This means that you can do something like:
var c:HBox = new HBox();
var rbg:RadioButtonGroup = new RadioButtonGroup( c );
// do stuff with rbg.
c.addChild( new RadioButton() );
The problem is that it may not be the most practical answer, but it has the decided benefit of being a workable solution.
Setting groupName should work.
All I can suggest is to step through the group() getter of the RadioButton component and see where exactly it is failing. Are you programmatically creating the group too? If that's the case, maybe it isn't initialized fully yet.

Resources