Part 9: 任務項

z.w發表於2015-01-19

準備

開始本教程之前,你需要了解以下幾點:

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

任務項CRUD

這部分將涵蓋以下內容:

  • 新建單個任務項檢視
  • 新建多個任務項的檢視
  • 新增任務集合
  • 利用Google’s API獲取任務

這裡需要處理一個父檢視和子檢視之間的關係,Backbone並沒有提供模型和檢視之間關係圖,這個例子中,我們理解的關係應該是任務項(tasks)屬於列表(lists),任務檢視(task views)屬於列表檢視(list views),雖然沒有這個關係沒有具體的呈現形式,但是在Backbone/Underscore已經又相應的程式碼去解決這個。

架子

開始之前,我們先新建好目錄:

$ mkdir app/js/views/tasks
$ mkdir app/js/templates/tasks

app/js/collections/tasks.js中新增新集合:

define(['models/task'], function(Task) {
  var Tasks = Backbone.Collection.extend({
    model: Task,
    url: 'tasks'
  });

  return Tasks;
});

Tasks還沒有做任何事,現在我們需要通過Google’s API請求一個tasklist來獲取任務項,在呼叫fetch的時候還需要一個額外的引數:

collection.fetch({ data: { tasklist: this.model.get('id') }, // ...

我們處理獲取得到的TaskLists,就和專案中已經存在的{ userId: '@me' }資料類似。

我們還需要一個包含新建任務項表單和任務項列表容器的任務項檢視模板,被儲存到app/js/templates/index.js:

<div class="span6">
  <div id="add-task">
    <form class="well row form-inline add-task">
      <input type="text" class="pull-left" placeholder="Enter a new task's title and press return" name="title">
      <button type="submit" class="pull-right btn"><i class="icon-plus"></i></button>
    </form>
  </div>
  <ul id="task-list"></ul>
</div>
<div class="span6">
  <div id="selected-task"></div>
  <div class="alert" id="warning-no-task-selected">
    <strong>Note:</strong> Select a task to edit or delete it.
  </div>
</div>

我們使用了Bootstrap得佈局一些樣式類,在app/js/templates/tasks/task.htmlTaskView檢視中,有title的span元素、notes的span元素和切換任務的狀態的checkbox核取方塊:

<input type="checkbox" data-task-id="" name="task_check_" class="check-task" value="t">
<span class="title "></span>
<span class="notes"></span>

檢視

TasksIndexView通過Tasks聚合載入任務項,用TaskView檢視來渲染展示任務,具體的TasksIndexView程式碼在app/js/views/tasks/index.js中:

define(['text!templates/tasks/index.html', 'views/tasks/task', 'collections/tasks'], function(template, TaskView, Tasks) {
  var TasksIndexView = Backbone.View.extend({
    tagName: 'div',
    className: 'row-fluid',

    template: _.template(template),

    events: {
      'submit .add-task': 'addTask'
    },

    initialize: function() {
      this.children = [];
    },

    addTask: function() {
    },

    render: function() {
      this.$el.html(this.template());

      var $el = this.$el.find('#task-list')
        , self = this;

      this.collection = new Tasks();
      this.collection.fetch({ data: { tasklist: this.model.get('id') }, success: function() {
        self.collection.each(function(task) {
          var item = new TaskView({ model: task, parentView: self });
          $el.append(item.render().el);
          self.children.push(item);
        });
      }});

      return this;
    }
  });

  return TasksIndexView;
});

使用collection.fetch獲取任務項,然後給每個任務項追加一個TaskView,這個TaskView如下:

define(['text!templates/tasks/task.html'], function(template) {
  var TaskView = Backbone.View.extend({
    tagName: 'li',
    className: 'controls well task row',

    template: _.template(template),

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

    initialize: function(options) {
      this.parentView = options.parentView;
    },

    render: function(e) {
      var $el = $(this.el);
      $el.data('taskId', this.model.get('id'));
      $el.html(this.template(this.model.toJSON()));
      $el.find('.check-task').attr('checked', this.model.get('status') === 'completed');

      return this;
    },

    open: function(e) {
      if (this.parentView.activeTaskView) {
        this.parentView.activeTaskView.close();
      }
      this.$el.addClass('active');
      this.parentView.activeTaskView = this;
    },

    close: function(e) {
      this.$el.removeClass('active');
    }
  });

  return TaskView;
});

父檢視會判斷確定open是否執行,是看是不是其他任務被點選和被關閉(刪除active),實現這個又很多方式:可以遍歷檢視看看是不是關閉狀態(關閉狀態會通過$('selector').removeClass('active')刪除相關的classactive)或者在模型中通過觸發事件。我覺得應該在檢視中處理檢視相關的程式碼,模型和集合也應該是類似。

接下來我們需要把TasksIndexView新增app/js/views/lists/menuitem.jsdefine中,修改open方法,讓他去例項化一個TasksIndexView檢視:

open: function() {
  if (bTask.views.activeListMenuItem) {
    bTask.views.activeListMenuItem.$el.removeClass('active');
  }

  bTask.views.activeListMenuItem = this;
  this.$el.addClass('active');

  // Render the tasks
  if (bTask.views.tasksIndexView) {
    bTask.views.tasksIndexView.remove();
  }

  bTask.views.tasksIndexView = new TasksIndexView({ collection: bTask.collections.tasks, model: this.model });
  bTask.views.app.$el.find('#tasks-container').html(bTask.views.tasksIndexView.render().el);

  return false;
}

app/js/models/task.js中新增一個預設的模型Task:

define(function() {
  var Task = Backbone.Model.extend({
    url: 'tasks',
    defaults: { title: '', notes: '' }
  });

  return Task;
});

我們給它了一個預設值{ title: '', notes: '' },新增預設值的原因—主要是避免我們通過Google Tasks獲取任務為空時出現錯誤

有了這些模板,檢視和修改,我們現在可以選擇列表,看到自己的任務,也可以選擇任務了

樣式

enter image description here

如圖,我們應用程式沒有太多的視覺元素, Bootstrap完全可以滿足需求—我們只去Bootstrap官網下載圖片和樣式檔案放到我們專案的app/imgapp/css目錄下,然後在我們的頁面app/index.html引入css/bootstrap.min.css

我們再給應用的任務皮膚加一些自定義樣式,這樣就看起來和Things介面差不多。

Backbone 0.9.10

我已經將Backbone升級到了0.9.10版本,app/js/gapi.js中當呼叫options.successBackbone.sync方法有點區別,修改下:

options.success(model, result, request);

總結

教程中完整的程式碼可以在這裡找到:alexyoung / dailyjs-backbone-tutorial, commit 0491ad

相關文章