Adobe Flex

Applying selection dynamically to Spark RichText component

Flex 4 documentation article named "Selecting and modifying text" lists an finite final list of components that supports the selection:

  • RichEditableText
  • Label (Spark only)
  • TextInput (both MX and Spark)
  • TextArea (both MX and Spark)
  • RichTextEditor and all controls that have a TextArea as a subcomponent

   This list does not include RichText component, but luckily with a new Text Layout Framework (TLF) available starting from Flash player 10, it is quite easy to "simulate" dynamic selection of the text with some ActionScript 3 code.

   In the following example, try to drag the slider to the left and to the right to control the dynamic selection of the text of RichText component:

Get Adobe Flash player

Code listing:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/halo"
               width="200"
               height="75">
    <s:layout>
        <s:HorizontalLayout paddingLeft="25"
                            paddingTop="25"
                            paddingRight="25"/>
    </s:layout>
    <fx:Script>
        <![CDATA[            
            import flashx.textLayout.edit.EditManager;
            import flashx.textLayout.edit.SelectionState;
            import flashx.textLayout.formats.TextLayoutFormat;            
 
            protected function highlightItem(endSelectionCharIndex : int):void
            {
                var containerFormat:TextLayoutFormat = new TextLayoutFormat();                
                var paragraphFormat:TextLayoutFormat = new TextLayoutFormat();                
                var characterFormat:TextLayoutFormat = new TextLayoutFormat();
 
                characterFormat = _selectedTextFormat;
 
                var selectionState : SelectionState = new SelectionState(rt.textFlow, 0, endSelectionCharIndex, _selectedTextFormat);                
 
                // apply format to the selection
                _textEditManager.applyFormat(
                    characterFormat, 
                    paragraphFormat, 
                    containerFormat, 
                    selectionState);                                
 
                characterFormat = _notSelectedTextFormat;                
 
                // apply format to the rest of the text
                var notSelectionState : SelectionState = new SelectionState(rt.textFlow, endSelectionCharIndex, rt.text.length, _notSelectedTextFormat);                
                _textEditManager.applyFormat(
                    characterFormat, paragraphFormat, containerFormat, notSelectionState);
            }
 
 
            protected function onRichTextCreationComplete():void
            {
                _textEditManager = new EditManager();
                rt.textFlow.interactionManager = _textEditManager;
 
                _selectedTextFormat = new TextLayoutFormat();
                _selectedTextFormat.backgroundColor = 0xFF99CC;
                _selectedTextFormat.color = 0x000000;
 
                _notSelectedTextFormat = new TextLayoutFormat();
                _notSelectedTextFormat.backgroundColor = 0xFFFFFF;
                _notSelectedTextFormat.color = 0x000000;
            }
 
            private var _textEditManager : EditManager;
 
            private var _selectedTextFormat : TextLayoutFormat;
 
            private var _notSelectedTextFormat : TextLayoutFormat;            
 
        ]]>
    </fx:Script>                   
    <s:RichText id="rt" text="Sample text" 
                creationComplete="onRichTextCreationComplete()"/>
    <s:HSlider id="slider" 
              minimum="0" 
              maximum="10" 
              change="highlightItem(slider.value)">        
    </s:HSlider>        
</s:Application>

When overriding “commitProperties” put “super.commitProperties” call to the end of the method

My use-case
   Recently I've tried to introduce skin states to the pretty complex UI component in Adobe Flex 4. I have got puzzled by the behaviour that my invalidateSkinState() calls never had triggered execution of getCurrentSkinState method that controlls the current UI component skin state value as expected.

Invalidation of the same phase while processing that phase is ignored - by Alex Harui

   Under closer examination I found out that I fall into the same invalidation trap that James Polanco did in November 2010.

   I must stress out that it was easy to fall into this trap in my case because inside "commitProperties" call I was doing pretty complex calculations that involved, for example, the creation of display object sub-children, listening for "preinitialize" events from those sub-children, reacting to it, etc.

    To cut the talk short, to prevent this property invalidation trap from happening I suggest to always follow the simple rule of the thumb:

When overriding "commitProperties" put "super.commitProperties" call to the end of the method

override protected function commitProperties():void
{
    if (myPropertyChanged)
    {        
        disableMyUIComponent = true;
        invalidateSkinState();
        myPropertyChanged = false;
    }
    super.commitProperties();
}
 
override protected function getCurrentSkinState():String { 
    var returnState:String = "normal"; 
 
    // Use information in the class to determine the new view state of the skin class. 
    if (disableMyUIComponent)     { 
        returnState = "disabled"; 
    } 
    return returnState; 
}

Futher reading:
Discussion at 2008 at Flexcoders mailing list (yeah, at that time, this mailing list was more interesting to read) "Must call super.commitProperties at END of overrided commitProperties when extending XXXX?"

When it is time to reset dirty flag “invalidatePropertiesFlag” in Flex invalidation framework?

   Invalidation in Flex is a mechanism by which changes made to a component's property values are queued and postponed for the final procession till the end of the current Flash movie frame play. This property value invalidation mechanism in Flex is controlled by setting and resetting boolean variables called dirty flags.

   One of such a dirty flags used internally in Flex invalidation mechanism is invalidatePropertiesFlag flag.

   This particular flag is reset in validateProperties() method from mx.core.UIComponent class from Flex 3 SDK:

public function validateProperties():void
{
    if (invalidatePropertiesFlag)
    {
        commitProperties();
        invalidatePropertiesFlag = false;
    }
}

   Inside commitProperties() function the actual change occur to the invalidated property's value, for example, it can be "width" property.

   The question is - why dirty flag invalidatePropertiesFlag is reset after call to commitProperties() method, but not before this call?

    My far-fetched speculation is that this had happened because:

  • It is just a matter of life == code convention to put the dirty flag at the end of IF block;
  • If code within commitProperties() raises RTE, we will still have a chance to execute the code in commitProperties() during playing next frame in Flash Player and this time maybe the code will not throw RTE.

   At this point, you may start to wonder why the exact location (at the very beginning or at the very end of IF sequence) of the dirty flag can be important?

   The reason is:

   If the code within commitProperties() implementation will try to invalidate some other component's property (e.g "height" property) that tries to invoke another invalidation routine by using dirty flag, then this invalidation call will not be added to mx.managers.LayoutManager.invalidatePropertiesQueue, because dirty flag invalidatePropertiesFlag is still equal to true, see the listing of invalidateProperties method from UIComponent for the method logic:

public function invalidateProperties():void
    {
        if (!invalidatePropertiesFlag)
        {
            invalidatePropertiesFlag = true;
 
            if (parent && UIComponentGlobals.layoutManager)
                UIComponentGlobals.layoutManager.invalidateProperties(this);
        }
    }

NEVER use selectable=false for editable List or DataGrid Flex components

It is very likely that the authors of Adobe Flex SDK have never devised the following simple use-case for both List and DataGrid components from Flex SDK:

  • "selectable" property is set to false
  • "editable" property is set to true

    To prove this fact, try to play with the following samples:

    mx:List

    Get Adobe Flash player

    Click the first row, in a List, then the itemEditor appears on the first row.
    Click the second row in a List , but the itemEditor doesn't appear on the second row

    That's bug SDK-15309, documented over year ago.

    mx:DataGrid

    Get Adobe Flash player

    Code listing:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                    creationComplete="initApp();"
                    width="400"
                    height="300"
                    styleName="plain"
                    paddingLeft="0"
                    paddingRight="0"
                    paddingTop="0"
                    paddingBottom="0">
     
        <mx:Script>
            <![CDATA[
                import mx.events.DataGridEvent;
     
                private function initApp():void {
                    dg.dataProvider =                     [
                        {Artist:'Carole King', Album:'Tapestry', Price:11.99},
                        {Artist:'Paul Simon', Album:'Graceland', Price:10.99},
                        {Artist:'Original Cast', Album:'Camelot', Price:12.99},
                        {Artist:'The Beatles', Album:'The White Album', Price:11.99}
                        ];
                }
     
                private function onItemEditBegin(event : DataGridEvent) : void
                {
                    trace ("DG onItemEditBegin");
                }
     
            ]]>
        </mx:Script>
        <mx:DataGrid id="dg"
                     rowHeight="20"
                     editable="true"
                     selectable="false"
                     width="100%"
                     height="100%"
                     itemEditBegin="onItemEditBegin(event)">
            <mx:columns>
                <mx:DataGridColumn
                                   dataField="Artist"
                                   width="100">
                </mx:DataGridColumn>
                <mx:DataGridColumn
                                   dataField="Album"/>
                <mx:DataGridColumn
                                   dataField="Price"/>
            </mx:columns>
        </mx:DataGrid>
    </mx:Application>

    Now, try to click on the DataGrid header, oops, you've just hit SDK-19436 bug (itemEditor instance is created unwillingly at first column, first row of DataGrid).

    Then try to scroll the DataGrid with scroller buttons, and again you've hit another, albeit similar, SDK-21726 bug (itemEditor instance is created unwillingly at first column, first row of DataGrid).

    Now scroll the contents of Datagrid a little bit down with a scroller thumb and start to edit cell in the first topmost visible row, first column, now move the current focus by pressing keyboard combination Shift+Tab, and once again, you've hit a SDK-16262 bug.

    To conclude this post: do not set selectable on mx:List or mx:DataGrid components to false if you are going to edit their values using itemEditors, until all above mentioned bugs will be fixed.

  • Always keep an eye on most recent stable release of Adobe Flex SDK

       I was going to enter a new issue into Adobe JIRA Flex SDK bugbase that I could confirm to be present in the most recent milestone Adobe Flex SDK release 3.1.0.2710, shipped together with an August 2008 Flex Builder 3.0.1 update.

       Just before submitting the bug issue, I've tested the functionality under the question under the latest Adobe Flex SDK stable build (3.2.0.3794 at the time of writing) - and I was surprised to find out that the bug is already fixed. Joy! Joy!

        I rushed to find the reasoning for this change for UITextFormat class by reading change notes at Flex SDK download section at Adobe open source web-site, but could not find any remarks regarding this change - would be handy to have a better insight behind this change for the community.

       ps If you wonder, the issue was incorrect implementation of an internal function copyFrom from UITextFormat class. UITextField's public getUITextFormat() method relies on this copyFrom function to return UITextFormat object for the UITextField class instance. In a nutshell UITextFormat object is just a wrapper around textFormat class that should contain all the properties from wrapped textFormat class instance + add some additional functionality.

       As it turned out, In Adobe Flex SDK 3.1 and below, 7(!) different properties were not copied into UITextFormat object from existing textFormat format object because of wrong implementation of UITextFormat.copyFrom function.

       Now, we are safe, this particular issue is fixed!

    Hint: Always better to use ‘itemUpdated’ method instead of ‘setItemAt’ when updating existing items in ArrayCollection

       When you want to update already existing data item in your ArrayCollection in ActionScript 3 with a new values for object's properties you have 2 options to select from:

    1. use itemUpdated(obj) ArrayCollection's method.
    2. use setItemAt(obj, index) ArrayCollection's method.

       ActionScript code sample:

        //Update an existing person in the ArrayCollection via setItemAt
     
        public function updatePersonViaSetItemAt():void {
            var currentlySelectedItem : Object = dg.selectedItem;
            currentlySelectedItem.first = firstInput.text;
            currentlySelectedItem.last = lastInput.text;
            ac.setItemAt(currentlySelectedItem, dg.selectedIndex);
        }
     
        // Update an existing person in the ArrayCollection via itemUpdated
     
        public function updatePersonViaItemUpdated():void {
            var currentlySelectedItem : Object = dg.selectedItem;
            currentlySelectedItem.first =  firstInput.text;
            currentlySelectedItem.last =  lastInput.text;
            ac.itemUpdated(currentlySelectedItem);
     
        }

       Which one to choose?

       I would strongly advice to always use 'itemUpdated' because of the following reasons:

    • Calling setItemAt(x) on your existing data item in ArrayCollection is an equivalent to calling the removeItemAt(x) method and then calling the addItemAt(..., x) method on your data item.

         Thus after making the call to setItemAt(x, index) you will loose the current selection in your ArrayCollection dependant UI component (mx:DataGrid, mx:Tree, mx:Combobox.. etc..)

    • If the Sort rule is already applied to your ArrayCollection then you can receive some funky results like unwanted inserting of a new row when partly changing several properties of the existing data item in ArrayCollection.

       Interactive sample with "View source" option enabled to watch the difference:
    http://jabbypanda.com/labs/updateArrayCollectionItem/updateArrayCollectionItem.html

    You can try the following:

    1. First apply sorting to any of the DataGrid's column
    2. Select the first row in the Datagrid
    3. Rename the surname of the person to something completely new starting with a new letter
    4. Press "Update via setItemAt()" button control

    Voilà, new unwanted row becomes visible in the Datagrid.

    How to hide the unwanted content visible outside of mx:SWFLoader boundaries

    The issue:

    I was stumbled upon once ago when I loaded my external SWF file authored by the Flash IDE by the help of component and suddenly (?) every content object that was originally placed outside of the Stage inside the Flash IDE became visible in my Flex 2 based web application outside of the boundaries after loading sequence for my external SWF file was over (the file is Flash8 based, AVM1 if this matters).

    The corresponding task in Adobe JIRA bug-database:
    http://bugs.adobe.com/jira/browse/SDK-14590

    I currently fight this issue by applying the mask over the content that is loaded by the component, see the code below, may be you can find it helpful too:

    [sourcecode language="xml"]
    <?xml version="1.0" encoding="utf-8"?>
    <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
    resize="applyMask()" >

    <mx:Script>
    <![CDATA[
    private function applyMask() : void {
    if (swf != null &&
    swf.content != null &&
    swf.content.loaderInfo.width > 0 &&
    swf.content.loaderInfo.height > 0) {
    var scaleRatioX : Number = swf.content.loaderInfo.width /
    this.width;
    var scaleRatioY : Number = swf.content.loaderInfo.height /
    (this.height - this.controlBar.height);
    var scaleRatio : Number = Math.max(scaleRatioX, scaleRatioY);

    swf.width = swf.content.loaderInfo.width > 0 ?
    Math.round(swf.content.loaderInfo.width / scaleRatio) : 100;
    swf.height = swf.content.loaderInfo.height > 0 ?
    Math.round(swf.content.loaderInfo.height / scaleRatio) : 100;

    var s : Shape = new Shape();
    s.graphics.beginFill(0xFFFFFF);
    s.graphics.drawRect(0, 0, swf.width, swf.height);
    s.graphics.endFill();
    swf.addChild(s);
    s.visible = false;
    swf.mask = s;
    }
    }
    ]]>
    </mx:Script>
    <mx:SWFLoader id="swf"
    complete="applyMask()"
    source="navigation.swf"/>
    </mx:VBox>
    [/sourcecode]

    Incompatibility issue when migrating Flex SWC Library projects from Flex 2 Builder to Builder 3 Beta 2 project and back

    The steps I underwent:

    1. I had installed Flex Builder 3 beta 2 (FB 3 beta 2) as a plugin on my Eclipse 3.2
    2. Opened my Flex SWF Library projects inside of Flex Builder 3 beta 2
    3. Selected Flex 3 M3 (Beta 2) SDK at the dialog (default value)
    4. Flex Buidler 2 had given me the following error in my log file: (full error info skipped for the sake of text clarity)
    5. java.lang.IllegalArgumentException: Attempted to beginRule: P/FlexLibrary, does not match outer scope rule: P/as3corelib
      at com.adobe.flexbuilder.project.actionscript.internal.FlexProjectPreferences.setUpgradeFlexSDK
      (FlexProjectPreferences.java:147)
      at com.adobe.flexbuilder.project.actionscript.internal.ActionScriptProjectSettings$2.runInUIThread
      (ActionScriptProjectSettings.java:285)

    6. Nevertheless of the received error, I was able to compile ALL my Flex 2 based projects that relies on my Flex SWC Library projects in FB 3 Beta 2
    7. Then I had reopened my plain Flex projects inside Flex Builder 2 again, because at the company we are using currently Flex Builder 2 for the production.
    8. But Flex Builder 2 constantly had failed to recompile all Flex SWC Library projects that were previously opened at least once at FB 3 beta2.

    My resolution to this issue was to delete manually a config file created by FB 3 beta 2 and located at each Flex SWC library project folder at the following path:

    c:\workspace\\.settings\com.adobe.flexbuilder.project.prefs

    How big is your Flex?

    Mine Flex 2 authored SWF is 19 levels deep in its full glory:

    [sourcecode language="actionScript3"]_level0.index0.VBox5.GradientBox25.indexViewStack.
    mainPage.contentPane.mainViewStack.customerViewStack.
    presentationViewer.swf.FlexLoader396.instance458.sfiViewer.
    bank.foreground.sa04d14eaa39b3fd.content.clip[/sourcecode]

    Hyperlink component for Flex 2 ver 0.1

        Sometimes the look of <mx:LinkButton> look is not enough to satisfy the designer's requirement to display hyperlinks inside you Flex 2 application in a way we got used to see them in HTML world, e.g. to display hyperlinks inside Flex 2 in a plain text.

    Luckily Flash 9 supports pseudo CSS style selectors like a:hover, a:link, a:active. Let's use this fact to our advantage!

    Source MXML code of the example project:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    
    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application
        xmlns:mx="http://www.adobe.com/2006/mxml"
        layout="vertical"
        xmlns:text="jabbypanda.controls.*"
        width="100%" height="100%"
        horizontalAlign="center"
        backgroundGradientColors="[#000000, #282828]" backgroundColor="#282828"
        verticalAlign="middle" viewSourceURL="srcview/index.html">
     <mx:Script>
         <![CDATA[
             import jabbypanda.controls.HyperLink;
             import jabbypanda.controls.events.HyperlinkEvent;
     
            private function onLinkClick(evt : HyperlinkEvent) : void {
                // here you can invoke loading of external web-page if you want    via URLRequest
                 var hControl : HyperLink = HyperLink(evt.target);
                txtDataUrl.text = "You just had clicked '" + hControl.linkText + "'";
                txtDataUrl.visible = true;
              }
     
              private function changeStyle() : void {
                  hLink.setStyle("colorLink", 0xFFFF00);
                  hLink.setStyle("colorHover", 0xFFFF00);
                  txtDataUrl.visible = false;
              }
         ]]>
     </mx:Script>
     <mx:Style>
         HyperLink {
             colorLink : #FF0000;
             colorHover : #00FF00;
             colorActive : #0000FF;
         }
     
         Label {
             color : #CCCCCC;
             fontSize : 16;
         }
     
     </mx:Style>
      <text:HyperLink linkText="Click me! I am a hypertext link" id="hLink" linkClick="onLinkClick(event)"/>
      <mx:Button id="btn" label="Change the style" click="changeStyle()"/>
      <mx:Label id="txtDataUrl" visible="false"/>
    </mx:Application>

    See the demo below.

    Example: http://jabbypanda.com/labs/hyperLink/htmlText.html
    Source files: http://jabbypanda.com/labs/hyperLink/srcview/index.html

    ps
    I marked this component version as 0.1 because I feel there is a open room for suggestion how to correct/ extend Hyperlink component to make it more useful.

    Tell me frankly, does this hyperlink component look good enough to match a view and feel of plain old <a href> tag from HTML world?

    Acknowledgments

    Hyperlink component example written by Tracy Spratt