27

I've got a backbone view model that I'm rendering here and making it draggable with jquery ui.

render: ->
$(this.el).attr('class', 'item').html(this.template(this.options.model.toJSON() ))
viewmodel = this
$(this.el).draggable
    revert: true
    drag: () ->
        console.log(viewmodel)

Above, I have viewmodel available and can remove it from the dom, call methods on its model, etc. But what I want is to drag this view model into a droppable container --like a trash can-- and then call a few of the view model's methods and remove it from the DOM.

What I see though, is the callback method for when an item is dropped into a container would be:

$(function() {
    $("#trash").droppable({
        drop: function(event, ui) {
          console.log(ui.draggable);
        }
    });
});

So, I'm able to see ui.draggable and remove it from the DOM, but i have no reference to its view model. Am I doing something wrong? Any way to work around this?

jdkealy
  • 4,807
  • 6
  • 34
  • 56

4 Answers4

41

I think I ran into the same issue; instead of adding meta-data to the element or storing it globally, I just stored a reference to the actual view itself on the DOM element, which then gives you access to the model, and any info you need from there.

window.MyDraggableView = Backbone.View.extend({
    initialize: function(){
        $(this.el).draggable();
        $(this.el).data("backbone-view", this);
    }
});

window.MyDropTarget = Backbone.View.extend({
    initialize: function(){
        $(this.el).droppable({
            drop: function(ev, ui){
                // get reference to dropped view's model
                var model = $(ui.draggable).data("backbone-view").model;
            },
        });
    },
});
Christopher Scott
  • 2,383
  • 2
  • 25
  • 23
18

I've had this problem. I solved it thusly: Give the drop target a reference to the model collection. Set a property data-cid="<%= cid %>" on the draggable. Now you can look up the model in the collection from the $(ui.draggable).data('cid'). Since backbone asserts that CIDs are unique, you could even scan a collection of collections, in case there were multiple model classes you wanted to be trashable.

Elf Sternberg
  • 16,129
  • 6
  • 60
  • 68
  • So i can get the item in the collection but what about getting the view model itself.Running collection.remove(id) removes it from the collection but not my view. – jdkealy Sep 07 '11 at 05:23
  • 3
    Bind 'remove' event on the collection to your view's method that removes the view from the DOM. – kulesa Sep 07 '11 at 06:46
  • 1
    You'll have to track the View, putting them into an array or an object tracked by CID. – Elf Sternberg Sep 07 '11 at 14:26
  • Thanks a bunch. This totally worked and is game-changing in the way I see javascript. :) – jdkealy Sep 07 '11 at 21:19
11

The approach I use is to pass the event from the droppable to the draggable through a custom event.

var DroppableView = Backbone.View.extend({
  events: { 'drop': 'dropHandler' },
  initialize: function() { this.$el.droppable(); },
  dropHandler: function(e, ui) { ui.draggable.trigger('drop:dropview'); }
})

var DraggableView = Backbone.View.extend({
  events: { 'drop:dropview': 'dropviewDropHandler'},
  initialize: function(){ this.$el.draggable(); },
  dropviewDropHandler: function() { this.doSomething(); }
});

This let's you execute the drop handler in the context of the dragged view, which is often more useful than executing it in the context of the droppable view.

Brennan Roberts
  • 608
  • 6
  • 15
  • +1 for the idea to have it execute in the context of the draggable instead of the droppable. – g19fanatic Sep 06 '13 at 17:59
  • this is certainly the best solution of all on this thread. You can also pass the `DroppableView` instance as an arguement of `drop:dropview`, and `dropviewDropHandler` will have access to both `DroppableView` and DraggableView` instances. – midu Jun 17 '14 at 15:16
1

We solved this problem with a global property that is placed in the app-namespace, called dragging.

As there is only one view being dragged at one time, the dragging view binds to the drag event and writes its own model into window.dragging.

When it gets dropped on a droppable view, that view gets the current dragging-model via that dragging variable.

btw that property can better be placed within the global accessible application namespace instead of directly adding it to window. This was App.View.tool in our application.

Like this:

dragging = null;

draggableview = new Backbone.View.extend({

    //...
    initialize: function() {

        //...
        $(this.el).bind("dragStart",
        function() {
            window.dragging = this.model;
        },
        this);

        //remove reference for garbage collection purpose
        $(this.el).bind("dragStop",
        function() {
            delete window.dragging;
        },
        this);
    },

});

droppableview = new Backbone.View.extend({

    //...
    initialize: function() {

        //...
        $(this.el).bind("drop",
        function() {
            var draggedmodel = window.dragging;
            delete window.dragging;
            // for garbage collection purpose
            //do funky stuff
            alert("You dropped " + draggedmodel.get('title') + " on " + this.el.get('title'));
            //...
        },
        this);
    },
});
abstraktor
  • 955
  • 9
  • 20