views:

1004

answers:

3

I am making a input field for keywords, and while the users writing I'm displaying suggestions for keyword on a list underneath the caret position. The input field is single line, so I am using the arrow up/down key to select a suggestion and Enter to insert it. And it's mostly working, with the big exception that the up/down key also changes the caret position to the begining/ending of the TextField.

I've tried using preventDefault() and stopImmediatePropagation() in the KeyboardEvent.KEY_DOWN event listener that I also use to change the selected suggestion, but that does not change anything. I checked the carret has not yet moved when KeyboardEvent.KEY_DOWN event is fired, bu visually I can see that it is being move before key is released (key up).

I could just save the caret position, and then reset af the default behaviour. But that would properly involve some hack-like code using a timer.

So does anyone know how to prevent the default behaviour?

+3  A: 

You can't prevent it, but you can reverse it. To do that you add to handlers with different priorities to the same event - KeyboardEvent.DOWN. One will execute before the TextField's , and save selection indexes and the other after, restoring them. Working code follows:

    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.ui.Keyboard;

    import mx.controls.TextInput;
    import mx.events.FlexEvent;

    public class FooledTextInput extends TextInput
    {
     private var ssi : int = 0;
     private var sei : int = 0;

     public function FooledTextInput()
     {
      super();

      this.addEventListener(KeyboardEvent.KEY_DOWN, onBeforeKeyDown, false, 10000);
      this.addEventListener(KeyboardEvent.KEY_DOWN, onAfterKeyDown, false, -10000);
     }

     private function onBeforeKeyDown(e : KeyboardEvent) : void
     {
      if (e.keyCode == Keyboard.UP || e.keyCode == Keyboard.DOWN)
      {
       ssi = this.selectionBeginIndex;
       sei = this.selectionEndIndex;
      } 
     } 

     private function onAfterKeyDown(e : KeyboardEvent) : void
     {
      if (e.keyCode == Keyboard.UP || e.keyCode == Keyboard.DOWN)
      {
       this.setSelection(ssi, sei);
      } 
     } 
    }

You might want better names for ssi (selection start index) and sei (selection end index), I was to lazy to find some. I think this solution was based on a post of Alex Harui, I don't remember exactly, but he's got a lot of good Flex related stuff on his blog.

bug-a-lot
I am making this in Flash, so I am using a TextField instead of a TextInput.I can not get this to work properly, the problem seems to be that the onAfterKeyDown is called before the default behavior.Any ideas?
Lillemanden
Are you sure you're adding the onAfterKeyDown with low priority? Try even a lower number than -10000, though I don't think that would be the issue.
bug-a-lot
Yes, I tried with both -10000 and int.MIN_VALUE.
Lillemanden
It's weird that it wouldn't work. Try adding the listeners on the component holding the TextField. Either have one component for each TextField, or one for all of them. The reason it should work that way is, because of the bubbling of the events, the handler for the TextField is triggered first, and then those of it's parent. And for the same reason you might want to actually add onBeforeKeyDown listener on the TextField to store the values, and onAfterKeyDown on its parent to restore them. That should do the trick.
bug-a-lot
Ahh, of cause. Clever thinking with bubbling. I will give it a go.
Lillemanden
It still wont work, I even tried listening on the to resture the value. But when the KEY_DOWN event is fired on the stage, the values have not changed yet.Damn Adobe and their use of magic in their components...
Lillemanden
A: 

This is my workaround for that problem. I'm sure there is a better way but it would probably require extending the TextInput control.

private function onKeyDown(event:KeyboardEvent):void
{
    if(event.keyCode == flash.ui.Keyboard.UP)
    {
     var begin:int = textinput.selectionBeginIndex;
     var end:int = textinput.selectionEndIndex;
     textinput.setSelection(begin,end);
    }
}

Not sexy but it works.

Leak
That does not work for me. I would properly have to save the positions and the apply them af the default behavior.
Lillemanden
+1  A: 

The solution by bug-a-lot is intriguing - I didnlt think to use priorities. Instead, I made use of the different phases of the event process. The problem for me was to decide whether the arrow keys should advance or retreat one character within the given text field, or jump to the next or previous field in the form.

First, I override the stage's event handling for the keys:

stage.addEventListener(KeyboardEvent.KEY_UP, typing);
// Sneak in before the normal processing handles right and left arrow keystrokes.
stage.addEventListener(KeyboardEvent.KEY_DOWN, pretyping, true, 10);

Note that I will process each key twice - before the system does it's wizardry (pretyping is called before the KEY_DOWN) so I can see where the caret is before Flash moves it, and after the system handles it (with typing, called after KEY_UP).

/** Capture prior state of text field so we can later tell if user should tab to next field or previous field. */
private function pretyping(evt:KeyboardEvent):void {
    var keyString:String = Configuration.getKeyString(evt);
    if (isFocusInTextBox()) {
     var tf:TextField = getFocus() as TextField;
     capturePhaseCaret = tf.caretIndex;
    }
}

getKeyString is a method of mine - all it does is turn those codes into convenient mnemonics. And isFocusInTextBox is a call to my focus manager - I replaced the standard focus manager to overcome other Flash issues. I just need to know if the thing is a text field or not.

Next I have to process the key after Flash has already moved the caret and maybe even jumped to a new field, and by looking at the prior state, decide what Flash did and undo it and then do what ought to happen. My function "typing" has a lot of stuff not needed for this discussion, but the important thing it does is call allowKeyMapping. allowKeyMapping decides if the user entered forward arrow (or down arrow) from the last character position in the text field, or entered backward arrow from the beginning. If so, "typing" will tab to the next or previous field, respectively.

 /** Prefer default behavior and do not allow certain kestrokes to be reinterpreted by key mappings, such as left and right cursor keys in text boxes. */
 private function allowKeyMapping(keyString:String) {
  var allow:Boolean;
  // If focus is in a text field, allow right arrow to advance to next field only if caret is at end of text.
  // Conversely, allow left arrow to back up to previous field only if caret is at beginning of text.
  // The trick is that the normal processing of keystrokes by TextFields occurs before this method is called,
  // so we need to know the caret position from before the key was pressed, which we have stored in capturePhaseCaret, set in pretyping.
  if (isDragging) {
   allow = false;
  }
  else if (isFocusInTextBox()) {
   var tf:TextField = getFocus() as TextField;
   if (keyString == Configuration.LEFT_CURSOR_KEY) {
    allow = tf.caretIndex == 0 && capturePhaseCaret == 0;
   }
   else if (keyString == Configuration.RIGHT_CURSOR_KEY) {
    allow = tf.caretIndex == tf.text.length && capturePhaseCaret == tf.text.length;
   }
   else {
    allow = true;
   }
  }
  else {
   allow = true;
  }
  return allow;
 }

I am sorry I do not have a compact example prepared. I just thought it important to emphasize that you can get around Flash's penchant for doing things for you whether you want them to happen or not.

Paul Chernoch
I have something similar to this now, but when using the KEY_UP, you can sometimes see the caret jump a bit around.So I would prefer to avoid that.
Lillemanden