我是山姆鍋

山姆鍋對 JavaScript 大多數框架都不算熟悉,但因為《我。影化身》專案需要提供 Web 介面, 不得已也要選擇符合自己需要的框架來運用一下。幸好,我的目標只是一個相對簡單的使用者介面, 讓這個過程縮短不少時間,最後決定使用 JQuery Mobile + Backbone 這樣的框架組合。

為什麼選擇使用 Web 用戶介面?

《我。影化身》專案有桌面環境用戶介面的需求,也嘗試過使用 Qt
這種跨平台的程式庫。 但山姆鍋對於產生的發行包因為這樣就要多將近
20MB 的大小一直覺得耿耿於懷,雖然這只是個人偏好問題,
還是希望有一個較輕量的跨平台用戶介面。

另外一個做法就是使用各個平台的特有程式庫來開發 UI,對於 Python
程式,這表示:

  • 在 Windows 使用 PyWin32
  • 在 OS X 使用 PyObjC
  • 在 Linux 使用 PyGObject

理論上是可行,但要同時使用不同 API 在不同平台開發 UI, 只要 UI
稍微複雜點,
開發的困難度就會需要每個平台一個專職工程師了。《我。影化身》的確有使用上述
程式庫來整合桌面環境的訊息通知機制以及選單,但要開發完整
UI,即使像是《我。影化身》這種設定不需要複雜 UI
的程式,難度還是太高了。其它如 Toga, PyGUI 等等特殊的跨平台 UI
程式庫都因為缺乏專案所需的功能而無法採用。

不管原因為何,最後決定使用 Web 技術來提供跨平台 UI,
似乎也是很合理的方案。

為什麼選擇 JQuery Mobile + Backbone?

由於專業上需要,一直以來山姆鍋都有持續在關注 JavaScript 應用框架的發展,
也常常對於如雨後春筍般出現的新框架感到無所適從,所以之前都僅止於了解,並沒有太多實際運用的經驗。
如今要選擇專案所使用的框架,自然要先說說專案的需求:

提供一個類似應用程式(App)的用戶介面

:   因為這個專案就是個應用程式。
不用太多 HTML5, CSS3 設計

:   作為工程師,山姆鍋對於設計的美感真的不在行。
使用現有的用戶介面元件

:   希望開發的方式可以使用元件概念撰寫,也就是有明確的 Button, List,
    Table 這樣的概念。
同時支援桌面、平板以及手機裝置

:   沒有太多時間使用不同的框架。

應該也有其它方案符合需求,像是 Sencha Touch 等,但個人偏好 JQuery
Mobile。 作為 UI 框架,JQuery Mobile
基本功能完整,但要開發應用總還是希望程式要有更好的結構,
這就需要考慮採用其它應用框架來輔助,目前跟 JQuery Mobile
搭配最好的看來是 Backbone。

雖然網路上有許多關於 JQuery Mobile + Backbone 的教學文章,JQuery Mobile
官網甚至有提供示範案例,但這些例子大多假設專案需求龐大,硬是要使用
RequireJS 來提供模組化開發機制。 對於《我。影化身》這樣的專案來說,使用
RequireJS 總有點太過了 [1] 。 雖然網路上也找得到關於只使用 JQuery Mobile

  • Backbone 的範例,但事情如果順利的話, 就不會有這篇文章了!

問題出在哪?

老實說,山姆鍋對 JavaScript 相關框架真的不熟,只是一邊開發一邊學習。
對於網路找到的 JQuery Mobile + Backbone
範例為何不能正常運作也沒有時間深入了解,
也許是單純版本問題,或者只是山姆鍋眼睛脫窗哪裏沒有設定好!不管怎樣,本來認為一件簡單的事情,
卡了一段時間後終於試出來一個可以運作的組合。

除了 JQuery,JQM
本身並不依賴其它程式庫,雖說是完整的框架,但因為它缺少其它 MV*
應用框架的功能, 讓它其實更像 Twitter Bootstrap 這種 UI
框架。為了開發完整的應用,通常還是搭配 Backbone 這樣的應用框架。

但因為 JQM 有自己的頁面路由 (page
routing) 的機制,所以需要設定讓它不處理這部分功能,而由 Backbone
全權處理。JQM 相關的設定在 /js/jqm-config.js 這個檔案:

1
2
3
4
5
6
7
8
9
10
11
12
13
// JQM config
$(document).on("mobileinit", function () {
$.mobile.ajaxEnabled = false;
$.mobile.linkBindingEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.pushStateEnabled = false;
$.mobile.defaultPageTransition = "fade";
$.mobile.autoInitializePage = false;

$("div[data-role='page']").on("pagehide", function (event, ui) {
$(event.currentTarget).remove();
});
});
\'mobileinit\' 事件

:   在 JQM 完成初始化前會呼叫此事件,所以在此設定 JQM。
ajaxEnabled, linkBindingEnabled, pushStateEnabled 都設為 false

:   分別讓 JQM 不使用 Ajax 方式動態載入頁面, 不處理超連結點選事件,
    以及瀏覽器的導航歷史管理(navigation history),這些都會干擾
    Backbone 的運作。
hashListeningEnabled 設為 false

:   告訴 JQM 不要處理瀏覽器 URL hash 更動事件。 URL
    hash就是瀏覽器網址中 \'\#\' 那個部分,
    這個部分按照規範是不會傳給後端伺服器,只能在網頁中存取。JQM 跟
    Backbone 就是利用這個 hash 來決定要使用的頁面。
由於 JQM 不再負責頁面導航管理,需要由我們自己完成舊的頁面資料從 DOM 中移除的工作

:   `pagehide` 事件處理便是用來完成這個工作。

有了這樣的設定,基本上就可以使用 Backbone
來處理前端頁面導航以及其它功能。

其它注意事項

有幾個在實際開發中遇到的問題:

動態產生 JQM 頁面內容時,可能會發現網頁呈現不太正常

:   這點在專案樣板中沒有實例。這是因為 JQM
    沒有機會套用樣式到新增的網頁元素,需要特別通知 JQM:

    
1
$(this.el).trigger("create");
如果您有其它更好的方式,請不吝指教。
新版本的更換頁面方式

:   新版本的 JQM 管理頁面的方式有更動,改為下列方式:

    
1
$(":mobile-pagecontainer").pagecontainer( "change", $(page.el), { changeHash: false});
網路上找到的文章大多使用舊的方法,也就是:
1
$.mobile.changePage($(page.el), {changeHash:false});
jqm-config.js 的引入順序

:   `/js/jqm-config.js` 是用來設定 JQM, 它必須在 JQuery
    之後, JQM 之前在網頁中引入,如:
1
2
3
<script src="/js/jquery.min.js"></script>
<script src="/js/jqm-config.js"></script>
<script src="/js/jquery.mobile.min.js"></script>

JQM + Backbone 專案樣板

為了方便未來使用,山姆鍋在 GitHub
上整理了一個專案樣板:https://github.com/sampot/jqm-backbone-template

想要更實際的例子?那就請直接參考《我。影化身》的前端用戶介面實作 (webfront):https://github.com/eavatar/eavatar-me

結語

山姆鍋其實很喜歡 AngularJS
這個應用框架,但對於它的運作方式不是很了解,所以不在這個專案採用。
在實際使用後,發現 JQM + Backbone
這樣的組合還蠻不錯的:夠簡單、運作流程容易理解。 對於只需要簡單 Web UI
的應用專案,這是個不錯的選擇。

參考資料



  1. 如果預期專案需要多人一起開發或者功能較複雜,建議考慮使用 RequireJS。 ↩︎