Blowsie
Blowsie

Reputation: 40535

Update available drop targets after dragstart fires in jquery.event.drag

Overview:

I have a page which uses jquery.event.drag and jquery.event.drop. I need to be able to drag and drop onto elements which are constantly being added to the dom, even after the drag has started.


Problem:

When the dragstart event fires it checks for available drop targets and adds them to the drag object.

The problem I have is I am adding drop targets dynamically, after the dragstart event has fired, and therefore the user cannot drop onto these dynamically added drop targets.


Example:

http://jsfiddle.net/blowsie/36AJq/


Question:

How can I update the drag to allow dropping on elements which have been added to the dom after drag has started?

Upvotes: 19

Views: 3050

Answers (5)

A. Wolff
A. Wolff

Reputation: 74420

You can use this snippet.

The important function is: $.event.special.drop.locate();

Tested on chrome/safari/firefox/ie9 and seems to work.

SEE DEMO


UPDATE

For overlapping events, see if following code works. I set it inside an anonymous function just to avoid any global variable. Idea is to use currentTarget property of event to check if not the same element is triggering same event. I set an id on newdrop element just in purpose of test here.

SEE UPDATED DEMO

(function () {
    var $body = $("body"),
        newdrops = [],
        currentTarget = {},
        ondragstart = function () {

            $(this).css('opacity', .75);
        }, ondrag = function (ev, dd) {
            $(this).css({
                top: dd.offsetY,
                left: dd.offsetX
            });
        }, ondragend = function () {

            $(this).css('opacity', '');
            for (var i = 0, z = newdrops.length; i < z; i++)
            $(newdrops[i]).off('dropstart drop dropend').removeClass('tempdrop');
            newdrops = [];
        }, ondropstart = function (e) {
            if (currentTarget.dropstart === e.currentTarget) return;
            currentTarget.dropstart = e.currentTarget;
            currentTarget.dropend = null;
            console.log('start::' + e.currentTarget.id)
            $(this).addClass("active");
        }, ondrop = function () {
            $(this).toggleClass("dropped");
        }, ondropend = function (e) {
            if (currentTarget.dropend === e.currentTarget) return;
            currentTarget.dropend = e.currentTarget;
            currentTarget.dropstart = null;
            console.log('end::' + e.currentTarget.id)
            $(this).removeClass("active");
        };

    $body.on("dragstart", ".drag", ondragstart)
        .on("drag", ".drag", ondrag)
        .on("dragend", ".drag", ondragend)
        .on("dropstart", ".drop", ondropstart)
        .on("drop", ".drop", ondrop)
        .on("dropend", ".drop", ondropend);



    var cnt = 0;
    setInterval(function () {
        var dataDroppables = $body.data('dragdata')['interactions'] ? $body.data('dragdata')['interactions'][0]['droppable'] : [];

        var $newDrop = $('<div class="drop tempdrop" id="' + cnt + '">Drop</div>');
        cnt++;
        $("#dropWrap").append($newDrop);
        var offset = $newDrop.offset();
        var dropdata = {
            active: [],
            anyactive: 0,
            elem: $newDrop[0],
            index: $('.drop').length,
            location: {
                bottom: offset.top + $newDrop.height(),
                elem: $newDrop[0],
                height: $newDrop.height(),
                left: offset.left,
                right: offset.left + $newDrop.width,
                top: offset.top,
                width: $newDrop.width
            },
            related: 0,
            winner: 0
        };
        $newDrop.data('dropdata', dropdata);
        dataDroppables.push($newDrop[0]);
        $newDrop.on("dropstart", ondropstart)
            .on("drop", ondrop)
            .on("dropend", ondropend);
        $.event.special.drop.locate($newDrop[0], dropdata.index);
        newdrops.push($newDrop[0]);
    }, 1000);
})();

Upvotes: 5

Ali Gangji
Ali Gangji

Reputation: 1503

I wasn't able to get this working using jquery.event.drag and jquery.event.drop, but I did make it work with the native HTML5 events:

http://jsfiddle.net/R2B8V/1/

The solution was to bind the events on the drop targets within a function and call that to update the bindings. I suspect you could get this working with jquery.event.drag and jquery.event.drop using a similar principal. If I can get those working I will update my answer.

Here is the JS:

$(function() {
    var bind_targets = function() {
        $(".drop").on({
            dragenter: function() {
                $(this).addClass("active");
                return true;
            },
            dragleave: function() {
                $(this).removeClass("active");
            },
            drop: function() {
                $(this).toggleClass("dropped");
            }
        });
    };    

    $("div[draggable]").on({
        dragstart: function(evt) {
            evt.originalEvent.dataTransfer.setData('Text', 'data');
        },
        dragend: function(evt) {
            $('.active.drop').removeClass('active');   
        }
    });
  setInterval(function () {
      $("#dropWrap").append('<div class="drop">Drop</div>');
      // Do something here to update the dd.available
      bind_targets();
  }, 1000)
});

Upvotes: 1

Mooseman
Mooseman

Reputation: 18891

You can't. On dragstart, possible drop zones are calculated from the DOM, and can't be edited until dragend. Even constantly rebinding the .on() (Demo: http://jsfiddle.net/36AJq/84/) will not provide the desired effect.

I solved the issue a little differently. (Demo: http://jsfiddle.net/36AJq/87/)

  1. Start with every <div> in the HTML.
  2. Apply opacity: 0 to make it invisible, and width: 0 to keep it from getting a dropend when hidden.
  3. Use setInterval to show the next hidden div ($('.drop:not(.visible)').first()) each 1000ms.

JS:

$("body")
  .on("dragstart", ".drag", function () {
    $(this).css('opacity', .75);
  })
  .on("drag", ".drag", function (ev, dd) {
    $(this).css({
      top: dd.offsetY,
      left: dd.offsetX
    });
  })
  .on("dragend", ".drag", function () {
    $(this).css('opacity', '');
  })
  .on("dropstart", ".drop", function () {
    $(this).addClass("active");
  })
  .on("drop", ".drop", function () {
    $(this).toggleClass("dropped");
  })
  .on("dropend", ".drop", function () {
    $(this).removeClass("active");
  });
setInterval(function () {
    $('.drop:not(.visible)').first()
      .addClass('visible').removeClass('hidden');
}, 1000)

Upvotes: 0

Tobi
Tobi

Reputation: 141

Why not place all the divs into the page and set their visibility to hidden? Then use setInterval() to change each one's visibility every second.

Upvotes: -2

Ido Schacham
Ido Schacham

Reputation: 340

Enable the refreshPositions option.

Upvotes: -1

Related Questions