Fix for buggy Flash TextField selection (on mouse leave)

24th September 2008 @ 4:34 pm
nowhere
Today's post is about tiny details.

I've noticed a frustrating problem with text in Flash that I've finally managed to find a workaround for. I'm testing in Mac OS X using Firefox 3 and Flash 9, but I've seen it on other systems too, and recently had help reproducing this bug from our client at MSNBC, so I'm pretty sure it's widespread.

Since this is a fairly esoteric issue that will only bother Flash programmers, I'll continue only in the full version of this post.

Here's the setup to reproduce the bug:

  1. add an editable text field (flash.display.TextField or fl.controls.TextInput) to the left side of the stage
  2. select the text in one sweep, releasing the mouse outside the browser window
  3. start typing, or move the mouse back over the stage

If you start typing in this scenario, then the letters are erased as they are typed, because the text is still being selected as if you hadn't released the mouse. If you move your mouse back over the stage, then the selecting action is still in progress and doesn't stop until the mouse is clicked. Two-for-one buggy behaviour, how frustrating!

Note that the continued selection behaviour is a common problem in Flash that affects any site that uses SIFR to enhance headlines, for example.

Here's a swf that demonstrates the problem ("offscreen" includes outside of the flash):

[kml_flashembed movie="http://www.tom-carden.co.uk/wp-content/uploads/2008/09/textinputselectionbad.swf" height="50" width="500" bgcolor="#eeeeee" /]

And the code for the above swf.

package {
  import flash.display.Sprite;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.text.TextField;
  import flash.text.TextFieldType;

[SWF(backgroundColor="#eeeeee")] public class TextInputSelectionBad extends Sprite { public var textField:TextField; public function TextInputSelectionBad() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; textField = new TextField(); textField.type = TextFieldType.INPUT; textField.text = "select me and release the mouse off screen"; textField.x = textField.y = 10; textField.width = textField.textWidth + 8; textField.height = textField.textHeight + 4; addChild(textField); textField.textColor = 0x202020; textField.border = true; textField.borderColor = 0xdddddd; textField.background = true; textField.backgroundColor = 0xffffee; } } }

Once more I'm assuming you're familiar with Flex Builder's actionscript projects, if not then similar code should work in the Flash editor, but you're on your own with that I'm afraid.

OK OK, what's the fix?

The solution I came up with is to add a listener to the stage for mouse leave, and to temporarily disable selection. Text entry still works because the field maintains focus, but the text is not deleted on input. Additionally, the continued selection behaviour disappears. Clicking anywhere on the stage immediately restores the selectable flag on the field and everything should work as normal.

Here's an example of a swf with the fix implemented:

[kml_flashembed movie="http://www.tom-carden.co.uk/wp-content/uploads/2008/09/textinputselection.swf" height="50" width="500" bgcolor="#eeeeee" /]

Flash focus behaviour works differently in Mac and Windows, on Mac I think you have to click twice but this is normal once the mouse has been released outside an application – I haven't managed to get the caret to go to the clicked point in the field, caretIndex is read-only sadly.

The code for the fixed swf is:

package { import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.MouseEvent; import flash.text.TextField; import flash.text.TextFieldType;

[SWF(backgroundColor="#eeeeee")] public class TextInputSelection extends Sprite { public var textField:TextField; public function TextInputSelection() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; textField = new TextField(); textField.type = TextFieldType.INPUT; textField.text = "select me and release the mouse off screen"; textField.x = textField.y = 10; textField.width = textField.textWidth + 8; textField.height = textField.textHeight + 4; addChild(textField); textField.textColor = 0x202020; textField.border = true; textField.borderColor = 0xdddddd; textField.background = true; textField.backgroundColor = 0xffffee; stage.addEventListener(Event.MOUSE_LEAVE, onStageMouseLeave); } private function onStageMouseLeave(event:Event):void { textField.selectable = false; stage.addEventListener(MouseEvent.MOUSE_DOWN, onStageMouseDown, true); } private function onStageMouseDown(event:Event):void { stage.removeEventListener(MouseEvent.MOUSE_DOWN, onStageMouseDown, true); textField.selectable = true; textField.setSelection(0,0); } } }

I promise that my next post will have prettier pictures.