Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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"));
});
like image 391
Nev Stokes Avatar asked Nov 16 '11 17:11

Nev Stokes


2 Answers

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()

like image 71
maxl0rd Avatar answered Nov 15 '22 12:11

maxl0rd


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
} );
like image 32
Brave Dave Avatar answered Nov 15 '22 10:11

Brave Dave