[ActionScript] GroupingCollection2 클래스를 이용한 그룹핑(Grouping)

플래시빌더 4.7은 언제 정식 출시되나.. 지금 사용하고 있는 4.6의 차트 컴포넌트에 약간의 버그가 있고 4.7에서는 해결되기를 기대해 봅니다. 특히 4.7은 스레드 개념을 지원함으로써 더욱 유연한 개발이 가능할 것으로 기대됩니다. 이 스레드 개념은 HTML5의 Worker 기능에서 많은 힌트를 얻어 이번에 추가한 기능이 아닌가 싶습니다. 여튼………

어떤 데이터가 있다면.. 동일한 값을 가지는 녀석들을 묶는데.. 이렇게 1차적으로 묶인 녀석들에 대해서 또 한번 다른 필드의 동일한 값으로 또 묶어 그룹핑하는데 요긴하게 사용한 클래스입니다. 이 클래스의 존재를 모른체 직접 코드를 한땀.. 한땀.. 땀 한방울.. 땀 두방울 흘리며 코딩했답니다. 물론 지금은 이 클래스를 이용해 기능 개발을 했구요.

var grouping:GroupingCollection2 = new GroupingCollection2();
grouping.source = new ArrayCollection(rows);
grouping.grouping = new Grouping();
grouping.grouping.fields = 
    [ new GroupingField("FIELD1"), new GroupingField("FIELD2") ];
grouping.refresh();

성의없이.. 막 코드로 시작합니다. rows가 그룹핑할 배열(Array)입니다. 이 배열의 요소는 FIELD1과 FIELD2라는 이름의 속성값이 있습니다. 일단 FIELD1에 대해 동일한 요소들을 한번 묶고.. 다시 이렇게 묶인 그룹들을 대상으로 FIELD2로 또 한번 묶습니다.

결과적으로 그룹핑된 컬렉션은 grouping 객체(위의 코드 참조)의 getRoot 매서드를 통해 얻습니다. getRoot()의 결과에 대한 클래스 타입은, 배열을 대상으로 그룹핑 했으므로 ArrayCollection 타입입니다.

그룹핑되기 이전에는 단순한 1차원 배열이였던 것이 GroupingCollection2 클래스를 통해 그룹핑되면 3차원 배열이 됩니다. 이 3차원으로 재가공된 배열(실제로는 ArrayCollection 클래스 타입)의 원소들을 하나 하나 참조해야할 때가 있습니다. 설명하기엔 무척….. 염병같고……………. 걍 코드.. 예제 코드 나갑니다. ;-|

var groupingRows:ArrayCollection = grouping.getRoot() as ArrayCollection;
var driverNameAndCarNumber:Object = new Object();
for(i=0; i{
    var D1:Object = groupingRows.getItemAt(i);
    D1["iconIndex"] = 0;
     
    var carName:String = D1.GroupLabel;
    var D2:ArrayCollection = D1.children as ArrayCollection; 

    for(var j:int=0; j    {
        var item:Object = D2.getItemAt(j);
        var driverName:String = item.GroupLabel;
        item["iconIndex"] = 1;
      
        var D3:ArrayCollection = item.children as ArrayCollection;
        for(var k:int=0; k        {
            var lastItem:Object = D3.getItemAt(k);
            lastItem["iconIndex"] = 2;
        }
      
        var sortField:SortField = new SortField();
        sortField.name = "GroupLabel";
        var sort:Sort = new Sort();
        sort.fields = [sortField];
        D3.sort = sort;
        D3.refresh();
    }
}

이 코드는 그룹핑된 내부의 요소드를 또 다시 정렬해줌과 동시에 아이콘 적용을 위해 아이콘 인덱스 번호를 넣어주는 코드입니다. 시간이 지난후에 이 코드를 보고 제 스스로 이해할 수 있다면… 과연 그럴 수 있을까.. 싶습니다.,

[ActionScript] 날짜로 요일 계산하기

아래의 getDay 함수는 액션스크립트로 구성된 요일을 계산해 반환하는 함수입니다. 이 getDay 함수의 파라메터는 날짜로써 년(예:2012), 월(1 ~ 12), 일(1 ~ 31)입니다.

private function getDay(year:int, month:int, day:int):String
{
    const dayStrings:Object = {
        0:"일요일",
        1:"월요일",
        2:"화요일",
        3:"수요일",
        4:"목요일",
        5:"금요일",
        6:"토요일"
    };
    
    if (month == 1 || month == 2) year--;
    month = (month + 9) % 12 + 1;
    var y:int = year % 100;
    var century:int = year / 100;
    var week:int = ((13 * month - 1) / 5 
        + day + y + y / 4 + century / 4 - 2 * century) % 7;
    if (week < 0) week = (week + 7) % 7;

    return dayStrings[week];
}

이처럼 날짜로부터 요일을 계산하는 함수를 직접 작성해 사용할 수 있지만 다음처럼 액션스크립트에서 이미 제공하고 있는 Date 클래스를 사용해 보다 간단히 요일을 계산할 수 있습니다.

 const dayStrings:Object = {
        0:"일요일",
        1:"월요일",
        2:"화요일",
        3:"수요일",
        4:"목요일",
        5:"금요일",
        6:"토요일"
    };
     
    var d:Date = new Date(2012, 10-1, 4);
    Alert.show(dayStrings[d.day]);

여기서 주의해야할 점은 월(Month)은 1부터 시작하지 않고 0부터 시작한다는 것입니다. 그래서 11번 코드에서 지정한 (10-1)은 9월이 아니라 10월을 의미합니다.

플래시 빌더(Flash Builder)에서 협업을 위한 모듈화

플래시 빌더의 단점은 협업 개발이 어렵다는 것입니다. 사실.. 아직까지도 플래시에서 모듈을 통한 협업 개발이 충분한가라는 점에서.. 실제 프로젝트에 적용해보기 전까지는 확신할 수는 없지만 파악해본 바로는 충분히 가능하다.. 라고 판단됩니다.

메인 어플리케이션 프로젝트가 있고.. 이 메인 어플리케이션에서 사용하는 또 다른 별도의 모듈을 위한 프로젝트(모듈 프로젝트)가 있다고 하겠습니다. 모듈 프로젝트는 일반적인 플렉스 프로젝트(Flex Project)로 생성합니다.

이 모듈 프로젝트에 2개의 모듈을 추가하도록 하겠습니다. 첫번째는 단순히 함수만을 제공하는 모듈 클래스이고 두번째는 UI를 제공하는 모듈 클래스입니다. 먼저 함수만을 제공하는 모듈 클래스는 ActionScript Class로 해서 생성하며 클래스명은 Module1으로 지정하고 Super Class로는 반드시 ModuleBase로 지정합니다. 생성된 Module1.as 파일을 다음처럼 코딩합니다.

package
{
    import mx.modules.ModuleBase;
 
    public class Module1 extends ModuleBase
    {
        private var arg:Number = 1;
  
        public function Module1(arg:Number)
        {
            super();
            this.arg = arg;
        }
  
        public function sum(a:Number, b:Number):Number
        {
            return (a + b) * arg;
        }
    }
}

모듈 클래스의 생성자가 하나의 인자를 갖습니다. 그리고 sum이라는 함수를 제공합니다. 매우 단순한 모듈 클래스입니다. 이 모듈 클래스는 컴파일되어 Module1.swf 파일로 만들어져야 합니다만 확장자가 as는 자동으로 모듈로 만들어지지 않으므로 모듈 프로젝트의 속성창에서 Flex Modules에서 추가해야 합니다.

사용자 삽입 이미지
추가할때 Output Size 옵션을 Do not optimize로 선택해서 다양한 많은 어플리케이션에서 활용될 수 있도록 지정합니다.

이제 추가된 모듈 클래스인 Module1을 별개의 어플리케이션 프로젝트에서 사용해 보도록 하겠습니다. 버튼을 클릭했다고 할때 Module1 모듈 클래스를 활용하는 방식으로 하겠습니다. 버튼 클릭시 실행되는 코드는 다음과 같습니다.

private var iModuleInfo:IModuleInfo;
protected function onTest(event:MouseEvent):void
{
    iModuleInfo = ModuleManager.getModule(
        "../../eTAS_Modules/tstModule/bin-debug/Module1.swf");
    iModuleInfo.addEventListener(ModuleEvent.READY, onModuleReady);
    iModuleInfo.addEventListener(ModuleEvent.ERROR, onModuleError);
    iModuleInfo.load();
}

5번째 줄에 모듈 프로젝트에서 생성된 Module1.swf를 지정하고 있습니다. 로컬에서 테스트할때는 이렇게 폴더 경로로 사용되지만 실제로 웹서버에 올려질때는 URL 경로가 사용됩니다. 모듈의 준비가 잘되었을때 실행되는 onModuleReady와 실패했을때 실행되는 onModuleError 함수는 다음과 같습니다.

private function onModuleReady(e:Event):void
{
    e.target.removeEventListener(ModuleEvent.READY, onModuleReady);
    e.target.removeEventListener(ModuleEvent.ERROR, onModuleError);
    
    var mainClassName:String = iModuleInfo.factory.info().mainClassName;
    
    var mainClass:Class = 
        iModuleInfo.factory["getDefinitionByName"](mainClassName) as Class;
    var module:Object = new mainClass(100); 

    text.text = module.sum(100, 200);
 }
   
private function onModuleError(e:Event):void
{
    e.target.removeEventListener(ModuleEvent.READY, onModuleReady);
    e.target.removeEventListener(ModuleEvent.ERROR, onModuleError);
    
    // ERROR 
}

6번에서 모듈의 클래스명을 가져오고 이 클래스 명으로부터 실제 클래스 타입을 8번 코드를 통해 얻습니다. 그리고 이 클래스 타입을 통해 실제 인스턴스를 10번 코드를 통해 생성합니다. 그리고 12번 코드처럼 사용합니다.

이제 다음으로 UI를 제공하는 모듈에 대한 생성과 사용에 대해서 정리해 보겠습니다. 모듈 프로젝트에서 MXML Module로 하여 새로운 파일을 생성합니다.
 
사용자 삽입 이미지
파일명은 Module2로 하고 Output Size 옵션을 Do not optimize로 하여 다른 많은 프로젝트에서 재활용할 수 있도록 합니다. 생성된 Module2.mxml을 다음처럼 코딩합니다.

  xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">
 
    
                    import geoservice.XrMap;
   
            private var map:XrMap = null;
   
            public function setup(map:XrMap):void
            {
                this.map = map;
            }
   
            protected function onClick(event:MouseEvent):void
            {
                map.coordMapper.setMapScale(500);
                map.update();
            }
        ]]>

하나의 버튼 컴포넌트를 배치했고 이 버튼에 대한 클릭 이벤트를 지정했습니다. 그리고 setup이라는 함수를 하나 추가해서 이 UI 모듈의 코드들이 실행하기 위해 필요한 설정값들을 지정할 수 있도록 하였습니다.

이 Module2에 대한 모듈 UI를 별도의 어플리케이션 프로젝트에 사용해 보도록 하겠습니다. 어클리케이션 프로젝트의 메인 mxml에 다음과 같은 UI 컴포넌트를 추가합니다.

네! 바로 앞서 만들어 놓은 Module2 모듈 UI가 들어갈 자리입니다. id는 modLoader로 했음을 기억합시다! 버튼을 클릭하면 이 modLoader에 앞서 만들어 놓은 UI 모듈을 올려 보도록 하겠습니다.

protected function onTest(event:MouseEvent):void
{
    modLoader.addEventListener(ModuleEvent.READY, onUIModuleReady);
    modLoader.addEventListener(ModuleEvent.ERROR, onUIModuleError);
    modLoader.url = "../../eTAS_Modules/tstModule/bin-debug/Module2.swf"
}

모듈 로딩이 성공했을때와 실패했을때에 대한 이벤트 리스너를 지정했고 modLoader의 url 속성에 적재할 UI 모듈에 대한 경로와 파일명을 지정했습니다. 로컬에서는 이처럼 디렉토리 경로지만 실제 웹서버에 올라가면 URL 경로로 변경해줘야 합니다. 모듈 로딩이 성공했을때와 실패했을때에 대한 이벤트 리스너의 코드는 다음과 같습니다.

private function onUIModuleReady(e:Event):void
{
    modLoader.removeEventListener(ModuleEvent.READY, onModuleReady);
    modLoader.removeEventListener(ModuleEvent.ERROR, onModuleError);
    
    modLoader.child.addEventListener(FlexEvent.CREATION_COMPLETE, 
        onUIModuleCreateCompleted);
}

private function onUIModuleError(e:Event):void
{
    modLoader.removeEventListener(ModuleEvent.READY, onUIModuleReady);
    modLoader.removeEventListener(ModuleEvent.ERROR, onUIModuleError);
    
    // ERROR!
}

모듈이 성공적으로 로딩이 되면 6번 코드처럼 UI 모듈을 구성하는 UI가 생성되는 이벤트 리스너를 지정해야 합니다. UI가 생성되는 이벤트 리스너 함수인 onUIModuleCreateCompleted의 코드는 다음과 같습니다.

private function onUIModuleCreateCompleted(e:Event):void
{
    modLoader.child.removeEventListener(FlexEvent.CREATION_COMPLETE, 
        onUIModuleCreateCompleted);
    
    var ui:Object = modLoader.child;
    ui.setup(map);
}

7번 코드가 바로 앞서 UI 모듈에서 추가했던 함수입니다. UI 모듈의 버튼을 클릭하면 지정된 코드가 실행되게 됩니다.

비록…. 모듈에 대한 설명예를 단순하게 들었지만 필요한 것들에 대해서 최소한으로.. 그리고 필요한 모든 것을 설명한듯합니다. 물론 실전에서 적용해보고 부족한 부분에 대해서 덧붙이도록 하겠습니다.

[ActionScript] DataGrid(Spark)의 각 셀값 읽기

var iCol:int = 0;
var rows:XMLListCollection = grid.dataProvider as XMLListCollection;
var columns:ArrayList = grid.columns as ArrayList;
var cntCols:int = columns.length;
   
for(var iRow:int=0; iRow{
    var row:Object = rows[iRow];
    
    for(iCol=0; iCol    {
        var column:GridColumn = columns.getItemAt(iCol) as GridColumn;
        var key:String = column.dataField;
        var value:String = row[key]; // Cell Value !!

        ...
    }
}

ActionScript를 통한 코드 작성이 무슨 퍼즐도 아니고…. 이리 저리 코드 조합해 나가는 재미가… 하나도 없군요… ㅡ_ㅡ; 위 코드 조합해 내느라 2시간 정도 걸렸습니다.. 나중에 또 다시 이런 시간 허비가 없도록 하기 위해 포스팅합니다..

[Flex] SparkSkin을 통한 버튼 디자인

플랙스에서 컴포넌트의 스킨을 디자인하는 가장 쉬운 방법은 이미지를 이용하는 것입니다. 예를 들어 버튼의 경우.. 버튼의 up, over, down, disabled 상태에 따라 각기 다른 이미지를 만들어 주고 지정해 주면 됩니다. 디자이너의 역량에 따라 매우 높은 퀄리티를 낼 수 있습니다.

이 글은 이미지를 이용하는 방식이 아닌.. 각 상태에 따라 직접 그려주는 방법에 대한 설명입니다. 이렇게 이미지가 아닌 직접 버튼의 스킨을 그려주는 방식에 대한 장점은… 버튼의 크기가 변경이 되어도 스킨이 깨지않고 자연스럽게 표현할 수 있다는 점입니다.
사용자 삽입 이미지설명을 위해 제가 테스트 해 본 것은 버튼을 Aqua Style로 표현해 보는 것입니다. 위의 이미지가 실제 제 스스로 만들어본 버튼에 대한 Aqua 스킨입니다. SparkSkin 클래스의 파생 중에서 버튼에 대한 스킨은 SparkButtonSkin입니다.

일반적으로 SparkSkin은 레이어라는 개념으로 스킨을 입히게 됩니다. 여기서 직접 만들어 볼 버튼의 Aqua Style은 다음과 같은 레이어로 구성되어져 있으며 위의 이미지에 대한 버튼이 바로 아래의 레이어로 구성되어져 있습니다.

  1. border
  2. aqua
  3. labelDisplay
  4. blaze

좀더 고급스러운 Aqua 스타일을 제공하기 위해서는 더 많은 레이어가 필요하겠지만.. 간단이 이 정도의 레이어만을 구성해 봄으로써 Aqua 스타일의 스킨을 버튼에 입혀 보겠습니다. 위의 레이어 중 labelDisplay는 반드시 존재해야 하는 id 명으로써 버튼의 텍스트 라벨이 적용될 것으로 실제 Label 컴포넌트로 구성됩니다. 또한 위의 4개의 레이어의 순서 역시 중요합니다. 즉 가장 밑에 border가 존재하고 그 위에 aqua가.. 그리고 labelDisplay가 위치하며 가장 위에 blaze가 나타납니다.

먼저 실제 border 에 대한 FXG 코드입니다. 플렉스는 스킨에 대해 FXG(Flash XML Graphic)를 이용해 그려줍니다.


    
        
    
    
        
    

위의 FXG 코드는 다음과 같은 결과를 그려냅니다.

사용자 삽입 이미지
다음은 aqua 에 대한 FXG입니다.


    
        
            
            
    
    

    
        
    

위의 FXG 코드에 대한 결과는 다음과 같습니다.

사용자 삽입 이미지
그리고 라벨 텍스트를 표현할 코드는 다음과 같습니다.


    
        
        
    

텍스트를 표현해기 위해 Label 컴포넌트를 사용했습니다. 결과는 다음과 같습니다.

사용자 삽입 이미지
끝으로 blaze 레이어에 대한 FXG 코드는 다음과 같습니다.


    
        
            
            
        
    

이 레이어 만을 하얀색 배경의 화면상에서 보면 보이지 않는데요. 이 레이어는 버튼 위에 광택 효과를 주는 것으로 Aqua 버튼의 최정 결과를 보시면 딱! 아실 겁니다. ^^

뭐… 글로 아무리 길게 장황하게 써도.. 해당 스킨에 대한 전체 코드를 다음 url을 통해 다운로드 받으실 수 있으니 실제 플렉스에서 적용해 보시기 바랍니다.

참고로.. SparkSkin을 통한 스킨 적용은 Flash Builder 4.0 이상에서만 적용된다는 점에 유의하시기 바랍니다. 최근 4.6까지 나왔지요? 저는 4.5 버전을 쓰고 있습니다만…. 여튼.. 위의 스킨을 실제 버튼에 적용하기 위한 방법은 3가지가 있는데.. 그 중에 한가지에 대한 코드는 다음과 같습니다.

스킨을 만드는 작업은.. 코드의 길이에 비해 상당히 많은 시간이 소요되는 작업이라고 생각합니다. 또한 제대로 스킨을 만들려면 디자인 감각까지 갖춰야 하는 매우 고난위도의 작업입니다. 하지만 일단 한번만 만들어 놓으면 지속적으로 활용할 수 있으니… 스킨 제작 작업은 시간과 비용이 소요되더라도 꼭 제대로 해두는 것이 최종 프로젝트 산출물의 가치를 배가 시킬 수 있을 거라 확신합니다.