Saturday, July 24, 2010

Inline UIComponents within a TextFlow

Lately I've been thinking about how to go about coding a "fill in the gap" exercise. This kind of exercise is very popular in e-learning applications : the user is presented with text in which a few words are missing, and he has to type them in (or select them from a list).



Now, prior to Text Layout Framework, you would probably be forced to break down your text into single words, adding each one as a Label to a container (and of course a TextInput where needed). While this sounds  relatively easy, it's actually not once you start realizing that you need to position the labels yourself, taking into consideration things like line breaks, mixed RTL/LTR text, nested text styling, etc. etc., which no programmer should ever have be left to deal with on his own (that is, unless what he's actually coding IS a text framework).

Luckily, now that we do have the TLF available to us in Flex 4, things are much more simple. The example below is pretty straightforward (and very well commented), but the idea is: We add each word or phrase as a SpanElement inside a ParagraphElement. For the blank gaps we add an empty InlineGraphicElement  to serve as a placeholder. Once we update the display all we have left to do is calculate where that InlineGraphicElement  is (x,y), and add our own flex-UIComponent-based component in its place.

This example is a very basic one. I might post a more extensive one (based on given XML, etc.) if/when I ever get to writing one.




<?xml version="1.0" encoding="utf-8"?>
<s:Application creationComplete="init()" xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objectshere -->
    </fx:Declarations>
    <fx:Script>
        <![CDATA[
            import flashx.textLayout.container.ContainerController;
            import flashx.textLayout.edit.SelectionManager;
            import flashx.textLayout.elements.InlineGraphicElement;
            import flashx.textLayout.elements.ParagraphElement;
            import flashx.textLayout.elements.SpanElement;
            import flashx.textLayout.elements.TextFlow;
            import mx.collections.ArrayList;
            import mx.core.UIComponent;
            import spark.components.DropDownList;
            
            [Bindable]
            private var _textFlow:TextFlow;
            
            [Bindable]
            private var _ac:ArrayList = new ArrayList([{label:'red'},{label:'lazy'}]);
            
            private function init():void
            {    
                // create a new textflow
                _textFlow = new TextFlow();
                
                // create sprite to contain the text
                var sprite:Sprite = new Sprite();
                
                // contain the textflow in that sprite
                _textFlow.flowComposer.addController(new ContainerController(sprite, 500100));
                
                // create a paragraph and set font size
                var p:ParagraphElement = new ParagraphElement();
                p.fontSize = 14;
                
                // add some words
                addText(p, "The ");
                addText(p, "quick brown fox jumps over the");
                
                // add an empty InlineGraphicElement as a placeholder
                var pHolder:InlineGraphicElement = new InlineGraphicElement();
                pHolder.width = 120;
                pHolder.height = 20;
                p.addChild(pHolder);
                
                // add another phrase
                addText(p, "dog.");
                
                // add the paragraph to the textflow            
                _textFlow.addChild(p);                
                
                // allow only selection of text
                _textFlow.interactionManager = new SelectionManager();
                
                // update the display
                _textFlow.flowComposer.updateAllControllers();        
                
                // we need to figure out the absolute position of the InlineGraphicElement
                // note that an InlineGraphicElement's graphic position is always 0,0 inside a parent Sprite.
                //         var ph:Sprite = pHolder.graphic.parent as Sprite;            
                //         i.e. var phPoint:Point = new Point(pHolder.graphic.x, pHolder.graphic.y); // phPoint will always equal (0,0) !
                
                // create a new combobox and set its dimensions and data
                var cb:DropDownList = new DropDownList();
                cb.dataProvider = _ac;
                cb.width = pHolder.width - 20;
                cb.height = pHolder.height;
                
                // add the text to the display list
                container.addChild(sprite);
                
                // move the sprite from its (0,0) default just to prove that the position of the combobox is calculated correctly
                sprite.x = 50;
                sprite.y = 30;
            
                // position combobox over the InlineGraphicElement
                cb.move(pHolder.graphic.localToGlobal(new Point(0,0)).x + 10, pHolder.graphic.localToGlobal(new Point(0,0)).y);    
                
                // add the combobox. note that this is done AFTER adding the sprite so that it will be on top and interactive
                addElement(cb);
            }

            // creates a span with some text and adds it to the given paragraph
            private function addText(p:ParagraphElement, phraseText:String):void
            {
                var span:SpanElement = new SpanElement();
                span.text = phraseText;
                p.addChild(span);
            }
            
        ]]>
    </fx:Script>
    
    <s:SpriteVisualElement id="container" />    
    
</s:Application>


2 comments:

  1. exlellent, this is what I searched for my mobile app

    ReplyDelete
  2. I have added an InlineGraphicsElement in TextFlow which has some text in it. and i want to align the text vertically. ie. the text should be vertically aligned middle to the InlineGraphicsElement. rite now it is bottom aligned. how can i make it to middle aligned. please help me..?

    ReplyDelete

DermaRoller