nwebb

Flex, Flash, AIR

Archive for April, 2007

Modify / Format Text in a Flex Component

To modify text within a TextArea, TextInput or RichTextEditor control you can use the mx.controls.textClasses.TextRange class. There are slight differences between using this class for TextArea/TextInput and using it for the RichTextEditor - see docs for more details.

When you instantiate an instance of TextRange you can send in up to 4 parameters:

  • the text control you wish to affect
  • a boolean value indicating whether or not you wish to modify some currently selected text (this selection could have been set programmatically, or as the result of a user gesture)
  • a start index
  • an end index

The second parameter should always be false if you specify the begin and end indexes (to specify a selection, you would send in a value of true, then set the selection using the setSelection() method).

An example of formatting part of a word could look similar to this:

Actionscript:
  1. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  2.      creationComplete="onComplete();">
  3.     <mx:Script>
  4.         <![CDATA[
  5.             import mx.controls.textClasses.TextRange;
  6.             private function onComplete():void{
  7.                 var tr:TextRange = new TextRange(myTextArea,
  8.                                    false, 2, 5);
  9.                 tr.color = 0xFF0000; //set format property
  10.             }
  11.         ]]>
  12.     </mx:Script>
  13.     <mx:TextArea id="myTextArea" text="Hello World!" />
  14. </mx:Application>

The first line in the onComplete method simply gets your text range, and the next line modifies it.
You can change properties such as font family/colour/weight, you can change normal text in to htmlText, and so on.

If you were creating a standalone text-modification class you may wish to build in the option to set these properties at run time.
Here is a simplified example using a generic object (ideally you would create your own specific typed object for this):

Actionscript:
  1. public function someMethodOfYourChoosing():void{
  2.      var highlightedPropsObject:Object = {color:0xFF0000, fontWeight:"bold", fontSize:16};
  3.      applyTextHighlight(textField, startIndex, endIndex, highlightedPropsObject);
  4. }
  5.  
  6. public function applyTextHighlight(textField:UIComponent, startIndex:int,
  7.                                endIndex:int, highlightedPropsObject:Object):void
  8. {
  9.      var tr:TextRange = new TextRange(textField, false, startIndex, endIndex);
  10.      setTextFormat(tr, highlightedPropsObject);
  11. }
  12.  
  13. private function setTextFormat(tr:TextRange, obj:Object):void{
  14.      //loop through the objects properties and assign each value to your TextRange
  15.      //instance (only if the instance already has such a property declared)
  16.      for(var i:String in obj){
  17.     if(tr.hasOwnProperty(i)){
  18.         tr[i] = obj[i];
  19.     }else{
  20.          //some error handling here 
  21.         }
  22.      }
  23. }

You MUST ensure that the text has fully loaded before calling applyTextHighlight. If you get RangeError: Error #2006: The supplied index is out of bounds , it is likely that your text hasn't fully loaded.

I also found some strange (possibly buggy) behavior with the TextRange object during instantiation.
When I tried to use my highlighter class on a field which was part of a popup view (displayed by the PopupManager class), I ran in to the "text out of range" error, despite the fact that the TextRange object's text property was populated correctly at the time (as shown by the Flex debugger).

I found that this worked:

Actionscript:
  1. //WORKS...
  2. var myTA:TextArea = TextArea(_textField);
  3. var tr:TextRange = new TextRange(myTA);
  4. tr.text = myTA["text"];   
  5. tr.beginIndex = 0;
  6. tr.endIndex = 1;

yet this failed:

Actionscript:
  1. //FAILS...
  2. //the debugger showed that the TextRange object's text property was already populated correctly at this point
  3. var myTA:TextArea = TextArea(_textField);
  4. var tr:TextRange = new TextRange(myTA, false, 0, 1);

Also, the text in my TextArea component was populated via a binding, so to ensure that I didn't call my highlighter class until there was text there to highlight, I created a bindable text getter/setter, and only called the highlighter class from within the setter when the text changed. This didn't work. However, if I used the ChangeWatcher class instead, to watch my TextArea's text property, it did work. Here is what I used:

Actionscript:
  1. ChangeWatcher.watch(myTextArea, "text", onTextChanged);

Now, I'd say that last issue was more likely an instantiation problem, and more down to my misunderstanding of the framework than a bug, but it's certainly worth being aware of. Looking back on it, I expect that the getter/setter gets called even before the text is set, and again when it is set. This doesn't appear to happen with the ChangeWatcher - I seem to remember reading that unless the value of the property has changed, the ChangeWatcher doesn't dispatch an event - can I find that in the docs now? Nah, of course not.

No comments

OS Flex

I'm sure this will be all over every Flex blog in no time, but as the announcement is still pretty fresh (and for the sake of posterity) I may as well mention that Flex is now Open Source :)

No comments

Utility-code (checking for duplicate array elements efficiently)

I don't see too many people posting and explaining small utility-code snippets on their blogs. I guess this is because they are not the most exciting of things, but they can be a real aid to learning.

I had to quickly knock something together today (shame on me for not having this kind of thing in a library) and I thought I'd post it up whilst I remember. At the bottom of this post is a useful method for weeding out duplicate array elements. First I'll give an overview of how it works.

Let's say you have an ArrayCollection in Flex that looks like this:

Actionscript:
  1. var ac:ArrayCollection = new ArrayCollection([100,200,300,200,400,500,200,100,300,300,100,100]);

You want to rid the Arraycollection of all duplicate elements.

To do this you can use two loops (one nested inside the other).
To do this efficiently you want to ensure that you compare each element against another element once only.

If both for loops started at zero and looped until the iterator was "less than array.length" it would not be efficient; the reason being that on the first run you would compare somearray[0] against somearray[1], somearray[2] ... etc, and on the second run you would compare somearray[1] against somearray[0], somearray[1] ...etc. As you can see, you would have compared somearray[0] and somearray[1] twice already.

However, if we make the inner-loops value "one more than i", we can avoid this.
Also, the outer loop only needs to loop until i is less than totalLength-1, because the very last object doesn't have anything left to be compared to.

Okay, so you're probably a little confused now? In diagrammatic form, it may look something like this:

Actionscript:
  1. [0]1 2 3
  2.    [1]2 3
  3.       [2]3
  4.  
  5. /*
  6. COMMENTS:
  7. on the 1st iteration, compare 0 to 1,2,3
  8. on the 2nd iteration, compare 1 to 2,3 (1 has already been compared to 0 on the previous iteration)
  9. on the 3rd iteration, compare 2 to 3 (2 has already been compared to 0 and 1 on previous iterations)
  10. exit
  11. */

In Flex we can use the mx.utils.ObjectUtil class to compare two objects and see if they are the same. In our ArrayCollection example we're using numbers, but in Flex everything extends Object, so numbers are a type of object and this still works.

When you find a duplicate entry, you'll want to remove it straight away (else you will get more matches as the loops progress). In Flash, when the element is removed, the array's length changes so take that in to account. You also want to reset the value of j by one because everything will then shuffle up one position, therefore you want to recheck the position you just checked.

Anyway here's is the code. I'm not going to explain it in any more depth here, because (i) you will learn more from looking at the docs and (ii) you will learn a ton from using the Flex debugger to see what is actually going on
Hope this is helpful :]

Actionscript:
  1. private function removeDulicateEntities(ac:ArrayCollection):void{
  2.     for(var i:uint=0; i <(ac.length-1); i++){
  3.         var item:* = ac.getItemAt(i);
  4.         for(var j:uint = (i+1); j <ac.length; j++){
  5.             var compareItem:* = ac.getItemAt(j);
  6.             var result:int = ObjectUtil.compare(item, compareItem);
  7.             if(result == 0){
  8.                 ac.source.splice(j, 1);
  9.                 j-=1;
  10.             }
  11.         }
  12.     }
  13.     trace("FINAL ARRAY: " + ac);
  14. }

Normally when I loop over an array I work out the array-length outside of the loop (for efficiency), but remember you shouldn't do that here, because the length of the array will change if you find a match and splice it.
Note: In AS2, you could get away with this, however, in a stricter language such as AS3, if your conditional statement is comparing properties of two objects, you will get a RTE (run time error) if one of the objects does not exist.

10 comments

Next Page »

Bad Behavior has blocked 399 access attempts in the last 7 days.