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!

21 comments:

  1. 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/

    ReplyDelete
  2. 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.

    ReplyDelete
  3. Anonymous4:29 AM

    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

    ReplyDelete
  4. Check out Selection.getCaretIndex() in the docs.

    ReplyDelete
  5. Anonymous10:19 AM

    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.)

    ReplyDelete
  6. Anonymous11:41 AM

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

    ReplyDelete
  7. 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!

    ReplyDelete
  8. Anonymous8:25 AM

    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

    ReplyDelete
  9. 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();
    }

    ReplyDelete
  10. Why on earth is getCharBounds not working on AIR?

    ReplyDelete
  11. Anonymous5:55 AM

    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.

    ReplyDelete
  12. 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?

    ReplyDelete
  13. 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

    ReplyDelete
  14. 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?

    ReplyDelete
  15. 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.

    ReplyDelete
  16. 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.

    ReplyDelete
  17. Anonymous3:15 AM

    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

    ReplyDelete
  18. Anonymous3:16 AM

    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.

    ReplyDelete
  19. Anonymous3:16 AM

    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.

    ReplyDelete