Building Blocks

14Mar/082

Complex DataGrid filterFunction in Flex/Air

So I need to add robust user controlled filtering options in the application I am working on. This problem has dogged me for a month or two, with my co-workers politely insisting on more and better ways to filter their data. This is completely understandable. When you are staring down a datagrid that is virtually 12 feet long, getting to what you actually want to see is important.

This 2006 article from Bruce Phillips was a big help in the basic concept of multiple input filtering. As is his normal style, it is littered with good references to the source material from his research. This is only considering two filter parameters, however, and I need to consider n parameters, that will probably shift as the needs of the end user grow.

The problem is that each new filtering parameter exponentially increases the complexity of the filterFunction. I can see the pattern, but I haven't quite reached the epiphany that I need to break it down into one of those tight little functions I see real programmers creating (unfortunately, I haven't seen any that address my specific problem).

I thought I'd share my naive, and perhaps confusing filterFunction. It would be great to hear how others approach this problem, and come up with a concise solution.

This function utilizes a TextInput to get text search input from the user. It uses
a series of CheckBoxes for various status condition to generate an array [ 1, 0, 1, 1 ]
to represent their on/off state. It also uses two combo boxes to filter by staff members
as well as clients.

filterFunction:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
private function filterCases(item:CaseVO):Boolean
{
	//is the item visible or hidden
	var result:Boolean = false;
	//array of CheckBox.selected property values
       // in handy ones and zeroes. This is returned from a function
       // that takes the actual CheckBoxes as input and gives me this.
	var case_mask:Array = [ 1, 0, 1, 1 ];
        //a couple VOs to verify the selectedItem in the ComboBoxes.
	var client:PersonVO;
	var expert:PersonVO;
	//array iteration variable
	var i:int;
 
	var dateFormat:DateFormatter = new DateFormatter( )
	dateFormat.formatString = 'MM/DD/YY'
 
	//text search
	if ( caseGrid.filter.caseNumInput.text.length > 0 )
	{
		var searchResult:Boolean = ( ( String( '0' + item.case_id.toString() ).search( caseGrid.filter.caseNumInput.text ) >= 0 ) ||
										( item.style.toLowerCase().search( caseGrid.filter.caseNumInput.text.toLowerCase() ) >= 0 )  );
		if( item.description )
		{
			searchResult = searchResult || ( item.description.toLowerCase().search( caseGrid.filter.caseNumInput.text.toLowerCase() ) >= 0 )
		}
 
		if ( item.client_id )
		{
			searchResult = searchResult || ( personProxy.personFromId( item.client_id ).fullName.toLowerCase().search( caseGrid.filter.caseNumInput.text.toLowerCase() ) >= 0 )
		}
 
		if ( item.date_of_accident )
		{
			searchResult = searchResult || ( dateFormat.format(item.date_of_accident).search( caseGrid.filter.caseNumInput.text ) >= 0 )
		}
 
		for ( i = 0; i < case_mask.length; i++ )
		{
			if ( caseGrid.filter.client.selectedItem is PersonVO )
			{
				client = caseGrid.filter.client.selectedItem
 
				//is an expert selected for filtering also?
				if ( caseGrid.filter.expert.selectedItem is PersonVO && searchResult)
				{
					expert = caseGrid.filter.expert.selectedItem;
					//filter for client, expert, and status
					if ( (case_mask[i] && item.status == String( i + 1 ) )
							&& item.client_id == client.id
							&& item.expert_id == expert.id && searchResult  )
						result = true;
				}
				else
				{
					//filter for client and status
					if ( (case_mask[i] && item.status == String( i + 1 ) )
							&& item.client_id == client.id && searchResult  )
						result = true;
				}
			}
			else if ( caseGrid.filter.expert.selectedItem is PersonVO )
			{
				expert = caseGrid.filter.expert.selectedItem;
				for ( i = 0; i < case_mask.length; i++ )
				{
					//filter for expert and status
					 if ( (case_mask[i] && item.status == String( i + 1 ) )
					 		&& item.expert_id == expert.id && searchResult  )
					 	result = true
				}
 
			}
			else
			{
				//filter for client and status
				if ( (case_mask[i] && item.status == String( i + 1 ) )
						&& searchResult  )
					result = true;
			}
		}
	}
	//see if a client is selected for filtering
	else if ( caseGrid.filter.client.selectedItem is PersonVO )
	{
		client = caseGrid.filter.client.selectedItem
		for ( i = 0; i < case_mask.length; i++ )
		{
			//is an expert selected for filtering also?
			if ( caseGrid.filter.expert.selectedItem is PersonVO )
			{
				expert = caseGrid.filter.expert.selectedItem;
				//filter for client, expert, and status
				if ( (case_mask[i] && item.status == String( i + 1 ) )
						&& item.client_id == client.id
						&& item.expert_id == expert.id  )
					result = true;
			}
			else
			{
				//filter for client and status
				if ( (case_mask[i] && item.status == String( i + 1 ) )
						&& item.client_id == client.id  )
					result = true;
			}
		}
	}
	//client isn't selected, check the expert filter.
	else if ( caseGrid.filter.expert.selectedItem is PersonVO )
	{
		expert = caseGrid.filter.expert.selectedItem;
		for ( i = 0; i < case_mask.length; i++ )
		{
			//filter for expert and status
			 if ( (case_mask[i] && item.status == String( i + 1 ) )
			 		&& item.expert_id == expert.id  )
			 	result = true
		}
 
	}
	//finally, filter for the status
	else
	{
		for ( i = 0; i < case_mask.length; i++ )
		{
			//filter for status
			if ( case_mask[i] && item.status == String( i + 1 ) )
				result = true;
		}
	}
 
	return result
}

You can see that I am repeating myself from the top down to the final else statement, cutting the filter parameters down like a layer cake. It works, and is relatively quick on a grid with 1500 or so items. This just screams "REFACTOR ME" every time I see it.

Filed under: actionscript, AIR, flex, OCD 2 Comments
21Jan/080

Bram Cohen on what it takes to be a great programmer.

Bram's complete words here.

There are only two coding skills which mostly people who are completely self-taught as a programmer miss out on: proper encapsulation, and unit tests. For proper encapsulation, you should organize your code so that changes which require modifying code in more than one module are as rare as possible, and for unit tests you should write them to be pass/fail so that all unit tests can be run as a comprehensive suite. And now you know everything you need to about those two things. Anyone who is taught the above guidelines, and decides they really want to learn those skills, will with sufficient practice become good at them.

Coding skill is all well and good, and you can't become a great programmer without it, but it's far from everything. I'm decent at raw coding, but I know many people who are better, and some of them are abysmal programmers. I in particular can't deal with being tasked with fixing up spaghetti code. My brain simply locks down and refuses to make any modifications which it isn't convinced will work, which is of course impossible when the source material is an incurably bug-ridden mess.

I've felt this way about my own code with almost everything I've written to date. It has been a struggle for me, to go from an unstructured mess to something other people might be able to look at without throwing up.

It is apparent why people might skip over the 'proper encapsulation and unit tests' while teaching themselves software programming in a non-formal way. They are easily overlooked, and without a grade-book shaped stick hovering over your backside to force that sort of formality, one could easily just slide past these skills and program off the cuff. One of my challenges in learning to program has been to build things properly, to not re-invent the wheel every single time I sit down to make something meaningful in software. My preference is to stand on the shoulders of those that have come before me, and use the collective knowledge of masters, past and present, to expand my understanding and knowledge in appropriate directions. The trick is to do all of this with an open mind. We need to evaluateĀ  new ideas and differing opinions without locking onto one particular nerd dogma. I don't know that I will ever be a great programmer, but I will have fun trying.

Here's another interesting piece regarding software education and what it takes to be a great programmer: Who Killed the Software Engineer? (Hint: It Happened in College)

21Jan/080

Holding a Program in One's Head

A good programmer working intensively on his own code can hold it in his mind the way a mathematician holds a problem he's working on. Mathematicians don't answer questions by working them out on paper the way schoolchildren are taught to. They do more in their heads: they try to understand a problem space well enough that they can walk around it the way you can walk around the memory of the house you grew up in. At its best programming is the same. You hold the whole program in your head, and you can manipulate it at will.

I was discussing my recent experience with programming dreams, some might say nightmares, with Cliff Hall. He pointed me to this essay from Paul Graham that really nails it. It is so difficult to just shut down after a long period of working on a difficult programming problem. I find myself staying up way to late, because I don't want my brain to lose the trail with mere sleep. Will... sleep... when... dead...

Filed under: OCD, Paul Graham No Comments