Friday, January 19, 2007

A TextField Highlighter Class for Flex

One of the features I've always felt was missing from Flash TextFields is the ability to highlight a range of characters with a color. What I did in the past was highlight words by changing their color, which is not nearly as satisifying. So, I was really happy to learn that TextFields in Flash 9 have a new method, getCharBoundaries, which returns the bounding rectangle of the specified character. With this method, we can create a rectangle of color to place behind the TextField, highlighting the specified character or characters.

Keith Peters of BIT-101 and Betaruce both have released demos of this exact functionality. However, their demos lack support for word wrapping, or resizing and scrolling of the TextField. Since text highlighting is on the wishlist for an app I'm developing, I figured I'd Flex it up a bit.

My Highlighter class supports word wrapping, resizing, scrolling, and more. You can highlight all instances of a string, or highlight the next and previous instances of the string. You can also use multiple Highlighters on the same TextField for combinations of colors and functionality.

Try out the demo. You may have to wait a few seconds for the sample text and fonts to load. I'm using embedded fonts and a StyleSheet here just to test that it will handle them, but TextFields in general seem to perform better without them. Oh, and you can right-click and view source too (love that about Flex!).

Here's how it works: when you pass the Highlighter a string to highlight, it finds the instances of that string in the TextField and stores the indexes of each character separately in an array. When the TextField is scrolled or resized, the Highlighter determines which of those characters are visible on the screen and draws a Bitmap rectangle behind them. On every SCROLL or RESIZE event, the Bitmap is cleared and highlights are calculated and redrawn.

This could get to be a pretty processor-intensive system, especially when you have a lot of characters being highlighted. This is where I was really pleasantly surprised by Flash Player 9. When I first started the project, I thought "this will never work - it'll be too slow". But Player 9 handles it like a champ. Unbelievably fast.

There are some things that can screw up TextField.getCharBoundaries, such as newline, carriage return, and other whitespace characters (see my last post). Using condenseWhite on your TextField will take care of that. In my case, I have to preserve whitespace, so some pre-processing of the text is necessary to strip out the offending characters.

Also, in my demo I have padding on my TextArea. This throws off getCharBoundaries, and I compensate by passing an xOffset and yOffset value into the Highlighter class.

Speaking of TextAreas... In the Flex components that use a TextField, there is a property called textField, which would be ideal for my Highlighter class to make use of. But, no. It is protected. Am I really going to extend every component in the Flex framework that I want to use my Highlighter class with? Please. For now, I iterate through the children of the TextArea until I find a TextField. It seems to work fine, but I'd prefer to not resort to such hackery. Anyone else got an alternative?

I'm really happy with the Highlighter class so far, but I'm no architect. I could really use some feedback on what should be public, private, protected, etc... and just architectural advice in general, I guess. How can I make this thing fit in better with the Flex framework? Are there changes I could make that would simplify things, or improve performance? Looking forward to your comments.

Was this post helpful to you? If so, please consider making a small donation to keep this blog going.

16 Comments:

Anonymous Anonymous said...

Nicely done.

In a similar vein, here is a link to a class that extends mx.controls.Text, mx.controls.LinkButton, and mx.skins.halo.HaloFocusRect to make inline anchor links keyboard and screenreader accessible:

http://www.majordan.net/test/inlinelinksapp/

Cheers.

2:38 PM  
Anonymous Anonymous said...

really interesting, but can I pass an array of strings for it to hightlight? if yes, then I could use it in my new spell checker :)

2:59 PM  
Blogger tom said...

@ majordan - Nice stuff! When I get some time, I want to see how you handled the word wrap. My class treats each character individually, which may be a place I could improve performance.

dimitrios - I was considering that option, it should be pretty easy to do. It's really just a matter of modifying the search functions (which could probably use some optimization anyway).

5:36 PM  
Anonymous Anonymous said...

Good stuff.

To extend this it would be nice if there were some options to search for whole words.

i.e. if you search for 'the', the 'the' in 'their' wouldn't get selected.

6:19 AM  
Blogger tom said...

Tink, I'm working on improving the search functions, although at present they do find strings within words as you describe. However, they are case-sensitive, and I don't trim white space from the inputs, so sometimes you can accidentally get a space at the end of your word that screws things up.

Also, I'm working on some performance optimizations, so check back!

7:34 AM  
Blogger tom said...

Oh, I misread your comment Tink - I thought you DIDN'T want whole word search. :)

Whole word search would definitely be a nice addition - and String.match with a regex should be just the thing to get it done. I also want to add a case-insensitve option.

7:38 AM  
Anonymous Anonymous said...

Hi Tom,

I really love your component. It is really fast. I'm having some issues getting it to work with plain text. It works great if I use a whole HTML document, but not on a plain string. Any ideas on that? I'm trying to use it to highlight search terms in the item renederer of a datagrid.

Cheers

12:21 AM  
Blogger tom said...

Hi David, glad to hear you're getting some use out of my work. I don't believe there's anything in my code that would prevent it from working with plain text. You mentioned that you're trying to use it with a datagrid. What happens if you do a simple test case with a plain old TextField?

11:17 AM  
Anonymous Anonymous said...

Hi Tom,
In your demo code I tried changing the value of the variable 'txt' to "this is some text" and then I get this error:

RangeError: Error #2006: The supplied index is out of bounds.
at flash.text::TextField/getLineOffset()
at tl.text::StringBoundaries/::getLineEndOffset()
at tl.text::StringBoundaries/get isVisible()
at tl.text::Highlighter/::highlightVisibleBoundaries()
at tl.text::Highlighter/highlightWordInstances()
at HighlighterDemo/globalHighlight()
at HighlighterDemo/__HighlightButton_click()

I have found that it only works as a full html page. Any advice you can provide me would be great. I finally convinced a client to do this project in Flex, rather than HTML, but now they want text highlighting :-)

Cheers

4:55 PM  
Blogger tom said...

Hi David,

This looks like a classic example of me overlooking the simple fact that someone might want to hilight a word in a body of text that one occupies one line. In other words, I was focused on large amounts of text, and forgot to put in checks to handle scenarios for just a few words. How embarrassing. :) I promise to fix the issue soon.

6:13 PM  
Blogger tom said...

Hey David - I fixed the issue, and updated the source code. I never imagined this being used with a datagrid. Let me know how it works out!

9:04 PM  
Anonymous Anonymous said...

Tom, love your work ;-) Now I just have to work out how to make it run inside the item renderer. I'll post you the code if I get it going.

Cheers

7:30 AM  
Anonymous Anonymous said...

Thanks for the nice post!

6:55 AM  
Blogger Andrew said...

Tom,
I noticed if the word to be highlighted extends to the second line it doesn't highlight the first line. How can I fix this ?

2:59 PM  
Blogger Brian said...

A word of warning to everyone: make sure you call canvas.dispose() once you are finished with this class as the "canvas" object is grabbing 4 bytes of memory per pixel.

At 2000 by 2000 pixels, this can get very expensive if the memory is never freed up.

11:53 AM  
Anonymous Anonymous said...

ralph lauren poloburberry polo shirtthe north face jacketcolumbia jacketspyder jacketed hardy clothing ed hardy clothesed hardy shirts ed hardy t-shirts ed hardy sunglasses ed hardy mensed hardy womens Wholesale HandbagsCheap HandbagsWomens HandbagsCheap PursesDesigner HandbagsTennis RacquetTennis Racket
cheap tennis racquet
tennis racquet discount
cheap tennis racket
discount Tennis Rackethead junior tennis racketwilson tennis racquet
wilson tennis racket
head tennis racketbabolat tennis racket

1:57 AM  

Post a Comment

<< Home