探索Emberjs制作一个简单的Todo应用


Posted in Javascript onNovember 07, 2012

目标
使用Emberjs制作一个简单的Todo应用,实现这样一个效果:通过在文本框输入文本,创建一条代办事项,代办事项可以选择优先级,完成的事项可以删除。

准备
完成这个应用,需要做点准备:
1、创建一个html页面,暂时不管样式;
2、脚本:emberjs,handlebars、jQuery。这三个脚本可以从网上获得,我们将把他们加入到head标签里去。

制作
创建页面,加入脚本,就可以开始制作应用。html代码如下:

<!doctype html> 
<html> 
<head> 
<meta charset="utf-8"> 
<title>Ember--第一个应用</title> 
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> 
<script type="text/javascript" src="http://cloud.github.com/downloads/wycats/handlebars.js/handlebars-1.0.rc.1.js"></script> 
<script type="text/javascript" src="http://cloud.github.com/downloads/emberjs/ember.js/ember-1.0.0-pre.2.min.js"></script> 
</head> 
<body> 
</body> 
</html>

按照ember的要求,需要用Ember.Application.create()先创建应用实例,这也作为应用的命名空间。这个create方法可以传递一个对象属性ready,属性值是一个函数,在应用准备就绪时调用。Ember还可以使用缩写Em来代替。

在Ember中有一个Em.Logger对象,相当于window.console,可以用来调试。我们可以在这个ready加入一个消息,显示在控制台中。

现在,在head标签里再增加一个script标签来写应用的脚本,实例化一个ember应用,顺便把MVC各模块的区域也加上。脚本代码如下:

/******************** 
application 
********************/ window.App = Ember.Application.create( 
{ 
ready:function(){ 
Em.Logger.info('欢迎使用待办事项应用'); 
} 
} 
); 
/******************** 
model 
********************/ 

/******************** 
view 
********************/ 

/******************** 
controlle 
********************/

然后,我们需要一个输入框来输入代办事项,需要创建一个ember文本框视图。ember视图可以用Ember.View类来创建(使用create方法)或扩展(使用extend方法)一个新的视图类。不过对于文本框视图,ember提供了更直接的方式——Ember.TextField类,我们可以先使用这个类来扩展一个自定义的视图,然后再实例化添加到页面上。我们将这个文本框视图类命名AddItemView 。

在脚本代码里的view区域添加上文本框视图代码:

App.AddItemView = Ember.TextField.extend({ });

可以给它加个提示语,html5支持placeholder,可以拿来用。还需要在按下回车时将内容添加到代表事项列表,这里需要用到一个属性:insertNewline,在按下回车时会调用相应的函数。加入后的代码如下:

App.AddItemView = Ember.TextField.extend({ 
placeholder:'输入待办事项', 
insertNewline:function(){} 
});

由于现在还没确定具体添加方法,函数体暂时先不写。

用户在按下回车时增加一条代办事项,需要一个列表来显示,在ember中可以创建CollectionView来存放列表项目视图,对于CollectionView,默认会有一个content属性用于存放列表项目对象,其属性值是一个数组。为了让其列表显示为ul列表,需要定义CollectionView的标签名(tagName)为“ul”。我们给这个列表视图命名为ListView,并增加到文本框视图的下方。最后代码如下:

App.ListView = Ember.CollectionView.extend({ 
content:[], 
tagName:'ul' 
});

现在如果打开页面,是没显示任何内容的,因为视图还没被渲染,要将视图显示出来,需要handlebar模板的支持。

现在来修改html页面的body块,加入刚创建的两个视图,看看效果。

添加handlebar模板的方法是<script type="text/x-handlebars">/*视图助手*/</script>,还可以指定模板名称,在data-template-name属性里定义,待会我们添加列表项目时会需要用到。

在模板里需要通过视图助手(helper)来添加视图,语法也很简单,用两个花括号对包裹,里面通过模板关键字来指定要显示的视图,如:{{view App.AddItemView}}。其他模板助手可以在handlebar网站查到:http://handlebarsjs.com/。

现在先把文本框跟列表视图添加到页面上,修改后的body代码如下:

<body> 
<script type="text/x-handlebars"> 
<span>请输入待办事项:</span>{{view App.AddItemView}}<br/> {{view App.ListView}} 
</script> 
</body>

现在刷新页面,会显示一句“请输入代办事项”跟一个文本框,列表由于没有内容,不会显示。

这个时候我们可以在content里添加点内容,比如content:['a','b','c'],然后刷新页面,你会发现列表区域只有三个小黑点(如果你没重置列表样式的话)。因为你在content里添加了三项,但列表项还没有指定一个显示的模板,所以,显示为空。为了让你看到效果,我们来给列表项定义个显示的模板吧。这里需要处理两个地方,第一是在页面加指定名称的模板,第二是在列表视图里定义列表项目的属性。

定义列表项目,需要用到itemViewClass,它会将每个content项传递进去并用指定的模板显示。先来修改列表视图ListView 吧,给它增加itemViewClass属性,这也是一种视图,所以需要用Ember.View来创建,在创建时同时指定用来显示的模板名称为itemTemplate,这个名称同时将为出现在html的handlebar模板名称里。修改后的代码如下:

App.ListView = Ember.CollectionView.extend({ 
content:['a','b','c'], 
tagName:'ul', 
itemViewClass: Ember.View.extend({ 
templateName:'itemTemplate', 
}) 
});

还差一步就完成了,现在来修改html,我们需要在body里再新建一个handlebar模板,并且会用到上面给出的模板名称,代码如下:

<script type="text/x-handlebars" data-template-name="itemTemplate"> </script>

接着同样是添加模板助手,要把每一个content项传递给助手,会用到view.content。添加如下代码:

<script type="text/x-handlebars" data-template-name="itemTemplate"> 
{{view.content}} 
</script>

完成后,刷新页面,现在终于把content里的内容显示出来了,而且,模板会自动加上li标签。

继续完善我们的应用。我们总不能把content的内容写成固定的吧,这样用户还怎么添加呢。所以,现在考虑把用户要添加的项目保存到一个数组里,然后content自己去取这个数组的内容。同时,ember框架支持双向绑定,当数组内容修改时,通过绑定的content也会同时改变,反之亦然。现在,就创建一个ember数组,然后跟content绑定吧。

ember数组可以通过ArrayController类来创建,它会把你传进去的普通javascript转变为一个新的ember数组对象,我们把用来管理项目的数组命名为todoStore,放到html页面的controller区域,创建的代码如下:

App.todoStore = Ember.ArrayController.create({ 
content:[] 
});

现在可以把ListView 里的content数组放到这个todoStore 的数组里,然后绑定ListView 里的content到todoStore 上,这两个对象将修改为如下所示:

App.ListView = Ember.CollectionView.extend({ 
contentBinding:'App.todoStore', 
tagName:'ul', 
itemViewClass: Ember.View.extend({ 
templateName:'itemTemplate' 
}) 
}); /******************** 
controlle 
********************/ 
App.todoStore = Ember.ArrayController.create({ 
content:['a','b','c'] 
});

Binding是个后缀,表示绑定,属性值是绑定的对象,默认取该对象的content属性。修改完成后刷新页面,如果你看到的页面跟修改之前的一样,说明修改成功了。接着,是时候去掉content里的值了,我们需要的数据将由用户在文本框里输入。

考虑现在的交互过程,用户在文本框输入内容,按下回车,程序获取到该事件,调用一个方法创建一个新对象,再把这个新对象送给todoStore ,由于绑定作用,列表会自动增加一项。

是时候改造下文本框视图了,还记得insertNewline吗?我们可以在这里创建新的项目。我们会用到三个方法:set()设置属性值、get()获取属性值、pushObject()添加数据,修改AddItemView 后的代码如下:

App.AddItemView = Ember.TextField.extend({ 
placeholder:'输入待办事项', 
insertNewline:function(){ 
var item = this.get('value'); 
App.todoStore.pushObject(item); 
this.set('value',''); 
} 
});

现在刷新页面,然后输入内容,按回车,列表会添加输入的内容,说明修改成功,如果你没把todoStore 的content属性里的内容清空的话,现在会有4个列表项了。

距离我们的目标还有一半啊,我们还缺少两个功能:选择优先级跟删除完成的项目。

要增加下拉列表,可以使用另一个方便的视图:Ember.Select。我们可以直接在模板里直接创建一个,同样通过绑定,把下拉列表视图的content绑定到另一个ember对象上,然后设置默认选中的优先级。优先级也需要用到绑定,这样在页面上选择的时候,才会同时修改对应的ember对象里的内容。先来创建这个ember对象,自定义该对象的selected属性表示选中的值,其他名称也行,这段代码会加到todoStore对象的下面,命名为selectController,代码如下:

App.selectController = Ember.Object.create({ 
selected:'低', 
content:['高','中','低'] 
});

然后增加一个模板助手,并绑定selectController 里对应的属性,选中项的绑定需要用到selectionBinding,顺便给个文字提示,然后添加到文本框模板的下面,修改后的代码如下:

<script type="text/x-handlebars"> 
<span>请输入待办事项:</span>{{view App.AddItemView}}<br/> 
<span>请选择优先级:</span>{{view Ember.Select contentBinding="App.selectController.content" selectionBinding="App.selectController.selected"}} 
{{view App.ListView}} 
</script>

现在刷新页面就会出现下拉列表了。

要想让列表项也出现这个优先级,还得花点功夫啊。是时候用model了,我们来创建一个model类,当按下回车时,从这个类创建一个实例,再把实例扔到todoStore里就可以了,另外,模板也要跟着修改,文本框视图的创建方法也得改。这次改动比较多了点。另外,还会用到一个计算属性的功能,当依赖的属性变化时,自动更新。把这个model命名为TodoModel,放到model区域,创建代码如下:

/******************** 
model 
********************/ App.TodoModel = Em.Object.extend({ 
status:'', 
value:'', 
title:function(){ 
return '['+this.get('status')+']'+this.get('value'); 
}.property('status','value') 
});

status表示选择的优先级,value表示文本框里的值,title表示列表项目要显示的内容,这些属性名都是自定义的。其中title不需要提供,因为它设置为计算属性,依赖于status跟value属性,自动计算获取,property()方法就是ember函数转变为计算属性的方法,后面的参数表示title依赖的属性,当status或value变化时,就会自动给出。

接着修改AddItemView的insertNewline属性,需要取到两个数据,一个是文本框里的内容,一个是下拉列表里选中的项目。文本框的值已经知道怎么获取了,下拉列表的值呢?别忘了我们已经将选中项绑定到selectController里的selected属性了,直接从那里取就可以了。修改后的AddItemView代码如下:

App.AddItemView = Ember.TextField.extend({ 
placeholder:'输入待办事项', 
elementId:'add', 
insertNewline:function(){ 
var item = App.TodoModel.create({ 
status:App.selectController.get('selected'), 
value:this.get('value') 
}); 
App.todoStore.pushObject(item); 
this.set('value',''); 
} 
});

现在可以通过TodoModel类来实例化一个待办项目并添加到todoStore里了,最后是修改项目列表的模板itemTemplate来显示,在模板里需要取到当前项目的title值来显示,代码如下:

<script type="text/x-handlebars" data-template-name="itemTemplate"> 
{{view.content.title}} 
</script>

现在添加的新待办事项会显示优先级了。

好了,最后一个功能,删除。跟添加pushObject相反,删除用到的是removeObject。因为它是显示在每个列表项目里的,所以,需要修改itemViewClass跟itemTemplate模板,我们在itemViewClass里添加一个方法,当用户点击时调用,把该方法命名为removeItem,代码如下:

App.ListView = Ember.CollectionView.extend({ 
contentBinding:'App.todoStore', 
tagName:'ul', 
itemViewClass: Ember.View.extend({ 
templateName:'itemTemplate', 
removeItem:function(){this.getPath( 'contentView.content' ).removeObject(this.get( 'content' ));} 
}) 
});

最后是在itemTemplate模板里增加一个接受点击的链接,用到的是action助手,第一个参数是方法名,target属性用来指定对象,点击时调用指定对象下的方法。修改后的itemTemplate代码如下:

<script type="text/x-handlebars" data-template-name="itemTemplate"> 
{{view.content.title}} <a href="#" {{action removeItem target="this"}} >X</a> 
</script>

现在新增加的项目都会有个叉在右边,点击时就把当前项目删除。

最后还可以做点改进,当鼠标移动到项目上时才显示删除链接,完成这个功能,需要修改itemViewClass以及在模板里增加逻辑判断助手{{#if}}。你可以自己试着去做,也可以看看最后完整的代码。

full code 
<!doctype html> 
<html> 
<head> 
<meta charset="utf-8"> 
<title>Ember--第一个应用</title> 
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> 
<script type="text/javascript" src="http://cloud.github.com/downloads/wycats/handlebars.js/handlebars-1.0.rc.1.js"></script> 
<script type="text/javascript" src="http://cloud.github.com/downloads/emberjs/ember.js/ember-1.0.0-pre.2.min.js"></script> 
<script> /******************** 
application 
********************/ 
window.App = Ember.Application.create( 
{ 
ready:function(){ 
Em.Logger.info('欢迎使用待办事项应用'); 
} 
} 
); 
/******************** 
model 
********************/ 
App.TodoModel = Em.Object.extend({ 
status:'', 
value:'', 
title:function(){ 
return '['+this.get('status')+']'+this.get('value'); 
}.property('status','value') 
}); 
/******************** 
view 
********************/ 
App.AddItemView = Ember.TextField.extend({ 
placeholder:'输入待办事项', 
elementId:'add', 
insertNewline:function(){ 
var item = App.TodoModel.create({ 
status:App.selectController.get('selected'), 
value:this.get('value') 
}); 
App.todoStore.pushObject(item); 
this.set('value',''); 
} 
}); 
App.ListView = Ember.CollectionView.extend({ 
contentBinding:'App.todoStore', 
tagName:'ul', 
itemViewClass: Ember.View.extend({ 
templateName:'itemTemplate', 
removeItem:function(){this.getPath( 'contentView.content' ).removeObject(this.get( 'content' ));}, 
mouseEnter:function(){this.set('hover',true);}, 
mouseLeave:function(){this.set('hover',false);} 
}) 
}); 

/******************** 
controlle 
********************/ 
App.todoStore = Ember.ArrayController.create({ 
content:[] 
}); 
App.selectController = Ember.Object.create({ 
selected:'低', 
content:['高','中','低'] 
}); 
</script> 
</head> 
<body> 
<script type="text/x-handlebars"> 
<span>请输入待办事项:</span>{{view App.AddItemView}}<br/> 
<span>请选择优先级:</span>{{view Ember.Select contentBinding="App.selectController.content" 
selectionBinding="App.selectController.selected"}} 
{{view App.ListView}} 
</script> 
<script type="text/x-handlebars" data-template-name="itemTemplate"> 
{{view.content.title}} {{#if view.hover}}<a href="#" {{action removeItem target="this"}} >X</a>{{/if}} 
</script> 
</body> 
</html>
Javascript 相关文章推荐
javascript函数作用域学习示例(js作用域)
Jan 13 Javascript
jQuery中prependTo()方法用法实例
Jan 08 Javascript
Javascript基础教程之比较操作符
Jan 18 Javascript
表单验证插件Validation应用的实例讲解
Oct 10 Javascript
理解JavaScript中Promise的使用
Jan 18 Javascript
JQuery 传送中文乱码问题的简单解决办法
May 24 Javascript
IE8兼容Jquery.validate.js的问题
Dec 01 Javascript
浅谈jQuery before和insertBefore的区别
Dec 04 Javascript
JavaScript实现倒计时跳转页面功能【实用】
Dec 13 Javascript
基于代数方程库Algebra.js解二元一次方程功能示例
Jun 09 Javascript
JavaScript hasOwnProperty() 函数实例详解
Aug 04 Javascript
react 原生实现头像滚动播放的示例
Apr 21 Javascript
关于使用 jBox 对话框的提交不能弹出问题解决方法
Nov 07 #Javascript
seajs1.3.0源码解析之module依赖有序加载
Nov 07 #Javascript
Javascript引用指针使用介绍
Nov 07 #Javascript
JavaScript在多浏览器下for循环的使用方法
Nov 07 #Javascript
Javascript的数组与字典用法与遍历对象的属性技巧
Nov 07 #Javascript
JS正则中的RegExp对象对象
Nov 07 #Javascript
js模拟点击事件实现代码
Nov 06 #Javascript
You might like
深入file_get_contents函数抓取内容失败的原因分析
2013/06/25 PHP
将php数组输出html表格的方法
2014/02/24 PHP
PHP正则表达式之捕获组与非捕获组
2015/11/06 PHP
php设计模式之职责链模式实例分析【星际争霸游戏案例】
2020/03/27 PHP
jQuery 位置函数offset,innerWidth,innerHeight,outerWidth,outerHeight,scrollTop,scrollLeft
2010/03/23 Javascript
jQuery中live方法的重复绑定说明
2011/10/21 Javascript
jQuery 关于伪类选择符的使用说明
2013/04/24 Javascript
jQuery中after()方法用法实例
2014/12/25 Javascript
jq stop()和:is(:animated)的用法及区别(详解)
2017/02/12 Javascript
vue2.0嵌套路由实现豆瓣电影分页功能(附demo)
2017/03/13 Javascript
JS 中使用Promise 实现红绿灯实例代码(demo)
2017/10/20 Javascript
node.js支持多用户web终端实现及安全方案
2017/11/29 Javascript
vue 路由页面之间实现用手指进行滑动的方法
2018/02/23 Javascript
Angular利用内容投射向组件输入ngForOf模板的方法
2018/03/05 Javascript
AngularJS监听ng-repeat渲染完成的方法
2018/03/20 Javascript
jQuery中ajax请求后台返回json数据并渲染HTML的方法
2018/08/08 jQuery
详解三种方式解决vue中v-html元素中标签样式
2018/11/22 Javascript
Node.JS在命令行中检查Chrome浏览器是否安装并打开指定网址
2019/05/21 Javascript
python实现的阳历转阴历(农历)算法
2014/04/25 Python
Python脚本暴力破解栅栏密码
2015/10/19 Python
Django中利用filter与simple_tag为前端自定义函数的实现方法
2017/06/15 Python
python使用tkinter实现简单计算器
2018/01/30 Python
5分钟 Pipenv 上手指南
2018/12/20 Python
解决python文件双击运行秒退的问题
2019/06/24 Python
解决pytorch DataLoader num_workers出现的问题
2020/01/14 Python
浅谈Python中的字符串
2020/06/10 Python
python和C++共享内存传输图像的示例
2020/10/27 Python
HTML5 video 上传预览图片视频如何设置、预览视频某秒的海报帧
2018/08/28 HTML / CSS
高中生期末评语大全
2014/01/28 职场文书
《回乡偶书》教学反思
2014/04/12 职场文书
超市创业计划书
2014/04/24 职场文书
一帮一活动总结
2014/05/08 职场文书
学校督导评估方案
2014/06/10 职场文书
创业计划书之闲置物品置换中心
2019/12/25 职场文书
游戏《东方异文石:爱亚利亚黎明》正式版发布
2022/04/03 其他游戏
忘记Grafana不要紧2种Grafana重置admin密码方法详细步骤
2022/04/07 Servers