Part 10: 任務項新增、修改和切換狀態

z.w發表於2015-01-21

準備

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

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

任務項

enter image description here

如圖所示任務項的相關功能介面

可以看到我們還有很多功能要實現,現在我們只是實現了任務項列表,還沒有實現互動,下面將分幾個小節來實現:

  • 新增任務項
  • 編輯任務項
  • 刪除任務項
  • 切換任務項狀態

大部分的內容其實我們在實現任務列表的時候都已經講到過了,這部分只是帶大家鞏固下Backbone一下用法。

新增任務項

app/js/views/tasks/index.js中,確定我們有一個addTask事件:

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

initialize方法中,新增監聽到這個類的集合

initialize: function() {
  this.children = [];
  this.collection.on('add', this.renderTask, this);
},

這樣就實現了當我們集合中有新任務項被新增的時候會自動渲染任務項列表。

addTask函式裡會使用Task.prototype.save中的Google’s API來實現任務項儲存到服務端,成功回撥後會新增任務項到集合中,這樣就實現了新任務項的顯示,預設我們新增了Google’s Tasks指定的引數{ at: 0}是讓新任務項顯示在最前面,當然這需要連網支援,也可以先儲存到本地然後同步到Google,但這個不是我們的重點。

addTask: function() {
  var $input = this.$el.find('input[name="title"]')
    , task = new this.collection.model({ tasklist: this.model.get('id') })
    , self = this
    ;

  task.save({ title: $input.val() }, {
    success: function() {
      self.collection.add(task, { at: 0 });
    }
  });
  $input.val('');

  return false;
},

renderTask: function(task, list, options) {
  var item = new TaskView({ model: task, parentView: this })
    , $el = this.$el.find('#task-list');
  if (options && options.at === 0) {
    $el.prepend(item.render().el);
  } else {
    $el.append(item.render().el);
  }
  this.children.push(item);
},

renderTask方法裡有個option引數主要是用來確定如何將任務項新增到列表中,如果我們不想讓新新增的任務項到最前面,我們就可以重構下render如下:

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

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

  this.collection.fetch({ data: { tasklist: this.model.get('id') }, success: function() {
    self.collection.each(function(task) {
      task.set('tasklist', self.model.get('id'));
      self.renderTask(task);
    });
  }});
}

開啟app/js/views/lists/menuitem.js,修改open方法,通過Tasks集合來例項化bTask.views.tasksIndexView檢視:

bTask.views.tasksIndexView = new TasksIndexView({ 
       collection: new Tasks({ tasklist: this.model.get('id') }), 
       model: this.model 
 });

然後在define中加入Tasks: define(['text!templates/lists/menuitem.html', 'views/tasks/index', 'collections/tasks'], function(template, TasksIndexView, Tasks) {

要使得Google’s API可以起作用,我們還需要修改下app/js/gapi.js,給requestContent新增tasklistID:

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

  switch (model.url) {
    case 'tasks':
      requestContent.task = model.get('id');
      requestContent.tasklist = model.get('tasklist');
    break;

新增任務項到這裡就已經OK了,這裡我們沒有新建模板是因為我們在其他部分已經寫過了。

編輯任務項

要實現編輯任務項,我們需要:

  • 一個表單模板
  • 一個Backbone.View
  • 儲存時的觸發事件

新建一個表單模板app/js/templates/tasks/edit.html

<fieldset>
  <legend>
    Task Properties
    <a href="#" data-task-id="" class="pull-right delete-task btn"><i class="icon-trash"></i></a>
  </legend>
  <div class="control-group">
    <label for="task_title">Title</label>
    <input type="text" class="input-block-level" name="title" id="task_title" value="" placeholder="The task's title">
  </div>
  <div class="control-group">
    <label class="radio"><input type="radio" name="status" value="needsAction" > Needs action</label>
    <label class="radio"><input type="radio" name="status" value="completed" > Complete</label>
  </div>
  </div>
  <div class="control-group">
    <label for="task_notes">Notes</label>
    <textarea class="input-block-level" name="notes" id="task_notes" placeholder="Notes about this task"></textarea>
  </div>
</fieldset>
<div class="form-actions">
  <button type="submit" class="btn btn-primary">Save Changes</button>
  <button class="cancel btn">Close</button>
</div>

模板中我們使用了Bootstrap一些標籤和樣式,這樣能好看些。

相應的檢視app/js/views/tasks/edit.js如下:

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

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

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

    render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      return this;
    },

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

      this.model.set('title', title);
      this.model.set('notes', notes);

      if (status !== this.model.get('status')) {
        this.model.set('status', status);
        if (status === 'needsAction') {
          this.model.set('completed', null);
        }
      }

      this.model.save();
      return false;
    },

    cancel: function() {
      this.remove();
      return false;
    }
  });

  return TaskEditView;
});

表單提交的時候我們觸發submit方法,關閉的時候我們會有相應的cancel方法。

app/js/views/tasks/index.js中新增一個方法,來例項化TaskEditView

editTask: function(task) {
  if (this.taskEditView) {
    this.taskEditView.remove();
  }
  this.taskEditView = new TaskEditView({ model: task });
  this.$el.find('#selected-task').append(this.taskEditView.render().el);
}

保證TaskEditView載入:

define(['text!templates/tasks/index.html', 'views/tasks/task', 'views/tasks/edit', 'collections/tasks'], 
function(template, TaskView, TaskEditView, Tasks) {

同時我們還要知道修改的是哪個任務項例項,所以把如下程式碼加app/js/views/tasks/task.jsopen方法中:

this.parentView.editTask(this.model);

寫兩個檢視之間有很多的耦合, 這樣TaskView很難再服用,雖然看起來好像TasksIndexView沒有用,這裡我們要怎麼才能寫出更易維護的Backbone程式碼,需要我們自己多思考下。

刪除任務項

app/js/views/tasks/edit.js檔案中新增destroy方法:

destroy: function() {
  this.model.destroy();
  return false;
}

然後給帶垃圾桶圖示(類名為.delete-task)的標籤繫結上這個方法,這樣當這個模型被刪除就可以觸發這個事件:

events: {
  'submit': 'submit'
, 'click .cancel': 'cancel'
, 'click .delete-task': 'destroy'
},

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

切換任務項狀態

我們有個帶圖示標籤用來切換任務項狀態,隨著這一變化,應用程式將真正看起來一個真正的待辦事項列表的應用程式了,開啟app/js/views/tasks/task.js給列表的核取方塊新增一個叫change的事件:

events: {
  'click': 'open'
, 'change .check-task': 'toggle'
},

然後我們基於這個核取方塊的狀態通過toggle方法來實現status屬性的切換:

toggle: function() {
  var id = this.model.get('id')
    , $el = this.$el.find('.check-task')
    ;

  this.model.set('status', $el.attr('checked') ? 'completed' : 'needsAction');
  if (this.model.get('status') === 'needsAction') {
    this.model.set('completed', null);
  }

  this.model.save();
  return false;
}

依照Google針對任務項狀態的命名規範,還有completedneedsAction,如需瞭解更多的可以去看看文件。

總結

到這裡,我們要熟悉這些陌生的特性APIs需要很多耐心。如果你嘗試執行這個專案的程式碼,請確保你確實在Gmail裡有一些任務—如沒任務將執行不起來,這我還在之後修復這個它。

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

相關文章