Powerful filtering on collections in Flex

People search for things all the time. It is in our genes and we use tools to achieve our goals. On the internet a lot of us use Google to find what we need. So most of us are familiar with Google search techniques. Now some of these search techniques are put into a very powerful filter function for Flex.

The features supported

This filter has an ignore list too. If you add words to it, you can make it even more Google search like. For example Google ignores words like “a” or “the” to help their users in their quest for information.

Of course you do not have to use features like the ignore list, the synonyms and the case sensitive option. If you do not use these features you still have a very powerful filter that’s easy to implement. A nice feature to extend this filter with in your application would be to allow the user to select the columns to search in. This column selection is not in the demo application, but my guess is you are perfectly capable to do it yourself.

The source

The only code shown is the code of the demo application, but you can download the full source. The other code is not put on the page, because it are too many files to put on a webpage. Do not let this scare you, because it is not that much or that complicated.

<?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="creationCompleted(event)"
			   width="400" height="265">
	<fx:Script>
		<![CDATA[
		import mx.collections.ArrayCollection;
		import mx.events.FlexEvent;
		import nl.vanhulzenonline.collections.filter.Evaluator;
		import nl.vanhulzenonline.demo.Album;
		import spark.components.gridClasses.GridColumn;
		
		private var _collection:ArrayCollection;
		private var _evaluator:Evaluator = new Evaluator();
		
		private function creationCompleted(event:FlexEvent):void
		{
			_collection = Album.collection;
			_collection.filterFunction = filterCollection;
			
			_evaluator.synonyms["four"] = new ArrayCollection(["4"]);
			
			grid.dataProvider = _collection;
		}
		
		private function filterChanged(event:Event):void
		{
			update();
		}
		
		private function update():void
		{
			_evaluator.prepare(filter.text);
			_collection.refresh();
			
			formula.text = (_evaluator.tree) ? _evaluator.tree.toString() : "";
		}
		
		private function filterCollection(data:Object):Boolean
		{
			var labels:ArrayCollection = new ArrayCollection();
			for (var i:int; i < grid.columns.length; i++)
			{
				labels.addItem(
				  (grid.columns.getItemAt(i) as GridColumn).itemToLabel(data));
			}	
			return _evaluator.evaluate(labels);
		}
		
		private function caseSensitiveFilterChanged(event:FlexEvent):void
		{
			_evaluator.caseSensitive = caseSensitiveFilter.selected;
			update();
		}
		
		]]>
	</fx:Script>
	
	<s:VGroup left="5" right="5" bottom="5" top="5" >
		<s:TextInput id="filter" width="100%" change="filterChanged(event)" />
		<s:CheckBox id="caseSensitiveFilter" label="filter case sensitive" 
			valueCommit="caseSensitiveFilterChanged(event)" />
		<s:DataGrid id="grid" width="100%"  height="100%" >
		</s:DataGrid>
		<s:Label id="formula" />
	</s:VGroup>
</s:Application>

Fast searching in a ByteArray

For some reason there is no method to search for a specific text in a ByteArray. At least i could not find one. I tried a very straightforward search by simply scanning from every position. Although it works, it is very slow with bigger ByteArrays.

A quick look on the internet resulted in an algorithm that suited my needs: the Boyer-Moore-Horspool algorithm. Unfortunately there was no ActionScript alternative available for it yet. So i decided to port it myself and with great result, because it greatly improved the performance of the searches for my use case.

package nl.vanhulzenonline.utils
{
	import flash.utils.ByteArray;

	public class ByteArrayUtils
	{
		public static function getIndexOf(text:String, data:ByteArray, start:int = 0, end:int = -1):int
		{
			var pattern:ByteArray = new ByteArray();
			pattern.writeUTFBytes(text);
			pattern.position = 0;

			if (end == -1)
				end = data.length - 1;

			var i:int;
			var badCharSkip:Array = new Array();

			// initialize the table to default value
			// when a character is encountered that does not occur
			// in the pattern, we can safely skip ahead for the whole
			// length of the pattern.
			for (i = 0; i <= 255; i++)
				badCharSkip[i] = pattern.length;

			// then populate it with the analysis of the pattern
			var endOfPattern:int = pattern.length - 1;
			for (i = 0; i < endOfPattern; i = i + 1)
				badCharSkip[pattern.readUnsignedByte()] = endOfPattern - i;

			// do the matching

			// search the data, while the pattern can still be within it.
			var dataPart:int;
			var endOfData:int = end;
			var dataPosition:int = start;
			while (endOfData >= endOfPattern)
			{
				// scan from the end of the pattern
				i = endOfPattern;
				while(true)
				{
					data.position = dataPosition + i;
					pattern.position = i;
					if (data.readUnsignedByte() == pattern.readUnsignedByte())
					{
						// if the first byte matches, we've found it.
						if (i == 0)
							return dataPosition;
						i--;
					}
					else
						break;
				}

				// otherwise, we need to skip some bytes and start again.
				// note that here we are getting the skip value based on
				// the last byte of pattern, no matter where we didn't
				// match. so if pattern is: "abcd" then we are skipping
				// based on 'd' and that value will be 4, and for "abcdd"
				// we again skip on 'd' but the value will be only 1.
				// the alternative of pretending that the mismatched
				// character was the last character is slower in the normal
 				// case (eg. finding "abcd" in "...azcd..." gives 4 by
				// using 'd' but only 4-2==2 using 'z'.
				data.position = dataPosition + endOfPattern;
				dataPart = data.readUnsignedByte();
				endOfData -= badCharSkip[dataPart];
				dataPosition += badCharSkip[dataPart];
			}
			return -1;
		}
	}
}

Footer for the AdvancedDataGrid (2)

The previous article on this subject showed a simple solution for a footer on the AdvancedDataGrid. It has evolved in a better solution. This time it also supports having different columns for the footer. So it is possible to use different renderers for the grid and the footer.

The basic idea is still the same: two datagrids are used. One grid will be the regular grid with the rows and the header. The other grid will be used for the footer and it will contain only one row without headers. This sample will be able to handle:

This sample has 3 columns. The third column in the grid is the sum of column a and column b. The total of all the sum columns is displayed in the footer. Just change some of the values, move a column or resize a column and you will see it actually works.

Read more