블로그 이미지
ssun++

카테고리

[전체] (73)
Android (7)
JavaScript (9)
CI (5)
Language (14)
ETC (38)
Total317,158
Today0
Yesterday22

[참고문서]

http://docs.sencha.com/touch/2-0/#!/guide/controllers



[Controllers]

Controller는 앱 내부에서 발생하는 이벤트 처리를 담당합니다. 앱에 사용자가 탭 할 수 있는 로그아웃 버튼이 있다면, Controller는 버튼의 탭 이벤트를 감지하여 적절한 액션을 취할 것입니다. 이는 View 클래스로 하여금 데이터의 출력을 처리하고, Model 클래스는 데이터의 로드와 저장을 처리할 수 있게 합니다 - Controller는 View와 Model을 결합하는 역할을 합니다.



[Relation to Ext.app.Application]

Controller는 Application의 컨텍스트 내부에 존재합니다. Controller는 앱의 일부를 핸들링하고, Appllication은 여러개의 Controller로 구성됩니다. 예를 들어, 온라인 쇼핑몰의 주문을 처리하는 Application이라면 주문, 고객, 상품에 대한 Controller를 가질 것입니다.

Application이 사용하는 모든 Controller는 Application의 Ext.app.Application.controllers 설정에 명시됩니다. Application은 자동으로 Controller를 객체화하고 각각에 대한 레퍼런스를 유지하여, 보통은 Controller를 직접 객체화 할 필요가 없습니다. 관습적으로 Controller는 주로 처리하는 것(보통 Model)의 이름을 복수 형태로 따릅니다 - 예를 들어, 'MyApp'이라는 앱에 상품을 관리하는 Controller가 있다면, app/controller/Products.js 파일에 MyApp.controller.Products 클래스를 생성하는 것이 일반적입니다.



[Launching]

Application의 실행 프로세스에 4단계가 있고, 그중 2단계는 Controller에 있습니다. 먼저, Controller는 init 함수를 정의할 수 있으며, Application의 launch 함수 이전에 호출됩니다. 다음으로, Application과 Profile의 launch 함수가 호출된 후, Controller의 launch 함수가 마지막 단계로 호출됩니다.

  1. Controller#init 함수 호출
  2. Profile#launch 함수 호출
  3. Application#launch 함수 호출
  4. Controller#launch 함수 호출

대부분의 경우에 Controller에 따른 로직은 launch 함수에 위치해야 합니다. Application과 Profile의 launch 함수 이후에 호출되기 때문에, 앱의 초기 UI는 이 위치에 오는 것이 적절합니다. 앱이 실행되기 전에 처리되어야 하는 로직은 init 함수 내부에 구현해야 합니다.



[Refgs and Control]

Controller의 가장 중요한 것은 Ref와 Control입니다. 이 둘은 앱 내부에서 컴포넌트에 대한 레퍼런스를 쉽게 얻어서, 컴포넌트에서 발생하는 이벤트를 처리하는데 사용됩니다.


[Refs]

Ref는 강력한 ComponentQuery 문법을 사용하여 컴포넌트를 쉽게 위치할 수 있도록 합니다. Ref는 Controller에 원하는 만큼 정의할 수 있습니다. 예를 들어, ID가 'mainNav'라는 컴포넌트를 찾는 'nav'라는 ref를 정의하였습니다. 그런 다음 addLogoutButton 내부에서 ref를 사용하였습니다.

Ext.define('MyApp.controller.Main', {

    extend: 'Ext.app.Controller',


    config: {

        refs: {

            nav: '#mainNav'

        }

    },


    addLogoutButton: function() {

        this.getNav().add({

            text: 'Logout'

        });

    }

});

보통 ref는 key/value 짝입니다 - key(이 경우에 'nav')는 생성될 레퍼런스의 이름이고, value(이 경우에는 '#mainNav')는 컴포넌트를 찾기 위한 ComponentQuery 셀렉터입니다.

addLogoutButton이라는 간단한 함수를 만들어 'getNav' 함수를 통해 ref를 사용하였습니다. 이런 getter 함수는 ref 정의에 기반하여 생성되며 항상 같은 포맷을 따릅니다 - 'get' 다음에 ref 명 첫글자를 대문자로 바꾸어 붙입니다. 이 경우 nav 레퍼런스를 툴바인 것처럼 사용하였고, 함수가 호출되었을 때 로그아웃 버튼을 추가하였습니다. 이 ref는 아래와 같이 툴바를 인식합니다.

Ext.create('Ext.Toolbar', {

    id: 'mainNav',


    items: [

        {

            text: 'Some Button'

        }

    ]

});

이 툴바는 addLogoutButton 함수 호출 시 생성되었다고 가정하고(어떻게 호출되는지는 나중에 봅시다), 두번째 버튼을 가지게 됩니다.


[Advanced Refs]

Ref는 name과  selector이외에 추가적인 옵션을 가질 수 있습니다. autoCreate와 xtype이 있으며 거의 항상 사용됩니다.

Ext.define('MyApp.controller.Main', {

    extend: 'Ext.app.Controller',


    config: {

        refs: {

            nav: '#mainNav',


            infoPanel: {

                selector: 'tabpanel panel[name=fish] infopanel',

                xtype: 'infopanel',

                autoCreate: true

            }

        }

    }

});

Controller에 두번째 ref를 추가하였습니다. 동일하게 이름은 key이고(이경우에 'infoPanel'), 하지만 이번에는 value로 객체를 전달하였습니다. 이번에는 약간 복잡한 selector 쿼리를 사용하였습니다 - 앱이 탭패널을 포함하고 있고, 아이템 중 하나가 'fish'라는 이름을 가지고 있다고 가정합시다. selector는 xtype이 탭패널 내부에서 'infopanel'인 컴포넌트를 찾습니다.

여기에서 차이는 Controller에서 this.getInfoPanel을 호출 했을 때, 'fish' 패널 내부에 infopanel이 존재하지 않는 경우 자동으로 생성된다는 점입니다.


[Control]

Control은 컴포넌트에서 발생하는 이벤트를 감지하고 Controller가 반응할 수 있도록 하는 수단입니다. Control은 ComponentQuery selector나 ref를 key로, 리스너 객체를 value로 가질 수 있습니다.

Ext.define('MyApp.controller.Main', {

    extend: 'Ext.app.Controller',


    config: {

        control: {

            loginButton: {

                tap: 'doLogin'

            },

            'button[action=logout]': {

                tap: 'doLogout'

            }

        },


        refs: {

            loginButton: 'button[action=login]'

        }

    },


    doLogin: function() {

        // called whenever the Login button is tapped

    },


    doLogout: function() {

        // called whenever any Button with action=logout is tapped

    }

});

여기서 두개의 control을 선언하였습니다 - 하나는 loginButton ref, 다른 하나는 'logout' 액션을 받는 모든 버튼을 위한 것입니다. 각각의 선언에 대해서 우리는 하나의 이벤트 핸들러를 주었습니다 - 버튼이 탭 이벤트를 발생하는 경우 실행되어야 하는 액션을 명시하였습니다. Control 블럭 내부에서 'doLogin'과 'doLogout' 함수 이름을 문자열로 정의한 것을 주목합시다.

각 control에서 원하는 대로 이벤트를 감지할 수 있고, key로써 ComponentQuery selector와 ref를 mix and match 시킬 수 있습니다.



[Routes]

Sencha Touch 2에서는 Controller가 어떤 route를 따를 것인지 직접적으로 명시할 수 있습니다. 이는 앱에서 히스토리 지원을 가능하게 하며, route를 제공하는 임의의 부분으로 링크가 가능하게 합니다.

예를 들어, 로그인과 사용자 프로필을 보여주기 위한 Controller가 있고, url을 통해서 화면에 접근가능하게 하고자 합니다. 아래와 같이 시도해 볼 수 있습니다.

Ext.define('MyApp.controller.Users', {

    extend: 'Ext.app.Controller',


    config: {

        routes: {

            'login': 'showLogin',

            'user/:id': 'showUserById'

        },


        refs: {

            main: '#mainTabPanel'

        }

    },


    // uses our 'main' ref above to add a loginpanel to our main TabPanel (note that

    // 'loginpanel' is a custom xtype created for this application)

    showLogin: function() {

        this.getMain().add({

            xtype: 'loginpanel'

        });

    },


    // Loads the User then adds a 'userprofile' view to the main TabPanel

    showUserById: function(id) {

        MyApp.model.User.load(id, {

            scope: this,

            success: function(user) {

                this.getMain().add({

                    xtype: 'userprofile',

                    user: user

                });

            }

        });

    }

});

위에서 명시한 route는 브라우저 어드레스 바의 콘텐츠를 Controller의 함수에 매핑하여 route가 매치된 경우 호출 될 수 있도록 합니다. http://myapp.com/#login에 매치되는 route를 login route 처럼 간단한 텍스트로 사용할 수 있고, http://myapp.com/#user/123과 같은 url에 매치되는 route를 위해서 와일드카드를 포함하여 'user/:id'와 같이 사용할 수도 있습니다. 어드레스가 바뀌면 Controller는 자동적으로 명시된 함수를 호출합니다.

showUserById 함수 내부에서 User 객체를 최초로 로드해야 함을 알아둡시다. route를 사용할 때, route가 호출하는 함수는 데이터를 로딩하고 상태를 복원하는데 전적인 책임을 가지고 있습니다. 로드되어 캐시된 데이터를 클리어하기 때문에, 사용자는 url을 다른 사람에게 보내주거나 페이지를 간단히 리프레시 할 수 있습니다. route를 통한 상태 복원에 대해서는 application architecture guides에 더 자세히 설명되어 있습니다.



[Before Filters]

Routing 컨텍스트에서 Controller가 제공하는 마지막 기능은 route에 정의된 함수 이전에 실행되는 filter 함수를 정의하는 것입니다. 이는 사용자 인증하기 위한 동작, 또는 페이지에 필요한 클래스 로딩을 위한 최적의 위치입니다. 예를 들어, 사용자가 쇼핑몰의 Product를 수정하기 위해서는 인증이 필요하다고 합시다.

Ext.define('MyApp.controller.Products', {

    config: {

        before: {

            editProduct: 'authenticate'

        },


        routes: {

            'product/edit/:id': 'editProduct'

        }

    },


    // this is not directly because our before filter is called first

    editProduct: function() {

        //... performs the product editing logic

    },


    // this is run before editProduct

    authenticate: function(action) {

        MyApp.authenticate({

            success: function() {

                action.resume();

            },

            failure: function() {

                Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");

            }

        });

    }

});

사용자가 http://myapp.com/#product/edit/123과 같은 url을 네비게이트할 때, Controller의 authenticate 함수가 호출되어 Ext.app.Action을 전달합니다. Action은 before filter가 존재하지 않는 경우 실행될 것입니다. Action은 Controller와 function(이 경우에는 editProduct) 그리고 url에서 파싱된 ID 등의 데이터를 표현합니다.

filter는 동기, 비동기에 관계없이 필요한 어떤 동작이든 실행할 수 있습니다. 이경우에 사용자가 현재 로그인 되어있는지 확인하기 위해서  authenticate 함수를 사용합니다. 서버에서 사용자의 인증을 확인하여 비동기로 동작하기 위한 AJAX 요청을 할 수 있습니다 - 인증이 성공한 경우 action.resume() 함수를 호출 함으로써 action을 계속하고, 실패할 경우 먼저 로그인 하도록 알릴 수 있습니다.

어떤 action이 실행되기 전에 before filter는 추가적인 클래스를 로드하는데 사용할 수 있습니다. 예를 들어, 잘 사용되지 않는 어떤 action이 있을 때 소스코드 로딩을 지연시켜 Application의 구동이 빨라지게 할 수 있습니다. 이를 위해서 Ext.Loader를 사용하는 filter를 설정함으로써 필요시 코드를 로드할 수 있습니다.

action마다 여러개의 before filter를 명시할 수 있고, 그럴 때는 배열로 전달할 수 있습니다.

Ext.define('MyApp.controller.Products', {

    config: {

        before: {

            editProduct: ['authenticate', 'ensureLoaded']

        },


        routes: {

            'product/edit/:id': 'editProduct'

        }

    },


    // this is not directly because our before filter is called first

    editProduct: function() {

        //... performs the product editing logic

    },


    // this is the first filter that is called

    authenticate: function(action) {

        MyApp.authenticate({

            success: function() {

                action.resume();

            },

            failure: function() {

                Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");

            }

        });

    },


    // this is the second filter that is called

    ensureLoaded: function(action) {

        Ext.require(['MyApp.custom.Class', 'MyApp.another.Class'], function() {

            action.resume();

        });

    }

});

filter는 순서대로 실행되고, 각각은 action.resume() 호출해야 합니다.



[Profile-specific Controllers]

수퍼클래스:

Ext.define('MyApp.controller.Users', {

    extend: 'Ext.app.Controller',


    config: {

        routes: {

            'login': 'showLogin'

        },


        refs: {

            loginPanel: {

                selector: 'loginpanel',

                xtype: 'loginpanel',

                autoCreate: true

            }

        },


        control: {

            'logoutbutton': {

                tap: 'logout'

            }

        }

    },


    logout: function() {

        // code to close the user's session

    }

});

폰 Controller:

Ext.define('MyApp.controller.phone.Users', {

    extend: 'MypApp.controller.Users',


    config: {

        refs: {

            nav: '#mainNav'

        }

    },


    showLogin: function() {

        this.getNav().setActiveItem(this.getLoginPanel());

    }

});

태블릿 Controller:

Ext.define('MyApp.controller.tablet.Users', {

    extend: 'MyApp.controller.Users',


    showLogin: function() {

        this.getLoginPanel().show();

    }

});


Posted by ssun++

댓글을 달아 주세요

최근에 달린 댓글

최근에 받은 트랙백

글 보관함