YIXUNFE / blog

文章区
151 stars 25 forks source link

EmberJS 入门指南 #40

Open zdddrszj opened 8 years ago

zdddrszj commented 8 years ago

现在,我们经常可以看到复杂的 JavaScript 应用程序,一长串的 jQuery 回调语句或者通过应用程序在各个状态执行不同的函数调用,这些做法都会变得无法再让人接受,这导致了 JavaScript 开发人员开始寻找一种组织和效率更优秀的开发方式。

实现组织和效率其中一个最常用的架构模式,就是我们熟知的 Model View Controller (MVC) 模式,这种模式鼓励开发人员将其应用程序的不同部分分割为更易于管理的模块,我们不必使用一个函数直接调用数据库,通过创建了一个 模型 Model 来管理数据库;通过模板 Template 或视图View 来简化显示代码; 最后,通过使用控制器 Controller 来处理我们的应用程序请求,MVC 模式尽量降低每个模块之间的耦合度,提供程序的开发效率。

我们熟知的Javascript MVC框架有:Ember.jsBackbone.jsKnockout.jsSpine.jsBatman.jsAngular.js等。

今天我们就来简单了解一下 EmberJs 框架,我们将做一个可以自定义书签的社交化分享应用,你可以在这里 查看这个应用,这个应用可以做到:

list

detail

add

一、Ember核心?

1、模型:模型代表我们展示给用户的应用领域内的对象。在上述例子中,一个书签就代表一个模型,包括它的标题、摘要、作者和链接。模型可以通过 jQuery 加载服务器端的 JSON 数据,也可以通过 Ember Data 来获取和更新。Ember Data 是一个客户端的 ORM 实现,可以利用它方便地对底层的持久性存储进行 CURD 操作。Ember Data 提供一个仓库接口,可以借助提供一些适配器配置。 Ember Data 提供的两个核心适配器是 RESTAdapterFixtureAdapter 适配器。在本文中,我们将使用 LocalStorage 适配器,该适配器将数据持久化为 HTML5LocalStorage。详情请参考 此文档

2、路由器和路由:路由器指定应用的所有路由,路由器将 URL 映射到路由。例如,当用户访问 #/bookmark/new 的时候,将渲染 newbookmark 模版。该模版展现了一个 HTML 表单,用户可以通过创建 Ember.Route 子类来定制路由。在上述例子中,用户访问 #/bookmark/new 将渲染一个基于 newbookmark 模版的默认模型。NewBookmarkRoute 会负责将默认的模型分配给 newbookmark 模版。详情请参考 此文档

3、控制器:控制器可以做两件事——首先它装饰路由返回的模型,接着它监听用户执行的行动。例如,当用户提交表单的时候,NewBookmarkController 负责通过Ember Data API 将表单的数据持续化到存储层。详情请参考 此文档

二、创建应用

1、设置 Ember

Ember 依赖于 jQueryHandlebars 库。为美观这里用 Bootstrap 来添加样式。我们把一系列 js 文件保存到本地,app.js 是应用程序代码,localstorage_adapter.js 是本地存储代码,moment.min.js 是日期格式化代码。接下来创建 index.html 页面:

<!DOCTYPE html>
<html>
      <head>
           <meta charset="utf-8" />
           <title>EmberJS</title>
           <link rel="stylesheet" href="css/normalize.css">
           <link rel="stylesheet" type="text/css" href="css/bootstrap.css">
           <link rel="stylesheet" href="css/style.css">
      </head>
      <body>
           <script type="text/x-handlebars">
               <nav class="navbar navbar-default navbar-fixed-top navbar-inverse" role="navigation">
                   <div class="container">
                       <div class="navbar-header">
                           <a class="navbar-brand" href="#">书签</a>
                       </div>

                   </div>
               </nav>
               <div id="main" class="container">
                   {{outlet}}
               </div>
           </script>

           <script src="js/libs/jquery-1.9.1.js"></script>
           <script src="js/libs/handlebars-1.0.0.js"></script>
           <script src="js/libs/ember-1.1.2.js"></script>
           <script src="js/libs/ember-data.js"></script>
           <script src="js/libs/localstorage_adapter.js"></script>
           <script src="js/libs/moment.min.js"></script>
           <script src="js/app.js"></script>
      </body>
</html>

上述 html 中,<script type="text/x-handlebars"> 代表我们的应用模版。应用模版使用 {{outlet}} 标签为其他模版预留位置,其内容取决于 url

2、新建模版

我们首先实现提交新书签的功能。当用户访问 #/bookmark/new 的时候,会展示一个表单。 在 App.Router.Map 中增加一个绑定 #bookmark/new 的新路由:

// app.js
App = Ember.Application.create();
App.Router.map(function() {
  this.resource('newbookmark' , {path : 'bookmark/new'});
});

接着我们在 index.html 中添加一个渲染表单的 newbookmark 模板:

<script type="text/x-handlebars" id="newbookmark">
    <form class="form-horizontal" role="form">
        <div class="form-group">
            <label for="title" class="col-sm-2 control-label">标题</label>
            <div class="col-sm-10">
                <input type="title" class="form-control" id="title" name="title" placeholder="标题" required>
            </div>
        </div>
        <div class="form-group">
            <label for="excerpt" class="col-sm-2 control-label">摘要</label>
            <div class="col-sm-10">
                <textarea class="form-control" id="excerpt" name="excerpt" placeholder="摘要" required></textarea>
            </div>
        </div>

        <div class="form-group">
            <label for="url" class="col-sm-2 control-label">地址</label>
            <div class="col-sm-10">
                <input type="url" class="form-control" id="url" name="url" placeholder="链接地址" required>
            </div>
        </div>
        <div class="form-group">
            <label for="tags" class="col-sm-2 control-label">标签</label>
            <div class="col-sm-10">
                <textarea id="tags" class="form-control" name="tags" placeholder="逗号分隔标签" rows="3" required></textarea>
            </div>
        </div>
        <div class="form-group">
            <label for="fullname" class="col-sm-2 control-label">姓名</label>
            <div class="col-sm-10">
                <input type="text" class="form-control" id="fullname" name="fullname" placeholder="请输入自己的姓名" required>
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                <button type="submit" class="btn btn-success" {{action 'save'}}>提交</button>
            </div>
        </div>
    </form>
</script>

访问 #/bookmark/new 即可查看表单:

add

接着我们在首页导航条中添加一个链接,这样可以在导航中直接访问:

<script type="text/x-handlebars">
    <nav class="navbar navbar-default navbar-fixed-top navbar-inverse" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <a class="navbar-brand" href="#">书签</a>
            </div>
            <ul class="nav navbar-nav pull-right">
                <li>{{#link-to 'newbookmark'}}<span class="glyphicon glyphicon-plus"></span> 新建{{/link-to}}</li>
            </ul>
        </div>
    </nav>
    <div id="main" class="container">
        {{outlet}}
    </div>
</script>

上面我们用 {{#link-to}} 创建了一个指向路由的链接。详情请参考此文档

3、存储数据

如前所述,Ember Data 是一个客户端的 ORM 实现,它使在底层存储进行 CRUD 操作很容易。这里我们将使用 LSAdapter。在 app.js 中加入:

App.ApplicationAdapter = DS.LSAdapter.extend({
    namespace: 'bookmarks'
});

接着,定义数据模型,分别有标题、摘要、地址、标签、姓名。在下面的模型中,我们使用了字符串和日期类型。适配器默认支持的属性类型为字符串、数字、布尔值和日期。

App.Bookmark = DS.Model.extend({
    title : DS.attr('string'),
    excerpt : DS.attr('string'),
    url : DS.attr('string'),
    tags : DS.attr('string'),
    fullname : DS.attr('string'),
    submittedOn : DS.attr('date'),

    tagnames : function(){
        var tags = this.get('tags').split(',');
        var tagArray = new Array();
        for(var i = 0;i<tags.length;i++ ){
            tagArray.push(tags[i].trim())
        }
      //  console.log(tagArray);
        return tagArray;
    }.property('tags')
});

接着我们编写 NewbookmarkController 来持久化内容:

App.NewbookmarkController = Ember.ObjectController.extend({
 actions :{
    save : function(){
        var url = $('#url').val();
        var tags = $('#tags').val();
        var fullname = $('#fullname').val();
        var title = $('#title').val();
        var excerpt = $('#excerpt').val();
        var submittedOn = new Date();
        var store = this.get('store');
        var bookmark = store.createRecord('bookmark',{
            url : url,
            tags : tags,
            fullname : fullname,
            title : title,
            excerpt : excerpt,
            submittedOn : submittedOn
        });
        bookmark.save();
        this.transitionToRoute('index');
    }
 }
});

以上代码展示了如何从获取表单中的值,然后使用 store API 在内存中创建记录。为了在localstorage 中存储记录,我们需要调用 Bookmark 对象的 save 方法。最后,我们将用户重定向到 index 路由。

接着我们测试下这个应用,创建一个新的标签,接着打开 Chrome 开发者工具,在资源区域你可以查看:

ls

4、显示列表

接着我们要做的是,当用户访问首页 / 的时候,展示所有列表。正如之前提到的,路由负责询问 model。我们将加上 IndexRoute ,它会找出本地存储中保存的所有内容:

App.IndexRoute = Ember.Route.extend({
    model : function(){
        var bookmarks = this.get('store').findAll('bookmark');
        return bookmarks;
    }
});

每个路由支持一个模板。IndexRoute 支持 index 模板,因此我们需要修改 index.html

<script type="text/x-handlebars" id="index">
    <div class="row">
        <div class="col-md-4">
            <table class='table'>
                <thead>
                <tr><th>最近更新</th></tr>
                </thead>
                {{#each content}}
                <tr><td>
                    {{#link-to 'bookmark' this}}
                    {{title}}
                    {{/link-to}}
                </td></tr>
                {{/each}}
            </table>
        </div>
        <div class="col-md-8">
            {{outlet}}
        </div>
    </div>
</script>

还有一个问题,列表没有按照时间顺序排列。我们将创建一个 IndexController 负责排序。我们指定依照 submittedOn 属性倒序排列,以确保新的书签出现在上面。

App.IndexController = Ember.ArrayController.extend({
    sortProperties : ['submittedOn'],
    sortAscending : false
});

现在访问 /,我们会看到下面列表:

list

5、查看单个书签

最后要实现的功能是:用户点击某个书签的时候会看到详细信息。我们加一个路由:

App.Router.map(function() {
    this.resource('index',{path : '/'},function(){
        this.resource('bookmark', { path:'/bookmarks/:bookmark_id' });
    });
    this.resource('newbookmark' , {path : 'bookmark/new'});
});

以上的代码展示了如何嵌套路由。:bookmark_id 部分叫做动态字段,因为相应的 id 会被注入 URL ,然后我们添加根据书签 id 获取书签的 BookmarkRoute

App.BookmarkRoute = Ember.Route.extend({
    model : function(params){
        var store = this.get('store');
        return store.find('bookmark',params.bookmark_id);
    }
});

最后,我们在 index.html 中添加下面代码:

<script type="text/x-handlebars" id="bookmark">
      <h1>{{title}}</h1>
      <h3><a {{ bind-attr href=url }} target='_blank'>链接</a></h3>
      <h2> by {{fullname}} <small class="muted">{{submittedOn}}</small></h2>
      {{#each tagnames}}
            <span class="label label-primary">{{this}}</span>
      {{/each}}
      <hr>
      <p class="lead">
          {{excerpt}}
      </p>
</script>

修改完成后,可以在浏览器中看到:

de

6、日期格式化

Ember 下有辅助函数的概念。所有 Handlebars 模板都可以调用辅助函数。我们将使用 moment.js 库为日期添加格式。在 app.js 文件中添加辅助函数:

Ember.Handlebars.helper('format-date', function(date){
    return moment(date).fromNow();
});

最后我们在模板中加入 format-data 辅助函数:

<script type="text/x-handlebars" id="bookmark">
      <h1>{{title}}</h1>
      <h3><a {{ bind-attr href=url }} target='_blank'>链接</a></h3>
      <h2> by {{fullname}} <small class="muted">{{format-date submittedOn}}</small></h2>
      {{#each tagnames}}
            <span class="label label-primary">{{this}}</span>
      {{/each}}
      <hr>
      <p class="lead">
          {{excerpt}}
      </p>
</script>

7、删除功能

到这里主要知识点已经介绍差不多了,接下来看一下删除功能,修改 index.htmlindex 模版:

<script type="text/x-handlebars" id="index">
    <div class="row">
        <div class="col-md-4">
            <table class='table'>
                <thead>
                <tr>
                    <th colspan="2">最近更新</th>
                </tr>
                </thead>
                {{#each controller itemController='BookmarkDel' }}
                <tr>
                    <td>
                        {{#link-to 'bookmark' this}}
                        {{title}}
                        {{/link-to}}
                    </td>
                    <td>
                        <button type="submit" class="btn btn-danger" {{action 'del' this}}>删除</button>
                    </td>
                </tr>
                {{/each}}
            </table>
        </div>
        <div class="col-md-8">
            {{outlet}}
        </div>
    </div>
</script>

app.js 中更新代码如下:

App.IndexController = Ember.ArrayController.extend({
    sortProperties: ['submittedOn'],
    sortAscending: false,
    itemController:'BookmarkDel'
});
App.BookmarkDelController = Ember.ObjectController.extend({
    actions: {
        del: function (record) {
            this.store.deleteRecord(record);
            record.save();
        }
    }
});

结果如下:

detail

8、编辑功能

index.html 修改如下:

<script type="text/x-handlebars" id="bookmark">
    <h1>{{title}}</h1>
    <h3><a {{ bind-attr href=url }} target='_blank'>链接</a></h3>
    <h2> by {{fullname}}
        <small class="muted">{{format-date submittedOn}}</small>
    </h2>
    {{#each tagnames}}
    <span class="label label-primary">{{this}}</span>
    {{/each}}
    <hr>
    <p class="lead">{{excerpt}}</p>
    <button type="submit" class="btn" {{action 'edit'}}>编辑</button>
    {{#if isediting}}
    {{partial 'bookmark/edit'}}
    {{/if}}
</script>
<script type="text/x-handlebars" id="bookmark/edit">
    <h1>{{input value=title class='form-control'}}</h1>
    <h4>{{input value=url class='form-control'}}</h4>
    <h2> by {{input value=fullname class='form-control'}} </h2>
    {{textarea value=tags class='textarea' class='form-control'}}
    <hr>
    {{textarea value=excerpt class='form-control' rows='3'}}
    <button type="submit" class="btn update btn-success" {{action 'update' this}}>更新</button>
</script>

app.js 中添加如下代码:

App.BookmarkController = Ember.ObjectController.extend({
    actions: {
       edit: function () {
            this.set('isediting', true);
        },
       update: function () {
            var content = this.get('content');
            this.set('submittedOn',new Date());
            content.save();
            this.set('isediting', false);
        }
    }
});

结果如下:

mod
:yum::yum::yum: 好了,今天就到这里吧!

查看demo
参考资料:

https://blog.openshift.com/day-19-ember-the-missing-emberjs-tutorial/ http://segmentfault.com/a/1190000000365519 http://www.cnblogs.com/rush/archive/2013/04/29/3051191.html


Thanks