Nev Stokes
Nev Stokes

Reputation: 9799

Linked jQuery sortable lists and Backbone collections

I'm still finding my way with Backbone and I've always use Prototype instead of jQuery in the past so please forgive me if I'm doing something stupid.

I'm trying to develop a UI containing several connected unordered lists where each sortable list is represented by a separate Backbone collection. I'm using ICanHaz and Mustache templates but that's not of importance for my question.

When dragging items between the lists, how would I best achieve the automatic updating of the collections (remove a model from one and insert it into another)? I'm currently trying to use the receive and remove methods in the jQueryUI Sortable interaction — am I at least on the right lines?

var WS = {};

(function(ns) {
    ns.Item = Backbone.Model.extend();

    ns.Content = Backbone.Collection.extend({
        model: ns.Item,
        url: location.href,
        initialize: function(el) {
            this.el = $(el);
            this.deferred = this.fetch();
        },
        recalculate: function() {
            var count = this.length;
            this.el.next(".subtotal").html(count);
        },
        setOrder: function() {
            $.ajax({
                url: this.url + "/reorder",
                type: "POST",
                data: "tasks=" + $(this.el).attr("id") + "&" + this.el.sortable("serialize")
            });
        }
    });

    ns.ContentRow = Backbone.View.extend({
        tagName: "li",
        className: "item",
        events: {
            "click .delete":  "destroy"
        },
        initialize: function(options) {
            _.bindAll(this, "render", "destroy");
            this.model.bind("change", this.render);
            this.model.view = this;
        },
        render: function() {
            var row = ich.item(this.model.toJSON());
            $(this.el).html(row);
            return this;
        },
        destroy: function() {
            if (confirm("Really delete?")) {
                this.model.destroy({
                    success: function(model, response) {
                        $(model.view.el).remove();
                    },
                    error: function(model, response) {
                        console.log(response);
                    }
                });
            }
        }
    });

    ns.ListView = Backbone.View.extend({
        initialize: function(collection) {
            this.el = collection.el;
            this.collection = collection;

            this.collection.bind("add", this.addOne, this);
            _.bindAll(this, "addOne");

            this.el.sortable({
                axis: "y",
                connectWith: ".tasks",
                receive: _.bind(function(event, ui) {
                    // do something here?
                }, this),
                remove: _.bind(function(event, ui) {
                    // do something here?
                }, this),
                update: _.bind(function(event, ui) {
                    var list = ui.item.context.parentNode;
                    this.collection.setOrder();
                }, this)
            });
        },
        insert: function(item) {
            var prefix = this.el.parentsUntil('ul').parent().attr("id"),
                view = new ns.ContentRow({
                    model: item,
                    id: prefix + "_" + item.id
                });

            this.el.append(view.render().el);
        },
        addOne: function(item) {
            if (item.isNew()) {
                item.save({}, {
                    success: _.bind(function(model, response) {
                        // I should set id from JSON response when live
                        model.set({ id: this.collection.length });
                        this.insert(model);
                    }, this)
                });
            } else {
                this.insert(item);
            }
        },
        addAll: function() {
            this.collection.each(this.addOne);
        },
        render: function() {
            this.collection.deferred.done(_.bind(function() {
                this.addAll();
            }, this));
        }
    });

    ns.AppView = Backbone.View.extend({
        lists: [],
        initialize: function(holder) {
            holder.find("ul").each(_.bind(function(index, list) {
                var Items = new WS.Content(list),
                    App = new WS.ListView(Items);

                App.render();

                this.lists.push(Items);
            }, this));
        }
    });

})(WS);

$(document).ready(function() {
    var App = new WS.AppView($("#tasks"));
});

Upvotes: 6

Views: 3878

Answers (2)

Brave Dave
Brave Dave

Reputation: 1300

Just use Backbone.CollectionView.. it has this functionality built in out of the box.

var listView = new Backbone.CollectionView( {
  el : $( "#list1" ),
  sortable : true,
  sortableOptions : {
      connectWith : "#list2"
  },
  collection : new Backbone.Collection
} );

var listView = new Backbone.CollectionView( {
  el: $( "#list2" ),
  sortable : true,
  sortableOptions : {
      connectWith : "#list1"
  },
  collection : new Backbone.Collection
} );

Upvotes: 1

maxl0rd
maxl0rd

Reputation: 1446

You are on the right track. You will probably want to add the id of each sortable element into the template somewhere. Then when you receive the event, you know which model to add or remove from the collection. For example add...

<div data-id={{id}}> ... my thing ... </div>

And in the sortable call get the target's id attribute and call Collection.add() or remove()

Upvotes: 2

Related Questions