迷你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 ...
随机推荐
- 大快DKhadoop安装教程与常见问题汇总
上周分别就DKHadoop的安装准备工作以及服务器操作系统配置写了两篇分享的文章,这是个人第一次尝试写一个系统性的分享文章,必然会有很多疏漏的地方,还望见谅吧.今天分享的是DKHadoop安装以及常见 ...
- [boost] : lightweight_test库
lightweight_test轻量级单元测试框架, 只支持最基本的单元测试, 不支持测试用例, 测试套件的概念, 简单小巧, 适合要求不高或者快速测试的工作. 基本用法 需要包含头文件#includ ...
- 【shell】sed命令
sed是一个很好的文件处理工具,本身是一个管道命令,主要是以行为单位进行处理,可以将数据行进行替换.删除.新增.选取等特定工作,下面先了解一下sed的用法sed命令行格式为: sed ...
- keepalived配置主从备份
keepalived配置主从备份 keepalived是一个用于做双机热备(HA)的软件,常和haproxy联合起来做热备+负载均衡,达到高可用. 运行原理 keepalived通过选举(看服务器 ...
- VBox修改uuid
1.使用VBoxManage命令时,需要先在命令行中切换到VirtualBox的安装目录下 2.修改vdi的uuid:VBoxManage internalcommands sethduuid D: ...
- Django 实现CRM 问卷调查功能组件
目录结构: 母版 {% load staticfiles %} <!DOCTYPE html> <html lang="zh-CN"> <head&g ...
- 如何检测NFC芯片型号?NFC手机即可!
拿到了NFC标签之后,因为很多项目中的需求,用户需要对自已的NFC芯片进行选型,也就需要判断NFC芯片的类型?芯片是原装进口还是国产兼容的?芯片内存有多少?芯片存储内部结构如何......,而且用户还 ...
- android studio 更新 Gradle错误解决方法(Gradle sync failed)
android studio 更新 Gradle错误解决方法 Android Studio每次更新版本都会更新Gradle这个插件,但由于长城的问题每次更新都是失败,又是停止在Refreshing ...
- wxWidgets:简单消息处理
早期的wxWidgets使用类似MFC的方式进行消息处理:在新版中这种映射方式仍然得以保留. 在MyFrame.h中添加: private: void OnQuitButton(wxCommandEv ...
- 杂项-TOOL:NPIO
ylbtech-杂项-TOOL:NPIO NPOI是指构建在POI 3.x版本之上的一个程序,NPOI可以在没有安装Office的情况下对Word或Excel文档进行读写操作.NPOI是一个开源的Ja ...