准备

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

  • alexyoung / dailyjs-backbone-tutorial提交到了fcd653ec6版本
  • 第二部分中的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 fcd653ec6

线框

下面是我们需要构建的一些界面UI元素:

  • 两栏布局:左边任务右边列表
  • 每个列表都有编辑和删除
  • 一些用于删除项目和清除已完成项目的按钮
  • 复选框(任务状态控制)

具体的线框如下图所示:

enter image description here

在这里,我们将用一个无序列表来展示任务列表。

列表项

尽管比较简单,实现一个任务菜单导航还是涉及到了Backbone.js的一些元素:

  • HTML模板
  • Backbone视图: ListMenuView, ListMenuItemView
  • Backbone集合: TaskLists

ListMenuView主要用于任务左侧的菜单导航,ListMenuItemView用于具体的任务列表项目,具体的都是用的ul包含li来实现的。

新建app/js/views/lists目录来放任务列表相关的Backbone.View,新建app/js/templates/lists放对应的模板

菜单列表容器视图:ListMenuView

这个视图写在app/js/views/lists/menu.js:

define(['views/lists/menuitem'], function(ListMenuItemView) {
  var ListMenuView = Backbone.View.extend({
    el: '.left-nav',
    tagName: 'ul',
    className: 'nav nav-list lists-nav',

    events: {
    },

    initialize: function() {
      this.collection.on('add', this.render, this);
    },

    render: function() {
      // TODO
    }
  });

  return ListMenuView;
});

这个文件依赖了views/lists/menuitem模块。绑定的.left-nav元素是在AppView的模板中的。导航菜单本身是一个无序列表,而且还是用了一些相关的className来相关联。

这个视图还需要一个集合,来实例化视图。例如:new ListMenuView({ collection: lists })就是通过lists集合来实例化的。

render方法就是这样的:

render: function() {
  var $el = $(this.el)
    , self = this;

  this.collection.each(function(list) {
    var item, sidebarItem;
    item = new ListMenuItemView({ model: list });
    $el.append(item.render().el);
  });

  return this;
}

所有视图的元素都包含在ListMenuItemView里,通过遍历模型集合来实例化这个视图。

菜单列表项视图:ListMenuItemView

app/js/views/lists/menuitem.js和上面的视图大体上类似,它区别是主要是通过使用视图模板和Backbone来进行事件绑定:

define(['text!templates/lists/menuitem.html'], function(template) {
  var ListMenuItemView = Backbone.View.extend({
    tagName: 'li',
    className: 'list-menu-item',

    template: _.template(template),

    events: {
      'click': 'open'
    },

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

    render: function() {
      var $el = $(this.el);
      $el.data('listId', this.model.get('id'));
      $el.html(this.template(this.model.toJSON()));
      return this;
    },

    open: function() {
      var self = this;
      return false;
    }
  });

  return ListMenuItemView;
});

app/js/templates/lists/menuitem.html模板:

<a href="#" class="list-title" data-list-id=""></a>

请注意,{}是用于插入值。这是由Underscore的模板系统提供的

在视图中,click事件发生时会触发open处理函数,模型也绑定了destroychange两个事件来执行不能的处理

render里可以用template方法来插入模板数据:

$el.html(this.template(this.model.toJSON()));

模型通过toJSONtitleid转换成template需要的数据格式

调用ListMenuView

修改app/js/app.js文件将ListMenuView作为依赖加入到define中:

define([
  'gapi'
, 'views/app'
, 'views/auth'
, 'views/lists/menu'
, 'collections/tasklists'
],

function(ApiManager, AppView, AuthView, ListMenuView, TaskLists) {

在之前我们加了console.log在控制台输出列表的名称,删除这段替换成渲染ListMenuView视图的代码:

connectGapi: function() {
  var self = this;
  this.apiManager = new ApiManager(this);
  this.apiManager.on('ready', function() {
    self.collections.lists.fetch({ data: { userId: '@me' }, success: function(res) {
      self.views.listMenu.render();
    }});
  });
}

回到App构造函数里通过相关集合来实例化listMenu视图:

var App = function() {
  this.views.app = new AppView();
  this.views.app.render();
  this.views.auth = new AuthView(this);
  this.views.auth.render();
  this.collections.lists = new TaskLists();
  this.views.listMenu = new ListMenuView({ collection: this.collections.lists });

  this.connectGapi();
};

运行

运行服务器node server,访问http://localhost:8080,你可以看到一个无序简单的任务列表

总结

现在我们通过Google API拿到注册用户的任务列表并显示到了也页面中。虽然我们还没有任何的样式可能也不算什么,但是你可以用Google APIs和相同的服务来做类似的应用,还是值得自我鼓励下的。

这部分教程的代码:commit 82fe08e on GitHub