LineLimitText Component: Text With maxLines Property

June 17th, 2008 by Unknown Morphician

A little while ago I worked on a project that required some Flex screens to have text fields display dynamic text using a specified maximum number of lines. A simple solution would be to merely define a height on a Text component. In this case there were other components that flowed in the layout below the text field. Setting a height would show an unnecessary space for text that had fewer than the maximum number of lines. Also setting a height wouldn't work well with changing the text styles.

So what was I to do? After reading the Flex docs and scanning the Text, Label, and TextField source, I decided to subclass the Text component. Here is the resulting component:


package com.themorphicgroup.controls {

   import flash.text.TextLineMetrics;

   import mx.core.mx_internal;
   import mx.core.UITextField;
   import mx.controls.Text;

   use namespace mx_internal;

   public class LineLimitText extends Text {

       protected var _maxLines:uint = 0; // max lines to show; 0 = do nothing;
       private var _isTruncated:Boolean = false;
      private var explicitHTMLText:String = null; 

      public function get maxLines():uint {
         return _maxLines;
      }

      public function set maxLines(value:uint):void {
         if (_maxLines != value) {
            _maxLines = value;
            invalidateDisplayList();
            invalidateSize();
         }
      }

      /**
       * Returns the number of lines displayed.
       */
      public function get numLines():int {
         var lines:int;

         if (_maxLines > 0) {
            var metrics:TextLineMetrics = textField.getLineMetrics(0);
            var textHeight:Number = Math.ceil(metrics.height) * _maxLines;
            if (textField.height > textHeight) {
               lines = _maxLines;
            } else {
               lines = textField.numLines;
            }
         } else {
            lines = textField.numLines;
         }
         return lines;
      }

      override public function set text(value:String):void {
         super.text = value;
         explicitHTMLText = null;
      }

       override public function set htmlText(value:String):void {
          super.htmlText = value;
          explicitHTMLText = value;
       }

      /**
       * Overriding measure and trucating the lines here.
       * If not HTML and is truncated, add ellipses.
       */
      override protected function measure():void {
         super.measure();

         // if not html
         if (!isHTML) {
            // if previously truncated, reset the text to the original
            if (_isTruncated) {
               textField.text = super.text;
               textField.validateNow();
            }
         }         

         // if want to limit lines
         if ((_maxLines > 0) && (textField.numLines > _maxLines)) {
            // if not html
            if (!isHTML) {
               _isTruncated = true;

               var newText:String = "";
               var line:String;
               // loop through lines and collect text
               for (var i:uint = 0; i < _maxLines; i++) {
                  line = textField.getLineText(i);
                  if (i < (_maxLines - 1)) {
                     newText += line;
                  // if on last line
                  } else {
                     // remove end and add ...'s
                     line = line.substr(0, line.length - 3);         // cut 3 chars off end
                     line = line.substr(0, line.lastIndexOf(" "));   // cut at last space
                     newText += (line + "...");
                  }
               }
            }

            // calc and set the measured height
            var metrics:TextLineMetrics = textField.getLineMetrics(0);
            var newTextHeight:Number = Math.ceil(metrics.height) * _maxLines;
            measuredHeight = newTextHeight + UITextField.TEXT_HEIGHT_PADDING;

            // if not html
            if (!isHTML) {
               textField.text = newText;
            }
         } else {
            // if not html
            if (!isHTML) {
               // if truncated before, but dont need it anymore, reset size
               if (_isTruncated) {
                  textField.autoSize = "left";
                  measuredHeight = textField.textHeight + UITextField.TEXT_HEIGHT_PADDING;
               }
               _isTruncated = false;
            }
         }
      }

       private function get isHTML():Boolean {
         return explicitHTMLText != null;
      }
   }
}

The key to this component was overriding the protected measure() method and accessing the protected textField variable. The measure() method uses the new maxLines property and limits the number of lines in the textField if necessary. Now this new measure() method is more expensive so I wouldn't go and use it everywhere.

Here is an example application that uses LineLimitText:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:controls="com.themorphicgroup.controls.*" layout="absolute">
   <mx:Script>
      <![CDATA[
         [Bindable]
         private var _lipsum:String = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam et mi. Sed mi. In sit amet tortor. Curabitur eget neque. Ut nisi quam, convallis eget, elementum at, condimentum vitae, lorem. Vestibulum adipiscing hendrerit ligula. Donec vitae dolor id libero ultricies condimentum. Nulla dapibus sapien. Aliquam consectetuer lacus non nisl. Suspendisse rutrum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.";
      ]]>
   </mx:Script>
   <mx:Style>
      FormItem {
         backgroundColor: #FFFFFF;
      }
   </mx:Style>
   <mx:Form>
      <mx:FormItem label="mx:Label, width=200">
         <mx:Label text="{_lipsum}" width="200"/>
      </mx:FormItem>
      <mx:FormItem label="mx:Text, width=200">
         <mx:Text text="{_lipsum}" width="200"/>
      </mx:FormItem>
      <mx:FormItem label="mx:Text, width=200, height=60">
         <mx:Text text="{_lipsum}" width="200" height="60"/>
      </mx:FormItem>
      <mx:FormItem label="controls:LineLimitText, width=200">
         <controls:LineLimitText text="{_lipsum}" maxLines="4" width="200" color="#FF0000"/>
      </mx:FormItem>
   </mx:Form>
</mx:Application>

This results in the following screenshot:

The text in red in the screenshot uses LineLimitText. Also in the screenshot is a Text component with a defined height. That one doesn't have the fancy ellipses though. This component was an interesting exercise in understanding Flex components, reading docs and source.

Share and Enjoy:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • DZone
  • Fark
  • LinkedIn
  • Live
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  • Yahoo! Bookmarks
  • RSS
  • email

2 Responses to “LineLimitText Component: Text With maxLines Property”

  1. tom says:

    […] former peeps over at Yahoo just released 10 more components, 3 Flash and 5 Flex components. The also fixed some of the bugs […]

  2. John Gag says:

    This is great, thanks!

Leave a Reply