반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리


반응형
지난 시간에는 New 버튼을 달고 더미 데이터를 넣어서 List 를 출력해 봤습니다.
잘 되셨나요?

지난주까지 진행한 소스코드 아래에 있습니다.
필요하신 분은 받아서 보세요.


오늘은 Note Editor 부분을 하겠습니다.
Notes List 에서 Note Editor 화면으로 전환하고 새 데이터를 넣고 저장하고 이것을 List 로 새로 출력하는 기능을 할 겁니다.
이 과정에서 validation도 체크하는 기능도 다룰거구요.

아래 오늘의 내용을 보시죠.

Writing a Sencha Touch Application, Part 3

Part 1,2 에서는 Notes 앱 중에 Notes List view 부분을 다뤘습니다. 오늘은 Note Editor view를 다루겠습니다.
타이틀은 Edit Note 가 될 텐데요. Note의 생성, update, 삭제 기능이 있을 겁니다.
아래 Mock up 이미지와 완성 화면이 있습니다.








***** Creating a Sencha Touch Form Panel

Note Editor mockup을 보면 우리는 툴바와 필드들을 만들어야 합니다.
우선 form field들 부터 만들겠습니다.

NotesApp.views.noteEditor = new Ext.form.FormPanel({
    id: 'noteEditor',
    items: [
        {
            xtype: 'textfield',
            name: 'title',
            label: 'Title',
            required: true
        },
        {
            xtype: 'textareafield',
            name: 'narrative',
            label: 'Narrative'
        }
    ]
});


이 필드들을 만들기 위해 Ext.form.FormPanel 클래스를 사용했습니다. 센차터치에서 form 필드들을 만들 때 가장 손쉽게 만들 수 있는 방법입니다.
제목은 textfield를 사용하고 내용을 넣는 공간은 Text field를 사용합니다.
title 부분에 required 를 true로 했습니다. 반드시 이곳에 값을 넣어야 한다는 겁니다. 디비의 not null 이죠. 나중에 save 하는 단계에서 이곳에 값을 넣지 않으면 어떻게 되는지 보실 수 있습니다.

툴바를 넣기 전에 이 form들의 모습이 어떤지 미리 보면 좋겠죠? viewport 부분을 약간 수정해서 이 form이 보이도록 해 보죠.
viewport의 items 부분을 수정해 아래처럼 수정해 보세요. 그러면 지난번에 만들었던 list가 아니라 edit form이 나올겁니다.

NotesApp.views.viewport = new Ext.Panel({
    fullscreen: true,
    layout: 'card',
    cardAnimation: 'slide',
    items: [NotesApp.views.noteEditor] // TODO: revert to [NotesApp.views.notesListContainer]
});

제대로 됐다면 아래처럼 보일겁니다.




***** Adding top and bottom toolbars to a Sencha Touch Panel

이제 툴바를 만들 차례입니다.
첫번째 툴바는 Home 과 Save 버튼이 있는 위쪽 툴바입니다.

NotesApp.views.noteEditorTopToolbar = new Ext.Toolbar({
    title: 'Edit Note',
    items: [
        {
            text: 'Home',
            ui: 'back',
            handler: function () {
                // TODO: Transition to the notes list view.
            }
        },
        { xtype: 'spacer' },
        {
            text: 'Save',
            ui: 'action',
            handler: function () {
                // TODO: Save current note.
            }
        }
    ]
});

위 소스 콛를 보시면 툴바를 만들면서 ui config 옵션을 사용하신 걸 보실 수 있을 겁니다. (back, action 으로 정의했죠?) home 버튼에는 back 이라고 정의했습니다. 왜냐하면 이 버튼을 누르면 back 해서 main view 로 갈 것이기 때문입니다. 그리고 save 버튼에는 action이라고 정의했는데요. 이것은 유저가  이 버튼을 누르면 note를 save 할 거라는 겁니다. 이 기능이 가장 중요한 기능 중 하나죠?



화면 아래에 있는 툴바에는 휴지통 버튼이 들어갈 겁니다. 이 버튼을 누르면 해당 note 가 delete 되겠죠?
NotesApp.views.noteEditorBottomToolbar = new Ext.Toolbar({
    dock: 'bottom',
    items: [
        { xtype: 'spacer' },
        {
            iconCls: 'trash',
            iconMask: true,
            handler: function () {
                // TODO: Delete current note.
            }
        }
    ]
});

여기에 몇가지 주의하실 부분이 있습니다. 첫번째로 dock config를 어떻게 사용했는지 보세요. 화면 아래에 배치하기 위해 bottom을 사용했습니다. 그리고 iconCls와 iconMask config 옵션을 보세요. 이 기능들은 버튼 안에 trash icon을 render 하기 위한 옵션들입니다.



이제 툴바를 add 하실 수 있습니다.

NotesApp.views.noteEditor = new Ext.form.FormPanel({
    id: 'noteEditor',
    items: [
        {
            xtype: 'textfield',
            name: 'title',
            label: 'Title',
            required: true
        },
        {
            xtype: 'textareafield',
            name: 'narrative',
            label: 'Narrative'
        }
    ],
    dockedItems: [
            NotesApp.views.noteEditorTopToolbar,
            NotesApp.views.noteEditorBottomToolbar
        ]
});

약간 코드를 수정해서 지금까지 한 작업을 화면으로 볼 수 있도록 해 보죠.

NotesApp.views.viewport = new Ext.Panel({
    fullscreen: true,
    layout: 'card',
    cardAnimation: 'slide',
    items: [NotesApp.views.noteEditor] // TODO: revert to [NotesApp.views.notesListContainer]
});

성공했으면 아래와 같은 화면을 보실 수 있을 겁니다.




아주 훌륭하죠?

지금까지 우리는 Notes List와 Note Editor 뷰를 만들었습니다. 이제 필요한 것은 이 앱의 workflow 입니다. 뷰 이외에 다른 비지니스 로직을 어떻게 구현하느냐를 말하는 것이죠. 우선 첫번째 기억할 비지니스 로직(Navigate screen) 은 Notes List 에서 New 버튼을 누르면 Notes Edit View 로 간다는 겁니다.



****** How to change views in a Sencha Touch application

이제 Notes List view 로 가 볼까요? 이제 New 버튼의 handler 부분을 다룰 차례입니다. 이 버튼이 눌려지면 새로운 note를 만들 수 있어야 합니다. 그러니까 이 버튼을 누르면 Note Editor를 화면에 보이도록 하면 되곘죠.

NotesApp.views.notesListToolbar = new Ext.Toolbar({
    id: 'notesListToolbar',
    title: 'My Notes',
    layout: 'hbox',
    items: [
        { xtype: 'spacer' },
        {
            id: 'newNoteButton',
            text: 'New',
            ui: 'action',
            handler: function () {

                var now = new Date();
                var noteId = now.getTime();
                var note = Ext.ModelMgr.create(
                    { id: noteId, date: now, title: '', narrative: '' },
                    'Note'
                );

                NotesApp.views.noteEditor.load(note);
                NotesApp.views.viewport.setActiveItem('noteEditor', {type: 'slide', direction: 'left'});
            }
        }
    ]
});

순서대로 한번 볼까요?
첫번째로 ModelMgr 클래스의 create()메소드를 사용해서 새로운 note를 생성합니다.

var now = new Date();
var noteId = now.getTime();
var note = Ext.ModelMgr.create(
    { id: noteId, date: now, title: '', narrative: '' },
    'Note'
);

그리고 나서 이 새 note를 Note Editor에 전해줍니다. FormPanel에서 이 부분이 보이도록 하기 위해 load 메소드를 사용했습니다. 이 load 메소드는 필드들을 보여줄겁니다. 이때 value 값들이 있으면 그 value 값들도 보여 줄 거구요.

NotesApp.views.noteEditor.load(note);

다음으로는 우리가 viewport에서 card layout을 사용했듯이 viewport이 setActiveItem()메소드를 사용해서 Note Editor를 보이도록 할 겁니다.
이 setActiveItem()을 call 하면 우리는 우리가 active 시키려고 하는 card의 id를 전달해 주고 그 객체가 transition 하는 animation 기능을 사용할 수 있습니다.

NotesApp.views.viewport.setActiveItem('noteEditor', {type: 'slide', direction: 'left'});

이 New 버튼이 어떻게 작동하는지 확인하기 전에 viewport의 items array를 약간 수정해야 합니다.

NotesApp.views.viewport = new Ext.Panel({
    fullscreen: true,
    layout: 'card',
    cardAnimation: 'slide',
    items: [
        NotesApp.views.notesListContainer,
        NotesApp.views.noteEditor
    ]
});



여기까지 완성 됐나요? 그러면 이제 user가 작성한 note를 save 할 수 있도록 하시면 됩니다.
이 기능은 당연히 Note Editor의 Save 버튼에 달아야 겠죠.

***** Validating a data model in Sencha Touch

save 버튼을 누르게 되면 우선 체크해야 할 것들이 몇가지 있습니다.

1. form field의 title과 narrative  정보는 Note Model 인스턴스에서 capture 됩니다.
2. note의 title의 value 가 없으면 유저에게 alert 화면을 띄울 겁니다.
3. note 가 new일 경우 이 note를 cache에 add 할 겁니다. 이 note 가 이전에 저장 됐던 것이면 cache에 있는 해당 note를 update 할 겁니다.
4. 그리고 나서 Notes List 를 refresh 해서 보여줄 겁니다.

자 이 기능들을 구현해 볼까요?
save 버튼의 tap 핸들러를 구현하기 전에 Note data model을 수정해서 validation 부분을 구현하는데 좀 더 편하도록 해 봅시다. 우리가 할 것은 model의 title 필드의 validation 함수를 사용해서 message 프로퍼티를 override 할 겁니다. 이 기능은 이 필드가 invalid 할 경우 메세지를 띄우는 기능입니다.

Ext.regModel('Note', {
    idProperty: 'id',
    fields: [
        { name: 'id', type: 'int' },
        { name: 'date', type: 'date', dateFormat: 'c' },
        { name: 'title', type: 'string' },
        { name: 'narrative', type: 'string' }
    ],
    validations: [
        { type: 'presence', field: 'id' },
        { type: 'presence', field: 'title', message: 'Please enter a title for this note.' }
    ]
});

이 메세지는 우리가 Note Editor view 에서 note의 validation을 할 때 사용할 겁니다.
이제 save 버튼의 tap 핸들러를 구현해서 이 validation 이 어떻게 이뤄지는지 확인해 보겠습니다.

NotesApp.views.noteEditorTopToolbar = new Ext.Toolbar({
    title: 'Edit Note',
    items: [
        {
            text: 'Home',
            ui: 'back',
            handler: function () {
                NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });
            }
        },
        { xtype: 'spacer' },
        {
            text: 'Save',
            ui: 'action',
            handler: function () {

                var noteEditor = NotesApp.views.noteEditor;

                var currentNote = noteEditor.getRecord();
                // Update the note with the values in the form fields.
                noteEditor.updateRecord(currentNote);

                var errors = currentNote.validate();
                if (!errors.isValid()) {
                    currentNote.reject();
                    Ext.Msg.alert('Wait!', errors.getByField('title')[0].message, Ext.emptyFn);
                    return;
                }

                var notesList = NotesApp.views.notesList;
                var notesStore = notesList.getStore();

                if (notesStore.findRecord('id', currentNote.data.id) === null) {
                    notesStore.add(currentNote);
                } else {
                    currentNote.setDirty();
                }

                notesStore.sync();
  notesStore.sort([{ property: 'date', direction: 'DESC'}]);

                notesList.refresh();

                NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });

            }
        }
    ]
});

이 handler 함수에서 첫번째로 form panel의 getRecord() 메소드를 사용했습니다. 이 메소드는 form에 Note model이 로드되는 reference를 가질 수 있도록 해 줍니다. 그 다음에는 updateRecord() 메소드를 사용합니다. 이 메소드는 이 model reference로 폼 필드로부터 value들을 가져와서 전달해 줍니다.
validation dms Note data model에서 validate()를 call 함으로서 Errors 객체를 return 하도록 합니다. isValid()는 이 과정에서 에러가 있는지 없는지 알 수 있게 해 줍니다. 지난 시간에 했듯이 Note Editor 에서는 title 만이 not null입니다. 그래서 이 앱에서는 그 부분만 체크할 겁니다. 에러를 확인하기 위해서는 model의 validations config 옵션에서 정의했듯이 errors.getByField('title')[0] 메세지를 통해서 확인할 수 있습니다.

이 validation을 통화 하면 이 note를 cache에 add 해야 합니다. 대신 이 데이터가 이전에 있는지 확인하는 기능이 있어야 합니다.
아래 findRecord()메소드를 통해서 이 작업을 간단하게 해 주시구요.

var notesStore = notesList.getStore();
if (notesStore.findRecord('id', currentNote.data.id) === null) {
    notesStore.add(currentNote);
}


update 시키신 후에는 sync() 메소드를 이용해서 완전히 이 데이터를 저장하시면 됩니다. 오라클에서 commit; 기능하고 비슷합니다.
그 다음에 sort() 메소드를 사용해서 note를 날짜 순서로 정렬합니다. 그러면 notes list에 render 할 준비가 끝나는 겁니다.


notesList.refresh();

NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });

type을 보시면 slide와 right가 있을 겁니다. 이것은 화면 전환 할 때 된쪽에서 오른쪽으로 slide하게 화면이 바뀌도록 합니다.

이제 남은 것은 Home button에 handler를 구현하는 겁니다.
이 홈 버튼을 누르면 단순히 Notes List로 돌아가기만 하면 됩니다.

{
    text: 'Home',
    ui: 'back',
    handler: function () {
        NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });'s next
    }
}

한번 화면으로 테스트 해 보고 싶으신가요? 이제 실행 해 보시면 save하고 새 note를 create하는 것들이 다 가능할 겁니다.

***** What’s next
다음에 우리가 구현 할 것은 기존에 존재하는 notes를 수정하고 지우는 기능입니다.
이 기능은 다음 시간에 구현해 보겠습니다.

오늘까지 한 js 소스는 아래와 같습니다.

var App = new Ext.Application({
    name: 'NotesApp',
    useLoadMask: true,
    launch: function () {

        Ext.regModel('Note', {
            idProperty: 'id',
            fields: [
                { name: 'id', type: 'int' },
                { name: 'date', type: 'date', dateFormat: 'c' },
                { name: 'title', type: 'string' },
                { name: 'narrative', type: 'string' }
            ],
            validations: [
                { type: 'presence', field: 'id' },
                { type: 'presence', field: 'title', message: 'Please enter a title for this note.' }
            ]
        });

        Ext.regStore('NotesStore', {
            model: 'Note',
            sorters: [{
                property: 'date',
                direction: 'DESC'
            }],
            proxy: {
                type: 'localstorage',
                id: 'notes-app-store'
            }
        });

        NotesApp.views.noteEditorTopToolbar = new Ext.Toolbar({
            title: 'Edit Note',
            items: [
                {
                    text: 'Home',
                    ui: 'back',
                    handler: function () {
                        NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });
                    }
                },
                { xtype: 'spacer' },
                {
                    text: 'Save',
                    ui: 'action',
                    handler: function () {

                        var noteEditor = NotesApp.views.noteEditor;

                        var currentNote = noteEditor.getRecord();
                        // Update the note with the values in the form fields.
                        noteEditor.updateRecord(currentNote);

                        var errors = currentNote.validate();
                        if (!errors.isValid()) {
                            currentNote.reject();
                            Ext.Msg.alert('Wait!', errors.getByField('title')[0].message, Ext.emptyFn);
                            return;
                        }

                        var notesList = NotesApp.views.notesList;
                        var notesStore = notesList.getStore();

                        if (notesStore.findRecord('id', currentNote.data.id) === null) {
                            notesStore.add(currentNote);
                        } else {
                           currentNote.setDirty();
                        }

                        notesStore.sync();
                        notesStore.sort([{ property: 'date', direction: 'DESC'}]);

                        notesList.refresh();

                        NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });

                    }
                }
            ]
        });

        NotesApp.views.noteEditorBottomToolbar = new Ext.Toolbar({
            dock: 'bottom',
            items: [
                { xtype: 'spacer' },
                {
                    iconCls: 'trash',
                    iconMask: true,
                    handler: function () {
                        // TODO: Delete current note.
                    }
                }
            ]
        });

        NotesApp.views.noteEditor = new Ext.form.FormPanel({
            id: 'noteEditor',
            items: [
                {
                    xtype: 'textfield',
                    name: 'title',
                    label: 'Title',
                    required: true
                },
                {
                    xtype: 'textareafield',
                    name: 'narrative',
                    label: 'Narrative'
                }
            ],
            dockedItems: [
                    NotesApp.views.noteEditorTopToolbar,
                    NotesApp.views.noteEditorBottomToolbar
                ]
        });

        NotesApp.views.notesList = new Ext.List({
            id: 'notesList',
            store: 'NotesStore',
            itemTpl: '
<div class="list-item-title">{title}</div>
' +
                '
<div class="list-item-narrative">{narrative}</div>
',
            onItemDisclosure: function (record) {
                // TODO: Render the selected note in the note editor.
            },
            listeners: {
                'render': function (thisComponent) {
                    thisComponent.getStore().load();
                }
            }
        });

        NotesApp.views.notesListToolbar = new Ext.Toolbar({
            id: 'notesListToolbar',
            title: 'My Notes',
            layout: 'hbox',
            items: [
                { xtype: 'spacer' },
                {
                    id: 'newNoteButton',
                    text: 'New',
                    ui: 'action',
                    handler: function () {

                        var now = new Date();
                        var noteId = now.getTime();
                        var note = Ext.ModelMgr.create(
                            { id: noteId, date: now, title: '', narrative: '' },
                            'Note'
                        );

                        NotesApp.views.noteEditor.load(note);
                        NotesApp.views.viewport.setActiveItem('noteEditor', { type: 'slide', direction: 'left' });
                    }
                }
            ]
        });

        NotesApp.views.notesListContainer = new Ext.Panel({
            id: 'notesListContainer',
            layout: 'fit',
            html: 'This is the notes list container',
            dockedItems: [NotesApp.views.notesListToolbar],
            items: [NotesApp.views.notesList]
        });

        NotesApp.views.viewport = new Ext.Panel({
            fullscreen: true,
            layout: 'card',
            cardAnimation: 'slide',
            items: [
                NotesApp.views.notesListContainer,
                NotesApp.views.noteEditor
            ]
        });
    }
});

이제 저도 Sencha Touch 에 대해 대충 감이 잡히네요.
다음시간에 첫번째 센차터치 앱 Notes 를 완료하겠습니다.
그리고 계속 하이브리드 모바일 앱을 위해 Corona SDK, HTML5, Sencha Touch, Phone Gap 등 관련 기술을 익히고 싶네요.

꾹~~ 꾹~~ 응원 부탁드려요... ~~~~~~~
반응형


반응형

Writing a Sencha Touch Application, Part 2

지난 시간에는 Sencha로 만드는 첫번째 앱인 My Notes 앱을 배웠습니다.
Notes List view를 말들고 있었습니다.
지난시간에 아래 화면까지 작업했습니다.



여기에 아래 mock-up 이미지처럼 툴바에 버튼을 달고 유저가 생성한 Notes들을 랜더링해서 보여주는 기능을 Ext.list를 이용해서 구현할 겁니다.



***** Creating a Data Model in Sencha Touch

우선 데이타 모델을 만들겠습니다. DB를 이용한다면 필드들을 만드는 겁니다.
리스트에 표시할 내용들에 대한 데이터 모델인데요 id,date,title,narrative 등이 있어야 합니다.
이 데이터 모델은 Ext.regModel() 메소드를 이용해서 만들겁니다.

Ext.regModel('Note', {
    idProperty: 'id',
    fields: [
        { name: 'id', type: 'int' },
        { name: 'date', type: 'date', dateFormat: 'c' },
        { name: 'title', type: 'string' },
        { name: 'narrative', type: 'string' }
    ],
    validations: [
        { type: 'presence', field: 'id' },
        { type: 'presence', field: 'title' }
    ]
});

데이타 모델과 관련한 API는 여기를 클릭해서 보시면 도움이 될 겁니다.
Sencha Touch의 이 데이터 모델관련 메소드에서는 내부적으로 validation을 지원합니다.
위에는 id와 title에 validations를 주었습니다. 데이터가 꼭 있어야 된다는 내용입니다. sql로 치면 not null 입니다.
이 validation이 어떻게 동작하는지에 대해서는 나중에 살펴보도록 하죠.
또한 associations 기능도 지원을 하는데요 우리가 만드는 앱에서는 사용하지는 않습니다.
아주 유용한 기능이니까 API를 꼭 참조하세요.

***** Configuring a Sencha Touch Data Store to use HTML5 local storage

이제 데이터를 Cacheg하는 기능이 필요합니다. Ext.regstore() 함수가 데이터 store를 생성하도록 할 겁니다.
아래 소스코드를 참조하세요.

Ext.regStore('NotesStore', {
    model: 'Note',
    sorters: [{
        property: 'date',
        direction: 'DESC'
    }],
    proxy: {
        type: 'localstorage',
        id: 'notes-app-localstore'
    }
});

Ext.regStore() 는 프레임 워크의 Store Manager 와 같이 store를 생성하고 register 합니다. 프레임워크의 다른 클래스들 처럼 여러분은 앱의 데이터 store를 lookup 하고 modify 하는데 이 Store Manager를 사용할 수 있습니다.
우리의 NotesStore의 모델 config 옵션은 Note model 입니다. 우리는 또한 sorters 옵션도 사용해서 날짜 역순으로 표시할 겁니다.
우리의 앱은 브라우저 세션을 이용해서 notes를 저장할 수 있어야 합니다. 그러기 위해서는 store의 proxy로 모델데이터를 로드하고 저장하는 일을 해야 합니다. 그래서 proxy config 옵션을 사용합니다. 이 옵션을 사용함으로서 proxy 는 Ext.data.LocalStorageProxy 클래스의 인스턴스가 됩니다. 이 클래스는 HTML5의 LocalStorage API를 사용해 사용자 브라우저에 모델 데이터를 저장하게 됩니다.

***** Creating a Sencha Touch list

데이터 모델과 store 가 준비 됐으면 이제 notes list 를 생성할 수 있습니다.

NotesApp.views.notesList = new Ext.List({
    id: 'notesList',
    store: 'NotesStore',
    itemTpl: '
<div class="list-item-title">{title}</div>
' +
        '
<div class="list-item-narrative">{narrative}</div>
'
});

복잡한 거는 없습니다. store config 옵션에 이름을 넣습니다. render 하기 위해 itemTpi config 옵션을 사용합니다. 그 안에 있는 태그(markup)은 list-item-title과 list-item-narrative 클래스가 정의 돼 있습니다. 이 클래스는 app.css 파일에서 사용할 겁니다.

.list-item-title
{
    float:left;
    width:100%;
    font-size:90%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.list-item-narrative
{
    float:left;
    width:100%;
    color:#666666;
    font-size:80%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.x-item-selected .list-item-title
{
    color:#ffffff;
}
.x-item-selected .list-item-narrative
{
    color:#ffffff;
}

이제 panel의 items config 옵션을 사용해서 container panel에 list를 추가해 보겠습니다.

NotesApp.views.notesListContainer = new Ext.Panel({
    id: 'notesListContainer',
    layout: 'fit',
    html: 'This is the notes list container',
    dockedItems: [NotesApp.views.notesListToolbar],
    items: [NotesApp.views.notesList]
});

시뮬레이터에서 확인하기 전에 dummy note를 집어 넣겠습니다. 그래야 브라우저나 디바이스로 테스트하면 이 dummy 내용이 나올 테니까요. 아래와 같이 data config 옵션을 넣어 보세요.

Ext.regStore('NotesStore', {
    model: 'Note',
    sorters: [{
        property: 'date',
        direction: 'DESC'
    }],
    proxy: {
        type: 'localstorage',
        id: 'notes-app-store'
    },
    // TODO: remove this data after testing.
    data: [
        { id: 1, date: new Date(), title: 'Test Note', narrative: 'This is simply a test note' }
    ]
});

에뮬레이터에서 Notes List 는 아래와 같이 보일 겁니다.



이제 우리의 Notes List view는 거의 완성 됐습니다. 툴바를 만들었고 Note List도 만들었습니다. 그리고 data model,store도 구현했구요 이러한 것들은 이 notes를 cache 하게 될 겁니다. 아직 안된게 두개가 있습니다. 다른 페이지로 넘어가도록 하는 버튼들인데요. 툴바에 있는 New 버튼하고 각각의 List item에 있는 disclosure 버튼입니다.



***** Adding buttons to a Sencha Touch toolbar

아래 소스코드는 New 버튼을 툴바에 add 하는 방법을 보여줍니다.
NotesApp.views.notesListToolbar = new Ext.Toolbar({
    id: 'notesListToolbar',
    title: 'My Notes',
    layout: 'hbox',
    items: [
        { xtype: 'spacer' },
        {
            id: 'newNoteButton',
            text: 'New',
            ui: 'action',
            handler: function () {
                // TODO: Create a blank note and make the note editor visible.
            }
        }
    ]
});

보시면 툴바에 New button을 달 수 있도록 hbox 레이아웃과 spacer를 넣었습니다. 그리고 handler function 도 넣었습니다. 여기에는 나중에 Note Editor view 와 관련한 기능이 추가 될 겁니다.

***** Implementing disclosure buttons in a Sencha Touch list

각 리스트에 달릴 disclosure 버튼을 다는것도 쉽습니다. 아래 코드에서 보듯이 onItemDisclosure function을 사용하시면 됩니다.

NotesApp.views.notesList = new Ext.List({
    id: 'notesList',
    store: 'NotesStore',
    itemTpl: '
<div class="list-item-title">{title}</div>
' +
        '
<div class="list-item-narrative">{narrative}</div>
',
    onItemDisclosure: function (record) {
        // TODO: Render the selected note in the note editor.
    }
});

리스트 아이템에 tap event에 대한 listener를 달기 위해 onItemDisclsure를 override 하고 있습니다. 이 리스너가 하는 일은 유저가 리스트를 탭하면 이를 감지해서 Note Editor view에 알려주는 역할을 하게 될 겁니다.
이 기능은 Note Editor를 만들고 나서 나중에 구현하겠습니다.

***** Where are we?

여기까지 무사히 마쳤다면 아래와 같은 화면을 부실 수 있을 겁니다.



이제 어플리케이션의 main view를 완성했습니다. 두개의 이벤트 핸들러를 구현하는 부분은 남았지만 비쥬얼한 부분은 다 완성됐습니다.

이제 다음시간에는 Note Editor를 만들어 보겠습니다.



아래는 지금까지 한 소스 입니다.

var App = new Ext.Application({
    name: 'NotesApp',
    useLoadMask: true,
    launch: function () {

        Ext.regModel('Note', {
            idProperty: 'id',
            fields: [
                { name: 'id', type: 'int' },
                { name: 'date', type: 'date', dateFormat: 'c' },
                { name: 'title', type: 'string' },
                { name: 'narrative', type: 'string' }
            ],
            validations: [
                { type: 'presence', field: 'id' },
                { type: 'presence', field: 'title' }
            ]
        });

        Ext.regStore('NotesStore', {
            model: 'Note',
            sorters: [{
                property: 'date',
                direction: 'DESC'
            }],
            proxy: {
                type: 'localstorage',
                id: 'notes-app-localstore'
            },
            // TODO: remove this data after testing.
            data: [
                { id: 1, date: new Date(), title: 'Test Note', narrative: 'This is simply a test note' }
            ]
        });

        NotesApp.views.notesList = new Ext.List({
            id: 'notesList',
            store: 'NotesStore',
            itemTpl: '
<div class="list-item-title">{title}</div>
' +
                '
<div class="list-item-narrative">{narrative}</div>
',
            onItemDisclosure: function (record) {
                // TODO: Render the selected note in the note editor.
            }
        });

        NotesApp.views.notesListToolbar = new Ext.Toolbar({
            id: 'notesListToolbar',
            title: 'My Notes',
            layout: 'hbox',
            items: [
                { xtype: 'spacer' },
                {
                    id: 'newNoteButton',
                    text: 'New',
                    ui: 'action',
                    handler: function () {
                        // TODO: Create a blank note and make the note editor visible.
                    }
                }
            ]
        });

        NotesApp.views.notesListContainer = new Ext.Panel({
            id: 'notesListContainer',
            layout: 'fit',
            html: 'This is the notes list container',
            dockedItems: [NotesApp.views.notesListToolbar],
            items: [NotesApp.views.notesList]
        });

        NotesApp.views.viewport = new Ext.Panel({
            fullscreen: true,
            layout: 'card',
            cardAnimation: 'slide',
            items: [NotesApp.views.notesListContainer]
        });
    }
});

앞으로 두 번만 더 다루면 센차터치로 만드는 첫 번째 앱이 완성됩니다.
정말 기대가 되는데요.
다음 시간에 또 뵙구요... 꾹 꾹 추천 한방 눌러 주세요.... ~~~~
반응형


반응형
오늘은 Corona로 그럴듯하게 눈발 흩날리는 느낌을 한번 줘 보겠습니다.

ramdom하고 translate, newImageRect 를 이용해서 이 효과를 줘 봤습니다.

우선 먼저 소스를 보고 분석 해 보죠.

display.setStatusBar( display.HiddenStatusBar )
local mRand = math.random; math.randomseed(os.time())
local xGravity,yGravity,xWind, yWind = 0,18,45,2
local bg = display.newImage("usa_bg2.png")
local snow = display.newGroup()
local function animateSnow( event )
    for i=1, snow.numChildren do
        local flake = snow[i]
        flake:translate((flake.xVelocity+xWind)*0.1,(flake.yVelocity+yWind)*0.1)
        if flake.y > display.contentHeight then
            flake.x, flake.y = mRand(display.contentWidth), mRand(60)
        end
    end
    if mRand(64) == 1 then xWind = 0-xWind; end
end

local function initSnow(snowCount)
    for i=1,snowCount do
        --local flake = display.newImageRect(snow,"flake.png",1,1)
        local flake = display.newImageRect(snow,"drophose.png",6,6)
        if mRand(1,2) == 1 then flake.alpha = mRand(25,100) * .01; end
        flake.x, flake.y = mRand(display.contentWidth), mRand(display.contentHeight)
        flake.yVelocity, flake.xVelocity = mRand(60), mRand(50)
    end
    Runtime:addEventListener("enterFrame", animateSnow)
end
initSnow(1000)

처음엔 아이폰의 status바 없애는 부분이고요.
두번째 줄은 mRand라는 변수에 랜덤값을 넣구요 randomseed로는 os time을 사용합니다.
중력은 y방향으로반 18을 주고 바람의 첫번째 값은 x로 45 y로 2 입니다.
다음는 배경화면을 깐겁니다.
5번째 줄에 그룹을 만들었는데요 이 그룹은 눈을 담을 겁니다.
눈 이미지는 하나만 사용할 거구요. 이 이미지를 1000개 만들어서 이 그룹에 넣게 됩니다.

다름에 animateSnow 함수가 나옵니다.

이 부분이 눈발을 흩날리는 부분입니다.
처름에 for문을 돌리는데요. snow그룹안에 있는 인자들의 갯수만큼 for문을 돌립니다.
 맨 아랫줄 보시면 1000이라는 숫자가 보이죠? snow그룹안에는 인자가 천개가 될 테니까 1000번 돌겁니다.
그 다음 flake라는 변수에 snow의 i번째 인자를 담구요 이 flake를 translate 시킵니다.
이 falke가 y방향으로 밖으로 나가게 되면 다시 화면 안으로 들어오게만들구요.

여기서 falke의 방향하고 속도는 initSnow에서 랜덤하게 정해진 값입니다.

initSnow함수는 snowCount만큼 for문을 돌립니다. 역시 1000개가 되겠죠?
flake에 imageRect를 할당합니다.
여기서 Rect에 크기를 조절하면 싸리눈도 되고 함박눈도 됩니다.
그리고 이 flake에 alpha값을 랜덤하게 지정해 줍니다.
위치도 랜덤하게 지정하구요.
다음으로 속도도 랜덤하게 적용합니다.

이렇게 1000개의 flake를 만들구요. 그 다음에 animateSnow를 Runtime의 enterframe으로 실행 시킵니다.

맨 마지막 줄은 이 initSnow함수를 실행시키는데 인자 값으로 1000을 전달합니다.
이 숫자가 적으면 눈이 적게 내리겠고 많으면 눈이 많이 내래겠죠?



완성된 화면입니다.
눈이 바람에 흩날리면서 내립니다.
눈(flake)의 갯수나 imageRect 사이즈를 조정하시면 눈을 많이 내리게도 할 수 있고 조금 내리게도 할 수 있고 싸리눈이 될 수도 있고 함박눈이 될 수도 있습니다.

이렇게 그럴듯한 눈내리는 효과를 단 25줄로 처리해 버립니다.
코로나SDK 아주 훌륭합니다...
이 효과 잘 활용하시구요. 꾹~~ 꾹~~ 추천 부탁드립니다.~~~~~
반응형