LinkedTextFields Class To Split Text Into Multiple TextFields (update)

While watching Adobe Creative Suite 5 demonstration, I got inspired to create LinkedTextFields Class. In Flash CS5 IDE there is a new function that lets you link text fields in order to split one text into all fields based on how much fits in each (for example article columns). I tought it must be possible to do this with ActionScript 3. And it is. I created this lightweight LinkedTextFields Class that does exact the same thing as the new tool in CS5. LinkedTextFields Class lets you render plain text as well as HTML text. By default, text is splited based on white space characters, but you can define your own delimiters. Long story short, here is the proof of concept:

sk.yoz.text.LinkedTextFields Class.

Update (Apr 15, 2010): improved performance, <br> tag support

package sk.yoz.text
{
    import flash.events.EventDispatcher;
    import flash.text.TextField;
    
    public class LinkedTextFields extends EventDispatcher
    {
        public static const DEFAULT_TEXT_DELIMITER:RegExp = /(\s)/;
        public static const DEFAULT_HTML_DELIMITER:RegExp = /([\s<>])/;
        
        public var autoRender:Function = null;
        public var textDelimiter:RegExp;
        public var htmlDelimiter:RegExp;
        
        protected var list:Array = [];
        protected var _text:String;
        
        public function LinkedTextFields(autoRender:Function = null, 
            delimiter:RegExp = null, htmlDelimiter:RegExp = null)
        {
            super();
            
            this.autoRender = autoRender;
            this.textDelimiter = textDelimiter 
                ? textDelimiter 
                : DEFAULT_TEXT_DELIMITER;
            this.htmlDelimiter = htmlDelimiter 
                ? htmlDelimiter 
                : DEFAULT_HTML_DELIMITER;
        }
        
        public function set text(value:String):void
        {
            _text = value;
            if(autoRender != null)
                autoRender();
        }
        
        public function get text():String
        {
            return _text;
        }
        
        public function add(textField:TextField, index:int = -1):void
        {
            if(index == -1)
                list.push(textField);
            else
                list.splice(index, 0, textField);
            if(autoRender != null)
                autoRender();
        }
        
        public function remove(textField:TextField):void
        {
            var index:int = list.indexOf(textField);
            if(index != -1)
                list.splice(index, 1);
            if(autoRender != null)
                autoRender();
        }
        
        public function renderText():void
        {
            emptyTextFields("text");
            if(!this.text)
                return;
            
            var chunk:String, prevText:String;
            var chunks:Array = text.split(textDelimiter);
            var textField:TextField = list[0];
            var last:Boolean = !nextTextField(textField);
            
            while(chunks.length)
            {
                chunk = chunks.shift();
                if(chunk == "\r")
                    chunk = "\n";
                prevText = textField.text;
                textField.appendText(chunk);
                if(!last && textField.maxScrollV > 1)
                {
                    textField.text = prevText;
                    textField = nextTextField(textField);
                    textField.text = chunk;
                    if(!nextTextField(textField))
                        last = true;
                }
            }
        }
        
        public function renderHtmlText():void
        {
            emptyTextFields("htmlText");
            if(!this.text)
                return;
            
            var chunk:String, text:String = "", prevText:String;
            var chunks:Array = this.text.split(htmlDelimiter);
            var textField:TextField = list[0];
            var last:Boolean = !nextTextField(textField);
            var tag:String = "", tagName:String, isTag:Boolean, tags:Array = [];
            
            while(chunks.length)
            {
                chunk = chunks.shift();
                    
                if(chunk == "<")
                    isTag = true;
                    
                if(isTag && tag == "<")
                {
                    tagName = chunk.toLowerCase();
                    if(tagName.substr(0, 1) == "/")
                        removeLastTag(tags, tagName.substr(1));
                    else
                        addTag(tags, tagName);
                }
                
                if(isTag)
                    tag += chunk;
                    
                if(isTag && chunk == ">")
                {
                    isTag = false;
                    if(tag.substr(-2) == "/>" || tagName == "br")
                        removeLastTag(tags, tagName);
                    else if(tag.substr(0, 2) != "</")
                        addLastTagDefinition(tags, tag);
                    chunk = tag;
                    tag = "";
                }
                
                if(isTag)
                    continue;
                
                prevText = text;
                text += chunk;
                textField.htmlText = text + writeAllTagClosage(tags);
                
                if(last || textField.maxScrollV <= 1)
                    continue;
                
                textField.htmlText = prevText + writeAllTagClosage(tags);
                textField = nextTextField(textField);
                text = writeAllTagDefinitions(tags) + chunk;
                if(!nextTextField(textField))
                    last = true;
            }
            
            textField.htmlText = text + writeAllTagClosage(tags);
            
        }
        
        public function emptyTextFields(type:String = "text"):void
        {
            for each(var textField:TextField in list)
                textField[type == "text" ? type : "htmlText"] = "";
        }
        
        protected function addTag(list:Array, tagName:String):void
        {
            list.push({name:tagName});
        }
        
        protected function addLastTagDefinition(list:Array, definition:String)
            :void
        {
            list[list.length - 1].definition = definition;
        }
        
        protected function writeAllTagDefinitions(list:Array):String
        {
            var definitions:String = "";
            for each(var item:Object in list)
                definitions += item.definition;
            return definitions;
        }
        
        protected function writeAllTagClosage(list:Array):String
        {
            var closage:String = "";
            for(var i:int = list.length - 1; i >= 0; i--)
                closage += "</" + list[i].name + ">";
            return closage;
        }
        
        protected function removeLastTag(list:Array, tagName:String):void
        {
            if(list[list.length - 1].name == tagName)
                list.splice(list.length - 1, 1);
        }
        
        protected function nextTextField(textField:TextField):TextField
        {
            var index:int = list.indexOf(textField);
            if(index == -1 || index + 1 >= list.length)
                return null;
            return list[index + 1];
        }
    }
}

Usage:

package
{
    import flash.display.Sprite;
    import flash.text.TextField;
    
    import sk.yoz.text.LinkedTextFields;
    
    [SWF(width="465", height="465", frameRate="30", backgroundColor="#ffffff")]
    
    public class Linked extends Sprite
    {
        private var link:LinkedTextFields = new LinkedTextFields();
        
        public static const TEXT:String = "Lorem ipsum dolor sit amet, consec" +
                "tetur adipiscing elit. Vivamus mattis purus ac diam bibendum" +
                " vitae rhoncus sapien posuere.\n\nVestibulum gravida mi vel " +
                "pis cursus sit amet interdum eros egestas. Nunc fermentum ul" + 
                "tricies velit, non dictum est venenatis ultricies. Pellentes" +
                "que vehicula lectus nec nibh mollis pellentesque. Aenean vit" +
                "ae tortor lectus. Nulla imperdiet erat nec sapien ornare ut " +
                "laoreet sem venenatis. Mauris ipsum augue, lacinia sed solli" +
                "citudin interdum, rutrum ornare nisl. Nulla interdum lorem a" +
                "ccumsan leo tincidunt adipiscing. Nunc egestas blandit nibh," +
                " ultrices accumsan tortor commodo non. Cras tempus scelerisq" +
                "ue ullamcorper.\n\nNullam velit lacus, facilisis vel pharetr" +
                "a in, lacinia vitae purus. Sed nisl lorem, lacinia a molesti" +
                "e eu, pretium at justo. Nulla facilisi. Maecenas sagittis te" +
                "llus quis sapien vestibulum gravida. Suspendisse potenti. Cu" +
                "rabitur at felis et nisl tincidunt condimentum ut nec eros. " +
                "Pellentesque neque magna, venenatis sit amet bibendum eget, " +
                "cursus eu sapien. Nulla malesuada convallis felis nec congue" +
                ". Sed lorem massa, egestas a pharetra commodo, consectetur i" +
                "mperdiet odio. Nullam nec neque ac metus ultrices commodo. M" +
                "aecenas a lorem sed augue tincidunt tincidunt. Nam lobortis " +
                "vestibulum massa, ut viverra leo venenatis a. Praesent scele" +
                "risque, velit non tempor euismod, tellus nulla aliquam nisi," +
                " vitae vestibulum lectus est vel neque. Class aptent taciti " +
                "sociosqu ad litora torquent per conubia nostra, per inceptos" +
                " himenaeos. Proin eleifend turpis vel lectus vehicula accums" +
                "an. Vestibulum aliquam mi et metus tristique fermentum. Null" +
                "a eget lorem in mi feugiat sollicitudin at quis lacus.";
        
        public static const HTML:String = "<font size='20'>Lorem ipsum <font " + 
                "color='#ff0000'>dolor sit amet</font>, consectetur <font fac" + 
                "e='_sans'><b>adipiscing</b> elit. Aliquam ac orci <u>urna, e" + 
                "u ornare ue. <b>Nullam <font color='#00ff00'>suscipit, <font" + 
                " color='#0000ff'>turpis vitae viverra ultrices</font>, turpi" + 
                "s nisl euismod lorem, convallis tristique</font> lectus risu" + 
                "s</b> convallis</u> orci. Vitae vestibulum lectus</font> <fo" + 
                "nt size='15' color='#999999'>est vel neque. Class aptent tac" + 
                "iti <b>sociosqu ad litora <u>torquent</u> per conubia nostra" + 
                "</b>, per inceptos eget lorem in mi feugiat sollicitudin at " + 
                "quis lacus ultrices accumsan tortor commodo non. Cras tempus" + 
                " scelerisq</font></font>";
        
        public function Linked():void
        {
            super();
            
            var textField1:TextField = new TextField();
            textField1.x = 10;
            textField1.y = 10;
            textField1.width = 410;
            textField1.height = 100;
            textField1.border = true;
            textField1.wordWrap = true;
            addChild(textField1);
            
            var textField2:TextField = new TextField();
            textField2.x = 10;
            textField2.y = 120;
            textField2.width = 200;
            textField2.height = 100;
            textField2.border = true;
            textField2.wordWrap = true;
            addChild(textField2);
            
            var textField3:TextField = new TextField();
            textField3.x = 220;
            textField3.y = 120;
            textField3.width = 200;
            textField3.height = 100;
            textField3.border = true;
            textField3.wordWrap = true;
            addChild(textField3);
            
            link.add(textField1);
            link.add(textField3);
            link.add(textField2, 1);
            
            //link.text = TEXT;
            link.text = HTML;
            link.renderHtmlText();
        }
    }
}

You may ask, wheter this is also possible on for example Flex components, and the answer is: YES! Every single component that uses text uses TextField to render it, but there text fields are hidden under internal namespace. For example for TextInput/TextArea components you reach TextField this way:

import mx.core.mx_internal;
use namespace mx_internal;
var tf:TextField = TextField(textAreaId.getTextField());

2 comments so far

  1. Burak April 27, 2010 11:08

    Thank you soo much.. I have been searching for one month to solve this problem..

  2. Alejandro November 30, 2010 18:35

    Thank you! This is great and so easy!

Leave a comment

Please be polite and on topic. Your e-mail will never be published.