Emberjs——第一个应用

目标

使用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
 1 <!doctype html>
 2 <html>
 3 <head>
 4 <meta charset="utf-8">
 5 <title>Ember--第一个应用</title>
 6 <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
 7 <script type="text/javascript" src="http://cloud.github.com/downloads/wycats/handlebars.js/handlebars-1.0.rc.1.js"></script>
 8 <script type="text/javascript" src="http://cloud.github.com/downloads/emberjs/ember.js/ember-1.0.0-pre.2.min.js"></script>
 9 <script>
10 
11     /********************
12     application
13     ********************/
14 
15     window.App = Ember.Application.create(
16     {
17         ready:function(){
18         Em.Logger.info('欢迎使用待办事项应用');
19         }
20     }
21     );
22 
23     /********************
24     model
25     ********************/
26 
27     App.TodoModel = Em.Object.extend({
28     status:'',
29     value:'',
30     title:function(){
31         return '['+this.get('status')+']'+this.get('value');
32     }.property('status','value')
33     });    
34 
35     /********************
36     view
37     ********************/
38     App.AddItemView = Ember.TextField.extend({
39     placeholder:'输入待办事项',
40     elementId:'add',
41     insertNewline:function(){
42             var item = App.TodoModel.create({
43                 status:App.selectController.get('selected'),
44                 value:this.get('value')
45             });
46             App.todoStore.pushObject(item);
47             this.set('value','');
48               }
49     });
50 
51     App.ListView = Ember.CollectionView.extend({
52         contentBinding:'App.todoStore',
53         tagName:'ul',
54         itemViewClass: Ember.View.extend({
55             templateName:'itemTemplate',
56             removeItem:function(){this.getPath( 'contentView.content' ).removeObject(this.get( 'content' ));},
57             mouseEnter:function(){this.set('hover',true);},
58             mouseLeave:function(){this.set('hover',false);}
59         })
60     });
61 
62 
63 
64     /********************
65     controlle
66     ********************/
67     App.todoStore = Ember.ArrayController.create({
68     content:[]
69     });
70     
71     App.selectController = Ember.Object.create({
72     selected:'低',
73     content:['高','中','低']
74     });
75 
76 </script>
77 </head>
78 <body>
79 <script type="text/x-handlebars">
80     <span>请输入待办事项:</span>{{view App.AddItemView}}<br/>
81     <span>请选择优先级:</span>{{view Ember.Select contentBinding="App.selectController.content"
82                  selectionBinding="App.selectController.selected"}}
83     {{view App.ListView}}
84 </script>
85 <script type="text/x-handlebars" data-template-name="itemTemplate">
86     {{view.content.title}} {{#if view.hover}}<a href="#" {{action removeItem target="this"}} >X</a>{{/if}}
87 </script>
88 </body>
89 </html>
posted @ 2012-11-04 00:05  Kiinlam  阅读(5091)  评论(6编辑  收藏  举报