• Knockout学习之创建自定义绑定


    创建自定义绑定
    在Knockout对MVVM的解释中,绑定是连接View和ViewModel的中介。
    他们(绑定)可以执行双向更新:
    ①绑定会监听ViewModel(可以理解为数据)的变化,并对应的更新View的DOM。
    ②绑定会捕获DOM的事件并相应的更新ViewModel的属性(数据)。

    Knockout 有一套灵活且全面的内置绑定属性(如text,click,foreach)。
    但是它还不仅仅如此--你只通过几行代码就可以创建自定义绑定(属性)

    OK,现在我们可以试试造两个自定义的绑定了。

    首先你将看到了一个没什么亮点但很功能齐全的调查页面。
    这是通过前两篇的知识完成的一个简单Demo。

    <!DOCTYPE HTML>
    <html>
    <head>
        <title>Custom Bindings</title>
        <script src="../JS/jquery-latest.min.js" type="text/javascript"></script>
        <script src="knockout-2.2.0.js" type="text/javascript"></script>
        <style type="text/css">
            body { font-family: Helvetica, Arial }
            input:not([type]), input[type=text], input[type=password], select { background-color: #FFFFCC; border: 1px solid gray; padding: 2px; }
            
            table { background-color: #cde; padding: 1em; border-radius: 0.5em; }
            table th { text-align:left; }
            table th:last-child { min-width: 130px; }
        </style>
    </head>
    <body>
        <h3 data-bind="text:question"></h3>
        <p>请分配 <b data-bind="text:pointsBudget"></b>点到一下选项.</p>
        <table>
            <thead>
                <tr><th>选项</th><th>重要度</th></tr>
            </thead>
            <tbody data-bind="foreach:answers">
                <tr>
                    <td data-bind="text:answerText"></td>
                    <td>
                        <select data-bind="options:[1,2,3,4,5],value:points"></select>
                    </td>
                </tr>
            </tbody>
        </table>
        
        <h3 data-bind="visible:pointsUsed() > pointsBudget">点数已用光!删除点吧。</h3>
        <p>您剩余的可用点数为:<b data-bind="text:pointsBudget - pointsUsed()"></b></p>
        <button data-bind="enable:pointsUsed() <= pointsBudget,click:save">提交</button>
    
        <script type="text/javascript">
            
            //Model
            function Answer(text) {
                this.answerText = text;
                this.points = ko.observable(1);
            }
    
            //ViewModel
            function SurveyViewModel(question, pointsBudget, answers) {
                this.question = question;
                this.pointsBudget = pointsBudget;
                this.answers = $.map(answers, function(text) {
                    return new Answer(text);
                });
                this.save = function() {
                    alert("To Do");
                };
                this.pointsUsed = ko.computed(function() {
                    var total = 0;
                    for (var i = 0; i < this.answers.length; i++) {
                        total += this.answers[i].points();
                    }
                    return total;
                }, this);
            }
    
            //Bind
            ko.applyBindings(new SurveyViewModel(
                "哪些因素会影响你的技术选择?"
                , 10
                , [
                    "Functionality,compatibility,pricing-all that boring stuff"
                    , "How often it is mentioned on Hacker News"
                    , "Number of gradients/dropshadows on project homepage"
                    ,"Totally believable testimonials on project homepage"
                ]
            ));
        </script>
    </body>
    </html>

    现在我们通过三种方式来提高这个页面的体验。

    • 给警告信息-"You've used too many points"添加动画过渡
    • 改进[Finished]按钮的样式
    • 用比较有趣的[星星等级]代替无聊的下拉菜

     ①添加动画过渡

    当访客分配点数超支时,警告-“点数已用光!删除点吧。”很不平滑的显示出来,因为它的显示依赖于[visible]属性绑定。
    如果你想让警告文字平滑的淡入淡出,可以写一个快捷的,可重用的自定义绑定属性,使用jQuery的fade方法实现动画效果。

    先通过给ko.bindingHandlers对象指定一个新属性来定义一个自定义绑定属性。该属性会暴露两个回调函数:
    init:当该绑定第一次发生时调用(设置初始状态或注册事件处理器是有必要的)
    update:只要相关联的数据发生变化就会被调用(然后就可以更新相应的DOM了)

    通过在ViewModel顶部添加以下代码定义一个[fadeVisible](平滑可见)绑定

    ko.bindingHandlers.fadeVisible = {
        update:function(ele,valueAccessor){
            var shouldDisplay = valueAccessor();
            shouldDisplay?$(ele).fadeIn():$(ele).fadeOut();
        }
    };

    如你所见,[update]处理器被赋予了两个参数--被绑定的元素,返回关联数据当前值的函数。
    基于那个当前值,我们可以用jQuery让元素淡入或淡出。

    用我们刚完成的自定义绑定就可以简单的修改那个警告文字,用fadeVisible代替visible。

    <h3 data-bind="fadeVisible:pointsUsed() > pointsBudget">点数已用光!删除点吧。</h3>

    现在如果运行的话淡入淡出的效果就应该完美实现了。真的么?好像初始化的时候它会淡入一次,然后才淡出。

    设置元素初始状态
    出现刚才的超出预期的现象,原因就是我们没有给出绑定的初始状态。
    所以,我们需要用一个[init]处理器确保元素初期状态匹配ViewModel数据的初期值

    ko.bindingHandlers.fadeVisible = {
        init: function(ele, valueAccessor) {
            //Start visible/invisible according to initial value
            var shouldDisplay = valueAccessor();
            $(ele).toggle(shouldDisplay);
        },
    
        update: function(ele, valueAccessor) {
            //更新的时候,淡入/淡出
            var shouldDisplay = valueAccessor();
            shouldDisplay ? $(ele).fadeIn() : $(ele).fadeOut();
        }
    };

    小功告成!虽说这个自定义的绑定貌似太小了点,但它是完全可复用的,可以把所有自定义的绑定放到一个单独的文件中,这样以后就方便使用了。


    ②集成第三方组件

    刚才我们自定义的绑定是关于动画效果的属性。如果还想让View中包含一些第三方UI组件(jQueryUI/YUI/LigerUI...),并且绑定到ViewModel属性上,
    最简单的方法就是创建一个自定义绑定,介于你的ViewModel和第三方组件之间。

    我们用jQuery UI的"Button"部件来提高"Finish"按钮的用户体验。

    首先定义一个绑定jqButton--将以下代码添加到ViewModel顶部。

    ko.bindingHandlers.jqButton = {
        init: function(ele) {
            $(ele).button();
        },
        update: function(ele, valueAccessor) {
            var currentValue = valueAccessor();
            //jQuery UI中设置属性的一种方式。
            $(ele).button("option","disabled",currentValue.enable === false);
        }
    };

    修改页面上的"Finish"按钮

    <button data-bind="jqButton:true,enable:pointsUsed() <= pointsBudget,click:save">提交</button>

    这样,基于第三方UI的可复用的自定义绑定就完成了。(别忘了添加对jQuery UI js和CSS的引用)


    ③实现自定义部件(点亮星星)

    现在我们来让页面变得更有趣一点。(整天对着下拉菜单是件很蛋疼的事)
    用星级系统来代替select。
    其实这种东西在网上一抓一大把(example),但为了学习,我们从头来过。

    首先要定义starRating绑定,为此要添加以下代码到ViewModel顶部:

    ko.bindingHandlers.starRating = {
        //初期化时,改变DOM内容及属性从而渲染元素
        init: function(ele, valueAccessor) {
            $(ele).addClass("starRating");
            for (var i = 0; i < 5; i++) {
                $("<span>").appendTo(ele);
            }
        },
        //根据当前数据指定适当的CSS
        update: function(ele, valueAccessor) {
            var observable = valueAccessor();
            $("span", ele).each(function(index) {
                //$.toggleClass(className,[switch])
                $(this).toggleClass("chosen",index < observable());
            });
        }
    };

    这段代码插入了一堆span元素。为了将他们渲染成星星,我们也必须准备一段CSS(在完整的代码中会看到)。
    再修改一下绑定的属性,这样我们就可以代替select了。

    <tbody data-bind="foreach:answers">
        <tr>
            <td data-bind="text:answerText"></td>
            <td data-bind="starRating:points"></td>
        </tr>
    </tbody>

    当mouse hover时,让图片高亮
    当用户将光标移动到星星上时,将他们要选中的星星高亮是个不错的idea。
    而此种高亮状态没必要保存到ViewModel(我们只保存用户选择了的数据),
    最简单的方式就是通过jQuery来完成这个效果。

    ....some code
    init: function(ele, valueAccessor) {
        var observable = valueAccessor();
        $("span", ele).each(function(index) {
            $(this).hover(
                function() {
                    $(this).prevAll().add(this).addClass("hoverChosen");
                },
                function() {
                    $(this).prevAll().add(this).removeClass("hoverChosen");
                }
            );
        });
    }

    现在鼠标移到哪高亮就到哪!

    保存数据到ViewModel
    当用户点击某个星星时,我们应该在ViewModel中保存这一选择状态,以便能动态更新UI。
    这也不难:用jQuery的点击方法来捕获用户的点击星星这一事件。

    ....some code
    init: function(ele, valueAccessor) {
        var observable = valueAccessor();
        $("span", ele).each(function(index) {
            $(this).hover(
                function() {
                    $(this).prevAll().add(this).addClass("hoverChosen");
                },
                function() {
                    $(this).prevAll().add(this).removeClass("hoverChosen");
                }
            ).click(function() {
                var observable = valueAccessor();
                observable(index + 1);
            });
        });
    }

    收工!贴上完整代码

    <!DOCTYPE HTML>
    <html>
    <head>
        <title>Custom Bindings</title>
        <script src="../JS/jquery-latest.min.js" type="text/javascript"></script>
        <script src="knockout-2.2.0.js" type="text/javascript"></script>
        <script src="jquery-ui.min.js" type="text/javascript"></script>
        <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/themes/start/jquery-ui.css" />
        <style type="text/css">
            body { font-family: Helvetica, Arial }
            input:not([type]), input[type=text], input[type=password], select { background-color: #FFFFCC; border: 1px solid gray; padding: 2px; }
            
            table { background-color: #cde; padding: 1em; border-radius: 0.5em; }
            table th { text-align:left; }
            table th:last-child { min-width: 130px; }
    
            .starRating span { width:24px; height:24px; background-image: url(IMG/stars.png); display:inline-block; cursor: pointer; background-position: -24px 0; }
            .starRating span.chosen { background-position: 0 0; }
            .starRating:hover span { background-position: -24px 0; }
            .starRating:hover span.hoverChosen { background-position: 0 0;}
        </style>
    </head>
    <body>
        <h3 data-bind="text:question"></h3>
        <p>请分配 <b data-bind="text:pointsBudget"></b>点到一下选项.</p>
        <table>
            <thead>
                <tr><th>选项</th><th>重要度</th></tr>
            </thead>
            <tbody data-bind="foreach:answers">
                <tr>
                    <td data-bind="text:answerText"></td>
                    <td data-bind="starRating:points"></td>
                </tr>
            </tbody>
        </table>
        
        <h3 data-bind="fadeVisible:pointsUsed() > pointsBudget">点数已用光!删除点吧。</h3>
        <p>您剩余的可用点数为:<b data-bind="text:pointsBudget - pointsUsed()"></b></p>
        <button data-bind="jqButton:{enable:pointsUsed() <= pointsBudget },click:save">提交</button>
    
        <script type="text/javascript">
            
            //Model
            function Answer(text) {
                this.answerText = text;
                this.points = ko.observable(1);
            }
    
            //ViewModel
            function SurveyViewModel(question, pointsBudget, answers) {
                //第三方组件扩展
                ko.bindingHandlers.jqButton = {
                    init: function(ele) {
                        $(ele).button();
                    },
                    update: function(ele, valueAccessor) {
                        var currentValue = valueAccessor();
                        
                        //jQuery UI中设置属性的一种方式。
                        $(ele).button("option","disabled",currentValue.enable === false);
                    }
                };
                
                //自定义绑定
                ko.bindingHandlers.fadeVisible = {
                    init: function(ele, valueAccessor) {
                        //Start visible/invisible according to initial value
                        var shouldDisplay = valueAccessor();
                        $(ele).toggle(shouldDisplay);
                    },
    
                    update: function(ele, valueAccessor) {
                        //更新的时候,淡入/淡出
                        var shouldDisplay = valueAccessor();
                        shouldDisplay ? $(ele).fadeIn() : $(ele).fadeOut();
                    }
                };
    
                ko.bindingHandlers.starRating = {
                    //初期化时,改变DOM内容及属性从而渲染元素
                    init: function(ele, valueAccessor) {
                        var observable = valueAccessor();
                        
                        $(ele).addClass("starRating");
                        for (var i = 0; i < 5; i++) {
                            $("<span>").appendTo(ele);
                        }
    
                        $("span", ele).each(function(index) {
                            $(this).hover(
                                function() {
                                    $(this).prevAll().add(this).addClass("hoverChosen");
                                },
                                function() {
                                    $(this).prevAll().add(this).removeClass("hoverChosen");
                                }
                            ).click(function() {
                                observable(index + 1);
                            });
                        });
                    },
                    //根据当前数据指定适当的CSS
                    update: function(ele, valueAccessor) {
                        // Give the first x stars the "chosen" class, where x <= rating
                        var observable = valueAccessor();
                        $("span", ele).each(function(index) {
                            $(this).toggleClass("chosen", index < observable());
                        });
                    }
                };
                
                this.question = question;
                this.pointsBudget = pointsBudget;
                this.answers = $.map(answers, function(text) {
                    return new Answer(text);
                });
                this.save = function() {
                    alert("To Do");
                };
                this.pointsUsed = ko.computed(function() {
                    var total = 0;
                    for (var i = 0; i < this.answers.length; i++) {
                        total += this.answers[i].points();
                    }
                    return total;
                }, this);
            }
    
            //Bind
            ko.applyBindings(new SurveyViewModel(
                "哪些因素会影响你的技术选择?"
                , 10
                , [
                    "功能、兼容性之类的东西"
                    , "在黑客新闻上被提及的频率"
                    , "项目主页上的梯度数量"
                    ,"在项目主页上完全可信的客户评价"
                ]
            ));
        </script>
    </body>
    </html>

    效果图

    素材(星星图)


  • 相关阅读:
    题解 P2280 【[HNOI2003]激光炸弹】
    线段树求逆序对
    题解 P3378 【【模板】堆】
    动态规划-最大算式 蓝桥杯ALGO-116
    动态规划-树形动态规划-结点选择
    Trie树(字典树)-题解 P2580 【于是他错误的点名开始了】
    清北学堂-DAY2-数论专题-中国剩余定理(CRT)
    听课笔记--DP--Authentication Failed
    听课笔记--DP--最大子矩阵和
    多媒体基础
  • 原文地址:https://www.cnblogs.com/TiestoRay/p/2825345.html
Copyright © 2020-2023  润新知