迷你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是最好的。
迷你MVVM框架 avalonjs 学习教程18、一步步做一个todoMVC的更多相关文章
- 迷你MVVM框架 avalonjs 学习教程19、avalon历史回顾
avalon最早发布于2012.09.15,当时还只是mass Framework的一个模块,当时为了解决视图与JS代码的分耦,参考knockout开发出来. 它的依赖收集机制,视图扫描,绑定的命名d ...
- 迷你MVVM框架 avalonjs 学习教程11、循环操作
avalon是通过ms-repeat实现对一组数据的批量输出.这一组数据可以是一个数组,也可以是一个哈希(或叫对象).我们先从数组说起吧. 第二节就说,凡是定义在VM中的数组,如果没有以$开头或者没放 ...
- 迷你MVVM框架 avalonjs 学习教程4、数据填充
MVVM是前端的究极解决方案,你们可能用过jQuery,但那个写的代码不易维护:你们可以听过说requirejs与seajs,传说中的模块开发,加载器,但它们的最终目标是打包:你们可能听过unders ...
- 迷你MVVM框架 avalonjs 学习教程3、绑定属性与扫描机制
在MVVM框架中,你都会看到页面定了许多奇怪的属性,比如knockout的data-☆,angular的ng-☆,avalon的ms-☆,此外还有一些只写文本节点上的双花括号,它们统称为指令.ms-☆ ...
- 迷你MVVM框架 avalonjs 学习教程1、引入avalon
avalon是国内最强大的MVVM框架,没有之一,虽然淘宝KISSY团队也搞了两个MVVM框架,但都无疾而终.其他的MVVM框架都没几个.也只有外国人与像我这样闲的架构师才有时间钻研这东西.我很早之前 ...
- 迷你MVVM框架 avalonjs 学习教程20、路由系统
SPA的成功离开不这三个东西,分层架构,路由系统,储存系统.分层架构是我们组织复杂代码的关键,这里特指MVVM的avalon:路由系统是将多个页面压缩在一个页面的关键:储存系统特指本地储存,是安全保存 ...
- 迷你MVVM框架 avalonjs 学习教程16、过滤器
avalon的过滤器是参考自angular与rivets.它也被称做管道文本过滤器,它的处理对象只能是文本(字符串),它只能用在文本绑定中,并且只能是双花括号形式.下面是各大家的过滤器比较: rive ...
- 迷你MVVM框架 avalonjs 学习教程2、模块化、ViewModel、作用域
一个项目是由许多人分工写的,因此必须要合理地拆散,于是有了模块化.体现在工作上,PM通常它这为某某版块,某某频道,某某页面.某一个模块,必须是包含其固有的数据,样式,HTML与处理逻辑.在jQuery ...
- 迷你MVVM框架 avalonjs 学习教程22、avalon性能大揭密
avalon之所以能在页面处理1W个绑定(angular对应的数字是2000),出于两个重要设计--基于事件驱动的双向绑定链及智能CG回收机制. avalon的双向绑定链是通过Object.defin ...
随机推荐
- BaseAction编写:免去一些重复的代码,比如继承ActionSupport和实现ModelDriven接口
1.BaseAction package com.learning.crm.base; import java.lang.reflect.ParameterizedType; import java. ...
- PHP $_SERVER 祥细解读(有事例)
为了看的更明白,添加上了事例 例如 'www.ceshiyuming.com/ceshi.php?p=123';Array( [HOSTNAME] => [PATH] => ...
- vue 感觉很好的渲染模式
<ul v-if="todos.length"> <li v-for="todo in todos"> {{ todo }} </ ...
- BASIC-18_蓝桥杯_矩形面积交
解题思路: 1.先将可能的情况列出,根据分类确定计算的方式; 示例代码: #include <stdio.h>#define N 8 int main(void){ int i = 0 , ...
- Python网络爬虫-xpath模块
一.正解解析 单字符: . : 除换行以外所有字符 [] :[aoe] [a-w] 匹配集合中任意一个字符 \d :数字 [0-9] \D : 非数字 \w :数字.字母.下划线.中文 \W : 非\ ...
- bzoj 4842: [Neerc2016]Delight for a Cat
Description ls是一个特别堕落的小朋友,对于n个连续的小时,他将要么睡觉要么打隔膜,一个小时内他不能既睡觉也打隔膜 ,因此一个小时内他只能选择睡觉或者打隔膜,当然他也必须选择睡觉或打隔膜, ...
- innodb的锁、update单条记录的花费时间压测
观察innodb的锁时间,需要关注: mysqladmin extended-status -r -i 1 -uroot | grep "Innodb_row_lock_time" ...
- mongo获取lbs数据
进入mongo目录执行./mongo 命令 #切换数据库use coachloc db.runCommand({geoNear : "coachloc" ,near : [113. ...
- centos7 设置系统时间与网络同步
1.安装ntpdate工具 yum -y install ntp ntpdate 2.设置系统时间与网络时间同步 ntpdate cn.pool.ntp.org 3.将系统时间写入硬件时间 hwclo ...
- Windows下MySQL免安装版的安装、卸载
一.安装 1.下载 到MySQL官网http://dev.mysql.com/downloads/mysql/ 下载mysql-5.6.15-win32.zip. 2.拷贝 将mysql-5.6.15 ...