zoukankan      html  css  js  c++  java
  • 迷你MVVM框架 avalonjs 学习教程18、一步步做一个todoMVC

    大凡出名的MVC,MVVM框架都有todo例子,我们也搞一下看看avalon是否这么便宜。

    我们先从react的todo例子中扒一下HTML与CSS用用。

    <!doctype html>
    <html lang="en" data-framework="react">
      <head>
        <meta charset="utf-8">
        <title>React • TodoMVC</title>
        <link rel="stylesheet" href="bower_components/todomvc-common/base.css">
      </head>
      <body>
        <section id="todoapp"></section>
        <footer id="info">
          <p>Double-click to edit a todo</p>
          <p>Created by <a href="http://github.com/petehunt/">petehunt</a></p>
          <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
        </footer>
    
        <script src="bower_components/todomvc-common/base.js"></script>
        <script src="bower_components/react/react-with-addons.js"></script>
        <script src="bower_components/react/JSXTransformer.js"></script>
        <script src="bower_components/director/build/director.js"></script>
    
        <script src="js/utils.js"></script>
        <script src="js/todoModel.js"></script>
        <!-- jsx is an optional syntactic sugar that transforms methods in React's
        `render` into an HTML-looking format. Since the two models above are
        unrelated to React, we didn't need those transforms. -->
        <script type="text/jsx" src="js/todoItem.jsx"></script>
        <script type="text/jsx" src="js/footer.jsx"></script>
        <script type="text/jsx" src="js/app.jsx"></script>
      </body>
    </html>
    

    改成下面这样

    <!doctype html>
    <html lang="cn">
        <head>
            <meta charset="utf-8">
            <title>avalon • TodoMVC</title>
            <link rel="stylesheet" href="http://todomvc.com/architecture-examples/react/bower_components/todomvc-common/base.css">
        </head>
        <body>
            <section id="todoapp"></section>
            <footer id="info">
                <p>Double-click to edit a todo</p>
                <p>Created by <a href="https://github.com/RubyLouvre/avalon">司徒正美</a></p>
                <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
            </footer>
        </body>
    </html>
    

    由于用了许多新标签与CSS3,因此肯定在旧式IE下一塌糊涂,大家需要在chrome下浏览。

    我们添加一些avalon东西,改一下版权什么的

    <!doctype html>
    <html lang="cn">
        <head>
            <meta charset="utf-8">
            <title>avalon • TodoMVC</title>
            <link rel="stylesheet" href="http://todomvc.com/architecture-examples/react/bower_components/todomvc-common/base.css">
            <script src="avalon.js"></script>
            <style>
                .ms-controller{
                    visibility: hidden;
                }
            </style>
            <script>
    
                var model = avalon.define({
                    $id: "todo"
                })
    
    
            </script>
        </head>
        <body ms-controller="todo" class="ms-controller">
            <section id="todoapp">
                <header id="header">
                    <h1>todos</h1>
                    <form id="todo-form"  autocomplete="off">
                        <input id="new-todo"  placeholder="What needs to be done?"  autofocus>
                    </form>
                </header>
    
            </section>
            <footer id="info">
                <p>Double-click to edit a todo</p>
                <p>Created by <a href="https://github.com/RubyLouvre/avalon">司徒正美</a></p>
                <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
            </footer>
        </body>
    </html>
    

    好了,有模有样了。我们添加一些实际功能了。todoMVC主要是演示往一个数组里添加东西,然后可以编辑删除它。那么我们就先得有一个变量来保存要添加的东西,及一个可以往里面添加的东西的数组。我把它们命名为newTodo与todos。添加是一个用户行为,因此我们需要一个addTodo的提交回调。

    <!doctype html>
    <html lang="cn">
        <head>
            <meta charset="utf-8">
            <title>avalon • TodoMVC</title>
            <link rel="stylesheet" href="http://todomvc.com/architecture-examples/react/bower_components/todomvc-common/base.css">
            <script src="avalon.js"></script>
            <style>
                .ms-controller{
                    visibility: hidden;
                }
            </style>
            <script>
    
                var model = avalon.define({
                    $id: "todo",
                    newTodo: "",
                    todos: [],
                    addTodo: function(e) {
                        e.preventDefault()//阻止页面刷新
                        var newTodo = model.newTodo.trim()
                        if (!newTodo.length) {
                            return
                        }
                        model.todos.push({
                            title: newTodo,
                            completed: false
                        });
                        model.newTodo = ""//清空内容
                    }
    
                })
    
            </script>
        </head>
        <body ms-controller="todo" class="ms-controller">
            <section id="todoapp">
                <header id="header">
                    <h1>todos</h1>
                    <form id="todo-form" ms-submit="addTodo" autocomplete="off">
                        <input id="new-todo" ms-duplex="newTodo" placeholder="What needs to be done?"  autofocus>
                    </form>
                </header>
                <section id="main" ms-visible="todos.size()" >
                    <input id="toggle-all" type="checkbox" >
                    <label for="toggle-all">Mark all as complete</label>
                    <ul id="todo-list">
                        <li ms-repeat-todo="todos" ms-class="completed: todo.completed" >
                            <div class="view">
                                <input class="toggle" type="checkbox" >
                                <label >{{todo.title}}</label>
                                <button class="destroy" ></button>
                            </div>
                            <form>
                                <input class="edit" >
                            </form>
                        </li>
                    </ul>
                </section>
    
            </section>
            <footer id="info">
                <p>Double-click to edit a todo</p>
                <p>Created by <a href="https://github.com/RubyLouvre/avalon">司徒正美</a></p>
                <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
            </footer>
        </body>
    </html>
    

    我们看到ms-visible是对应todos.size()而不是todos.length,那是因为数组的原生属性length无法hack进去,因此实现不了监控功能,实现不了监控也无法双向绑定了。

    我们再来看如何实现编辑删除。编辑需要一个双击事件,并且一个时期内只能有一个todo处于编辑状态。这里我使用editingIndex属性,它是保存正在编辑的元素的索引值,如果不想编辑,将它置为NaN就行了。比如那个文本域绑定一个失去焦点事件,那个回调就是这样干。移除元素,直接使用ms-repeat绑定临时生成的$remove方法。

    editingIndex: NaN,
    editTodo: function($index) {
        model.editingIndex = $index
        //为了用户体验,有时不得不写一些DOM处理
        var el = this.parentNode.parentNode
        setTimeout(function() {//让光标定位于文本之后
            var input = el.querySelector("input.edit")
            input.focus()
            input.value = input.value
        })
    },
    doneEditing: function() {//还原
        model.editingIndex = NaN
    },
    


    <ul id="todo-list">
        <li ms-repeat-todo="todos" ms-class="completed: todo.completed" ms-class-1="editing: $index === editingIndex">
            <div class="view">
                <input class="toggle" type="checkbox" >
                <label ms-dblclick="editTodo($index)">{{todo.title}}</label>
                <button class="destroy" ms-click="$remove"></button>
            </div>
            <form>
                <input class="edit" ms-duplex="todo.title"  ms-blur="doneEditing" >
            </form>
        </li>
    </ul>
    

    接着来做全选非选功能,我们需要一个变量来保存全选状态,然后监听它来同步下面UI列表的checkbox的勾选情况,这个使用$watch回调实现就行了。但UI列表的每个元素的complete是否打勾也会影响到上方的全选checkbox,这时不可能为每个元素添加$watch回调,我们改用data-duplex-changed回调checkOne实现。

     // 下面几行在define函数里
      allChecked: false,
      checkOne: function() {//点击UI列表的checkbox时
          model.$unwatch() //阻止下面allChecked的$watch回调触发
          model.allChecked = model.todos.every(function(val) {
            return  val.completed
          })
           model.$watch()
    
      },
     //这是在define函数外
     model.$watch("allChecked", function(completed) {//点击上方checkbox时
          model.todos.forEach(function(todo) {
               todo.completed = completed
         })
     })
    


     <section id="main" ms-visible="todos.size()" >
            <input id="toggle-all" type="checkbox" ms-duplex-radio="allChecked">
            <label for="toggle-all">Mark all as complete</label>
            <ul id="todo-list">
                <li ms-repeat-todo="todos" ms-class="completed: todo.completed" ms-class-1="editing: $index === editingIndex">
                    <div class="view">
                        <input class="toggle" type="checkbox" ms-duplex-radio="todo.completed" data-duplex-changed="checkOne">
                        <label ms-dblclick="editTodo($index)">{{todo.title}}</label>
                        <button class="destroy" ms-click="$remove"></button>
                    </div>
                    <form>
                        <input class="edit" ms-duplex="todo.title"  ms-blur="doneEditing" >
                    </form>
                </li>
            </ul>
        </section>
    

    接着我们做页脚部分,这是有几个按钮,一个用来删除所有选中的todo,几个是用链接摸拟的,用于切换状态,还有一段描述文本。它们涉及到一个数组,用于装载三个状态值,一个表示当前状态的变量,还有两个数值remainingCount(当前有多少个todo没有被选中),completedCount(当前有多少个todo被选中),还有一个事件回调,用于移除removeCompleted。

    难点有两处。第一处是remainingCount与completedCount的计算,没有什么智能的计算方法,需要我们自己写一个方法,当数组的长度发生变化,用户点击了各种checkbox时触发。

    第二个是状态值的表示,todoMVC要求它们首字母大写,这里我引入一个自定义过滤器capitalize实现它。

    <!doctype html>
    <html lang="cn">
        <head>
            <meta charset="utf-8">
            <title>avalon • TodoMVC</title>
            <link rel="stylesheet" href="http://todomvc.com/architecture-examples/react/bower_components/todomvc-common/base.css">
            <script src="avalon.js"></script>
            <style>
                .ms-controller{
                    visibility: hidden;
                }
            </style>
            <script>
                avalon.filters.capitalize = function(a) {
                    return a.charAt(0).toUpperCase()  + a.slice(1)
                }
                var model = avalon.define({
                    $id: "todo",
                    newTodo: "",
                    todos: [],
                    addTodo: function(e) {
                        e.preventDefault()//阻止页面刷新
                        var newTodo = model.newTodo.trim()
                        if (!newTodo.length) {
                            return
                        }
                        model.todos.push({
                            title: newTodo,
                            completed: false
                        });
                        model.newTodo = ""//清空内容
                    },
                    editingIndex: NaN,
                    editTodo: function($index) {
                        model.editingIndex = $index
                        //为了用户体验,有时不得不写一些DOM处理
                        var el = this.parentNode.parentNode
                        setTimeout(function() {//让光标定位于文本之后
                            var input = el.querySelector("input.edit")
                            input.focus()
                            input.value = input.value
                        })
                    },
                    doneEditing: function() {//还原
                        model.editingIndex = NaN
                    },
                    allChecked: false,
                    checkOne: function() {//点击UI列表的checkbox时
                         model.$unwatch()
                        model.allChecked = model.todos.every(function(val) {
                            return  val.completed
                        })
                         model.$watch()
                        updateCount()
                    },
                    state: "all",
                    status: ["all", "active", "completed"],
                    remainingCount: 0,
                    completedCount: 0,
                    removeCompleted: function() {
                        model.todos.removeAll(function(el) {
                            return el.completed
                        })
                    }
                })
    
                function updateCount() {
                    model.remainingCount = model.todos.filter(function(el) {
                        return el.completed === false
                    }).length
                    model.completedCount = model.todos.length - model.remainingCount;
                }
    
                model.$watch("allChecked", function(completed) {//点击上方checkbox时
                    model.todos.forEach(function(todo) {
                        todo.completed = completed
                    })
                    updateCount()
                })
    
                model.todos.$watch("length", updateCount)
    
            </script>
        </head>
        <body ms-controller="todo" class="ms-controller">
            <section id="todoapp">
                <header id="header">
                    <h1>todos</h1>
                    <form id="todo-form" ms-submit="addTodo" autocomplete="off">
                        <input id="new-todo" ms-duplex="newTodo" placeholder="What needs to be done?"  autofocus>
                    </form>
                </header>
                <section id="main" ms-visible="todos.size()" >
                    <input id="toggle-all" type="checkbox" ms-duplex-radio="allChecked">
                    <label for="toggle-all">Mark all as complete</label>
                    <ul id="todo-list">
                        <li ms-repeat-todo="todos" ms-class="completed: todo.completed" ms-class-1="editing: $index === editingIndex">
                            <div class="view">
                                <input class="toggle" type="checkbox" ms-duplex-radio="todo.completed" data-duplex-changed="checkOne">
                                <label ms-dblclick="editTodo($index)">{{todo.title}}</label>
                                <button class="destroy" ms-click="$remove"></button>
                            </div>
                            <form action="javascript:void(0)" ms-submit="doneEditing">
                                <input class="edit" ms-duplex="todo.title"  ms-blur="doneEditing" >
                            </form>
                        </li>
                    </ul>
                </section>
                <footer id="footer" ms-visible="todos.size()">
                    <span id="todo-count">
                        <strong >{{remainingCount}}</strong>
                        item{{remainingCount>1 ? "s" : ""}} left
                    </span>
                    <ul id="filters">
                        <li ms-repeat="status">
                            <a ms-class="selected: state == el" href="#/{{el}}" >{{ el | capitalize }}</a>
                        </li>
                    </ul>
                    <button id="clear-completed" ms-visible="completedCount" ms-click="removeCompleted">
                        Clear completed ({{completedCount}})
                    </button>
                </footer>
            </section>
            <footer id="info">
                <p>Double-click to edit a todo</p>
                <p>Created by <a href="https://github.com/RubyLouvre/avalon">司徒正美</a></p>
                <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
            </footer>
        </body>
    </html>
    

    这就完了,当然有人可能问如何切换状态,在todoMVC里是使用路由系统实现,当我完善了自带的路由系统时再补上吧。总结一下,用avalon来实现todoMVC,所有JS代码与HTML行数是最少的。JS代码包括avalon库与用户写的代码。像angular,backbone, polymer虽然吹得这么响,它们在实现todoMVC时有许多部分是非常不直观的,冗长的,唯有avalon是最好的。

  • 相关阅读:
    mybatis公用代码抽取到单独的mapper.xml文件
    mysql与oracle常用函数及数据类型对比00持续补充
    人民币在岸 离岸 中间价的含义与关系
    mysql hang and srv_error_monitor_thread using 100% cpu(已解决)
    long和BigDecimal引发的管理思考
    mybatis 3的TypeHandler深入解析(及null值的处理)
    mysql 5.7.17发布
    rabbitmq connection/channel/consumer/queue的数量关系详细分析
    rabbitMQ publish丢包分析
    INFO: task java:27465 blocked for more than 120 seconds不一定是cache太大的问题
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/4025797.html
Copyright © 2011-2022 走看看