目标
使用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>
探索Emberjs制作一个简单的Todo应用
声明:登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。
Reply on: @reply_date@
@reply_contents@