Welcome, Guest. Please login or register.
Did you miss your activation email?
05/21/12, 03:05
Home Help Search Login Register
News: Parsley Flex framework review featuring quiz application, in our Flex frameworks series
Flex SDK 4.5 mobile roadmap: begin with your mobile development
Swiz Flex framework review featuring quiz application
New homepage we release our new Homepage, take a look ...

+  Flash-db
|-+  The Library
| |-+  Technical Reference Area (Moderators: Flash-db, Musicman, BurtonRider1983, vesa kortelainen, Ronald Wernecke, Jorge Solis)
| | |-+  DataGrid and ComboBoxCellRenderer
0 Members and 1 Guest are viewing this topic. « previous next »
Pages: [1] Print
Author Topic: DataGrid and ComboBoxCellRenderer  (Read 21639 times)
Jason
Server what's that
*
Posts: 6



View Profile Email
« on: 10/25/04, 16:10 »

I created a CellRenderer class for displaying ComboBoxes in an editable DataGrid. When the user resizes one of the columns of the DataGrid, the ComboBox in the first row disappears.

So, I went on a search for someone else's version of a ComboBox CellRenderer, which brought me to this site. I looked at the tutorial here:

http://www.flash-db.com/Tutorials/cellrenderer/

Unfortunately, the ComboBoxCellRender on the tutorial has exactly the same bug. To reproduce the bug, do the following:

1) Go to the above link
2) Open, then close the first ComboBox in the second column on the DataGrid
3) Resize that column by selecting the divider just to the right and moving it


Has anyone determined the cause of this problem, or found a possible solution?

Any help is much appreciated.

Thanks,
~j.
Logged
John Nolan
Server what's that
*
Posts: 14



View Profile Email
« Reply #1 on: 10/26/04, 05:42 »

Hi Jason, the comboBoxCellRenderer... I have had some fun with that thing. Well I will tell you what I have done so far with this.

After looking at the tutorial I decided to try and fill it dynamicly from a database and get the value to default to a db value. That was alot of fun I can tell you but eventually I cracked it with a little help from a friend. We were all excited when we noticed that as soon as you clicked outside of the comboBox area, the box dissapeared and came up with [object, object]. At that point we decided that it just wasn't a tidy way of presenting data as it was too buggy. I will post the code we did to see if its any help in another post below this one. But to get to the point we came up with an idea of instead creating a movie clip of a comboBox and doing an onClick event on the datagrid cell so that when you select the cell a dropdown is shown over the current cell and you cn choose the value there and then when you finsihed get it to update the value on the datagrid and dissapear. Hope this helps.

Otherwise you could try creating a function for the on (columnStretch) in the datagrid which re-does the datagrid with the comboBox width = to the column width.

Unfortunately i can't do these examples as I am at work but hopefully it might bring some ideas for you. i will give it a bash tonight if you want? Otherwise, Jorge is very good at this, try and get his attention  Grin

Johnboy
Logged
John Nolan
Server what's that
*
Posts: 14



View Profile Email
« Reply #2 on: 10/26/04, 05:45 »

Code:

//***********************************************************
// Flash-db CellRenderer API Tutorial
// Developed by Jorge Solis (solisarg@flash-db.com)
// Please port your questions on [url=http://www.flash-db.com/Board/]http://www.flash-db.com/Board/[/url]
//***********************************************************
this.clientDrop_wc.trigger();


import mx.controls.gridclasses.DataGridColumn;

//Create the three Datagrid columns and set it's cellRenderer property
var column = new DataGridColumn("Selected");
column.headerText = "Selected";
column.width = 40;
column.cellRenderer = "CheckCellRenderer";
myDataGrid_dg.addColumn(column);

var column = new DataGridColumn("Id");
column.headerText = "Id";
column.width = 100;
//column.cellRenderer = "IconCellRenderer";
myDataGrid_dg.addColumn(column);

var column = new DataGridColumn("Model");
column.headerText = "Model";
column.width = 200;
column.cellRenderer = "ComboBoxCellRenderer";
myDataGrid_dg.addColumn(column);



data_array = new Array(); // Create new array to store loaded data.

//Event listener//
var clientDropListener:Object = new Object(); //Create event listener for on result of web conenctor.
clientDropListener.result = function() { //...con't
   var len = clientDrop_wc.results.length; // make len the number of the results.
   var modelInput:Array = new Array(); //Create new array to store individual data items.
   var idInput:Array = new Array();
   
      for (i = 0; i < clientDrop_wc.results.length; i++) { //for loop to go through each record.
            
            var myObj = new Object(); //Create new object to create label and data holders for the array later on.
            myObj.label = clientDrop_wc.results[i].CLIENT_NAME; //Store in the label part of the object.
            myObj.data = clientDrop_wc.results[i].CLIENT_NUMBER; //Store in the data part of the object.
            modelInput[i] = myObj; //Insert the myObj data into a position in the array.
            delete myObj; //Delete object to clear the values.
      }
      for (i = 0; i < clientDrop_wc.results.length; i++) {
         data_array.push({Id:clientDrop_wc.results[i].ID, Model:modelInput});
      }
   myDataGrid_dg.dataProvider = data_array; //Set the dataprovider to the new array.
}
clientDrop_wc.addEventListener("result", clientDropListener); //Add the event listener.
//End of//

//Avoid columns resizing
myDataGrid_dg.resizableColumns = false;
//Handler of the Show details button

show.onPress = function(){
   var selection:Boolean = false //flah to see is something is selected
   info.htmlText = ""; //clear textfield
   for(i=0; i< myDataGrid_dg.length; i++)
      if(myDataGrid_dg.getItemAt(i).Selected){
         selection = true
         //Show selected
         if(myDataGrid_dg.getItemAt(i).ModelName!=undefined) info.htmlText += "<b>Model: </b>"+myDataGrid_dg.getItemAt(i).ModelName+" -- <b>Price: </b>"+myDataGrid_dg.getItemAt(i).ModelVal+"<br>"
         //If nothing was selected from combobox, show default         
         else info.htmlText += "<b>Model: </b>"+myDataGrid_dg.getItemAt(i).Model[0].label+" -- <b>Price: </b>"+myDataGrid_dg.getItemAt(i).Model[0].data+"<br>"            
      }
   //If not there's no selection, ask to select something
   if(!selection) info.text = "                    -------------- Make a choice -------------"
}
stop();
Logged
Jason
Server what's that
*
Posts: 6



View Profile Email
« Reply #3 on: 10/26/04, 11:13 »

I found the cause of the disappearing ComboBox bug. The bug is in the DataGridRow class (or in the ComboBox class, depending on how you call it).

When you click a ComboBox, the drop-down list is created through the PopUpManager class. The PopUpManager class attaches an invisible modal window to the ComboBox instance which is used for focus control. This invisible MovieClip's width and height is equal to the size of the entire movie .

Now, in the DataGridRow class, the method drawCell() is called when the columns are resized. In this method, the DataGridRow attempts to center the cell vertically by checking its _height value and setting its _y position. Because of the large invisible MovieClip attached to the ComboBox, the _height value is huge. The result is that the cell is positioned outside the visible area of the row. In my case, the _y value of the cell was -300, when it should have been close to 0. So the ComboBox doesn't disappear really, it's just being repositioned incorrectly.

The offending line in DataGridRow.drawCell():

 cell._y = (__height-cell._height)/2;


It should really be retreiving the cell's __height property (note the two underscores), or perhaps the CellRenderers getPreferredHeight() method. I tried over-riding the default MovieClip._height property in my CellRenderer class by adding getter/setter methods for it. For some reason, this didn't work. So, now I'm working on an ugly hack. I don't want to sub-class the DataGrid just to fix one method, because I would have to go through and replace all of the many DataGrids used in my (large) application. Instead, I've almost gotten it to work by having the CellRenderer reach up to its parent (the DataGridRow) and overwrite its drawCell() method.

An ugly hack, but it seems to work.


Thanks for your suggestion, John. I'm currently beta-testing my software, so I'm looking for a fix that requires the least amout of code change. I use a lot of DataGrid's with ComboBoxes, so implementing your idea would require that I make many modifications throughout the application.

~j.


Logged
John Nolan
Server what's that
*
Posts: 14



View Profile Email
« Reply #4 on: 10/26/04, 11:28 »

jason, if you are willing then I would love to see the code you use to make the comboBoxes work properly when you have finished. It would be a great help to me (and my boss Roll Eyes).

Good luck with it all!
Johnboy
Logged
Jason
Server what's that
*
Posts: 6



View Profile Email
« Reply #5 on: 10/26/04, 13:14 »

Okay, here is my ComboCellRender class. See the example usage in the next post.

~j.

Code:



import mx.core.UIComponent
import mx.controls.ComboBox

/**
*   A CellRenderer for loading ComboBoxes into the DataGrid
*   See the Macromedia CellRenderer API for details.
*
*   @author      Jason Cowley
*   @version   1.0
*/
class ComboCellRenderer extends UIComponent {

   var combo : MovieClip;
   var label : TextField;
   var listOwner : MovieClip; // the reference we receive to the list
   var getCellIndex : Function; // the function we receive from the list
   var   getDataLabel : Function; // the function we receive from the list
   var isCellEditor:Boolean = true; // tell grid that we'll handling editing, thank you
   var comboFunction : Function; // function for filling ComboBox with data
   var comboOwner : MovieClip;  // reference to the object that holds the comboFunction
   var columnName : String; // column id
   var myItem : Object; // item data passed from grid
   var inited : Boolean = false;

   /**
    *   Constructor
    */
   function ComboCellRenderer() {}

   /**
    *   Layout components
    */
   function createChildren(Void):Void {
      
      if (label == undefined)   {
         createLabel("label", 1);
         label.selectable = false;   
         // position label a little to the right so we can line it up with the ComboBox
         label._x = 1;
         
         //:: DataGrid Bug
         //:: The DataGrid sets the position of this cell based on its _height value.
         //:: The _height returns the size of the entire screen, because
         //:: the ComboBox creates an invisible MovieClip equal to
         //:: the size of the screen when it is first opened for focus control.
         //:: This fix overwrites the drawCell() function in the DataGrid row.
         
         // define a new drawCell() method
         var newDrawCell = function(cellNum, xPos, w, bgCol) {
            // call original drawCell() method first
            this.originalDrawCell(cellNum, xPos, w, bgCol);
            // get a reference to the cell
            var cell = this.cells[cellNum];
            // Re-position the cell using the __height value, if it exists
            if (cell.__height != null) {
               cell._y = (__height-cell.__height)/2;
            }
         }
         
         // If it has not already been done by another cell,
         // attach the above function to our parent instance (DataGridRow)
         if (this._parent.originalDrawCell == null) {
            // save a reference to the original drawCell() function
            this._parent.originalDrawCell = this._parent.drawCell;
            // replace drawCell() with our custom function
            this._parent.drawCell = newDrawCell;
         }
      }
      size();
   }

   /**
    *   Initiate components
    */
   function initRenderer(Void):Void {
      if (!inited) {
         inited = true;
         listOwner.addEventListener("cellFocusIn", this);   
      }
   }
   
   /**
    *   Handle focus events generated by the grid. This event
    *   is generated when the user selects any cell.
    *
    *   @param   evt      The event object generated by the DataGrid
    */
   function cellFocusIn(evt:Object):Void {
      if (getCellIndex().itemIndex == evt.itemIndex
            && this["columnIndex"] == evt.columnIndex) {
                  
         createCombo();
         
         // call function to retreive ComboBox data on the comboOwner, if it exists,
         // else, just call comboFunction()
         var comboData = (comboOwner != undefined)
                 ? comboFunction.apply(comboOwner, [myItem, columnName])
                 : comboFunction(myItem, columnName);
         
            
         // fill combo with data
         if (combo.dataProvider != comboData.list) {
            // fill combo box with data
            combo.dataProvider = comboData.list;
         }
         
         // set selected index
         combo.selectedIndex = comboData.selectedIndex;
         
         combo._visible = true;
         label._visible = false;
      }
      else if (combo._visible) {
         combo._visible = false;
         label._visible = true;
      }
   }
   
   /**
    *   Resize the component to it's width and height settings.
    *   Note that setSize is implemented by UIComponent and calls size(),
    *   after setting __width and __height.
    */
   function size(Void) : Void {
   
      var h = __height;
      var w = __width;
      
      label.setSize(w-4, Math.max(h, listOwner.rowHeight - 2));
      combo.setSize(w - 2, Math.max(h, listOwner.rowHeight - 2));
      
   }

   /**
    *   Set the value to display in the cell
    *
    *   @param   str      The text to display
    *   @param   item   The object that holds all data for the row
    *   @param   sel      This row is selected
    */
   function setValue(str:String, item:Object, sel:Boolean):Void {
      
      initRenderer();
      
      if (item == undefined && label != undefined)    {
         // unused cell, delete data and hide stuff
         label.text = "";
         label._visible = false;
         combo._visible = false;
         combo.dataProvider = null;
      }
      else {
         // used cell, set label value
         combo._visible = false;         
         label._visible = true;
         
         // get references to objects that will be used for populating the ComboBox
         var columnIndex = this["columnIndex"]; // private property of cell
         columnName = listOwner.getColumnAt(columnIndex).columnName;
         // the function for filling the ComboBox comboFunction() should be attached
         // to the column by the class that set up the DataGrid
         comboFunction = listOwner.getColumnAt(columnIndex).comboFunction;
         // get a reference to the object that holds the comboFunction
         comboOwner = listOwner.getColumnAt(columnIndex).comboOwner;
         
         // get label data
         var labelFunction = listOwner.getColumnAt(columnIndex).labelFunction;
         
         // set label, call the function on the comboOwner if it exists,
         // else just call the function
         label.text = (comboOwner != undefined)
                  ? labelFunction.apply(comboOwner, [item])
                  : labelFunction(item);
         
         // save reference to row data for filling ComboBox
         myItem = item;
         
      }
   }
   
   /**
    *   Create the ComboBox. This methods is not called until the
    *   user actually selects the cell for the first time. Delaying
    *   the creation of the ComboBox helps spread out initiation so
    *   that all cells do not try to perform this action simultaneously.
    *   Simultaneous creation of the ComboBox by all cells causes a
    *   long delay in the DataGrid initiation, and may cause Flash to
    *   display an error message on low-performing computers.
    */
   function createCombo() {
      if (combo == undefined) {
         // create combo box for the first time
         combo = createObject("ComboBox", "combo", 2,
               {owner:this, _visible:false});
         
         combo._y = -2;
         
         combo.addEventListener("change", this);
         size();
      }
   }
   
   /**
    *   Retreive the value of the cell
    *
    *   @return      The current value of the data in the cell
    */   
   function getValue():String {
      
      var index = listOwner.__focusedCell.itemIndex;
      var colName = listOwner.columns[listOwner.__focusedCell.columnIndex].columnName;
      
      //:: Avoid annoying bug in dataGrid where it gets the value
      //:: from us and puts it in another cell
      var oldData = listOwner.__dataProvider.getEditingData(index, colName);
      if (oldData==undefined) {
         oldData = listOwner.__dataProvider.getItemAt(index)[colName];
      }
      
      return oldData;
   }

   /**
    *   Get the preferred height of the ComboBox
    *
    *   @return      The preferred height of the component
    */
   function getPreferredHeight(Void):Number {
      return 16;
   }


   /**
    *   Get the preferred width of the ComboBox
    *
    *   @return      The preferred width of the component
    */
   function getPreferredWidth(Void):Number   {
      return 20;
   }

   /**
    *   Handle change events that are generated when the user changes
    *   the ComboBox selection. The new value for the cell is updated
    *   in the model.
    */
   function change() {
      listOwner.dataProvider.editField(getCellIndex().itemIndex, getDataLabel(), combo.value);
      label._visible = true;
      combo._visible = false;
   }

}


Logged
Jason
Server what's that
*
Posts: 6



View Profile Email
« Reply #6 on: 10/26/04, 13:19 »

Usage instructions for the above ComboCellRender class:

Step 1) Set up the ComboCellRenderer class (one way of doing it)

Save the ComboCellRender class below (as ComboCellRenderer.as) at a location on your hard drive that is in your classpath, or in the same folder as your .fla file.

Create an empty MovieClip in your library and check "export for ActionScript", assign it a linkage name of "ComboCellRenderer", and set the class to "ComboCellRenderer"


Step 2) Set up the DataGrid

In your class or code that configures the DataGrid:

[*]set "ComboCellRenderer" as the cellRenderer for the desired column
[*]set up a 'labelFunction' and a 'comboFunction' and attach them to the column
[*]set a 'comboOwner' property on the column that contains a reference to the object on which the 'labelFunction' and 'comboFunction' should be called
[/list] .

Note: Setting 'comboOwner' is only necessary if you define your 'labelFunction' or 'comboFunction' as methods of the class that set up the DataGrid, or if you're accessing properties of that class from within the functions.

Example:

Code:


// set the columns to display in the grid
myGrid.columnNames = ["fieldOne", "fieldTwo", "fieldThree"];

// make column one a comboBox column
var column = myGrid.getColumnAt(0);
column.cellRenderer = "ComboCellRenderer";

// set a reference to this class for calling functions
column.comboOwner = this;

// labelFunction takes a reference to the row data in the grid, returns a string for the label
column.labelFunction = function(item):String {
   return (item.myFieldValue);    
}

// comboFunction points to a class method, see below
column.comboFunction = myComboFunction;




In the class that contains the code above, I'll set one function to be used by all ComboBox columns in the grid. You could, alternatively, just define the comboFunction inline like the labelFunction above.


Code:

/**
* Function for loading comboboxes in grid with data
*
* @param   item      A reference to the row data
* @param   columnName   The name of the column
* @return            An object containing 'list', 'selectedIndex',
*                  and 'label' properties
*/
public function myComboFunction(item:Object, columnName:String):Object {
   switch(columnName) {
      case "fieldOne":
         // 'pageNumbers' is a reference to a  property defined in
         // this class, an array that contains possible values for all
         // comboBoxes in this column.
         
         return {list:pageNumbers, selectedIndex:(item.itemNumber),
                      label:(item.itemNumber + 1)};
         break;


      case "fieldTwo":

         // if the second column of the grid was also a CombBox column
         // we would define code for that here

   }
}
Logged
kuan
Server what's that
*
Posts: 7



View Profile Email
« Reply #7 on: 11/19/04, 21:19 »

Hey Jason,

Thanks for sharing your code.  Am learning a lot from it.

One question I had is in the lableFunction you return "item.myFieldValue".  What is "item.myFieldValue"?  How do I get a value here?

thanks

Kuan
Logged
Jorge Solis
Administrator
Systems Administrator
*****
Posts: 14600


View Profile
« Reply #8 on: 11/20/04, 05:03 »

Hi Jason

Good point about the Combobox cellrenderer ! I have added the size() fix to the example in Cellrenderer tutorial (I know about it, but waits until someone fix it  Wink ) Also attaching functions to manage returned values is a great enhance. I'll move this thread to the Technical Reference Area and point to it also at the end of the Cellrenderer Tutorial.

Jorge
Logged

Jason
Server what's that
*
Posts: 6



View Profile Email
« Reply #9 on: 11/22/04, 12:16 »


One question I had is in the lableFunction you return "item.myFieldValue".  What is "item.myFieldValue"?  How do I get a value here?


"item" is the object that holds the data for an entire row. For example, one row in a RecordSet or one item in an array. "myFieldValue" would just be the name of the field.  

So, lets say you return an array of objects from the server, where each item in the array is an object with the properties: 'id', 'firstName', 'lastName', 'date'. The labelFunction for the 'firstName' column might just return (item.firstName). Or, in the case of the 'date' field, you might want to apply some formatting instead of just returning the default string representation of the 'date' field.

Hope that helps.

~j.
« Last Edit: 11/22/04, 12:17 by Jason » Logged
Jason
Server what's that
*
Posts: 6



View Profile Email
« Reply #10 on: 11/22/04, 12:24 »


Good point about the Combobox cellrenderer ! I have added the size() fix to the example in Cellrenderer tutorial (I know about it, but waits until someone fix it  Wink ) Also attaching functions to manage returned values is a great enhance. I'll move this thread to the Technical Reference Area and point to it also at the end of the Cellrenderer Tutorial.

Jorge

Jorge,

I'm not sure the hack I added to the createChildren() method is really the best way to fix the size issue -- i.e. overwriting the DataGridRow's drawCell() method. But it's the only fix I could come up with. Maybe someone else will come up with a cleaner solution.

I still can't figure out why overriding the setter method for the CellRender's __height property doesn't work. I think that would be the right way to do it. Perhaps the __height setter is overwritten somewhere else in one of those complex MM "mixin" implementations.

~j.
« Last Edit: 11/22/04, 12:28 by Jason » Logged
kuan
Server what's that
*
Posts: 7



View Profile Email
« Reply #11 on: 11/22/04, 12:46 »

Hi Guys,

Jason, thanks for clarifying the code.  I came across this basic tutorial called "Creating Custom Component Cell Renderers" by Joey Lott that explains the methods in the cell renderer class.  It helped clarify things for me.  Flash documentation is just so vague...

Here is the link for anyone interested:

http://www.communitymx.com/abstract.cfm?cid=B4AED


Kuan
Logged
Hawk67
Server what's that
*
Posts: 1


View Profile
« Reply #12 on: 06/30/05, 13:49 »

 Huh

Ok, so I've got the basic gist of it, but I'm having a few problems with the CheckBox working properly in my own app. I've linked everything the same way as it was in the article, but when I make reference to the if (myDataGrid_dg.getItemAt(i).Selected) I'm getting the Selected property returned as undefined.

Did I miss something here?
Logged
Pages: [1] Print 
« previous next »
Jump to:  


Powered by MySQL Powered by PHP Powered by SMF 1.1.16 | SMF © 2011, Simple Machines Valid XHTML 1.0! Valid CSS!