SliderDataTip border skinning patch

In the previous post I discussed an issue with the SliderDataTip class' border skinning. http://www.as3offcuts.com/?p=57

Since then I've submitted a fix as a patch to the Adobe bugs system. This was the first time I'd submitted a patch, so had to jump through a few hoops to get there, but it was all pretty straightforward.

It basically involved:

  • Signing Adobe's contributor license forms, emailing them in, and waiting for a reply.
  • Checking out the 3.x branch and testing my fix in it.
  • Building the altered branch and running Abode's check in tests on it.
  • Making a patch of the fix with tortoise or something similar.
  • Submitting the patch on Adobe's bug site.
  • A good run through of the process can be found here and here.

    The patch is essentially the same as the fix described in the previous post, with the addition of removing the border construction from createChildren and just having it exist in styleChanged. There was no need for the code in createChildren; the border was already being created before createChildren was called in the first call to styleChanged.

    Have a look at the patch file here: Patch (sdk-24282)

    SliderDataTip border skinning fix (work in progress).

    There's a small issue with the border of the data tip in the two Flex Slider classes (VSlider and HSlider) (which I raised yesterday. https://bugs.adobe.com/jira/browse/SDK-24282)

    I quickly knocked up a fix for it that could either be placed in ToolTip, SliderDataTip or in an extension to SliderDataTip.

    If the fix is going into the ToolTip class, then the styleChanged method becomes the following:

    
    override public function styleChanged(styleProp:String):void 
    { 
        // This will take care of doing invalidateSize() if styleProp 
        // is "styleName" or a registered layout style such as "borderStyle". 
        super.styleChanged(styleProp); 
        // However, if the borderStyle changes from "errorTipAbove" to 
        // "errorTipBelow" or vice versa, the measured size won't change. 
        // (The pointy part of the skin simply changes from the bottom 
        // to the top or vice versa.) This means that the LayoutManager 
        // won't call updateDisplayList() because the size hasn't changed. 
        // But the TextField has to be repositioned, so we need to 
        // invalidate the layout as well as the size. 
        if (styleProp == "borderStyle" ||         styleProp == "styleName" ||         styleProp == "borderSkin" ||         styleProp == null         ) 
        { 
            if( !(border is getStyle("borderSkin")) ) 
            {
                if(border) 
                    removeChild(DisplayObject(border)); 					                     var borderClass:Class = getStyle("borderSkin");
                border = new borderClass();
                if (border is ISimpleStyleClient)                 ISimpleStyleClient(border).styleName = this;
                addChildAt(DisplayObject(border), 0); 
            }
     
            invalidateDisplayList(); 
        } 
    }
    
    
    

    This basically adds a check to see if the borderSkin style has changed. If so it remakes the border in the same way it's originally made in createChildren.

    If it's going into SliderDataTip or a subclass of ToolTip or SliderDataTip then add the above as an override to the styleChanged method, but also remember to import the mx_internal namespace and add the use namespace mx_internal line so that you can access the border property.

    So far I've tested runtime changes to border styles in the style defined for dataTipSlideName in the relevant slider, and they appear to work fine. Changing the dataTipSlideName itself won't update the data tip while it's open, but will when it is next opened. Fixing this would require a seperate addition to the styleChanged method of the Slider class.

    I'll try and get round to testing this a bit more and submitting it as a patch.

    HTML Text in a Flex Alert box

    A quick and easy way to render HTML text with the body of a Flex Alert box. There is no API to set the htmlText property of the Alert text field, but you can access it using the mx_internal namespace.

     
    var title:String ="HTML Alert!"; 
    var htmlBody:String = "This is my HTML message with <b>Bold</b> text and <br/> HTML formatting <b/>"; 
     var alert:Alert = Alert.show(str,  htmlBody);  // now target the Alert text field (after we have opened it) and set the HTML text property	
     
    use namespace mx.core.mx_internal; 
    IUITextField(alert.alertForm.textField).htmlText = htmlBody; 
     

    Using namespaces for access control.

    Using your own namespaces can be a good way to add another level of access control to class libraries that sits somewhere between public and internal.

    If you want to expose methods to classes in different packages, but not make them readily available to the whole of the outside world then this is a good way to do it. The Flex framework itself makes quite heavy use of the mx_internal namespace for inter-package communication and undocumented APIs

    First is to make the namespace. This can be done in an actionscript file that contains the following:

     
    package com.as3offcuts.namespaces 
    { 
         public namespace offcuts_internal; 
    } 
    

    That's it. Now you can import this namespace in your classes and either declare methods using it instead of public/internal etc. or call methods from other classes using it. The following demonstrates using the new namespace on an instance method:

     
    package com.as3offcuts.somepackage 
    { 
    import com.as3offcuts.namespaces.offcuts_internal; 
    
    use namespace offcuts_internal; 
     
    public class SomeClass 
     { 
        offcuts_internal function doSomething():void 
        { 
            trace("something"); 
        } 
     } 
    }
    

    There are three elements in the above example. The first is to import the namespace, much as you would import a class. The second is the use namespace declaration, and the third is use it in place of public or internal or whatever.

    To then access this method from another class simply repeat the first two steps (import the namespace and the 'use namespace ... ' line.) and then call the method as you would call a public or internal one.

     
    package com.as3offcuts.otherpackage 
    { 
     import com.as3offcuts.namespaces.offcuts_internal; 
     
    use namespace offcuts_internal; 
     
    public class SomeOtherClass 
     { 
        public function SomeOtherClass() 
        { 
            var someClass:SomeClass = new SomeClass(); 
            someClass.doSomething(); 
         }
     }
    }

    In terms of access control, as mentioned above, this sits somewhere between public and internal. It would be nice if it was just library level, but in reality anyone can import your namespace and use all the methods. They would however need to know about the namespace in first place. On the flip side though, another use for this is undocumented APIs, in which case you may actually want other people to use it.

    As a foot note, importing and using the mx_internal namespace opens up undocumented functionality in the Flex framework. As the mx_internal.as file warns though, these methods are liable to change in future releases.

    Encoding image byte data as a String.

    I was recently working with ways to hide (or obscure) images in an application's source code. I'm not sure there's many uses for encoding image data in a string; It's certainly not an easy or efficient way of working with images.  

    Never the less though here's a few notes on converting images to strings of byte data and back. First step was to write a small application that could load up an image and parse its data into a String format that could be copied/pasted/saved.

    
    
    <mx:Application 	xmlns:mx="http://www.adobe.com/2006/mxml" 	xmlns:local="*" 	layout="vertical" 	xmlns:net="flash.net.*" xmlns:display="flash.display.*"> 
     <mx:Script> 
     <![CDATA[ 
     [Bindable] 			private var text:String = ""; 
     private var loader:Loader; 
     
    private function process( bytes:ByteArray ) :void 
     { 
        bytes.position = 0; 
        var str:String = ""; 
     
        for(var i:int=0; i < bytes.length; i++)
        { 
            var byte:String = bytes.readUnsignedByte().toString(16); 
     
            if(byte.length<2) 
                byte= "0" + byte;  					str += byte; 				}  				text = str; 
        } 
     
        private function load( ) :void 
        {
            if(!loader) 					loader = new Loader(); 
            loader.contentLoaderInfo.addEventListener( Event.COMPLETE, onLoadComplete );
            loader.load( new URLRequest(urlInput.text) ); 
        } 
     
        private function onLoadComplete( e:Event ) :void 
        { 
            process( (e.target as LoaderInfo).bytes ); 
        } 
     ]]> 	</mx:Script> 
     
    <mx:TextArea text="{text}" width="100%" height="100%" /> 	
    <mx:TextInput id="urlInput" width="100%" /> 	
    <mx:Button id="processBtn" label="Process" click="load( )" />  
    </mx:Application>
     

    The main part of this is the process method, which takes a ByteArray and converts it into a string. In this case the bytes are supplied from the loaded image via the LoaderInfo object. This application will then display the string in a big text area. (The string may well be very long!).

    Second step is a little class that extends the Flex Image component and adds one property, byteSource, which it exposes as a getter/setter.  Below is the code for the setter:

     
    public function set byteSource( val:String ) :void 
    { 
        var newBytes:ByteArray = new ByteArray(); 
        var dataArr1:Array = val.split(""); 
        var dataArr2:Array = []; 
     
        for( var j:int=0; j < dataArr1.length; j+=2 ) 
        { 
            dataArr2.push("0x"+dataArr1[j]+dataArr1[j+1]);
        } 
     
        for( var k:int=0; k < dataArr2.length; k++ ) 
        { 
            newBytes[k] = dataArr2[k]; 	}  	this.source = newBytes;
            _byteSource = val;
        }
    }
    

    This method takes a string like the one generated from the application above, converts it into a ByteArray, and assigns that to the components source property. This component can now be added to any project, and supplied a string from which it will display an image. E.g.

     
    <controls:ByteImage byteSource="{ImageObject.image}" /> 
    

    Where ImageObject.image is a static constant String of image data.  

    Extending DateFormatter to add 'st', 'nd', 'rd', 'th' Support.

    I recently found myself with the task of configuring a date format for a new client.  The format that was specced looked something like:

    Wednesday 14th October, 13:33

    It was at this point I realised there is no support for the 'th' (or 'st', 'nd', 'rd') part of a date in the Flex DateFormatter.

    Rather than mess around with the string after it had been formatted, I had a poke around the DateFormatter's source code and decided to write an extended date formatter which added support for date suffixes. To achieve this I needed to extend three parts of the original DateFormatter.

    1. The format method of DateFormatter. This doesn't need to change much, most of what it does is validate and process the date object or string that was passed to it, before creating a StringFormatter to do the rest of the work. Unfortunately it won't let you access the processed value before it sends it on to the StringFormatter, and what gets returned from that isn't always re-usable, so I've duplicated all the processing code in my overridden format method. I've then altered the parameters that get passed to the new StringFormatter with the following 2 points.

    2. The private static constant VALID_PATTERN_CHARS that gets passed to the previously mentioned StringFormatter needs changing to add a new pattern character. This can't be overridden, so I define a new static constant that includes all the normal ones, plus 'T' to represent a date suffix.

    3. The internal static method extractTokenDate of the DateBase class that gets passed to the previously mentioned StringFormatter needs to be able to handle our new 'T' character. To acheive this I created my own static method that first calls extractTokenDate in DateBase, and then handles any empty result that has a token of 'T' before returning that result. Using the new DateFormatter with a 'T' in the format string will default to adding the correct 'st', 'nd', 'rd', 'th' to a date. I added a little extra customisation into this with the static var CUSTOM_NUMBER_SUFFIXES.

    This can be set to a 31 element array of any custom suffix that will be appended to the date matching it's index in the array plus one.