LoginSignup
2
2

More than 5 years have passed since last update.

sortableで並び替えたらBackbone.Collection,Viewも並び替えるjQueryプラグイン

Last updated at Posted at 2018-05-17

前段

今更ながらBackbone.jsネタです。

jQueryUIのsortableを使えば、ドラッグ&ドロップによる要素の並び替えが簡単に実現できます。ただ、Backbone.jsと使用する場合にはちょっと面倒なことがあります。

通常Backboneで一覧系の画面を描画するときは、リストの一行に相当するModelとViewを定義し、リストのコンテナにCollectionとコンテナViewを対応させるのが定石だと思います。サンプルコードを挙げます。

example.js
var CollectionView = Backbone.View.extend({
  tagName: 'ul',

  render: function() {
    this.itemViews = [];
    var that = this;
    this.collection.each(function(model) {
      that.add(model);
    });

    return this;
  },

  add: function(model) {
    var itemView = new ItemView({ model: model });
    this.$el.append(itemView.render().el);
    this.itemViews.push(itemView);
  },
});

var ItemView = Backbone.View.extend({
  tagName: 'li',

  render: function() {
    ...
    ...
    return this;
  },
});

ここでli要素をsortableで並び替えられるようにするのは簡単なのですが、ドラッグ&ドロップで並び替えた後の画面上の並びにcollectionitemViewsを同期させてやらないと、後々問題になることがよくあります。

というわけで、並び替え後にcollectionやviewsも自動的に並び替えてくれる、jQueryUIのsortableをラップしたbackboneSortableというjQueryプラグインを作成しました。

backboneSortable.js

コードは以下の通りです。

backboneSortable.js
/**
 * $.backboneSortable
 */
(function(){
  var methods = {
    init: function(options) {
      var settings = $.extend({
        // defaults
      }, options);

      return this.each(function() {
        var $this = $(this);
        var backboneSortable = new BackboneSortable($this, $.extend({}, settings, $this.data()));
        backboneSortable.sortable();

        $this.data('plugin_backbone_sortable', backboneSortable);
      });
    },
  };

  $.fn.backboneSortable = function(method) {
    if (methods[method]) {
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    } else if (typeof method === 'object' || !method) {
      return methods.init.apply(this, arguments);
    } else {
      $.error('Method ' +  method + ' does not exist on jQuery.backboneSortable');
    }
  };

  /**
   * BackboneSortable class
   */
  var BackboneSortable = (function() {

    var NAMESPACE = '.backboneSortable';

    var BackboneSortable = function($el, settings) {
      this.$el = $el;
      this.settings = settings;
    };

    // instance methods
    $.extend(BackboneSortable.prototype, {
      sortable: function() {
        var that = this;
        var fromIndex;
        this.$el.sortable($.extend({}, this.settings, {
          start: function(e, ui) {
            fromIndex = ui.item.index();
            that._delegate('start', e, ui);
          },
          update: function(e, ui) {
            var toIndex = ui.item.index();
            // Move a view
            var view = that.settings.backboneViews.splice(fromIndex, 1)[0];
            that.settings.backboneViews.splice(toIndex, 0, view);
            // Move a model
            that.settings.backboneCollection.remove(view.model, { silent: true });
            that.settings.backboneCollection.add(view.model, { at: toIndex, silent: true });
            that.settings.backboneCollection.trigger('sort' + NAMESPACE,
                                                     that.settings.backboneCollection,
                                                     { model: view.model, from: fromIndex, to: toIndex });
            that._delegate('update', e, ui);
          },
        }));
      },

      _delegate: function(funcName, e, ui) {
        var f = this.settings[funcName];
        if (typeof f === 'function') {
          f.call(this.$el, e, ui);
        }
      },
    });

    return BackboneSortable;
  })();
})();

Usage

使い方は、こんな感じです。

usage.js
var CollectionView = Backbone.View.extend({
  tagName: 'ul',

  initialize: function(options) {
    this.listenTo(this.collection, 'sort.backboneSortable', function(collection, options) {
      console.log('model moved', options.from, '->', options.to, options.model);
    });
  },

  render: function() {
    this.itemViews = [];
    var that = this;
    this.collection.each(function(model) {
      that.add(model);
    });

    this.$el.backboneSortable({
      backboneCollection: this.collection,
      backboneViews: this.itemViews,
      update: function(e, ui) {
        // Both the collection and the views have been updated as they are
        console.log(that.collection.pluck('name'), _.pluck(that.itemViews, 'cid'));
        console.log(this.sortable('toArray'));
      },
    });

    return this;
  },

  add: function(model) {
    var itemView = new ItemView({ model: model });
    this.$el.append(itemView.render().el);
    this.itemViews.push(itemView);
  },
});

var ItemView = Backbone.View.extend({
  tagName: 'li',

  render: function() {
    this.$el
      .text(this.model.get('name') + ' - ' + this.cid)
      .attr('id', this.model.id);
    return this;
  },
});


var collection = new Backbone.Collection([
  new Backbone.Model({ id: 'J', name: 'John' }),
  new Backbone.Model({ id: 'S', name: 'Sarah' }),
  new Backbone.Model({ id: 'A', name: 'Andrew' }),
]);

var collectionView = new CollectionView({ collection: collection });
$('body').append(collectionView.render().el);
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2