2014年11月4日 星期二

Take a peek at EmberJS - Phase2

前言:
接續上個月(拖好久...)的進度我們繼續把這個簡單的應用給完成吧~
第一階段
第一階段我們只是把最簡單的Model型態完成,但是卻還沒賦予Model跟View互動的機制,這階段就是要完成可以對TodoList的項目進行編輯以及把項目標示為已完成。

本文:
首先我們先完成選取在每個項目前面的勾勾就把該項目給畫上一個橫線表示完成的功能。在這個功能中我們會需要對列表的每個項目進行操作,我們的HTML一開始是長這樣的:
{{#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內容如下:
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我們改成這樣:
<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改寫成這樣:
<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來實現這個功能:
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時可以把該筆記錄刪除,為了達到這個目的我們的頁面繼續修改~
<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
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處理邏輯哪裡可以直接用,因此若願意的話歡迎在下方留言給予指教~