środa, 20 kwietnia 2011

Flex 4: Truncate Label in the middle

Flex offers the possibility to truncate Label's content (both Spark and MX) at the end and add "..." in place of truncated text.

I made a Spark component that derives from Label and truncates text in the middle. It's just a re-make based strongly on it's MX version:

http://cookbooks.adobe.com/post_Graphical_truncating_of_texts_and_labels__custom_t-13306.html

Here's the code for Spark version:

package
{
    import flash.text.TextField;
   
    import mx.core.UITextField;
    import mx.core.UITextFormat;
    import mx.core.mx_internal;
    import mx.managers.ISystemManager;
    import spark.components.Label;
    use namespace mx_internal;
   
    public class MiddleTruncatingLabel extends Label
    {
        public function MiddleTruncatingLabel()
        {
        }
       
        private var _trueText:String;
        private var _textChanged:Boolean = false;
        private var _isTruncated:Boolean = false;
       
        override public function set text(value:String):void
        {
            // Check if value changed and indicate that
            // to initiate new middle-truncating
            // Remember original text
            if (value != _trueText)
            {
                _trueText = value;
                super.text = truncateTextMiddle(_trueText, unscaledWidth);
                _textChanged = true;
            }
        }
       
        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            super.updateDisplayList(unscaledWidth, unscaledHeight);
           
            // check if need truncate and truncate
            if ( !isNaN(unscaledWidth) && _textChanged )
            {
                // override textfield's text
                text = truncateTextMiddle(_trueText, unscaledWidth);
               
                // update info to check if need truncate to do not run
                // middle-truncation too much of times
                _textChanged = false;
            }
        }
       
        public function truncateTextMiddle(fullText:String, widthToTruncate:Number) : String
        {
            if (!(fullText) || fullText.length < 3 || !this.parent)
            {
                // skip any truncating if no styles (no parent),
                // or text is too small
                return fullText;
            }
           
            // add paddings for some font oversize issues
            var paddingWidth:Number =
                UITextField.mx_internal::TEXT_WIDTH_PADDING +
                this.getStyle("paddingLeft") + this.getStyle("paddingRight");
           
            // Skip if width is too small
            if (widthToTruncate < paddingWidth + 10) return fullText;
           
            // Prepare measurement object
            // We create new TextField, and copy styles for it from this object
            // We cannot re-use internal original text field instance because
            // it will cause event firing in process of text measurement
            var measurementField:TextField = new TextField();
           
            // Clear so measured text will not get old styles.
            measurementField.text = "";
           
            // Copy styles into TextField
            var textStyles:UITextFormat = this.determineTextFormatFromStyles();
            measurementField.defaultTextFormat = textStyles;
            var sm:ISystemManager = this.systemManager;
            if (textStyles.font)
            {
                measurementField.embedFonts = sm != null && sm.isFontFaceEmbedded(textStyles);
            }
            else
            {
                measurementField.embedFonts = false;
            }
            if (textStyles.antiAliasType) {
                measurementField.antiAliasType = textStyles.antiAliasType;
            }
            if (textStyles.gridFitType) {
                measurementField.gridFitType = textStyles.gridFitType;
            }
            if (!isNaN(textStyles.sharpness)) {
                measurementField.sharpness = textStyles.sharpness;
            }
            if (!isNaN(textStyles.thickness)) {
                measurementField.thickness = textStyles.thickness;
            }
           
            // Perform initial measure of text and check if need truncating at all
           
            // To measure text, we set it to measurement text field
            // and get line metrics for first line
            measurementField.text = fullText;
            var fullTextWidth:Number = measurementField.getLineMetrics(0).width + paddingWidth;
            if(fullTextWidth > widthToTruncate){
                // get width of ...
                measurementField.text = "...";
                var dotsWidth:Number = measurementField.getLineMetrics(0).width;
               
                // Find out what is the half of truncated text without ...
                var halfWidth : Number = (widthToTruncate - paddingWidth - dotsWidth) / 2;
               
                // Make a rough estimate of how much chars we need to cut out
                // This saves steps of character-by-character preocessing
                measurementField.text = "s";
                var charWidth:Number = measurementField.getLineMetrics(0).width;
                var charsToTruncate:int = Math.round(
                    ((fullTextWidth - paddingWidth) / 2 - halfWidth) /
                    charWidth) + 2;
               
                // allow some distortion to account fractional widths part
                halfWidth = halfWidth - 0.5;
               
                // Below algorithm makes rough middle-truncating
                // Then it is corrected by adding or removing
                // characters for each part until reach required
                // width for each half. Algorith does checks
                // (min max and loop ciodnitions) so that string
                // cannot be less then one character for each half
               
                // see if right part of text approximately fits into half width
                var rightPart:String;
                var widthWithNextChar:Number;
               
                var len:int = fullText.length;
                var currLoc:int = Math.min(len/2 + charsToTruncate + 1, len-1);
                measurementField.text = fullText.substr(currLoc);
                var rightPartWidth:Number = measurementField.getLineMetrics(0).width;
               
                if (rightPartWidth > halfWidth) {
                    // throw away characters until fits
                    currLoc++;
                    while (rightPartWidth > halfWidth && currLoc < len) {
                        measurementField.text = fullText.charAt(currLoc);
                        rightPartWidth -= measurementField.getLineMetrics(0).width;
                        currLoc++;
                    }
                    rightPart = fullText.substr(currLoc - 1);
                } else {
                    // try to add characters one-by-one and
                    // see if it still fits
                    widthWithNextChar = 0;
                    do {
                        currLoc--;
                        rightPartWidth += widthWithNextChar;
                        measurementField.text = fullText.charAt(currLoc);
                        widthWithNextChar = measurementField.getLineMetrics(0).width;
                    } while (rightPartWidth + widthWithNextChar <= halfWidth && currLoc > 0);
                    rightPart = fullText.substr(currLoc + 1);
                }
               
                // Do the same with left part, but compare overall string
                // Overall is needed because character-by character
                // would not give us correct total width of string -
                // somehow overall text is measured with sapcers etc. and
                // also there are rounding issues.
                // This way, and by putting left part calculating as last, we allow
                // left part might be larger (may become more than half).
               
                // allow some distortion in widths fractions
                widthToTruncate = widthToTruncate - 0.5 - paddingWidth;
               
                currLoc = Math.max(len/2 - charsToTruncate, 1);
                measurementField.text = fullText.substr(0, currLoc) +
                    "..." + rightPart;
                var truncatedWidth:Number = measurementField.getLineMetrics(0).width;
                if (truncatedWidth > widthToTruncate) {
                    // throw away characters until fits
                    currLoc--;
                    while (truncatedWidth > widthToTruncate && currLoc > 0) {
                        measurementField.text = fullText.substr(0, currLoc) +
                            "..." + rightPart;
                        truncatedWidth = measurementField.getLineMetrics(0).width;
                        currLoc--;
                    }
                    currLoc++;
                } else {
                    // try to add characters one-by-one and
                    // see if it still fits
                    do {
                        currLoc++;
                        measurementField.text = fullText.substr(0, currLoc) +
                            "..." + rightPart;
                        widthWithNextChar = measurementField.getLineMetrics(0).width;
                    } while (widthWithNextChar <= widthToTruncate &&
                        currLoc < len-1);
                    currLoc--;
                }
               
                return fullText.substr(0, Math.max(currLoc,1)) +
                    "..." + rightPart;
            }
            return fullText;
           
        }
    }
}

Brak komentarzy:

Prześlij komentarz