以往在使用 UI Library 建置系統時,最重要的是如何隨時取得想控制的 ui 元件。在傳統的物件導向 UI 程式設計上(例如 Swing, SWT, VB),皆可以直接透過變數指標或父子走訪來獲得欲操作的元件。然而在 SPA (Single Page Application) 設計的網頁系統中,直接操作 DOM 卻成為最有效率的重點方法。
一般的 HTML 頁面我們可以透過 jQuery 等 Library 來 Select 元件,那麼 Sencha Touch 環境呢?其實在 Sencha Touch 的世界中,有著「隱藏 DOM 操作」的設計風格。一般我們只會透過元件 (Component) 的操作來實作系統,而不會直接操作 DOM,目的是有效劃分系統邊際、隱藏實作細節,當然 Sencha Touch 的底層依然是對 DOM 進行操作。我們先來介紹一下 Sencha Touch 提供的幾個常用的元件取得方法:
- Parent / Children Access
- Ext.getCmp()
- Ext.ComponentQuery.query()
- Ext.fly()
Parent / Children Access
這個方法表示透過 UI 的父子關係來取得元件,由於使用這樣的方式來取得元件,可以避免指定 id 來存取,因此少了相依性關係,元件的封裝性與重用度提高,也是最推薦的方法。實際的操作方法是透過 Ext.Container.getParent() 與 Ext.Container.getItems() 來走訪元件。
Ext.getCmp()
所有在 Ext Runtime 的環境中所建立的 UI 元件,都可以透過這個靜態方法取得。使用的索引是 id,用這樣的方法優點為速度快,缺點為彈性低。
Ext.ComponentQuery.query()
ComponentQuery 可以說是比較精簡的 jQuery,除了使用 Ext.ComponentQuery.query() 這個靜態方法來呼叫之外,所有繼承 Container 的元件也可以透過 query() 方法來指向其中的子元件,官方網站也有些 Ext Component Query 的介紹。Component Query 主要提供以下幾種方法來指定或搜尋元件:
- xtype
- id
- attributes
Ext.fly()
Ext.fly() 是一種超越 UI Runtime 的選擇器,透過這個方法可以直接透過 ID 取得非 Ext.Component 的 DOM 元素,算是比較底層的操作方式。
以上使用方法請參考完整範例程式
UI 畫面資料操作
在 SAP 的系統中,由於頁面不需要重新載入,因此所有資料的操作都是透過 JavaScript 操作 DOM 來實現。當然上面提過 Sencha Touch 封裝了 DOM 的操作,而提供對元件的直接操作來實現畫面顯示與變更,因此我們就必須了解幾種在 Sencha Touch Application 中處理畫面的基本方法。這裡先介紹以下兩種基本方法:
- 樣板模式 (Template Pattern) - Ext.Template / Ext.XTemplate
- 表單模式 - Ext.form.Panel / Ext.form.FieldSet
Ext Template Pattern
Template 機制主要是實作在 Ext.Component,因此所有繼承 Ext.Component 的元件都可以直接使用。使用的方法可以透過 tpl config 或者 Ext.Component.setTpl() 來設定 Template,然後再透過 Ext.Component.getData() 與 Ext.Component.setData() 來處理 Template 中的資料,範例程式如下:
Ext.define('DA.view.Template', { extend: 'Ext.Container', requires: [ 'Ext.Panel' ], config: { padding: 20, items: [ { xtype: 'panel', id: 'da-template-panel', tpl: '<h1>Ext.Template Demo</h1><p>Name: {name}</p><p>Gender: {gender}</p>' }, { xtype: 'button', text: 'Call setData()', listeners: { tap: function () { Ext.getCmp('da-template-panel').setData( { name: 'SJ', gender: 'M' } ); } } }, { xtype: 'spacer', height: 20 }, { xtype: 'button', text: 'Call getData()', listeners: { tap: function () { console.log(Ext.getCmp('da-template-panel').getData()); } } } ] } });
傳統的 Ext.Template 只能處理變數的替換工作,這對於複雜的業務流程是不夠的,因此 Sencha Touch 提供了進階的 Ext.XTemplate 來滿足需求。XTemplate 有點類似 MVC 中的 Template Engine (如同 JSTL, Smarty 等等),其多了以下功能:
- Basic math support: + - * /
- Auto Filling of arrays: <tpl for="">
- Conditional processing: <tpl if="">
- Switch Case: <tpl switch="">
- Member function: this.fnName()
以下程式範例實作了 ForEach 與 IF 兩種 Template 特性,詳細用法請參考完整範例程式碼
Ext.define('DA.view.XTemplate', { extend: 'Ext.Container', requires: [ 'Ext.Panel' ], config: { padding: 20, items: [ { xtype: 'panel', id: 'da-xtemplate-panel', tpl: [ '<h1>Ext.XTemplate Demo</h1>', '<p>Name: {name}</p>', '<p>Gender: ', ' <tpl if="gender == \'M\'">', ' Male', ' <tpl else>', ' Female', ' </tpl>', '</p>', '<p>Phones:</p>', '<table>', ' <tr>', ' <th>No.</th>', ' <th>Type</th>', ' <th>Number</th>', ' </tr>', ' <tpl for="phones">', ' <tr>', ' <td>{#}</td>', ' <td>{type}</td>', ' <td>{number}</td>', ' <tr>', ' </tpl>', '</table>' ] }, { xtype: 'button', text: 'Call setData()', listeners: { tap: function () { var cmp = Ext.ComponentQuery.query('#da-xtemplate-panel')[0]; cmp.setData( { name: 'SJ', gender: 'M', phones: [ {type: 'home', number: '09xx132142'}, {type: 'mobile', number: '07-6361636'}, {type: 'office', number: '022-6361636'} ] } ); } } }, { xtype: 'spacer', height: 20 }, { xtype: 'button', text: 'Call getData()', listeners: { tap: function () { var cmp = Ext.ComponentQuery.query('#da-xtemplate-panel')[0]; console.log(cmp.getData()); } } } ] } });
Ext Form Panel
除了 Template 之外,在網頁設計中我們也常用到表單來處理資料,在 Sencha Touch 中可以使用 Ext.form.Panel 來建立一個表單元件,其實 Ext.form.Panel 就相當於 HTML 中的 FORM TAG。而基本的 Sencha Touch 也已經提供了以下多種的 Ext.field 用以附加在 Form Panel 中,當然 Ext.field 的概念就相同於 HTML 中的 INPUT TAG,以下為目前可用的 Field 元件列表:
- Checkbox
- DatePicker
- Field
- Hidden
- Number
- Password
- Radio
- Search
- Select
- Slider
- Spinner
- Text
- TextArea
- Toggle
- Url
通常在使用 Form Panel 的時候,會搭配 Ext.form.FieldSet 元件來放置 Ext.field.*,FieldSet 只是視覺化的元件,其並沒有絕對需要存在的必要性。傳統的結構如下示意圖:
當我們要操作資料時也可以透過 Ext.form.Panel.getValues() 與 Ext.form.Panel.setValues() 來完成。程式範例如下:
Ext.define('DA.view.FormPanel', { extend: 'Ext.form.Panel', requires: [ 'Ext.form.FieldSet', 'Ext.field.Password', 'Ext.field.Email', 'Ext.field.Text' ], config: { id: 'da-form-panel', items: [ { xtype: 'fieldset', title: 'FieldSet Title', items: [ { xtype: 'textfield', name: 'name', label: 'Name' }, { xtype: 'emailfield', name: 'email', label: 'Email' }, { xtype: 'passwordfield', name: 'password', label: 'Password' } ] }, { xtype: 'button', text: 'Call setValues()', listeners: { tap: function () { Ext.getCmp('da-form-panel').setValues( { name: 'name', email: 'email', password: 'password' } ); } } }, { xtype: 'spacer', height: 20 }, { xtype: 'button', text: 'Call getValues()', listeners: { tap: function () { console.log(Ext.getCmp('da-form-panel').getValues()); } } } ] } });
結論
在 Sencha Touch 中除了使用 Form Panel 與 Template 操作畫面資料之外,也提供了更高階的 DataView,能夠透過 MVC + Store + Proxy 來實現各種功能,當然使用上也比較複雜。適時的選用簡單的資料處理方式也是必要的手段,兩者都能掌握才能應付各種需求。
參考資料