数组元素的新增/移除事件

前两篇博客已经很清楚的知道knockout.js通过observableArray()数组元素增减,可以实时的反映在UI上。 当然我们想在数组增加或移除元素时加上自定义逻辑就好比一个触发器的感觉,可以吗?

foreach提供了afterAdd及beforeRemove两个额外的事件,允许在数组新增、移除元素时执行特定逻辑。 在此继续沿用先前的用户列表呈现范例,加上两个效果:

  • 新增数据时,将最新加入的数据和表格进行着色修饰

  • 删除数据时,加上数据淡出特效

而在ViewModel里我们加上两个函数:

   //添加对象后才触发,第一次forach并不会触发
            self.afterAddEvent = function (element, index, data) {
                //通过nodeType过滤,只处理Element Node
                if (element.nodeType==1)  
                {
                    $(".new").removeClass("new");
                    $(element).addClass("new");
                }
            };
            //注意: beforeRemove事件后,要自已移除被刪除元素
            self.beforeRemoveEvent = function (element, index, data) {
                if (element.nodeType == 1) {
                    $(element)
                    .css("background-color", "#ff6a00")
                    .animate({ opacity: 0.2 },1000, function () {
                        $(this).remove();
                    })

                }
            };

afterAdd及beforeRemove函数会固定收到三个参数,element、index及data,其中 element 为模板容器中的各元素,即:

          <tr>
                    <td><span data-bind="text: id"></span></td>
                    <td><span data-bind="text: name"></span></td>
                    <td><span data-bind="text: score"></span></td>
                    <td><a href='#' data-bind="click: $root.removeUser">Remove</a></td>
                </tr>

实际运作时afterAdd/beforeRemove会收到不同的element被呼叫三次,原因是除了<tr>之外,<tbody>到<tr>之间的空白、</tr>到</tbody>间的空白也各算一个Element,(FF和chrome是忽略这个空格的)其 nodeType 为3即TEXT_NODE, 代表TEXT_NODE。 因此三次传入的element分别为TEXT_NODE、ELEMENT_NODE、TEXT_NODE,而第二次传入的ELEMENT_NODE是<tr>...</tr>间的内容,才是我们需要处理的对象,故加入if (elems.nodeType == 1)的判断。

要注意,一旦调用了了beforeRemove,konckout.js就不再自动帮你移除该笔数据在网页对应的元素,必须自行处理,但这也提供开发人员绝对的控制权,可自由安排HTML元素要怎么从网页上退出。

完整代码如下:

<!DOCTYPE html>
<html>
    
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>
            Index2
        </title>
        <style>
            table { width: 385px } td,th { border: 1px solid #0094ff; text-align:
            center; } .new { color: #0094ff; background-color:#b6ff00}
        </style>
        <script src="~/Scripts/jquery-2.0.3.js">
        </script>
        <script src="~/Scripts/knockout-2.3.0.js">
        </script>
        <script type="text/javascript">
            //定义user数据对象
            function UserViewModel(id, name, score) {
                var self = this;
                self.id = id;
                self.name = ko.observable(name);
                self.score = ko.observable(score);
            }
            //定义ViewModel
            function ViewModel() {
                var self = this;
                self.users = ko.observableArray(); //添加动态监视数组对象
                self.removeUser = function(user) {
                    self.users.remove(user);
                }
                self.totalscore = ko.computed(function() {
                    var total = 0;
                    $.each(self.users(),
                    function(i, u) {
                        total += u.score();
                    })
                    return total;
                });
                //添加对象后才触发,第一次forach并不会触发
                self.afterAddEvent = function(element, index, data) {
                    //通过nodeType过滤,只处理Element Node
                    if (element.nodeType == 1) {
                        $(".new").removeClass("new");
                        $(element).addClass("new");
                    }
                };
                //注意: beforeRemove事件后,要自已移除被刪除元素
                self.beforeRemoveEvent = function(element, index, data) {
                    if (element.nodeType == 1) {
                        $(element).css("background-color", "#ff6a00").animate({
                            opacity: 0.2
                        },
                        600,
                        function() {
                            $(this).remove();
                        })

                    }
                };
            };
            $(function() {
                var vm = new ViewModel();
                //预先添加一些数据
                vm.users.push(new UserViewModel("d1", "rohelm", 121));
                vm.users.push(new UserViewModel("d2", "halower", 125));
                $("#btnAddUser").click(function() {
                    vm.users.push(new UserViewModel(
                    $("#u_id").val(), $("#u_name").val(), parseInt($("#u_score").val())));
                });
                $("#btnUpdateScore").click(function() {
                    vm.users()[vm.users().length - 1].score(125).name("HelloWorld!");
                });
                ko.applyBindings(vm);
            });
        </script>
    </head>
    
    <body>
        <section style="margin:250px">
            <section>
                ID
                <input type="text" id="u_id" style="width:30px">
                Name
                <input type="text" id="u_name" style="width:30px">
                Score
                <input type="text" id="u_score" style="width:30px">
                <br/>
                <input value="Add" id="btnAddUser" type="button" style="width:200px; background-color:#ff6a00;"
                />
                <br/>
                共
                <span data-bind="text: users().length">
                </span>
                条--------------合计
                <span data-bind="text: totalscore">
                </span>
                分
            </section>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>
                                ID
                            </th>
                            <th>
                                Name
                            </th>
                            <th>
                                Score
                            </th>
                            <th>
                                Option
                            </th>
                        </tr>
                    </thead>
                    <tbody data-bind="foreach: { data: users, afterAdd: afterAddEvent, beforeRemove: beforeRemoveEvent}">
                        <tr>
                            <td>
                                <span data-bind="text: id">
                                </span>
                            </td>
                            <td>
                                <span data-bind="text: name">
                                </span>
                            </td>
                            <td>
                                <span data-bind="text: score">
                                </span>
                            </td>
                            <td>
                                <a href='#' data-bind="click: $root.removeUser">
                                    Remove
                                </a>
                            </td>
                        </tr>
                    </tbody>
                </table>
                <input value="Update测试" id="btnUpdateScore" type="button" style="width:200px; background-color:#ff6a00;"
                />
                <br/>
            </section>
        </section>
    </body>

</html>

运行效果如下: