准备

开始本教程之前,你需要了解以下几点:

  • alexyoung / dailyjs-backbone-tutorial提交到了82fe08e版本
  • 第二部分中的API key
  • 第二部分中的“Client ID”
  • 更新app/js/config.js成你自己的key(如果你检出了我的代码)

要检出源码,请运行以下命令(或用Git GUI工具):

git clone git@github.com:alexyoung/dailyjs-backbone-tutorial.git
cd dailyjs-backbone-tutorial
git reset --hard 82fe08e

获取任务列表的CRUD

本系列教程的最后几部分将讲讲Google’s Tasks API和OAuth认证。到现在,你应该能够登录并查看任务列表。

你已经看到,我们用Backbone.js使用RESTful API来实现CRUD(create创建,read读取,update更新和delete删除)。在Part 4已经了解怎样自定义一个Backbone.sync方法使用Google’s APIs,我们只用gapi.client.tasks实现了一个read(读取)功能

现在我们就要实现CRUD的所有操作来管理我们的任务列表。Part 4我们知道Google’s Tasks API 与Backbone的method对应关系:

Google Tasks API    Backbone.sync方法           解释
insert                create                     新建一个task
update                update                     更新已存在一个task
delete                delete                     删除一个task
list                read                     获取task列表

在这部分我们将新增“create”,基本实现和“read”类似。

新建任务列表

为了创建列表,需要几个新的组件:

  1. 新建按钮
  2. 表单模板
  3. 新建和编辑的视图
  4. 控制器代码

Backbone.js里模型和集合可以广播事件。当一个新的model被添加到TaskLists集合中事,我们需要触发相应的事件来管理任务列表视图

在Backbone.js里通过利用事件是一件值得推荐的好编程方式。

Backbone.sync

之前Backbone.sync实现了通过Google’s API读取任务列表。我们只需用把读取那段代码稍微扩展下就可以实现新建。

回到app/js/gapi.js文件,修改Backbone.sync方法,通过调用gapiRequest来实现create:

Backbone.sync = function(method, model, options) {
  var requestContent = {};
  options || (options = {});

  switch (method) {
    case 'create':
      requestContent['resource'] = model.toJSON();
      request = gapi.client.tasks[model.url].insert(requestContent);
      Backbone.gapiRequest(request, method, model, options);
    break;

是不是和read的代码看起来差不多,只是我们使用了Backbone.gapiRequest来处理单个的资源。完整的这个方法的代码如下:

Backbone.gapiRequest = function(request, method, model, options) {
  var result;
  request.execute(function(res) {
    if (res.error) {
      if (options.error) options.error(res);
    } else if (options.success) {
      if (res.items) {
        result = res.items;
      } else {
        result = res;
      }
      options.success(result, true, request);
    }
  });
};

Google’s API会返回一个数组或者一个简单的对象,值得我们注意的是:Backbone.sync是作为Backbone对象属性可以是用来设置模型的属性的。当success成功的回调接收到result,这个结果的模型会生成一个id属性。Backbone会自动的给模型设置上id属性。

模板修改

修改app/js/templates/app.html,把下面的div放到signed-in-container``div里:

<ul class="nav nav-tabs" id="top-nav">
  <li class="buttons">
    <div class="btn-group">
      <a href="#" class="btn" id="add-list-button"><i class="icon-plus">Add List</i></a>
      <a href="#" class="btn" id="edit-list-button"><i class="icon-cog">Edit List</i></a>
      <a href="#" class="btn delete-list" id="delete-list-button"><i class="icon-trash">Delete List</i></a>
    </div>
  </li>
</ul>
<div id="content-container">
  <div id="list-editor"></div>
  <div id="tasks-container"></div>
</div>

这个模板你回发现有个“添加任务” 按钮,点击这个按钮我们应该给有一个用于新增/编辑的form表单,所以我们还需要新建一个表单模板app/js/templates/lists/form.html

<fieldset>
  <legend>
    <span class="form-title">Edit List</span>
    <a href="#" class="pull-right delete-list btn"><i class="icon-trash"></i></a>
  </legend>
  <div class="control-group">
    <label class="control-label" for="list_title">Title</label>
    <div class="controls">
      <input type="text" class="input-xlarge" name="title" id="list_title" value="" placeholder="The list's title">
    </div>
  </div>
</fieldset>
<div class="form-actions">
  <button type="submit" class="btn btn-primary">Save Changes</button>
  <button class="cancel btn">Close</button>
</div>

我们可以看到变量插值,将用于新增和编辑任务。

添加和编辑视图

因为“add”和 “edit” 视图的区别并不是太大,他们可以共用一个form.html模板,所以我们只需 在app/js/views/lists/edit.js新加一个视图:

define(['text!templates/lists/form.html'], function(template) {
  var EditListView = Backbone.View.extend({
    tagName: 'form',
    className: 'form-horizontal well edit-list',
    template: _.template(template),

    events: {
      'submit': 'submit'
    , 'click .cancel': 'cancel'
    },

    initialize: function() {
      this.model.on('change', this.render, this);
    },

    render: function() {
      var $el = $(this.el);
      $el.html(this.template(this.model.toJSON()));

      if (!this.model.get('id')) {
        this.$el.find('legend').html('Add List');
      }

      return this;
    },

    submit: function() {
      var self = this
        , title = this.$el.find('input[name="title"]').val()
        ;

      this.model.save({ title: title }, {
        success: function() {
          self.remove();
        }
      });

      return false;
    },

    cancel: function() {
      this.$el.hide();
      return false;
    }
  });

  return EditListView;
});

在这个类里面,我们给提交和关闭表单绑定了相应的处理函数,而且初始化的时候,我们给模型绑定了change事件触发render,所以模型修改的时候会自动重绘。

render函数中,当模型的id不存在时,会改变表单legend的值为新建,当id存在时则将显示为其他并显示删除按钮。

对比的我们看下新建任务的app/js/views/lists/add.js文件:

define([
'models/tasklist'
, 'views/lists/edit'
],

function(TaskList, EditListView) {
var AddListView = EditListView.extend({
  submit: function() {
    var self = this
      , title = this.$el.find('input[name="title"]').val()
      ;

    this.model.save({ title: title }, { success: function(model) {
      // Add the updated model to the collection
      bTask.collections.lists.add(model);
      self.remove();
    }});

    return false;
  }
});

return AddListView;
});

这个文件里面,我们用RequireJS加载了依赖EditListView,并继承了EditListView视图,与编辑不同的只是我们表单submit方法做的事情不一样,当任务新建时,success成功回调后会更新模型,将这个模型添加到全局的lists集合中。这个表单自身会被删除掉。

新建任务按钮

很早之前我们就在app.html模板中就有一个添加任务的链接,现在只需在app/js/views/app.js添加一个addList函数:

addList: function() {
  var list = new bTask.collections.lists.model({ title: '' })
    , form = new AddListView({ model: list })
    , self = this
    ;

  this.$el.find('#list-editor').html(form.render().el);
  form.$el.find('input:first').focus();

  return false;
}

要通过AddListView模板显示标题字段,所以我们还需要在文件头部加上依赖文件AddListView

define([
  'text!templates/app.html'
, 'views/lists/add'
],

function(template, AddListView) {

最后,在AppView加上事件绑定:

events: {
  'click #add-list-button': 'addList'
},

总结

enter image description here

运行服务器node server,访问http://localhost:8080,你可以看到新建任务的表单。暂时这个看起来没那么好看,不过很快就变号了。

教程中完整的代码可以在这里找到:alexyoung / dailyjs-backbone-tutorial, commit 465523f