3 state checkbox for headerrenderer in datagrid

This post has been moved to http://srinichekuri.com/2011/05/20/3-state-checkbox-for-headerrenderer-in-datagrid/

**********************************************************************

Today I made by first contribution to Adobe Cook by posting about this topic. The idea on this topic started by showing my earlier post on gmail header to my colleague who also happens to be the owner of post in adobe cookbook that talks about 3State checkbox for TreeRenderer. He pointed out that the checkbox in the header can be threeState. Well thats a common problem but suprisingly this was not addressed in the online opensource community for Datagrid. I saw an opportunity and coded it.

Note:
There is a bug in my colleague’s post that it doesn’t take care of the initial state, I have taken care of that bug in my code.My code itself is inspired by my colleagues post.

The checkbox in headerRenderer will have three states:

  • All the rows in the datagrid are selected (‘select’ State).
  • None of the rows in the datagrid are selected(‘unselected’ State)
  • One or more rows (but not all) are selected(‘undecided’ State).

ScreenShots:
Change of state from ‘unselect’ state to ‘select’ state:

Change of state from ‘select’ state to ‘undecided’ state:

Code:

Here is the code for Main.mxml:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
				xmlns:s="library://ns.adobe.com/flex/spark"
				xmlns:mx="library://ns.adobe.com/flex/mx"
				creationComplete="creationCompleteHandler(event)">

	<fx:Script>
		<![CDATA[
			import mx.collections.ArrayCollection;
			import mx.controls.Alert;
			import mx.events.FlexEvent;

			private var _dataProvider:ArrayCollection;
			[Bindable]
			public function set dataProvider(value:Object):void{
				_dataProvider=value as ArrayCollection
			}
			public function get dataProvider():Object{
				return _dataProvider;
			}

			protected function creationCompleteHandler(event:FlexEvent):void
			{
				dataProvider = new ArrayCollection([
					{checked:true, name:'Jim Smith'},
					{checked:false, name:'Yancy Williams'}]);
			}

		]]>
	</fx:Script>

	<mx:DataGrid id="myDataGrid" left="10" top="10" dataProvider="@{dataProvider}">
		<mx:columns>
			<mx:DataGridColumn textAlign="center" dataField="checked"
									   width="90"
									   rendererIsEditor="true"
									   sortable="false"
									   itemRenderer="renderer.CheckboxItemRenderer"
									   headerRenderer="renderer.ThreeStateCheckBoxHeaderRenderer"
									   />
			<mx:DataGridColumn headerText="Name" dataField="name" width="110"/>
		</mx:columns>
	</mx:DataGrid>

</s:Application>

Code for headerRenderer

package renderer
{
	import flash.events.MouseEvent;

	import mx.collections.ArrayCollection;
	import mx.controls.DataGrid;
	import mx.controls.Image;
	import mx.controls.dataGridClasses.DataGridListData;
	import mx.controls.dataGridClasses.MXDataGridItemRenderer;

	import spark.components.CheckBox;

	/**
	 * HeaderRender with a Three State Checkbox.
	 * <p>Functionality:<br>
	 * 	  <li>Selecting the checkbox will select all the rows in the datagrid</li>
	 * 	  <li>Unselecting the checkbox will unselect all the rows in the datagrid</li>
	 *
	 * <p>The checkbox can be three states at any point:<br>
	 *    <li>select: This would mean that all the rows are selected</li>
	 * 	  <li>unselect: This would mean that none of the rows are selected</li>
	 *    <li>undecided: This would mean that one or more (but not all) of the rows are selected</li>
	 *
	 * @author Srinivas Chekuri
	 *
	 */
	public class ThreeStateCheckBoxHeaderRenderer extends MXDataGridItemRenderer
	{
		protected var myCheckBox:CheckBox;
		protected var myImage:Image;
		private var imageWidth:Number 	= 9.5;
		private var imageHeight:Number 	= 9.5;
		private var inner:String 	= "assets/inner.png";

		private const SELECT_STATE:String="select";
		private const UNSELECT_STATE:String="unselect";
		private const UNDECIDED_STATE:String="undecided";

		private var STATE:String = UNSELECT_STATE;

		/**
		 * Constuctor
		 *
		 */
		public function ThreeStateCheckBoxHeaderRenderer()
		{
			super();
		}

		/**
		 * overides the function <code>createChildren</code> in the component lifecyle. This instantiates
		 * <code>myCheckBox</code> and <code>myImage</code>.
		 *
		 */
		override protected function createChildren():void{
			super.createChildren();
			myCheckBox = new CheckBox();
			myCheckBox.setStyle("horizontalCenter", "0");
			myCheckBox.setStyle("verticalCenter", "0");
			myCheckBox.addEventListener( MouseEvent.CLICK, checkBoxClickHandler );
			addElement(myCheckBox);
			myImage = new Image();
			myImage.source = inner;
			myImage.addEventListener( MouseEvent.CLICK, imageClickHandler );
			myImage.visible=false;
			addElement(myImage);
		}

		/**
		 * overides the function <code>updateDisplayList</code> in the component lifecyle. This sets the
		 * display settings of <code>myCheckBox</code> and <code>myImage</code>. This also updates the state
		 * according to the selections made in the datagrid.
		 *
		 * @param unscaledWidth
		 * @param unscaledHeight
		 *
		 */
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
			super.updateDisplayList(unscaledWidth,unscaledHeight);

			myImage.x=myCheckBox.x+1.5;
			myImage.y=myCheckBox.y+5.5;

			myImage.width=imageWidth;
			myImage.height=imageHeight;

			if(areAllSelected()){
				STATE=SELECT_STATE;
			}else if(isAnyColumnSelected()){
				STATE=UNDECIDED_STATE;
			}else if(!isAnyColumnSelected()){
				STATE=UNSELECT_STATE;
			}
			checkState();
		}

		/**
		 * Makes adjustments to the <code>visible</code> property of <code>myImage</code> and
		 * <code>selected</code> of <code>myCheckBox</code> according to the state.
		 *
		 * @private
		 *
		 */
		private function checkState():void{
			if(STATE==SELECT_STATE){
				myImage.visible=false;
				myCheckBox.selected=true;
			}else if(STATE==UNSELECT_STATE){
				myImage.visible=false;
				myCheckBox.selected=false;
			}else{
				myImage.visible=true;
				myCheckBox.selected=false;
			}
		}

		/**
		 * Handler method for <code>MouseEvent.CLICK</code> event on <code>myImage</code>.
		 *
		 * @param event
		 *
		 */
		protected function imageClickHandler(event:MouseEvent):void{
			STATE=SELECT_STATE;
			checkState();
			selectAll();
		}

		/**
		 * Handler method for <code>MouseEvent.CLICK</code> event on <code>myCheckBox</code>.
		 *
		 * @param event
		 *
		 */
		protected function checkBoxClickHandler(event:MouseEvent):void{
			selectAll();
		}

		/**
		 * Selects all the rows in the datagrid.
		 *
		 */
		private function selectAll():void{
			var ac:ArrayCollection = DataGrid(DataGridListData(listData).owner).dataProvider as ArrayCollection;
			for each (var o:Object in ac){
				o.checked=myCheckBox.selected;
			}
			DataGrid(DataGridListData(listData).owner).dataProvider = ac;
		}

		/**
		 * Checks if all the rows are selected in the datagrid.
		 *
		 * @return Boolean: returns <code>true</code> if all rows are selected else returns
		 * <code>false</code>.
		 *
		 */
		private function areAllSelected():Boolean{
			var ac:ArrayCollection = DataGrid(DataGridListData(listData).owner).dataProvider as ArrayCollection;
			var b:Boolean=true;
			for each (var o:Object in ac){
				if(!o.checked){
					b=false;
					break;
				}
			}
			return b;
		}

		/**
		 * Checks to see if any one of the rows are selected.
		 *
		 * @return Boolean: return <code>true</code> if any one the rows are selected.
		 *
		 */
		private function isAnyColumnSelected():Boolean{
			var ac:ArrayCollection = DataGrid(DataGridListData(listData).owner).dataProvider as ArrayCollection;
			var b:Boolean=false;
			for each (var o:Object in ac){
				if(o.checked){
					b=true;
					break;
				}
			}
			return b;
		}
	}
}

Code for itemRenderer:

<!--?xml version="1.0" encoding="utf-8"?-->

		<![CDATA[
			import mx.collections.ArrayCollection;
			import mx.controls.DataGrid;
			import mx.controls.dataGridClasses.DataGridListData;
			import mx.events.FlexEvent;

			/**
			 * Handler function for <code>MouseEvent.CLICK</code> on checkbox.
			 *
			 * @param event MouseEvent
			 *
			 */
			public function clickHandler(event:MouseEvent):void{
				var o:Object = DataGrid(DataGridListData(listData).owner).selectedItem;
				o.checked=cb.selected;
				ArrayCollection(DataGrid(DataGridListData(listData).owner).dataProvider).setItemAt(o,DataGrid(DataGridListData(listData).owner).selectedIndex);
			}

			/**
			 * Overides <code>data</code> parameter to set the <code>selected</code> parameter of the
			 * checkbox.
			 *
			 * @param value Object
			 */
			override public function set data(value:Object):void{
				if(value!=null){
					super.data=value;
					cb.selected=value.checked;
				}
			}
		]]>

You can download the code from my post in Adobe cook book.

Advertisements

4 thoughts on “3 state checkbox for headerrenderer in datagrid

  1. Thanks for this. I have an issue, and want to know if you can repeat it. I have an application that has a datagrid on the main page, which uses your renderers. I have a button that opens a popup window that also has a datagrid that uses your renderers. That grid has many pages of data. When I click on one of the checkboxes, the headerrenderer does not update. Any suggestions?

    • Hi Bill, glad that you found it helpful. I tried recreating your scenario and the header seems to update fine in the popup too. If you provide me with your code, I can help you better. For starters, I used a attribute “checked” in my dataprovider to keep track of the status, make sure such a thing or anything equivalent exists in your popup too.

      You can email me directly at satya81(at)gmail(dot)com if you need more help.

  2. Hi ! Nice example. I have a question about your code in itemrenderer. In the set data function you provide an object named “cb” like so: cb.selected=value.checked. However, the variable is never declared in any of the code provided in this example. Could you please explain when and where to declare this variable? Thanks!

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