前言

接續上次的文章,我們這次要把要一步一步把功能給實做完成~!

內文

延續上次的文章,我們已經完成 Todo 應用的 CUD(新增、修改、刪除),接下來我們要做的便是可以讓使用者 filter 已經完成的 Todo,哪些又是尚未完成的 Todo。因此這邊我們需要把我們的 Template 分成數個子 Template 並利用框架的 Route 來做到讓這些事情在同一頁完成。 首先我們先把列表頁的內容抽出來變成一個新的 Template

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!-- data-template-name內的值就相當於是一個路由,index是讓這個Template顯示的路由值,裡面的資料來源從哪裡來則從route去設定 -->
<script type="text/x-handlebars" data-template-name="MyTodos/index">
    <ul id="todo-list">
        {{#each itemController="MyTodoList"}}
        <li {{bind-attr class="isCompleted:completed isEditing:editing" }}>
            {{#if isEditing}}
            {{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>
        {{/each}}
    </ul>
</script>

而原本的 Template 就變成如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<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=myTitle1 action="createTodo"}}
        </header>

        <section id="main">
            {{outlet}}
            <input type="checkbox" id="toggle-all">
        </section>
</script>
<!--忽略很多-->

Handlebars helper “{{outlet}}” 是用來幫助我們宣告一個區域,當我們改變 route 時可以 render 出指定 route 的畫面,而第一個 child route 就會是填滿這個區塊的 route。

改完 View 之後我們要新增一個 route.js 檔案,在裡面撰寫 route 相關的邏輯。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Todos.Router.map(function () {
    // 當 Uri 為 '/' 時去 render 名稱為「MyTodos」的 Template
    this.resource('MyTodos', { path: '/' }, function () {
        //額外的 child routes
    });
});

Todos.MyTodosRoute = Ember.Route.extend({
    // 表明這個 route 要顯示的 model 是那個
    model: function () {
        return this.store.find('MyTodo');
    }
});

// EmberJS 的 controller 預設的 route 名稱就是 Index,因此若直接存取預設的 controller 那麼根據框架就會去 render Index 的內容
Todos.MyTodosIndexRoute = Ember.Route.extend({
    // 這邊表示 index 的 model 就是 MyTodos
    model: function () {
        return this.modelFor('MyTodos');
    }
});

現在重新刷一次頁面應該就可以看到我們的清單顯示於頁面中。不過我們還要這個 Todo 應用可以切換以做完、未做完的顯示。首先還是來調整一下頁面。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<script type="text/x-handlebars" data-template-name="MyTodos">
<!--忽略很多-->
        <footer id="footer">
            <span id="todo-count">
                <strong>{{remaining}}</strong> {{inflection}} left
            </span>
            <ul id="filters">
                <li>
                    {{#link-to "MyTodos.index" activeClass="selected"}}All{{/link-to}}
                </li>
                <li>
                    {{#link-to "MyTodos.active" activeClass="selected"}}Active{{/link-to}}<--會打到active這個route>
                </li>
                <li>
                    {{#link-to "MyTodos.completed" activeClass="selected"}}Completed{{/link-to}}
                </li>
            </ul>
<!--忽略很多-->
</script>

接著調整一下 route.js 的內容,讓 route 知道這兩個 route 分別要呈現什麼東西。

 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
33
34
Todos.Router.map(function () {

    // 當Uri為'/'時去render名稱為「MyTodos」的Template
    this.resource('MyTodos', { path: '/' }, function () {
        // 這些route會在MyTodo這個Route之下
        this.route('active');// MyTodos.active
        this.route('completed');// MyTodos.completed
    });
});

<!--忽略...-->

Todos.MyTodosActiveRoute = Ember.Route.extend({
    model: function () {
        return this.store.filter('MyTodo', function (todo) {
            return !todo.get('isCompleted');
        });
    },
    renderTemplate: function (controller) {
        //這裡重複使用了既存的Template來render
        this.render('MyTodos/index', { controller: controller });
    }
});

Todos.MyTodosCompletedRoute = Ember.Route.extend({
    model: function () {
        return this.store.filter('MyTodo', function (todo) {
            return todo.get('isCompleted');
        });
    },
    renderTemplate: function (controller) {
        this.render('MyTodos/index', { controller: controller });
    }
});

現在重新刷一下畫面,應該已經可以切換完成、未完成的項目了。接下來我們新增一個可以清除所有已經完成的項目的功能。還是一樣,首先修改頁面

1
2
3
4
5
6
7
8
9
<footer id="footer">
<!--忽略...-->
    {{#if hasCompleted}}
    <button id="clear-completed">
        {{action "clearCompleted"}}
        Clear completed ({{completed}})
    </button>
    {{/if}}
</footer>

接下來我們要在 Controller 中新增以下功能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Todos.MyTodosController = Ember.ArrayController.extend({

// 忽略好大一段...

    // 清空已經完成的項目
    clearCompleted: function () {
        var completed = this.filterBy('isCompleted', true);
        // emberjs 的 array api 提供的方法
        completed.invoke('deleteRecord');
        completed.invoke('save');
    },

    hasCompleted: function () {
        return this.get('completed') > 0;
    }.property('completed'),

    completed: function () {
        return this.filterBy('isCompleted', true).get('length');
    }.property('@each.isCompleted')
)};

重刷一下畫面後,功能就實做完成了~!但,既然有清除全部已經做完當然也會有全部已做完的功能才是

因此我們再來改一下畫面

1
2
3
4
5
6
<!--忽略...-->
<section id="main">
    {{outlet}}
    {{input type="checkbox" id="toggle-all" checked=allAreDone}}
</section>
<!--忽略...-->

//忽略很多…

1
2
3
4
5
6
7
8
9
allAreDone: function (key, value) {
    if (value === undefined) {
        return !!this.get('length') && this.isEvery('isCompleted', true);
    } else {
        this.setEach('isCompleted', value);
        this.invoke('save');
        return value;
    }
}.property('@each.isCompleted')

好~改好之後存好檔,重新刷一下頁面這樣我們的全選功能就算完成了~!

以上這些小功能都完成後,一個簡單的 Todo 應用的基本能力就實做完成了,剩下的就是資料來源的問題,這就留到下次吧 = =汗