实例教程——代办事项

首先,我们来看一下将要实作的示例。 运行示例

上面例子的代码如下:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="/resources/cola-ui/semantic.css">
    <link rel="stylesheet" type="text/css" href="/resources/cola-ui/cola.css">
    <script src="/resources/jquery-2.1.3.js"></script>
    <script src="/resources/cola-ui/3rd.js"></script>
    <script src="/resources/cola-ui/semantic.js"></script>
    <script src="/resources/cola-ui/cola.js"></script>

    <script type="text/javascript">
        cola(function (model) {
            model.set({
                todos: [
                    { title: "Meeting", done: true },
                    { title: "Date" },
                    { title: "Lunch" , done: true},
                    { title: "Play basketball" },
                    { title: "Game" }
                ],
                sortOptions: ["", "done", "-done", "title", "-title", "done,title"]
            });

            model.action({
                add: function () {
                    model.get("todos").insert({
                        title: model.get("newItem")
                    });
                    model.set("newItem", null);
                },
                delete: function (todo) {
                    todo.remove();
                },
                stat: function () {
                    var todos = model.get("todos"), done = 0;
                    todos.each(function (todo) {
                        if (todo.get("done")) done++;
                    });
                    return done + "/" + todos.entityCount;
                }
            });
        });
    </script>
</head>
<body style="padding:20px">
<p>
    <input c-bind="newItem">
    <button c-onclick="add()" c-disabled="newItem?false:true">Add</button>
</p>
<p>
    Filter: <input c-bind="filterParam">
    Sort: <select c-bind="sortParam" c-options="sortOptions"></select>
</p>
<ul>
    <li c-repeat="todo in sort(filter(todos,filterParam),sortParam)">
        <input type="checkbox" c-bind="todo.done">
        <span c-bind="todo.title"></span>
        <button c-onclick="delete(todo)">Delete</button>
    </li>
</ul>
<p>
    <span c-bind="stat()"></span>
</p>
</body>
</html>

这是一个待办事项的列表界面。虽然界面略显粗糙,但功能已相当完整。

本页面最核心的部分是待办事项的列表,这个列表在HTML中是这样定义的:

<li c-repeat="todo in sort(filter(todos,filterParam),sortParam)">
    <input type="checkbox" c-bind="todo.done">
    <span c-bind="todo.title"></span>
    <button c-onclick="delete(todo)">Delete</button>
</li>

c-repeat指令的第一段todo in sort(filter(todos,filterParam),sortParam)里包含了对两个内置Action的调用, 用于完成对代办事项列表的过滤和排序。其中的filterParam和sortParam并不是两个固定的值,而是两个用于引用Model中数据的数据路径。 一开始我们并没有给Model中的filterParam和sortParam这两个数据项赋值,根据filter和sort这样两种转换器的定义,不传递参数就不会产生效果。 因此,我们一开始看到的列表是所有待办事项的初始顺序。

在列表上方的HTML中,我们可以找到这样一段定义:

<p>
    Filter: <input c-bind="filterParam">
    Sort: <select c-bind="sortParam" c-options="sortOptions"></select>
</p>

这里有一个<input>和一个<select>分别绑定到了之前提到的filterParam和sortParam这两个数据项。其中<select>的备选项的信息由来自于另一个名为sortOptions的数据项, 通过c-options指令完成绑定。

以绑定到filterParam的<input>为例,当用户通过<input>改变了filterParam数据项的值时,由于列表中c-repeat="todo in sort(filter(todos,filterParam),sortParam)"的声明是涉及filterParam数据项的, 因此Cola会重新计算执行c-repeat指令,刷新列表。此时由于filter的条件已发生改变,因此c-repeat重新计算的结果将是跟filterParam过滤后的结果。 这一数据变化最终会自动反应到代办事项的列表中。因此,我们在界面中看到的效果就是,当我们在Filter:后的输入框中输入内容时,下面的代办事项的列表会自动根据我们的录入内容进行数据过滤。

上面<select>的工作原理大致同样如此,这里不做累述。

我们再回到待办事项的列表的模板定义中,其中的<checkbox>的定义并不难理解,需要注意的是最后那个<button>的的定义方式。 为了用这个按钮实现删除当前行的功能,我这样定义了它的事件c-onclick="delete(todo)"。其中的todo是c-repeat产生的迭代变量,代表每一个当前的待办项。 delete()是我们为Model定义的一个Action,它的具体定义是这样的:

model.action({
    ...

    delete: function (todo) {
        todo.remove();
    }

    ...
});

即直接删除传入的待办事项。

当一个待办事项被删除后,c-repeat="todo in sort(filter(todos,filterParam),sortParam)"会自动感知到数据的变化,并重新执行迭代,刷新列表。因此该待办事项对应的DOM元素也会自动被移除。

列表下方的统计是这样的定义的:

<p>
    <span c-bind="stat()"></span>
</p>

其代码主体在Action中。逻辑并不复杂,对todos列表进行一次迭代,统计出已完成的代办事项数并返回。

model.action({
    ...

    stat: function () {
        var todos = model.get("todos"), done = 0, total = 0;
        todos.each(function (todo) {
            if (todo.get("done")) done++;
        });
        return done + "/" + todos.entityCount;
    }

    ...
});

最后,我们再来看一下添加功能的实现。<body>中的第一段内容是这样的:

<p>
    <input c-bind="newItem">
    <button c-onclick="add()" c-disabled="newItem?false:true">Add</button>
</p>

这里的<input>绑定到了名为newItem的数据项,用于供用户填写要添加的待办事项。 <button>c-disabled="newItem?false:true"的作用是声明该按钮只在newItem项的内容不为空时才可用。 add()的具体定义如下:

model.action({
    ...

    add: function () {
        model.get("todos").insert({
            title: model.get("newItem")
        });
        model.set("newItem", null);
    }

    ...
});

此处的逻辑其实也非常简单,将newItem中的值取出组装成一条新的待办事项,添加到todos集合中。同时清空newItem数据项的内容。 由于Cola提供的双向数据绑定的特性,我们对数据模型所做的操作都会自动体现到相应的界面显示中。 所以在整个例子的Javascript代码中我们完全看不到直接对于DOM的代码操作。