時間回到三年前(2011年)…那個時候做 Web Application 在前端 Framework 的選擇不像現在多的眼花繚亂,基本上套上 jQuery 與 jQuery UI 還有一票 jQuery 的徒子徒孫就是火力強大的工具了,那個時候也是我第一次使用完整的前端 framework solution。時間沒走多久前端的需求爆炸性的成長,只用 jQeury 來操作 DOM 元素已經不敷需求,另外用 jQuery 來做 SPA 要手工的地方太多也挺吃力的,所以很多 SPA 的框架就誕生了。目前最多人擁護的莫過於 AngularJS,而後起之秀 EmberJS 雖然也是有不少擁護者但就台灣的環境來說 AngularJS 已獲得壓倒性的勝利,一方面是因為 AngularJS 發展穩定,又有 Google 這個大公司當靠山,所以網路上的資源比任何 SPA Framework 都還要多。但我就是比較搞怪一點所以就選 EmberJS 來看看了 XD。目前 EmberJS 是 Open Source 的專案 host 在 GitHub 上 EmberJS EmberJS 相對於 AngularJS 變化比較大所以需要多關注 GitHub 上面的 Issues 或是 commits,有些時候官網上面的教學文件還落後最新的 Stable 版本一段差距。
這篇文章會以官方的 Todo 教學為實現的功能,功能會到讓這個 Todo 應用可以塞入新的 Todo 事項,當中會加上一些我對它的理解。這個 EmberJS Application 會用到幾個 libary,jQuery + Handlebars + Ember + Ember-Data 這其中 handlebars 至個 libary 還滿特別的,有用過 AngularJS 的人一定對他的功能不陌生,它其實就是一個讓我們可以方便建構語意式模版的工具,用法像是以下這樣:
1
2
3
4
5
6
| <div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
</div>
</div>
|
這個其實很多框架都有類似的功能(KnockoutJS 也有),就看習慣哪一個表達方式的問題了。
另外一個可以抓出來說一下的就是 Ember-data 這個 libary了,其實 Emberjs 是可以不用這個東西也可以 run 的很好的,不過這個 libary 有著 Ember 遠大的野心,用我的破英文來解讀是這樣的啦~XD
“Ember Data is designed to be agnostic to the underlying persistence mechanism, so it works just as well with JSON APIs over HTTP as it does with streaming WebSockets or local IndexedDB storage.”,
“In particular, Ember Data uses Promises/A+-compatible promises from the ground up to manage loading and saving records, so integrating with other JavaScript APIs is easy.”
這兩段話出自他本身的說明文件,看起來這個 libary 會是未來 EmberJS 收到資料時的預設處理工具吧?
這邊還是用我習慣的 ASP.NET MVC 來實做,首先依然是開啟一個空白的專案,然後把上述四個會用到的四個 Libary 包含進去。若有裝 ASP.NET Optimization Framework 的話可以這樣做:
新增一個 ScriptBundleConfig.cs 檔於 App_Start 資料夾中,並於 Global.asax.cs 中呼叫他的靜態方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| using System.Web.Optimization;
public class ScriptBundleConfig
{
public static void Register(BundleCollection bundleCollection)
{
bundleCollection.Add(new ScriptBundle("~/bundle/all").Include(
"~/Scripts/jquery-2.1.1.js",
"~/Scripts/handlebars-v1.3.0.js",
"~/Scripts/ember.prod.js",
"~/Scripts/ember-data.prod.js"));
// 這個範例的EmberJS版本為1.7
BundleTable.EnableOptimizations = true;
}
}
// addictional lines truncated for brevity
ScriptBundleConfig.Register(BundleTable.Bundles);
// addictional lines truncated for brevity
|
而在母版(Views/Shared/_Layout)的 .cshtml 地方加上這段,這樣就可以把我們所需要的 Libary 都載入了
1
| @System.Web.Optimization.Scripts.Render("~/bundle/all")
|
EmberJS 屬於前端的 MVC 框架,而其執行也遵照「naming conventions」這個準則,以下我們會由 Todo List 這個小功能來驗證這個特性。
我們先在 Controller 中新增一個 Action,Controller 的內容相當簡單,這個範例也只有在 Client 端
1
2
3
4
5
6
7
| public class ToDoController : Controller
{
public ActionResult Index()
{
return View();
}
}
|
要開始一個 EmberJS 應用首先我們需要一個起始的 js 檔案
1
2
| // new 一個 EmberJS 應用程式的 instance
window.Todos = Ember.Application.create();
|
上面這個檔案就是宣告一個全域變數叫 Todos,把這個檔案 include 進去我們的 Index 頁面,這個檔案必須是除了基底 libary 之外第一個要引用的檔案!
接下來就是 Index.cshtml 上面的頁面要寫什麼東西呢?
1
2
3
4
| @{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| // 模版的名稱會影響到由那個 Route 渲染這個 Template,這個例子則是 MyTodosRoute 會去渲染這個 Template
<script type="text/x-handlebars" data-template-name="MyTodos">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
{{input type="text" id="new-todo" placeholder="What needs to be done?"
value=newTitle action="createTodo"}}
</header>
<section id="main">
<ul id="todo-list">
{{#each}}
<li {{bind-attr class="isCompleted:completed"}}>
<input type="checkbox" class="toggle" />
<label>{{title}}</label><button class="destroy"></button>
</li>
{{/each}}
</ul>
<input type="checkbox" id="toggle-all">
</section>
</script>
@section JSSection{
<script src="~/Scripts/application.js"></script>
}
|
整塊用 script 包起來的就是 handlebar 的作用區域他的 type 就是「text/x-handlebars」,EmberJS 其實本身是可以不需要用這個 libary 就可以運作的,不過官方推薦搭配這個 libary 來讓 UI 更有表達能力,因為 Handlebars Template 本身就是 for UI 的 Html-like 的 DSL(特定領域語言),看起來是挺直覺的。
接著就是新增一個 route.js 檔案來告訴 Ember.js 要怎麼去 render 畫面以及這個畫面所需要用到的 Model 是哪一個,另外根據 nameing convention 也要有個同名的 controller 對應到它
1
2
3
4
5
6
7
8
9
10
11
| Todos.Router.map(function () {
// Ember 會去偵測當 Uri 為 '/' 時去 render 名稱為「todos」的 Template
this.resource('MyTodos', { path: '/' });
});
Todos.MyTodosRoute = Ember.Route.extend({
// 指示這個 Route 的 Model 要掛載哪一個 Model 進來
model: function () {
return this.store.find('MyTodo');
}
});
|
當建好這隻 Route 後,我們接著就是要生一個 Model 出來讓 Template render 時有東西可以 render。follow Ember 的 naming convention 準則,我們的 Model 也取名叫做 MyTodo,不過 Model 這邊倒是不強制一定要這樣命名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| Todos.MyTodo = DS.Model.extend({
title: DS.attr('string'),
isCompleted: DS.attr('boolean')
});
// 這個是 DS 所提供的 feature, Fixture data,要啟用這樣的功能我們需要回過頭在 application.js 裡面做些設定
Todos.MyTodo.FIXTURES = [
{
id: 1,
title: 'Learn Ember.js',
isCompleted: true
},
{
id: 2,
title: 'Benjamin Has something to do',
isCompleted: false
},
{
id: 3,
title: 'Profit!',
isCompleted: false
}
];
//補上以下的程式碼
// 使用 Fixure 資料, 這可以讓 DS 把資料存取由 memory 來,這個設定通常是用在開發與測試
// DS Model 是設計用來做為 EmberJS 的 persistence 機制
Todos.ApplicationAdapter = DS.FixtureAdapter.extend();
|
到目前為止,我們已經把頁面上面的靜態資料都實做完成了,若現在 run 這個 web site 的話就可以看到 browser 原先都看不懂的東西現在都已經被我們所輸入的靜態資料填滿,但若想要跟這個 Todo 互動的話我們還缺少 controller 的幫忙。
依據 EmberJS 的 naming convention 我們新增一個 MyTodosController 檔案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| Todos.MyTodosController = Ember.ArrayController.extend({
// 行為(方法)必須要包在 actions 這個物件中,這也是 Ember 的一個 naming conventioon
actions: {
createTodo: function () {
// 由 newTitle 取得 Title 的值
// 取 Template 中元素的 Value 值
// 這裡的 get 方法是由 EmberJS 提供
var title = this.get('newTitle');
if (!title) { return false; }
if (!title.trim()) { return; }
// 產生一個新的 Todo Model
var todo = this.store.createRecord('MyTodo', {
title: title,
isCompleted: false
});
// 把 newTitle 的值清空
this.set('newTitle', '');
// Save the new model
todo.save();
}
}
});
|
好了~現在再 run 一次 web site,我們現在就可以新增 Todo 清單了!不過刪除的行為我們還沒有實做,所以目前也只能新增 XD
不過寫到這邊也符合目前第一階段的目標了。
EmberJS 雖然不像 AngularJS 受大眾寵愛,但也是一個備受期待的 MVC 框架,不過這個框架的教學資源與學習文件確實不像 AngularJS 這麼豐富,我想這大概也是目前環境還很少人投入這個框架的原因之一吧~
不過學習東西嘛~快樂最重要 XD
之後應該還會有後續的階段,除了按照官方的文件寫之外也會加上實做上的一些心得
Reference#