Monday, January 22, 2007

Update - Flex Text Highlighter Class

Last night I updated my demo of tl.text.Highlighter, my TextField highlighting class for Flex. You point it at a TextField object, and then use one of its methods to highlight the word or words of your choosing. Here's the demo (right-click it to view source).

In this revision, I added some performance optimizations to reduce the number of calls to TextField.getCharBoundaries, which it turns out was the most expensive call I was making. In the last version, I highlighted each character individually, regardless of whether it was part of a word or not, because it was easier to handle word wrapping that way. This meant making a separate call to TextField.getCharBoundaries for each letter in a word. The performance was pretty good, but could get a little slow when highlighting a whole paragraph. Now, I make two calls per word: one for the first character, and one for the last character and combine them into a single Rectangle object that spans the whole word (a la Keith Peters). It's a bit trickier to manage when words or phrases wrap into multiple lines, but I was able to write some logic to handle those scenarios.

Anyway, this results in much better performance when highlighting many sequential characters in a TextField. To pull this off, I created a StringBoundaries class which returns an array of Rectangles that define the boundaries of the character range. The array has a length of 1 if the character range fits on one line.

Also, I moved the search logic into a separate class called Finder, where it will be easier to work with. I intend to add some features such as Match Whole Words Only and Case Sensitive/Insensitive options.

Keep those suggestions coming!

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

21 Comments:

Blogger Unknown said...

Another way to get TextArea's textField property is to use mx_internal namespace. Cleaner but not recommended by Adobe. Example of simple implementation: http://flashtexteditor.com/flexdemo/

9:28 AM  
Blogger tom said...

Thanks for the tip, Igor! It is a lot cleaner, and maybe more reliable. Is mx_internal documented anywhere, or is this something people have figured out on their own? I couldn't find any official documentation on the subject.

10:58 AM  
Anonymous Anonymous said...

Hi,
Is there any way to get the caret position in a text area? I am trying to build a syntax highlighter with some autocomplete features. I want to know whats the current position of the caret so that I can replace the character at that point with something else? for example, if the user types "fun" and presses space, I want to replace it with "function"... any ideas?

cheers,
uday

4:29 AM  
Blogger tom said...

Check out Selection.getCaretIndex() in the docs.

8:01 AM  
Anonymous Anonymous said...

Thanks, this was very helpful.

I noticed a bug when you search for the next or previous keyword for a 6-string-or-greater character that doesn't exist. It'll draw a box with the length of this integer starting at char 0 in the textfield.

I fixed this by added some if statements in the Highlighter class, to detect if no results were found. These functions are also return booleans now so that the main component can tell if nothing is returned.

Also added a few functions in the Highlighter class to find the first reference of a word only, and a function to that returns the number of occurrences of a word only (used in enabling/disabling feature buttons in the main component.)

10:19 AM  
Anonymous Anonymous said...

Also added another parameter on the Highlighter class for case-sensitivity.

11:41 AM  
Blogger tom said...

Sounds like useful improvements! Feel free to post a url here - I'd love to see it. Not sure what you mean by a 6-string-or-greater character... Can you elaborate on that? Thanks!

5:36 PM  
Anonymous Anonymous said...

Hi Tom,

I'm in a highly secure work environment where my internet is on a separate machine so I can't post, but I'd love to help and can elaborate:

Try typing "eeeeee" in the highlight next/previous window and hitting next to see what I mean. For some reason the highlighter only does this if the search term is only 6 characters or more and not found, I'm not sure why.

To get around this I basically added "if (i>o){do code}" inside your highlighter functions (inside the Highligher class) so that if finder.find*() returns negative vales that it doesn't bother running the rest of the highlighter code. (I also modified the finder class so that the subString it returns is not zero based to get this to work [by just adding 1, stringBoundries will also need to be adjusted because of this].

The highlighter idea I posted earlier didn't end up being ideal for adding into the highlighter class. Instead I created a global isCaseSensitive boolean inside the main component to act as a toggle. I added a second "isCaseSensitive" parameter to all highlighter functions that default to true. If isCaseSensitive is passed as false then both the searchWord (word) and the textfield (str, but in one function you have it as txt) are set to lowercase when searching for macthes. I found this way works best although I bet it could be a little more efficient.

Another good suggestion is to make your highlighter functions in your main component not completely reliant on the search textfield eg:

public function highlightNext(searchString:String=null):void{
if (!searchString){
searchString=SearchQueryInput.text;
}
..rest of code
}

That way the functions can be called by other things such as eventListeners.

I had already built my own highlight/search component halfway before I saw yours. Mine wasn't separated out into classes as nicely. Thanks for sharing it and I apologize I can't post my variation. But I can contribute in any other way. My idea was to make the highlighter similar to the one in Firefox's search.

-Mauv

8:25 AM  
Blogger Unknown said...

Added a new function to Highlighter.as. It will allow you to highlight all the words based on their indexes. It only takes 1 parameters an array of objects that have a start and end values.

So if you want to highlight the words at indexes (3,10) and (14, 19) you would do something like this:

var field : TextField = getTextField( ta );
var highlighter : Highlighter = new Highlighter( field );
var a : Array = new Array();
var delim : Object = new Object();
delim.start = 3;
delim.end = 10;
a.push( delim );

highlighter.highlightWords( a );

public function highlightWords( indexes : Array ) : void
{
for(var i : int = 0; i < indexes.length; i++)
{
var bounds : StringBoundaries = new StringBoundaries( this.field, indexes[i].start, indexes[i].end, this.xOffset, this.yOffset);
this.boundariesToHighlight.push( bounds );
}
this.highlightVisibleBoundaries();
}

6:20 PM  
Blogger alen said...

Why on earth is getCharBounds not working on AIR?

7:10 AM  
Anonymous Anonymous said...

I'm trying to use your component but I keep getting a 100 var'thisChild' has no type declaration error, any suggestions? I'm using flex 2.1 by the way.

5:55 AM  
Blogger Harshal said...

Tom,
Why doesn't this code work with TextInput or Label and only works with TextArea in spite of both of the latter ones having an internal TextField object. Any ideas?

1:12 PM  
Blogger Philipp said...

Added a method to Highlighter in order to have different words highlighted with one and the same highlighter object.

public function highlightWordArrayInstances(words:Array):void{

var txt:String = this.field.text;



for each (var word:String in words){
var temp:Array = this.finder.indexesOf(word);
var len:int = word.length-1;

for(var i:int = 0; i SMALERTHAN temp.length; i++){
var bounds:StringBoundaries = new StringBoundaries(this.field,temp[i],temp[i]+len,this.xOffset,this.yOffset);
this.boundariesToHighlight.push(bounds);
}
}



this.highlightVisibleBoundaries();
}

Its probably not such a good way to do, but its a start ;-)

Are you still on this topic? I am using it in a Flex 3 Scenario but do get Bitmap errors every now and then. ideas ?

Philipp

4:03 AM  
Blogger Able Virk said...

I was trying my hand at the Highlighter class. Well i was thinking about selecting some text in a text area and it would be highlighted in some color.
Why can't we just highlight some text like that and not change a lots of things? Is there some API which will let me do it?

5:32 PM  
Blogger Unknown said...

Hi Tom,

Thanks for the great class.

I am trying to use it in a TileList ItemRenderer displaying search results where I am highlighting matched search query strings. Each ItemRenderer has an instance of its own Highlighter that is constructed with the IR's Label (only had to modify the constructors addChild routine as the Labels TextField's child index is 0 to make it work).

However, each ItemRenderer responds to MOUSE_OVER, MOUSE_OUT events and once I introduced the Highlighter the mouse over events are being triggered in empty columns or rows.

I didn't see the Highlighter and supporting classes listen for mouse over events so not sure what caused this issue.

If you have any insight into using the Highlighter in an item renderer it would be much appreciated.

10:51 AM  
Blogger Harshal said...

Greg,

An easier implementation which I did was to use the htmltext property of the Label to show letters to be highlighted in one color and rest in a normal color. This works for me and is very performance efficient too.

10:59 AM  
Anonymous Anonymous said...

牙醫,植牙,假牙|矯正|牙周病,牙醫診所植牙,紋身,刺青,創業,批發,TATTOO,皮膚科,痘痘,雷射脈衝光除斑,洗包包|洗鞋子|清洗包包,中醫,腫瘤,腎臟病,僵直性脊椎炎,飛梭雷射,肉毒桿菌,玻尿酸,痘痘,脈衝光,醫美,毛孔粗大,醫學美容,seo,關鍵字行銷,關鍵字自然排序,網路行銷,關鍵字自然排序,關鍵字行銷seo,關鍵字廣告,網路行銷,seo,關鍵字行銷,關鍵字廣告,關鍵字,自然排序,部落格行銷,網路行銷,網路爆紅,牛舌餅,婚紗,台中婚紗

1:58 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:58 AM  
Anonymous Anonymous said...

Men's polo shirts was the shirt of choice for diverse groups of teenagers
Brightly coloured polo shirts can make you look like a Day-glo dirigible.
Wonderful!You can find the father who desire fashionable eg,uggs fashion,you can enjoy uggs online here, intellectual polo shirt simultaneously

3:15 AM  
Anonymous Anonymous said...

fantastic!God bless you!Meanwhile,you can visit my China Wholesale,we have the highest quality but the lowest price fashion products wholesale from China.Here are the most popular China Wholesale products for all of you.Also the polo clothing is a great choice for you.

3:16 AM  
Anonymous Anonymous said...

God bless you!I really agree with your opinions.Also,there are some new fashion things here,gillette razor blades.gillette mach3 razor bladesfor men.As for ladies,gillette venus razor blades must the best gift for you in summer,gillette fusion blades are all the best choice for you.

3:16 AM  

Post a Comment

<< Home