迷你MVVM框架 avalonjs 學習教程1八、一步步作一個todoMVC

大凡出名的MVC,MVVM框架都有todo例子,咱們也搞一下看看avalon是否這麼便宜。javascript

咱們先從react的todo例子中扒一下HTML與CSS用用。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>

改爲下面這樣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下瀏覽。java

咱們添加一些avalon東西,改一下版權什麼的react

<!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的提交回調。git

<!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進去,所以實現不了監控功能,實現不了監控也沒法雙向綁定了。github

咱們再來看如何實現編輯刪除。編輯須要一個雙擊事件,而且一個時期內只能有一個todo處於編輯狀態。這裏我使用editingIndex屬性,它是保存正在編輯的元素的索引值,若是不想編輯,將它置爲NaN就好了。好比那個文本域綁定一個失去焦點事件,那個回調就是這樣幹。移除元素,直接使用ms-repeat綁定臨時生成的$remove方法。chrome

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實現。api

 // 下面幾行在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是最好的。

相關文章
相關標籤/搜索