探索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中的delete介绍
Sep 02 Javascript
JS禁用浏览器退格键实现思路及代码
Oct 29 Javascript
基于socket.io和node.js搭建即时通信系统
Jul 30 Javascript
js 左右悬浮对联广告代码示例
Dec 12 Javascript
jQuery实现转动随机数抽奖效果的方法
May 21 Javascript
jQuery获取选中单选按钮radio的值
Dec 27 Javascript
node.js的事件机制
Feb 08 Javascript
Angular.JS利用ng-disabled属性和ng-model实现禁用button效果
Apr 05 Javascript
vue 通过下拉框组件学习vue中的父子通讯
Dec 19 Javascript
ES6 Promise对象概念及用法实例详解
Oct 15 Javascript
从0搭建vue-cli4脚手架
Jun 17 Javascript
JS前端轻量fabric.js系列之画布初始化
Aug 05 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
PHP函数nl2br()与自定义函数nl2p()换行用法分析
2016/04/02 PHP
PHP判断数组是否为空的常用方法(五种方法)
2017/02/08 PHP
PHP处理Ajax请求与Ajax跨域问题
2017/02/13 PHP
PHP实现的62进制转10进制,10进制转62进制函数示例
2019/06/06 PHP
Laravel 微信小程序后端搭建步骤详解
2019/11/26 PHP
My Desktop :) 桌面式代码
2008/12/29 Javascript
js实现的切换面板实例代码
2013/06/17 Javascript
简单的代码实现jquery定时器
2013/11/17 Javascript
js输出阴历、阳历、年份、月份、周示例代码
2014/01/29 Javascript
jquery显示隐藏input对象
2014/07/21 Javascript
JavaScript中的对象的extensible属性介绍
2014/12/30 Javascript
如何用angularjs制作一个完整的表格
2016/01/21 Javascript
jQuery检查元素存在性(推荐)
2016/09/17 Javascript
微信小程序入门教程
2016/11/18 Javascript
详解Vue路由开启keep-alive时的注意点
2017/06/20 Javascript
javascript 玩转Date对象(实例讲解)
2017/07/11 Javascript
微信小程序 五星评分的实现实例
2017/08/04 Javascript
关于axios不能使用Vue.use()浅析
2018/01/12 Javascript
解决使用Vue.js显示数据的时,页面闪现原始代码的问题
2018/02/11 Javascript
webpack之引入图片的实现及问题
2018/10/08 Javascript
vue-cli配置flexible过程详解
2019/07/04 Javascript
浅谈vue获得后台数据无法显示到table上面的坑
2020/08/13 Javascript
[01:05:56]Liquid vs VP Supermajor决赛 BO 第二场 6.10
2018/07/04 DOTA
跟老齐学Python之编写类之四再论继承
2014/10/11 Python
python过滤字符串中不属于指定集合中字符的类实例
2015/06/30 Python
Java Web开发过程中登陆模块的验证码的实现方式总结
2016/05/25 Python
浅谈Python中函数的参数传递
2016/06/21 Python
python获取Pandas列名的几种方法
2019/08/07 Python
香港唯港荟酒店预订:Hotel ICON
2018/03/27 全球购物
DogBuddy荷兰:找到你最完美的狗保姆
2019/04/17 全球购物
static函数与普通函数有什么区别
2015/12/25 面试题
汽车检测与维修应届毕业生求职信
2013/10/19 职场文书
污水厂厂长岗位职责
2014/01/04 职场文书
《少年王冕》教学反思
2014/04/11 职场文书
亲属关系公证书样本
2015/01/23 职场文书
技术员岗位职责
2015/02/04 职场文书