Writing a Sencha Touch MVC Application

在这篇文章中,我们将探讨的方法,我们可以创建一个的煎茶Touch应用程序使用MVC模式的。我会的基础上创建的Notes应用程序,我们在写一个煎茶触摸应用程序系列教程。我们的目标是建立一个新的版本的Notes应用程序使用煎茶触摸MVC 。
Notes应用程序允许用户做笔记,并将它们存储在设备上运行的应用程序。内置到应用程序的功能,我们的MVC版本中,我们将重现,有以下几种:
能够创建笔记
编辑现有票据
删除笔记的能力
坚持记录在设备上运行的应用程序,跨浏览器会话的能力
在用户界面,应用程序围绕着两个观点,说明问题列表视图及票据编辑观点:

准备好了?让我们开始吧。
在煎茶触摸的模型 - 视图 - 控制器模式

框架的实现模型 - 视图 - 控制器模式是在煎茶触摸1.1.0版,在我看来,不完整,记录不完整。然而,我们仍然可以利用它来打造高品质的应用程序。
一般来说,煎茶触摸MVC应用程序将包括一个或多个视图,一个或多个数据模式和存储,和一个或多个控制器。次数有双重作用:它们所呈现的数据模型中的表示,捕获和传递用户输入到控制器。控制器将转换成的数据和/或应用程序的行为的变化,用户的输入。模型代表应用程序的数据和状态。

在煎茶触摸MVC用语,意见的机制,将消息发送到控制器被称为调度,控制器的功能用于处理这些消息被称为行动。
让我们应用这些概念,我们的Notes应用程序,并拿出一个初步的设计。
Notes应用程序的使用情况下,MVC风格

1.根据我们的要求,我们可以把我们的用例,分为以下MVC的工作流程:
应用程序启动时,用户将与现有的注释,或一个空列表的列表,如果没有笔记保存。会发生这种情况,通过调用控制器的index动作,这反过来又会使债券的列表视图:


2. When our user needs to create a new note, she will tap the  New  button in the  Notes List  view. This will invoke the  newnote  action in a controller, where a new note will be created and loaded into the  Note Editor  view:

  1. Similarly, when our user needs to edit a note, she will tap the disclosure button for the given note. This will invoke the editnote action in a controller, causing the note to be loaded into the Note Editor view:
  2. Tapping the Save button in the Note Editor view will invoke a controller’s savenoteaction, where the note will be saved in notes cache and the Notes List view will be activated:
  3. If our user taps the Trash button in the Note Editor view, the view will invoke a controller’s deletenote action. This action will delete the note loaded in the view and activate the Notes List view:
  4. Tapping the Home button in the Note Editor view will invoke a controller’s canceleditaction, which will discard any changes made to the loaded note, and activate theNotes List view:

Notice that I have referred to “a controller” in my descriptions of the use cases. This means that we still don’t know if we will use one or many controllers in the application. We will make this decision in a few minutes.

All right. Now that we have an idea about how we’re going to build the Notes Application, MVC-style, let’s talk about files.

Distributing a Sencha Touch application across multiple files

You should consider distributing your Sencha Touch application’s source code across multiple files. A single source file might be fine for a very small application, however, as the application and the development team grow, you can cut development and maintenance costs by using a multiple-files model.

What we will do for this example is, first, create the right folder structure for the application, and then create the different source files that will contain the application’s components. The folder structure reflects the MVC pattern we will use to build the application:

We have a root folder called app, and under it, folders for the models, views, controllers and data stores. Besides being self-explanatory, it follows Sencha’s recommendation to place models, views and controllers in separate folders. Sencha’s build tools are capable of leveraging this structure to create the application’s namespaces when it’s time to build a production-ready version of the source files.

At this point we have modeled our MVC workflows and created the right folder structure. What do you say about writing some code?

The Note data model

The Note model is the data model that represents a note. Its source goes in the NoteModel.js file, which we will place in the models folder. This is the source:

Ext.regModel('NoteModel', {
    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.' }
    ]
});

The Notes data store

The NotesStore is our notes cache. It uses the NoteModel class as its model and it’s configured to use an instance of the Ext.data.LocalStorageProxy class as its proxy. This allows us to save notes across browser sessions. We will place the NotesStore class in the NotesStore.js file, which lives in the stores folder. This is the store’s source:

Ext.regStore('NotesStore', {
    model: 'NoteModel',
    sorters: [{
        property: 'date',
        direction: 'DESC'
    }],
    proxy: {
        type: 'localstorage',
        id: 'notes-app-store'
    },
    getGroupString: function (record) {
        if (record && record.data.date) {
            return record.get('date').toDateString();
        } else {
            return '';
        }
    }
});

NotesApp.stores.notesStore = Ext.StoreMgr.get('NotesStore');

The Main view

As in the first version of the application, the application’s main view will serve as the viewport, hosting the Notes List view and the Note Editor view. This view’s class goes in the MainView.js file, which we will place in the views folder:

NotesApp.views.MainView = Ext.extend(Ext.Panel, {
    fullscreen: true,
    layout: 'card',
    cardSwitchAnimation: 'slide',
    initComponent: function () {

        Ext.apply(NotesApp.views, {
            notesListView: new NotesApp.views.NotesListView({ notesStore: NotesApp.stores.notesStore }),
            noteEditorView: new NotesApp.views.NoteEditorView()
        });

        this.items = [
            NotesApp.views.notesListView,
            NotesApp.views.noteEditorView
        ]

        NotesApp.views.MainView.superclass.initComponent.call(this);

        this.on('render', function () {
            NotesApp.stores.notesStore.load();
        });
    }
});

The Notes List view

We will use an instance of the NotesListView class to render the list of cached notes and allow the user to start the “new note” and “edit note” workflows. Its file, NotesListView.js, goes in the views folder. This is the source:

NotesApp.views.NotesListView = Ext.extend(Ext.Panel, {

    notesStore: Ext.emptyFn,
    notesList: Ext.emptyFn,

    layout: 'fit',

    initComponent: function () {

        this.newButton = new Ext.Button({
            text: 'New',
            ui: 'action',
            handler: this.onNewNote,
            scope: this
        });

        this.topToolbar = new Ext.Toolbar({
            title: 'My Notes',
            items: [
                { xtype: 'spacer' },
                this.newButton
            ]
        });

        this.dockedItems = [this.topToolbar];

        this.notesList = new Ext.List({
            store: this.notesStore,
            grouped: true,
emptyText: '</pre>
<div style="margin: <span class=;">5px;">No notes cached.</div>
<pre>
<pre>',
            onItemDisclosure: true,
itemTpl: '
<div class="list-item-title">{title}</div>
<pre>' +
                            '<div class="list-item-narrative">{narrative}</div>'

        });

        this.notesList.on('disclose', function (record, index, evt) {
            this.onEditNote(record, index);
        }, this),

        this.items = [this.notesList];

        NotesApp.views.NotesListView.superclass.initComponent.call(this);
    },

    onNewNote: function () {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'newnote'
        });
    },

    onEditNote: function (record, index) {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'editnote',
            note: record
        });
    },

    refreshList: function () {
        this.notesList.refresh();
    }
});

Let’s pause here and examine a few details.

While the initComponent() function should be very familiar to you, things become interesting when we look at the helper functions onNewNote() and onEditNote(). These functions will allow us to signal the view’s controller that the user executed either the “new note” or “edit note” commands. The newnote or editnote controller actions will be invoked in response to these commands.

How does the view send these signals to the controller? Well, it’s pretty simple. Remember what I said earlier about dispatch? Every application gets a default instance of the Dispatcher class, which you can use to send requests to specific actions in a given controller. Invoking Ext.Dispatcher.dispatch() or its shorthand, Ext.dispatch(), will find the desired controller and call the correct controller action.

You should pass the controller and action arguments to the dispatch() function. And you can also pass any other parameters that the controller might need to execute the action correctly. For example, in the onEditNote() function we also pass the record corresponding to the note the user needs to edit.

The third function, refreshList(), will allow this view’s controller to re-render the list of notes in the view after the list has been modified.

OK, we already dispatched a couple of messages to a controller that does not exist. How about we create it?

How many controllers does a Sencha Touch MVC application need?

I don’t think they make rules for this. It depends on the application. I know that the Notes App is small: three simple views and six actions. It looks like one controller is enough for this one.

Let’s create the NotesController.js file in the controllers folder, and add the following code to it:

Ext.regController('NotesController',{

    'index': function (options) {

    },

    'newnote': function (options) {

    },

    'editnote': function (options) {

    },

    'savenote': function (options) {

    },

    'deletenote': function (options) {

    },

    'canceledit': function (options) {

    }
});

NotesApp.controllers.notesController = Ext.ControllerManager.get('NotesController');

Starting to make more sense now? As you can see, the controller has the actions we have discussed. This is where we need to implement our use cases. But, before we do it, let’s work on the last view.

The Note Editor view

We will use an instance of the NoteEditorView class to give our users the ability to edit and delete notes. This class goes in the NoteEditorView.js file, in the views folder:

NotesApp.views.NoteEditorView = Ext.extend(Ext.form.FormPanel, {

    initComponent: function () {

        this.backButton = new Ext.Button({
            text: 'Home',
            ui: 'back',
            handler: this.backButtonTap,
            scope: this
        });

        this.saveButton = new Ext.Button({
            text: 'Save',
            ui: 'action',
            handler: this.saveButtonTap,
            scope: this
        });

        this.trashButton = new Ext.Button({
            iconCls: 'trash',
            iconMask: true,
            handler: this.trashButtonTap,
            scope: this
        });

        this.topToolbar = new Ext.Toolbar({
            title: 'Edit Note',
            items: [
                this.backButton,
                { xtype: 'spacer' },
                this.saveButton
            ]
        });

        this.bottomToolbar = new Ext.Toolbar({
            dock: 'bottom',
            items: [
                { xtype: 'spacer' },
                this.trashButton
            ]
        });

        this.dockedItems = [this.topToolbar, this.bottomToolbar];

        NotesApp.views.NoteEditorView.superclass.initComponent.call(this);
    },

    backButtonTap: function () {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'canceledit'
        });
    },

    saveButtonTap: function () {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'savenote'
        });
    },

    trashButtonTap: function () {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'deletenote'
        });
    },

    items: [{
        xtype: 'textfield',
        name: 'title',
        label: 'Title',
        required: true
    }, {
        xtype: 'textareafield',
        name: 'narrative',
        label: 'Narrative'
    }]
});

The helper functions saveButtonTap(), trashButtonTap() and backButtonTab() will allow us to signal the controller that the “save note”, “delete note” or “cancel edit” commands were executed by the user. The application will run the savenotedeletenote orcanceledit controller actions in response to these commands.

Now that our views are finished, let’s implement the controller actions.

Implementing controller actions

Back in the NotesController class, as we’ve already discussed, the index action will create the Main view and activate the Notes List view:

'index': function (options) {

    if (!NotesApp.views.mainView) {
        NotesApp.views.mainView = new NotesApp.views.MainView();
    }

    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.notesListView
    );
}

The index action will be invoked when the application launches. We will see how this is done when we implement the application’s launch() function.

The newnote controller action will create a new note, load it into the Note Editor view, and activate the view:

'newnote': function (options) {

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

    NotesApp.views.noteEditorView.load(note);
    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.noteEditorView,
        { type: 'slide', direction: 'left' }
    );
}

We need the editnote action to load the selected note into the Note Editor view:

'editnote': function (options) {

    NotesApp.views.noteEditorView.load(options.note);
    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.noteEditorView,
        { type: 'slide', direction: 'left' }
    );
}

The savenote action will save the note in the cache and activate the Notes List view:

'savenote': function (options) {

    var currentNote = NotesApp.views.noteEditorView.getRecord();

    NotesApp.views.noteEditorView.updateRecord(currentNote);

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

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

    NotesApp.stores.notesStore.sync();

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

    NotesApp.views.notesListView.refreshList();

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

Next comes the deletenote action. This action will delete the note loaded in the Note Editor and activate the Notes List view:

'deletenote': function (options) {

    var currentNote = NotesApp.views.noteEditorView.getRecord();

    if (NotesApp.stores.notesStore.findRecord('id', currentNote.data.id)) {
        NotesApp.stores.notesStore.remove(currentNote);
    }

    NotesApp.stores.notesStore.sync();
    NotesApp.views.notesListView.refreshList();

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

Finally, the controller’s canceledit action will discard any changes made to the loaded note, and activate the Notes List view:

'canceledit': function (options) {

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

You with me so far? We’re almost done.

Compared to the first version of the app, we’ve moved most of the application’s behavior out of the views, which now deal strictly with UI matters, and into a central location. I can see the MVC version being easier to understand and support.

Now that we have our data store, model, views and controller in place, we’re just missing the piece that will get this whole mechanism going.

Launching the application

We will place our app’s entry point in the app.js file, app folder, together with the index.html and app.css files. We will use app.js to create our application’s instance like so:

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

    launch: function () {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'index'
        });
    }
});

And that’s it. Makes sense?

As you can imagine, this is not the only approach you can take for incorporating the MVC pattern in a Sencha Touch app. You can go from rolling out your own implementation, to taking full advantage of the framework’s built-in features, which I think are still a work in progress. Either way, you should be able to reach a satisficing solution that makes your application easier to develop and more maintainable.




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值