Extending the Flex 3.6 TextArea te get the coordinates of current cursor position

While working on our embedded commenting system I thought about adding the ability to mention users in your comments just like Facebook or Twitter allow you to do.  The idea is simple: Display a list of users when the user presses @ or another desired key combination( I chose  CTRL + Space ) and then Insert the name in the TextArea when the users clicks it.

The problem is that Flex does not give you the current coordinates of the  cursor position so you have to find them by your own. I searched for solutions and I found two, both had some fallbacks:

The simpler one: thekuroko  uses TextLineMetrics to get the number of lines, lineHeight, and the width of the last line to get the x coordinate.

Fallback: Cannot get cursor position if you set the cursor position in some position other than the end of the text.

Polygeek   on the other side user getColorBoundsRect method on the BitmapData to find the x/y coordinates of the last character.

Fallback:The same as above!

I wanted my solution to have no fallbacks. I chose the first solution and when the user puts the cursor somewhere in the middle of the text in the TextArea I assign the text from the start to the cursor position to another hidden TextArea and get the coordinates by using the same logic.

TextArea on Polygeek helped me a lot. Code follows:

package com.controls
{

import flash.events.KeyboardEvent;
import flash.text.TextLineMetrics;
import flash.ui.Keyboard;

import mx.controls.TextArea;
import mx.controls.textClasses.TextRange;
import mx.core.EventPriority;
import mx.core.ScrollPolicy;
import mx.core.UIComponent;
import mx.core.mx_internal;

public class textarea extends TextArea
{
private var _monitorCursorCoordinates:Boolean = false;

private var _cursorX:Number = -1;
private var _cursorY:Number = -1;

private var shadowTA:textarea;

use namespace mx_internal;
public function textarea(required:Boolean)
{
super();
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown,false,EventPriority.DEFAULT_HANDLER);
}

public function get cursorY():Number
{
return _cursorY;
}

public function get cursorX():Number
{
return _cursorX;
}

public function get numLines():int
{
this.validateNow();
var numLines:uint = this.mx_internal::getTextField().numLines;
return numLines;
}

public function get lineHeight():int
{
this.validateNow();
var textLineMetrics:TextLineMetrics = this.getLineMetrics(0);
return textLineMetrics.height;
}

public function get leading():int
{
var match:Array = this.htmlText.match(/<TEXTFORMAT[^><]+LEADING="(\d+)"[^><]*>/i);
return match[1];
}
//when text is entered recalculate the coordinates. It would be better to put this code in a texchange default event handler
private function onKeyDown(event:KeyboardEvent):void
{
if(shadowTA !=null)
{
shadowTA.text = this.text.substring(0, this.selectionBeginIndex);
shadowTA.width = width;
shadowTA.height = height;

var currentLineMet:TextLineMetrics = shadowTA.getLineMetrics(shadowTA.numLines-1);
_cursorX = currentLineMet.width;
_cursorY = (lineHeight * (shadowTA.numLines - this.verticalScrollPosition));
}
}
public function hasVerticalScrollBar():Boolean
{
if (super.verticalScrollBar == null || super.verticalScrollBar.visible == false)
return false;
return true;
}

public function hasHorizontalScrollBar():Boolean
{
if (super.horizontalScrollBar == null || super.horizontalScrollBar.visible == false)
return false;
return true;
}

public function get monitorCursorCoordinates():Boolean
{
return _monitorCursorCoordinates;
}

public function set monitorCursorCoordinates(value:Boolean):void
{
_monitorCursorCoordinates = value;
if(value)
{
shadowTA = new textarea(false);
shadowTA.includeInLayout = false;
shadowTA.visible = false;
this.addChild(shadowTA);
}
}

//insert text at the given position in the TextArea
public function insertTextAt(i:int,text:String):void
{
var b:String = this.text.substring(0,i);
var e:String = this.text.substr(i,this.text.length-1);
this.text = b+text+e;
}
}

}

The MXML code

<textarea id="note" keyDown="monitorCRLSPACE"  monitorCursorCoordinates="true"/>

<mx:Script>
<![CDATA[

function monitorCRLSPACE(evt)
{
if(evt.ctrlKey && evt.keyCode == 32)
{

//testPopup is a popup component instance
testPopup.open(note,null,false,"bottomCenter", note.cursorX-note.width/2, note.cursorY - note.height);
}
}

]]>
</mx:Script>

Well that should be enough to give you starting point the other things depend on your situation. Please feel free to suggest a better solution ?

happy coding

Leave a Reply

Your email address will not be published. Required fields are marked *