2009년 10월 26일 월요일

한글엔 maxChars 대신 maxBytes


기본적으로 Flex의 TextInput에는 readOnly(editable=false) 상태일 때 배경색이 바뀌질 않는다. enabled가 false일 경우에는 스타일에서 배경색을 지정할 수 있을 뿐이다.
그런데, readOnly 상태일 때 내용만 마우스로 선택해서 복사하고 싶을 때는 아무래도 readOnly 상태여야 하는데 입맛에 맞질 않아 readOnly 상태일 때 지정된 배경색이 되도록 하는 컴포넌트를 하나 만들었다. (아래에서 set editable 메소드)
그렇게 잘 쓰고 있었는데 이번에 DB컬럼의 바이트길이에 따라서 TextInput의 입력길이를 제한해야 할 일이 생겼다.(어디나 그렇겠지만..) 그런데, TextInput에는 maxChars라는 문자수 기준의 입력제한만 있어서 한글과 같이 멀티바이트 문자를 사용하는 언어는 맞아 떨어지질 않기 때문에 아래와 같이 기능을 추가한 컴포넌트를 만들었다.
전에 자바스크립트로도 이런 비슷한 기능을 만들어 봐서 ActionScript의 API만 찾으니 그렇게 오래 시간이 걸리지 않았다. 다만, ByteArray 클래스의 writeMultiByte라는 메소드의 두번째 인자가 꼭 있어야 하니 미리 입력되는 Character Set을 알고 있어야 하는 단점이 있긴 하다. 자바처럼 그냥 getBytes 하나로 처리할 수 있으면 언어에 상관없이 사용할 수 있을 텐데.. 아쉽다. 어딘가 환경설정 파일에 세팅해 놓고 그것을 읽게 하면 될 수도 있을 것 같다.

또 아쉬운 점은 KEY_DOWN으로 처리하면 규정길이를 넘는 문자를 입력해도 입력되는 문자가 안 보일 텐데 이 이벤트가 안 먹어서 KEY_UP 이벤트를 처리하니 입력되는 문자가 보였다가 지워져서 약간 모냥이 빠진다.
FOCUS_OUT 이벤트는 마지막으로 길이를 넘어가는 부분에 한글을 입력한 상태에서 탭이나 마우스로 포커스가 벗어나면 넘어간 문자가 안 지워져서 포커스가 벗어날 때도 확인해서 넘어가는 문자를 지우기 위해 이벤트리스너를 달았다.

참고로 addEventListener 메소드의 다섯번째 인자인 weakReference를 true로 한 것은, 이렇게 하면 이 컴포넌트가 지워질 때 리스너에 상관없이 Garbage Collection 대상이 되도록 하기 위해서다.

package com.customcomp
{
    import flash.events.Event;
    import flash.events.FocusEvent;
    import flash.events.KeyboardEvent;
    import flash.utils.ByteArray;
   
    import mx.controls.TextInput;

    // editable 속성이 true일 때는 배경색이 흰색, false일 때는 배경색이 옅은 회색인 TextInput 컴포넌트
    public class TextBox extends mx.controls.TextInput
    {
        // 한글 등 multi-byte 문자들의 입력길이 제한을 위해서 사용하는 속성
        public var maxBytes:int = -1;
       
        public function TextBox()
        {
            super();
           
            // Key 입력시마다 입력내용의 바이트길이를 체크해 넘는 만큼 잘라낸다
            this.addEventListener(KeyboardEvent.KEY_UP, maxBytesHandler, false, 0, true);
            // 포커스가 벗어날 때 입력내용의 바이트길이를 체크해 넘는 만큼 잘라낸다
            this.addEventListener(FocusEvent.FOCUS_OUT, maxBytesHandler, false, 0, true);
        }
       
        override public function set editable(value:Boolean):void {
            super.editable = value;
           
            if (value == false)
                this.setStyle("backgroundColor", 0xEEEEEE);
            else
                this.setStyle("backgroundColor", 0xFFFFFF);
        }
       
        public function maxBytesHandler(event:Event):void {
            if (this.maxBytes != -1 && getByteLength(this.text) > this.maxBytes) {
                this.text = getTrimmedString(this.text, this.maxBytes);
            }
        }
       
        // 문자열의 바이트 길이를 리턴
        private function getByteLength(str:String):int {
            var byteArr:ByteArray = new ByteArray();
            byteArr.writeMultiByte(str, "euc-kr");
           
            return byteArr.length;
        }
       
        // str에서 maxLength 바이트를 넘어가는 문자들을 잘라내고 리턴
        private function getTrimmedString(str:String, maxLength:int):String {
            var tempString:String = str;
           
            for (var i:int = str.length; i > 0; i--) {
                if (getByteLength(tempString) <= maxLength)
                    return tempString;
                else {
                    tempString = tempString.substr(0, tempString.length-1);
                }
            }
           
            return "";
        }
    }
}

maxBytes 속성을 사용하기 위해서는 여기서 사용한 컴포넌트를 사용하고 속성에 maxBytes를 사용하면 된다.
다 아는 얘기겠지만, 루트 컴포넌트에는 미리 namespace를 선언해 둬야 한다.

<mx:Panel xmlns:custom="com.customcomp.*" ...>
...
<custom:TextBox id="someId" maxBytes="10" .../>

이렇게 사용하면 someID라는 TextInput에 10바이트가 넘는 문자를 입력하면 자동으로 잘라준다.