迷你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 ...
随机推荐
- [转]csharp:Microsoft.Ink 手写识别(HandWriting Recognition)
原贴:http://www.cnblogs.com/geovindu/p/3702427.html 下載: //Microsoft Windows XP Tablet PC Edition 2005 ...
- pycharm中配置pep8
Pycharm本身是有pep8风格检测的,当你敲得代码中不符合规范时,会有下划波浪线提示.如何让代码修改为符合规范,去掉这些难看的波浪线呢? 1.安装autopep8 pip install aut ...
- java实验——将100-300之间的素数输出
public class 实验1 { //将100-300之间的素数输出 /** * @param args */ public static void main(String[] args) { / ...
- 动画js版本
动画: 1)css样式提供运动 2)js提供的运动 过渡的属性:transition 从一种情况到另一种情况叫过渡 transition:变化的属性 (attr) transition:花费的时间 ...
- 1006 Sign In and Sign Out (25 分)
1006 Sign In and Sign Out (25 分) At the beginning of every day, the first person who signs in the co ...
- Python 编程规范梳理
缘由 由于项目团队中新加入了几名攻城狮, 大家之前的背景各不相同,写出的代码也是“风格迥异”.正所谓:“无规则不成方圆”,因此需要对编程进行必要的规范. 整体的思路是:依照PEP8 Python 编码 ...
- Jade简单教程
Express框架里内嵌了Jade模板引擎.正好项目里也要用到,本篇整理了下Jade的相关用法. 安装与执行 标签和属性 多行文本 变量 语句 Mixin 模板 注释 过滤器 安装与执行 安装很简单: ...
- ASP.NET基于Redis的Provider库
因为session基于本地cache,以前我们自己写分布式缓存,或者数据库存储,或者cookie加密存储,来保存用户状态信息,但较少的直接通过创建一个继承 SessionStateStoreProvi ...
- sqoop操作之ORACLE导入到HIVE
导入表的所有字段 sqoop import --connect jdbc:oracle:thin:@192.168.1.107:1521:ORCL \ --username SCOTT --passw ...
- Executor框架(二)Executor 与 ExecutorService两个基本接口
一.Executor 接口简介 Executor接口是Executor框架的一个最基本的接口,Executor框架的大部分类都直接或间接地实现了此接口. 只有一个方法 void execute(Run ...