使用phoneGap和Sencha Touch 2開發Android應用程序(二)


        本文是「使用phoneGap和Sencha Touch 2開發Android應用程序系列教程的第2章, 在這一章,咱們會繼續開發一個可以在用戶手機上運行的用於保存筆記的小應用程序。 css

        到目前爲止,咱們正在開發用於顯示用戶所建立的筆記列表的視圖。 html

        爲了建立列表視圖,咱們在app/view文件夾下建立了NotesListContainer.js文件,這個js文件就是用來定義NotesListContainer視圖類的。如今咱們對這個視圖類進行一些改造,讓它能有擁有更好的封裝性和可維護性。 數據庫

        在上一章,NotesListContainer視圖的定義以下: 瀏覽器

Ext.define("NotesApp.view.NotesListContainer", {
    extend: "Ext.Container",
    config: {
        items: [{
            xtype: "toolbar",
            docked: "top",
            title: "My Notes",
            items: [{
                xtype: "spacer"
            }, {
                xtype: "button",
                text: "New",
                ui: "action",
                id:"new-note-btn"
            }]
        }]
    }
});

        如今咱們用視圖的initialize()方法來定義視圖所包含的組件,方法定義以下: 緩存

Ext.define("NotesApp.view.NotesListContainer", {
    extend: "Ext.Container",
    alias: "widget.noteslistcontainer",

    initialize: function () {
        this.callParent(arguments);
    }
});

        在這裏咱們使用了alias配置項,它的意義至關於咱們給新建的這個視圖類聲明瞭xtype,這樣,咱們就可以經過形如xtype=「noteslistcontainer」的方式來引用該類。在本章的後面部分,咱們會經過這種方式來實例化該類。 app

        在Sencha Touch2中,每個類都擁有一個initialize()方法,該方法一般在類被實例化的時候被用來執行一些邏輯,這個方法被用於替代Sench Touch 1中的initComponent()方法,下面咱們使用該方法來給頂部工具欄添加一個新建按鈕: 工具

Ext.define("NotesApp.view.NotesListContainer", {
    extend: "Ext.Container",
    alias: "widget.noteslistcontainer",

    initialize: function () {
        this.callParent(arguments);
        var newButton = {
            xtype: "button",
            text: 'New',
            ui: 'action',
            handler: this.onNewButtonTap,
            scope: this
        };

        var topToolbar = {
            xtype: "toolbar",
            title: 'My Notes',
            docked: "top",
            items: [
                { xtype: 'spacer' },
                newButton
            ]
        };

        this.add([topToolbar]);
    },
    onNewButtonTap: function () {
        console.log("newNoteCommand");
        this.fireEvent("newNoteCommand", this);
    },
    config: {
        layout: {
            type: 'fit'
        }
    }
});

        在這裏的initialize()方法中,咱們首先調用callParent()方法,而後定義了一個工具欄和一個新建按鈕,而後把間隔符和新建按鈕添加到工具欄中。 學習

        在initialize()方法的最後,咱們使用add()方法將該工具欄添加到視圖中去。 ui

        在回來看看新建按鈕的定義,咱們使用handler配置項給按鈕添加了一個用來處理單擊事件的方法onNewButtonTap: this

var newButton = {
    xtype: "button",
    text: 'New',
    ui: 'action',
    handler: this.onNewButtonTap,
    scope: this
};

        onNewButtonTap方法將捕獲按鈕上的單擊事件(動做),而後將它轉換成應用程序業務邏輯所能識別的某一具體事件(這裏是newNoteCommand),該方法代碼以下:

onNewButtonTap: function () {
    console.log("newNoteCommand");
    this.fireEvent("newNoteCommand", this);
}

        這是本章對NotesListContainer所作的一個重要的改變,在上一章,按鈕和對按鈕的事件處理咱們是經過在控制器中定義ref和對應control來實現的。

Ext.define("NotesApp.controller.Notes", {
    extend: "Ext.app.Controller",
    config: {
        refs: {
            newNoteBtn: "#new-note-btn"
        },
        control: {
            newNoteBtn: {
                tap: "onNewNote"
            }
        }
    },
    onNewNote: function () {
        console.log("onNewNote");
    }

    // Rest of the controller's code omitted for brevity.
});

        而如今,當捕獲到視圖中發生的事件時,會把通過轉換的新的事件newNoteCommand傳播出去,這個新的事件newNoteCommand將被控制器捕獲,而後由控制器進行相應的處理。(注:這樣作實際上就是把MVC中的V和C分開,V中只負責捕獲事件,並將事件傳播出去,對事件的定義和處理邏輯通通交給C來處理,這樣作提升了代碼的可維護性):

onNewButtonTap: function () {
    console.log("newNoteCommand");
    this.fireEvent("newNoteCommand", this);
}

        儘管兩種方法都是有效的,可是第二種作法會帶來更重要的好處:

  • 首先,視圖類會看起來更加簡潔,如今視圖所要作的僅僅只是傳播事件,這樣作更加符合業務邏輯(視圖類不負責其餘處理邏輯)。
  • 其次,視圖類更容易修改及維護,而控制器並不須要熟悉View的內部運做

        只要View的公共事件保持不變,控制器就可以與視圖協同運行。例如,咱們能夠在「視圖」中改變用來創建新的筆記的元素(注:好比在新建一個其餘按鈕,用來新建筆記),而不會影響控制器對這一事件的處理。控制器只須要監遵從視圖中觸發的newNoteCommand事件便可。

        接下來咱們將對app.js進行重構,咱們將修改application()方法,使用類的別名來建立咱們的NoteListContainer實例對象:

Ext.application({
    name: "NotesApp",

    controllers: ["Notes"],
    views: ["NotesListContainer"],

    launch: function () {

        var notesListContainer = {
            xtype: "noteslistcontainer"
        };

        Ext.Viewport.add(notesListContainer);
    }
});

        最後,咱們回到到控制器的代碼中,修改refs部分,這樣咱們能夠根據xtype 查找ref的引用對象,而再也不是根據id 查找:

Ext.define("NotesApp.controller.Notes",{
	extend : "Ext.app.Controller", // 
	config : {
		refs:{
			// 將檢索xtype爲noteslistcontainer的視圖
			notesListContainer:"noteslistcontainer"
		},
		control : {
			notesListContainer :{
				// 這兩個command將被notes list container觸發
				newNoteCommand : "onNewNoteCommand",
				editNoteCommand : "onEditNoteCommand"
			}
		}
	},
	
	// 新增及修改的command
	onNewNoteCommand : function(){
		// 當 New按鈕點擊時調用
		alert("onNewNoteCommand");
	},
	onEditNoteCommand : function(list,record){
		// 當 Edit按鈕點擊時調用
		alert("onEditNoteCommand");
	},
	// Notes類的init和launch方法
	launch : function(){
		this.callParent(arguments);
		//alert("controller Notes 已啓動");
	},
	init : function(){
		this.callParent(arguments);
		//alert("controller Notes 初始化");
	}
});

        注意,咱們還在控制器中添加了onEditNoteCommand事件和editNoteCommand事件處理方法。本系列教程的下一節中,當咱們建立筆記列表視圖的時候,將同時定義onEditNoteCommand 

        作完上述的改動後,咱們能夠在鏈接設備或者模擬器上運行本應用程序(注:原文是在WebKit瀏覽器中打開index.html頁面),確認運行效果是否和咱們預期的同樣。點擊「新建」按鈕,咱們的onNewButtonTap方法會彈出提示框:

建立Notes列表視圖

        筆記列表視圖是用來將本地緩存的全部筆記顯示出來的組件,對應的文件是app/view/NotesList.js 。爲了建立這個組件,咱們將繼承Ext.dataview.List類

Ext.define("NotesApp.view.NotesList",{
	extend:"Ext.dataview.List",
	alias:"widget.noteslist",
	config:{
		loadText:"正在加載筆記....",
		emptyText:'<pre><div class="notes-list-empty-text">沒有找到相關筆記。</div></pre>',
		onItemDisclosure:true,
		itemTpl:'<pre><div class="list-item-title">{title}</div><div class="list-item-narrative">{narrative}</div></pre>'
	}
});

        在上面的代碼中,咱們將onItemDisclosure的配置項設置爲true,這項配置可使列表中顯示的每條筆記記錄旁邊右邊加上顯示「按鈕:

        當單擊「顯示「按鈕時會進入筆記編輯功能。咱們稍後會建立該「顯示」事件的處理方法。

        在NotesList類中,咱們對itemTpl和emptyText兩個配置項使用了不一樣的CSS類 ,這樣將讓咱們可以很好地顯示列表項的樣式,以及當列表中沒有記錄時,顯示「沒有找到相關筆記」提示消息。

       咱們在resources/css目錄下,建立app.css文件,並把下面的樣式代碼加進去:

        下面是咱們所須要的css樣式:

/* Increase height of list item so title and narrative lines fit */
.x-list .x-list-item .x-list-item-label
{
     min-height: 3.5em!important;
}
/* Move up the disclosure button to account for the list item height increase */
.x-list .x-list-disclosure {
position: absolute;
bottom: 0.85em;
right: 0.44em;
}
.list-item-title
{
    float:left;
    width:100%;
    font-size:90%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding-right:25px;
    line-height:150%;
}
.list-item-narrative
{
    float:left;
    width:95%;
    color:#666666;
    font-size:80%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding-right:25px;
}
.x-item-selected .list-item-title
{
    color:#ffffff;
}
.x-item-selected .list-item-narrative
{
    color:#ffffff;
}
.notes-list-empty-text
{
    padding:10px;
}

        爲了顯示NotesList的實例,咱們首先須要到應用程序的views配置項中添加該視圖類:

views: ["NotesList", "NotesListContainer"]

        而後,咱們須要將它添加到NotesListContainer類。在NotesListContainer.js文件的initialize()方法中添加notesList變量

Ext.define("NotesApp.view.NotesListContainer",{
	extend : "Ext.Container",
	alias : "widget.noteslistcontainer",
	initialize : function(){
		this.callParent(arguments);
		var newButton ={
			xtype:"button",
			text:"New",
			ui:"action",
			handler:this.onNewButtonTap,
			scope:this
		};
		var topToolbar = {
			xtype:"toolbar",
			title:"My Notes",
			docked:"top",
			items:[
				{
				xtype:"spacer"
			},
				newButton
			]
		};
		
		var notesList = {
			xtype:"noteslist",
			store:Ext.getStore("Notes"),
			listeners:{
				disclose:{
					fn:this.onNotesListDisclose,
					scope:this
				}
			}
		}
		
		this.add([topToolbar,notesList]);
	},
	onNewButtonTap:function(){
		//alert("new note");
		this.fireEvent("newNoteCommand",this);
	},
	onNotesListDisclose:function(list,record,target,index,evt,options){
		// alert("editNoteCommand");
		this.fireEvent("editNoteCommand",this,record);
	},
	config:{
		layout:{
			type:"fit"
		}
	}
});

        一樣的,咱們對列表記錄的「顯示」事件設置一個監聽器:

var notesList = {
    xtype: "noteslist",
    listeners: {
        disclose: { fn: this.onNotesListDisclose, scope: this }
    }
};
        而後定義onNotesListDisclose()方法:
onNotesListDisclose: function (list, record, target, index, evt, options) {
    console.log("editNoteCommand");
    this.fireEvent('editNoteCommand', this, record);
}

        在這裏,咱們採起的方法,咱們遵循和以前「新建」按鈕相似的第二種作法,再也不採用控制器監聽筆記列表的「顯示」事件,而是建立editNoteCommand事件,並將其暴露給控制器。這種作法使應用更靈活,更易於維護。

建立Note(筆記)對應的Sencha Touch 數據模型

        筆記列表視圖須要一個數據存儲(Store)對象,這個store對象用於描述列表項(本例是筆記對象)的相關信息。爲了建立這個store對象,咱們首先須要定義一個數據模型,該模型表明筆記類(注:包含了類的屬性字段,以及字段驗證等等,這裏的模型即MVC中的Model,store對象熟悉ExtJs的應該不會陌生,主要是定義對哪一個model,採用那種代理處理,按照哪一個屬性對model排序等等,它是model和view的關聯)。

        下面咱們來定義Note類。咱們將會把這個類定義成Note.js文件將保存在model目錄下:

        筆記model包含4個屬性字段: id, date created(建立日期), title(筆記標題)和 narrative(筆記內容):

Ext.define("NotesApp.model.Note", {
    extend: "Ext.data.Model",
    config: {
        idProperty: 'id',
        fields: [
            { name: 'id', type: 'int' },
            { name: 'dateCreated', type: 'date', dateFormat: 'c' },
            { name: 'title', type: 'string' },
            { name: 'narrative', type: 'string' }
        ]
    }
});

        咱們將使用idProperty的配置項來明確聲明,id字段是能夠用來識別筆記對象的惟一標識。在咱們的例子中,這彷佛是微不足道的,由於咱們對數據模型的名稱有徹底的控制權(即這裏咱們可以自行定義id爲主鍵)。可是,在開發中可能遇到各類狀況,例如,數據模型的字段是嚴格綁定到一個已有的數據庫表的列名的,而用於惟一地標識一個記錄的列名並非是「id」 ,這時候idProperty的配置就是至關必要的。

在 Sencha Touch中設置模型驗證器

        id, dateCreated and title 這三個屬性字段是筆記模型的必填項。咱們須要用validations配置項來對每一個字段進行聲明驗證:

Ext.define("NotesApp.model.Note",{
	extend:"Ext.data.Model",
	config:{
		idProperty:"id",
		fields:[
		   {name:"id",type:"int"},
		   {name:"dateCreated",type:"date",dateFormat:"c"},
		   {name:"title",type:"string"},
		   {name:"narrative",type:"string"}
		],
		validations:[
		    {field:"id",type:"presence"},
		    {field:"dateCreated",type:"presence"},
		    {field:"title",type:"presence",message:"請輸入筆記的標題"}
		]
	}
});

        對於標題字段,咱們使用了message配置項來定義當用戶沒有輸入筆記標題而視圖保存時的提示信息。

        建立數據存儲以前,咱們須要將模型添加到應用程序的models配置項上:

Ext.application({
	name : 'NotesApp',
	
	models:["Note"],
      //下面的代碼省略..
});


建立Sencha Touch 數據存儲

        如今,咱們能夠開始建立數據存儲,該數據存儲將被填充到列表視圖中去。咱們將在app/store目錄下建立Notes.js,這個文件就是筆記模型的數據存儲類:

        咱們先暫時在store中以硬編碼的形式添加一些筆記記錄:

Ext.define("NotesApp.store.Notes",{
	extend:"Ext.data.Store",
	config:{
		model:"NotesApp.model.Note",
		data:[
		      {title:"Note 1", narrative:"這是Note 1 的筆記內容"},
		      {title:"Note 2", narrative:"這是Note 2 的筆記內容"},
		      {title:"Note 3", narrative:"這是Note 3 的筆記內容"},
		      {title:"Note 4", narrative:"這是Note 4 的筆記內容"},
		      {title:"Note 5", narrative:"這是Note 5 的筆記內容"},
		      {title:"Note 6", narrative:"這是Note 6 的筆記內容"},
		]
	}
});

        要特別注意的是,咱們須要經過model配置項來指明是對哪一個模型對象進行存儲。

        若是咱們但願對全部筆記按照建立日期來排序的話,可使用sorter配置項來指定排序字段以及升序仍是降序:

Ext.define("NotesApp.store.Notes",{
	extend:"Ext.data.Store",
	requires:"Ext.data.proxy.LocalStorage",
	config:{
		model:"NotesApp.model.Note",
		data:[
		      {title:"Note 1", narrative:"這是Note 1 的筆記內容"},
		      {title:"Note 2", narrative:"這是Note 2 的筆記內容"},
		      {title:"Note 3", narrative:"這是Note 3 的筆記內容"},
		      {title:"Note 4", narrative:"這是Note 4 的筆記內容"},
		      {title:"Note 5", narrative:"這是Note 5 的筆記內容"},
		      {title:"Note 6", narrative:"這是Note 6 的筆記內容"},
		],
		sorters:[{property:'dateCreated',direction:'DESC'}]
	}
});

        如今回到NotesListContainer.js,在noteList視圖對象的聲明中添加剛剛新建的store對象:        

var notesList = {
    xtype: "noteslist",
    store: Ext.getStore("Notes"),
    listeners: {
        disclose: { fn: this.onNotesListDisclose, scope: this }
    }
};

        讓咱們回到控制器的定義文件controller/Notes.js中,在launch()方法中,調用store對象的load()方法:

launch : function(){
		this.callParent(arguments);
		Ext.getStore("Notes").load();
		alert("controller Notes 已啓動");
	}

        在這裏,其實咱們並不須要這個調用load()方法 - 由於模型數據是硬編碼的 - 但咱們在本教程的下一章,咱們將中止使用硬編碼的數據,並開始使用存儲在瀏覽器緩存中的數據

        和models,views和controllers相似,咱們須要在app.js的配置中添加stores:

Ext.application({
	name : 'NotesApp',
	
	models:["Note"],
	controllers : ["Notes"],
	views : ["NotesListContainer","NotesList"],
	stores :["Notes"],
	launch : function() {
		// alert("App launch");
		// var notesListContainer = Ext.create("NotesApp.view.NotesListContainer");
		var notesListContainer = {
			xtype:"noteslistcontainer"
		}
		Ext.Viewport.add(notesListContainer);
	}
});
        啓動模擬器,就會看到咱們剛剛以硬編碼形式保存的全部筆記記錄都顯示在屏幕上了:

總結

        在這一章,咱們對應用程序的主視圖進行了一些修改。首先,在NotesListContainer類中增長了一個initialize()方法,並使用這個方法實例化了工具欄和筆記列表視圖。

        第二,更重要的變化是引入了兩個新的的事件, newNoteCommand和editNoteCommand ,當咱們的用戶須要建立或編輯筆記的時候這兩個事件將被觸發並傳播。這種修改方式提升了應用的可維護性和可靠性。

        第三,修改NotesListContainer類以後,咱們建立了NotesList類,繼承自Ext.dataview.List類,這是咱們用於緩存中筆記的組件。這個類須要一個數據存儲對象來保存全部已有的筆記,所以,咱們還建立了Notes store。

        在本教程的下一章,咱們將開始編寫用戶用來編輯和刪除筆記的視圖類。同時,咱們還將學習Sencha Touch的表單,模型驗證器,以及使用本地存儲代理對象來在客戶端進行緩存。

        未完待續!

下載

源代碼已發佈到迅雷快傳:http://kuai.xunlei.com/d/KINUTYSNDOIO

原文出處:http://miamicoder.com/2012/how-to-create-a-sencha-touch-2-app-part-2/

本教程快速連接

相關文章
相關標籤/搜索