前言

接續上個月(拖好久…)的進度我們繼續把這個簡單的應用給完成吧~

第一階段我們只是把最簡單的 Model 型態完成,但是卻還沒賦予 Model 跟 View 互動的機制,這階段就是要完成可以對 TodoList 的項目進行編輯以及把項目標示為已完成。

內文

首先我們先完成選取在每個項目前面的勾勾就把該項目給畫上一個橫線表示完成的功能。在這個功能中我們會需要對列表的每個項目進行操作,我們的 HTML 一開始是長這樣的:

1
2
3
4
5
6
{{#each}}
    <li {{bind-attr class="isCompleted:completed"}}>
        <input type="checkbox" class="toggle">
        <label>{{title}}</label><button class="destroy"></button>
    </li>
{{/each}}

在這裡我們可以新增一個 controller 來 wrapping 每個 each 裡的項目( Ember 裡的 controller 管理狀態與跟畫面的互動, manage state and decorate data),新的 controller 內容如下:

 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
30
31
32
Todos.MyTodoListController = Ember.ObjectController.extend({
    // 類似 Knockout.js 的 Computed property,這個屬性會依照相依的屬性值的變化而改變
    // 是否完成
    isItemCompleted: function (key, value) {
        var model = this.get('model');

        if (value === undefined) {
            // Getter, 把目前項目的 isCompleted 回傳
            return model.get('isCompleted');
        } else {
            // Setter
            model.set('isCompleted', value);
            model.save(); // 更改項目的 isCompleted 值
            return value;
        }
    }.property('model.isCompleted')

    // 或也可以寫成如下方式:
    // 這個寫法看起來比較明確點
    isItemCompleted: Ember.computed('model.isCompleted', function (key, value) {
        var model = this.get('model');

        if (value === undefined) {
            // Getter, 把目前項目的isCompleted回傳
            return model.get('isCompleted');
        } else {
            model.set('isCompleted', value);
            model.save(); // 更改項目的isCompleted值
            return value;
        }
    })
})

這個isItemCompleted屬性後面呼叫了property方法,這相當於宣告這個屬性是一個computed property。 然後在前端的html我們改成這樣:

1
2
3
4
5
6
7
8
<ul id="todo-list">
   {{#each itemController="MyTodoList"}}
      <li {{bind-attr class="isCompleted:completed"}}>
         {{input type="checkbox" checked=isCompleted class="toggle"}}
            <label>{{title}}</label><button class="destroy"></button>
      </li>
   {{/each}}
</ul>

這樣在我們點選勾勾時,就可以把該項目劃上一個橫線表示完成了~! 接下來我們還要為這個 Todo List 增添一個功能,讓已經寫下來的項目可以被修改,同樣的這個功能也是針對 each 裡面的每個項目的操作,因此我們要把它放在 MyTodoListController.js 這個檔案裡面。首先我們在 Index.schtml 改寫成這樣:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<ul id="todo-list">
    {{#each itemController="MyTodoList"}}
    // 這邊分別用 isCompleted 與 isEditing 兩個 Computed property 來切換樣式
    <li {{bind-attr class="isCompleted:completed isEditing:editing" }}>
    // Handlebars有if else可以用,這點比用knockout方便很多
        {{#if isEditing}}
            <input class="edit"/>
        {{else}}
        {{input type="checkbox" checked=isItemCompleted class="toggle"}}
        // 這邊就是綁定事件的地方,讓這個元素(Label)的doubleClick事件與ditTodo方法綁在一起
            <label {{action "editTodo" on="doubleClick" }}>{{title}}</label><button class="destroy"></button>
        {{/if}}
    </li>
    {{/each}}
</ul>

頁面改成這樣後,我們就要去 MyTodoListController.js 新增相對應的 Code 來實現這個功能:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Todos.MyTodoListController = Ember.ObjectController.extend({
    // action 屬性裡面的方法(其實也就是屬性)代表與外界互動的方法
    actions: {
        editTodo: function () {
            this.set('isEditing', true);
        }
    },
    // 這個屬性是用來判斷項目是否正處於編輯狀態
    isEditing: false,

    isItemCompleted: function (key, value) {
        var model = this.get('model');

        if (value === undefined) {
            // Getter, 把目前項目的isCompleted回傳
            return model.get('isCompleted');
        } else {
            // Setter
            model.set('isCompleted', value);
            model.save(); // 更改項目的 isCompleted 值
            return value;
        }
    }.property('model.isCompleted')
})

完成這些 Code 之後目前 Todo 的項目就可以點擊兩下後進入編輯模式,但顯然還不完整。

我們需要讓編輯後的 Title 可以被儲存,並且在清空 Title 時可以把該筆記錄刪除,為了達到這個目的我們的頁面繼續修改~

1
2
3
4
5
6
7
8
9
<li {{bind-attr class="isCompleted:completed isEditing:editing" }}>
   {{#if isEditing}}
   // 我這邊的寫法跟官方的 Toturial 不太一樣,官方是用另一個Handlebars來處理這個View,我這邊是直接使用原本的Handlebars來做
   {{input class="edit" value=title focus-out="acceptChanges" insert-newline="acceptChanges"}}
   {{else}}
   {{input type="checkbox" checked=isItemCompleted class="toggle"}}
   <label {{action "editTodo" on="doubleClick" }}>{{title}}</label><button {{action "removeTodo"}} class="destroy"></button>
   {{/if}}
</li>

而在 MyTodoListController 中新增相對應的方法來處理這個 View

 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
Todos.MyTodoListController = Ember.ObjectController.extend({
   actions: {
        editTodo: function () {
            this.set('isEditing', true);
        },
        acceptChanges: function () {
            this.set('isEditing', false);

            if (Ember.isEmpty(this.get('model.title'))) {
                // 呼叫removeTodo方法
                this.send('removeTodo');
            } else {
                this.get('model').save();
            }
        },
        removeTodo: function () {
            // 若 Todo 的 Title 為空則把該記錄移除
            var todo = this.get('model');
            // 這個方法是相對於 createRecord 方法
            todo.destroyRecord();
            // 把更新後的 model 儲存
            todo.save();
        }
    },
   .................
}

完成以上 Code 後我們的 Todo List 就擁有最基本的新增修改刪除功能了~!

而在這個範例的最後一個篇章會介紹如何讓這個Todo List的資料來源變成外部取得~

ps.在看這個部分的教學文件並實做時在 controller 這個角色上面卡了很久,不是很清楚在 Emberjs 裡面 Controller 扮演的角色是什麼,哪裡改新增 controller 處理邏輯哪裡可以直接用,因此若願意的話歡迎在下方留言給予指教~