Reusable Custom HeaderRenderer for AdvancedDataGrid control

Problem Statement:

Create a custom headerRender for the AdvancedDataGrid column with a checkbox control embeded and adhering to the following functional requirements

  • support(retain) the column sort functionality
  • must be a reusable component (no tight coupling)
  • easy to extend and plugin to use in any other grid control

Use Case:

Provide the Select All rowItems functionality for the grid column. Each row item will have a checkbox to allow its (un)selection.

Solution:

The Select All functionality for all the row items in the grid is a very common requirement for any application, but however before implementing we need to be clear about the dataProvider as well.

Will the dataProvider for the grid have dataFields, which can be binded to the state of the checkbox in the column header and for each of the row items?

If the dataProvider contains dataFields which can be binded to the UI it is fairly simple to handle the use case, but if the SelectAll is purely a UI operation for populating the list of selected items for further processing, then it becomes little more challenging.

One more challenge here is to preserve the sort functionality for the column, as any custom implementation of the headerRenderer or any component being used as the headerRenderer will strip off the sort functionality for the column. Check it out for yourself if you dont believe it.

Here is the approach that I followed to address the constraint on

>ReUsability: Program to an interface and avoid tight coupling with the parentDocument while handling the communication and binding with the grid.

>Extensibility: Encapsulate the behaviour in the class as a separate .as file extending UIComponent and provide the customizable behaviour in the  virtual methods which cane be overriden if required by the derived classes.

There are two approaches to implement the checkbox headerRenderer

Appraoch 1: Specify any implementation of UIComponent (say CustomCheckbox) as a headerRenderer for the grid column

But then you need to be prepared to handle the layout and rendering of the sortItemRenderer inorder to provide the sort functionality on the column. Also you need to be prepared to hook onto the instance of the headerRenderer for communication with the grid as you will be stumped to discover that the grid creates a new instance of the headerRenderer every now and then. Possible work around for this is to create the instance of the headerRenderer as an instance of the classFactory and store it as a bindable value in the parentDocument. After managing all of it you still need to be able to differentiate between a click to Sort and click to SelectAll by putting in vague logic based on the position of mouse click. I hope you agree that it cant get simpler than this 🙂

Approach 2: Implement the custom headerRenderer by extending the default AdvancedDataGridHeaderRender implementation and specify it in the grid column

This approach looks the obvious natural choice but then you need to know how and where to insert the custom implementation. Refer to my post on creating custom Flex controls for more info on it. Also you need to be able to differentiate between a click to Sort and click to SelectAll, which I managed it by controling the sort property of the column in response to mouse hover 😉 . Look below for the OnMouseRollOver method.

Show me the source code:

Here is the code for CheckboxHeaderRender.as:

import flash.display.DisplayObject;
	import flash.events.MouseEvent;

	import mx.controls.AdvancedDataGrid;
	import mx.controls.CheckBox;
	import mx.controls.advancedDataGridClasses.AdvancedDataGridColumn;
	import mx.controls.advancedDataGridClasses.AdvancedDataGridHeaderRenderer;
	import mx.core.UITextField;

	//Customizes the default AdvancedDataGridHeaderRenderer by displaying the checkbox in the header to
	//provide the SelectAll functionality in the grid. It uses the default sortItemRenderer of the grid, if not specified.
	public class CheckboxHeaderRenderer extends AdvancedDataGridHeaderRenderer
	{
		//stores the associated column
		private var _gridColumn:AdvancedDataGridColumn;
		//the checkBox control to be added as a child control
		private var checkBox:CheckBox;
		//stores the parentDocument(HostControl)
		private var _hostControl:IGridHostControl=null;
		//stores the default leftPadding for the checkbox control
		private const LEFT_PADDING:int = 5;

		//constructor
		public function CheckboxHeaderRenderer()
		{

			super();

			this.id = "checkboxHeaderRenderer";
			//initialize the checkbox control
			checkBox = new CheckBox();
			checkBox.label="";		 

			//BindingUtils.bindProperty(checkBox,"selected",HostControl,"allSelected");

			//subscribe to the events of interest...
			checkBox.addEventListener(MouseEvent.CLICK,OnCheckboxClick);
			addEventListener(MouseEvent.ROLL_OVER,OnMouseRollOver);
		}

		//gets the associated dataGrid column
		private function get gridColumn():AdvancedDataGridColumn{
			if(_gridColumn == null){
				_gridColumn = data as AdvancedDataGridColumn;
			}
			return _gridColumn;
		}

		////////////////////// EVENT Handlers ////////////////////////////

		//Response to Mouse RollOver event
		private function OnMouseRollOver(event:MouseEvent):void{

			//control the sortable property of the column
			if(event.localX > checkBox.x + checkBox.measuredWidth){
				if(gridColumn != null){
					gridColumn.sortable = true;
				}
			}
			else{
				if(gridColumn != null){
					gridColumn.sortable = false;
				}
			}
		}

		//response to the click event on the checkbox
		private function OnCheckboxClick(event:MouseEvent):void{

			//(un)select the items
			if(HostControl != null){

				HostControl.ToggleSelectionList(checkBox.selected);
			} 

			//refresh the UI
			if(listData != null){

				var grid:AdvancedDataGrid = listData.owner as AdvancedDataGrid;
				if(grid != null){
					grid.invalidateList();
				}
			}
		}

		////////////////////// VIRTUAL Methods ////////////////////////////

		//gets the associated HostControl(ParentDocument)
		//The ParentDocument control hosting the AdvancedDataGrid must implement IGridHostControl
		//Returns Null, if the HostControl(ParentDocument) doesn't implement IGridHostControl
		public virtual function get HostControl():IGridHostControl{

			if(_hostControl == null){
				_hostControl = this.parentDocument as IGridHostControl;
			}
			return _hostControl;
		}	 

		//gets the left padding for the checkbox in the header
		public virtual function get LeftPadding():int{

			if(HostControl != null){

				return HostControl.LeftPadding_CheckboxHeaderColumn;
			}

			return LEFT_PADDING;
		}

		////////////////////// OVERRIDEN Methods ////////////////////////////

		//overriden to add the custom components
		override protected function createChildren():void{

			super.createChildren();

			addChild(checkBox);

		}

		//performs the layout of the child controls
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{

			super.updateDisplayList(unscaledWidth,unscaledHeight);

			var textLabel:UITextField;
			var n:int = numChildren;
            for (var i:int = 0; i < n; i++)
            {
                var c:DisplayObject = getChildAt(i);

                if (c is CheckBox)
                {
                    c.x = LeftPadding;
                    c.y = (unscaledHeight - c.height)/2;                    

                }else if(c is UITextField){

                	textLabel = c as UITextField;
                }
            }

            //check to avoid overllaping of text on the checkbox
            var xMin_text:int = checkBox.x + checkBox.measuredWidth;
            if(textLabel !=null && textLabel.x < xMin_text){

            	textLabel.x = xMin_text;
            	textLabel.truncateToFit();

            }

		}

		//override to bind the checkbox state
		override protected function commitProperties():void{

			//set the properties of the checkBox
			if(HostControl != null){
				checkBox.selected = HostControl.AllSelected;
			}
			super.commitProperties();
		}

	}
&#91;/sourcecode&#93;

<strong>Here is the code for IGridHostControl.as:</strong>


//Contract that must be implemented by the UIComponent hosting(i.e, acting as a parentDocument for)
 //the AdvancedDataGrid(ADG) control that intends to use the CheckboxHeaderRenderer implementation
 //as a customHeader for its ADG column.
 public interface IGridHostControl
 {

 //gets the value of the Binding variable for the checkBox state in the custom header of the ADG
 function get AllSelected():Boolean;

 //sets the value of the Binding variable for the checkBox state in the custom header of the ADG
 function set AllSelected(value:Boolean):void;

 //Adds/Removes all the items to/from the selection list
 function ToggleSelectionList(selectedState:Boolean):void;

 //synchronize the header checkBox with its binding variable
 //This method is used to synchronize the checkBox in the header, when its binding variable is changed
 function UpdateHeaderSelection(selectedState:Boolean):void;

 //gets the Left padding to be associated with the checkbox control in the column header renderer
 function get LeftPadding_CheckboxHeaderColumn():int;

}

The parentDocument must implement the IGridHostControl interface to use CheckboxHeaderRenderer as the headerRenderer for the grid column.

Advertisements

17 comments so far

  1. vimal on

    Great work. Could you please provide an example to show how this can be used?

    Thanks

  2. Pavan on

    Any parentDocument that hosts the grid control need to implement the IGridHostControl and specify the headerRenderer for the grid column as checkboxHeaderRenderer. The exact implementation of the contract in the parentDocument will vary depending on the application.

    I will upload the source code of a sample implementation of it in the next week

  3. Pavan on

    Here is the code snippet for the parentDocument hosting the Grid with the checkbox header column

    
    <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"	
    	 width="100%" height="100%"
    	 implements="IGridHostControl">
    
    /*
    set the headerRenderer of the grid column to checkboxheaderrenderer
    */
    
    
    private var allSelected:Boolean=false;
    
    
    //gets the value of the Binding variable for the checkBox state in the //custom header of the ADG
    public function get AllSelected():Boolean{
        			 
    	return allSelected; 		 
    }
    //sets the value of the Binding variable for the checkBox state in the custom header of the ADG
    public function set AllSelected(value:Boolean):void{
        			 
    allSelected = value;    			
    }
    
    //Adds/Removes all the items to/from the selection list 
    public function ToggleSelectionList(selectedState:Boolean):void{
        		 	
    allSelected = selectedState;
    //clear all items in the selected list
        	if(selectedState){
        		//add all items to selected list
    }
    }
    
    //update the header selection
    public function UpdateHeaderSelection(selectedState:Boolean):void
    {
    //refresh the header column of all the columns
    
    /*this method will be useful to refresh the checkbox
    in the header column when the checkbox in the row is Unselected*/
    
    }
    
    
  4. Shishir on

    Hi Pawan,

    Nice work indeed.I am a newbie to Flex and have a requirement of extending AdvancedDataGridRenderer to embed a menu as a child which on being clicked shows a menu that can be used to do some operations on the column under selection like filtering etc. Could you please provide an example for the same?

    Thanks.

  5. Pavan on

    You could try embedding a combo box control instead of the checkbox control in the ADG header renderer.

  6. Shishir on

    Hi Pawan,

    Thanks for the reply.I followed your code and embedded a PopUpMenuButton and it works perfectly fine.I have added the option of Dynamic grouping in the menu and it works fine except for one small issue.I am able to dynamically group only for one column.Could you please provide an example for the same ? I would appreciate if you could analyse my code and point me in the right direction.

    Thanks a tonne for this post.

    Regards,
    Shishir.

  7. raju on

    Hi Pavan can u pls make a complete application with the above example and provide its source code.It would be more useful i think.
    Thanks.

  8. Chuck on

    You completely and utterly ROCK for posting this.

    I was just going to extend AdvancedDataGridHeaderRender to squeeze a popupButton in the left – I thought of adding it like sortItemRenderer. I expected more layout work.

    I will attempt to adding this to an extended ADG and not the outerDocument (canvas).

    Thanks Again!

  9. Fiona on

    Really useful, thanks!

    One possible improvement, I found the following in the Flex documentation for UIComponent:

    If you are overriding the updateDisplayList() method in a custom component, you should call the move() method rather than setting the x and y properties. The difference is that the move() method changes the location of the component and then dispatches a move event when you call the method, while setting the x and y properties changes the location of the component and dispatches the event on the next screen refresh.

  10. Armordillo on

    does any one have a working example I can down load? I am a newbie and get lost on where to add the code.

  11. Jeremy on

    What is needed in UpdateHeaderSelection that toggles the checkbox in the header based on unchecking an item? I know that function is getting called and allSelected is set to the correct boolean value, but I cannot get the headed checkbox to update.

  12. zhang on

    hi,
    how do u refresh column header in UpdateHeaderSelection,
    i can use grid.executeBindings() or grid.invalidateList() to refresh it,
    but i wonder which one is better, or any better solutions.

  13. Dmitry Ulmer-Morozov on

    Thnx from Russia! Very useful. Your code help me do same work in flex 4.

  14. ana on

    Hi,
    How do you refresh the check box in the column header after manually selecting/unselecting check-boxes from the grid?Could you maybe provide an example?

    Thanks a lot!

  15. Joélio Pereira on

    preciso de ajuda para usar esta classe, sou novato no Flex

  16. Joélio Pereira on

    Need help using this class, I am newbie in Flex

  17. Nikki on

    How can i use a Custom HeaderRenderer which extends TEXT in AdvancedDataGrid??


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: