Flex: Highlight Matching Strings In DataGrid Columns
It seems a logical continuation of the view filtering Flex demo to highlight each column and show the user where the matches were found.
In the shot below I've filtered the grid to only show items with "ar" in the description column and then underlined the matching part of the text and marked it green (click the image to go to the demo):
What surprised me was that I couldn't find a documented solution for this after a few Googles and I ended up deciding to tackle it myself.
Before long (an hour or two), I had what you see above, which just goes to show the power of Flex. I'd still class myself as somewhere slightly above "intermediate" level, yet I can already extend the functionality of the DataGrid simply by applying the bits of knowledge I've picked up along the way.
How Did I Do That?
Well, the (Advanced)DataGrid itself doesn't give us any way of doing this out of the box, so I had to use a custom ItemRenderer for the description column. Now, don't worry, this isn't as scary as it might sound. Creating your own components is really easy.
To tell the grid to use a custom component is this easy:
<mx:AdvancedDataGrid dataProvider="{documents}" id="viewExpenses"> <mx:columns> <mx:AdvancedDataGridColumn headerText="Date" dataField="date"/> <mx:AdvancedDataGridColumn headerText="Description" dataField="description" itemRenderer="components.CustomColumnRenderer" /> </mx:columns> </mx:AdvancedDataGrid>
Notice I've set the itemRenderer property for the second column to "components.CustomColumnRenderer".
All that "components.CustomColumnRenderer" tells Flex to do is look inside a folder called "components" for a file called "CustomColumnRenderer.mxml". It's this MXML file which contains the code for what appears in the column.
Creating a New Component
To add your own component, first turn to your Flex app's file structure, as below, where I've already added the folder and the component:
As you can see, the main application file is called Accounts.mxml and lives inside the app's "src" folder. Notice the "components" subfolder I've created inside the "src" folder. To create it was as easy as right-clicking the src folder and choosing "Create Folder".
With this new folder I then right clicked it and choose New -> MXML Component. As below:
In the next dialog I entered the name of the component and which Flex component to base it on. As below:
This gives us a new component which is based on a Label object and inherits all it's properties and methods. We can go on to extend it to do whatever it is we like. In our case we want to highlight some text in it. You can see the whole of the code for the component in this file, but the important part is the function below (where I've left bits missing so it's more readable):
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void { //Still run the function we're over-riding super.updateDisplayList(unscaledWidth, unscaledHeight); //What we're looking for var token:String = this.parentApplication.input.text; //Where it is var begin:int = this.text.toLowerCase().indexOf(token.toLowerCase()); if ( begin > -1 ){ //Highlight it var tr:TextRange = new TextRange(this, true, begin, begin+token.length); tr.color = "green"; tr.fontWeight = "bold"; tr.textDecoration = "underline"; } }
The key to this solution is that we're over-riding the "updateDisplayList" method of the label. All of the Flex UI Components implement this method and you can over-ride it so you have the ultimate control over it's appearance each and every time its "refreshed". Or at least that's my understanding of how it work. Over-riding this method seems to be common practice among Flex developers.
It should be fairly obvious what the function is doing. Notice the parentApplication property is used to get access to the main application that this component is a part of and read the value of the "filter" field on it. It then creates a TextRange on the Label component's text and highlights it.
So, fairly easy, no?
Memory Hungry?
If you're like me you probably worry about creating your own component for use in a cell in every row of a "view"? The more rows the more likely your "poor programming practices" will be reflected in poor performance. Well, worry not. Apparently Flex only creates enough instances of the cell's itemRenderers to cover the visible rows on the screen. If the datagrid had a million rows the fact each cell has its own renderer shouldn't affect performance. Apparently. Seems true as far as I can tell. On my live Accounts db with about a thousand rows the performance is just as with 10 or so.
Further Reading
If you want to know more about custom item renderers then this quick start guide is well worth the read and gives other examples of how much you can customise the data grid.
Hi Jake,
Very good ! (and very useful !)
Every day I want more Flex
Thanks ! ;)
Nothing better than a new feature that does not impact performance. Thanks!
THAT IS PIMP!!!
I think that is awesome. Useful, clean, and professional; everything you need for a successful add-in feature.
Thanks brother.
Can this be applied to a Hierarchical Data in an AdvancedDataGrid.
I remember trying but can't remember the outcome. Can't see why not though.
Show the rest of this thread
Hi,
I just happened to go through the demo. and I entered ".net" without quotes.
It throws an error.
Any comments regarding this would be really helpful.
Thanks.
Hi,
I just found the solution.
if you change the code to
tr = new TextRange(this);
tr.text = this.text;
tr.beginIndex = begin;
tr.endIndex = begin + token.length;
then it works..
i got it from. http://nwebb.co.uk/blog/?p=20&cpage=1#comment-124616
Excellent Post, very Useful
awesome article. so useful. :)