Search code examples
jqueryjsplumb

jsPlumb makeSource() and jQuery sortable() not working well together


I'm creating a user interface that allows users to create database tables (and their fields and relationships) via drag/drop interface.

Here is the jsFiddle I created, and although it looks like it has a lot going on, it's actually just the bare minimum necessary to demonstrate the problem. Here are my requirements, which jsPlumb handles beautifully separately, but I run into problems when I try to do them all together. In particular, it's combining #2 and #3 that are an issue.

  1. the "Tables" are draggable around the canvas (using jsPlumb.draggable())
  2. "fields" within a table can be reordered by dragging up/down (using jQuery sortable())
  3. users can drag to draw a new "relationship" between fields of two different tables
  4. The connecting lines between any two tables should automatically be redrawn, when the tables are dragged around, so that they always connect to/from whichever side is closest to the "other table"

To accomplish #4, I call jsPlumb.makeSource() and jsPlumb.makeTarget() on each "tile" that represents a field, rather than creating specific endpoints on the right and left side of each tile. That way jsPlumb can manage the redrawing of the connecting lines to whichever side of the "field" tile that is closer to the tile its connected to.

However, to accomplish #2 I'm applying jQuery sortable to the fields to give the user the "drag to re-order" feature. This creates a conflict as to which "action" you're requesting when you click on a field...sorting or initiating a jsPlumb connection? So to each field I append a red div (with a class of ".item-hit.area") and I add a filter to the makeSource() call so that only that red div can be used to create a new jsPlumb connection.

jsPlumb.makeSource($('.item'), {
    filter:function(event, element) {
        return ($(event.target).hasClass('item_hit_area'));
    }
    ....
}

So now clicking on the red div tells jsPlumb to initiate a connection, or clicking anywhere else within a "field" element is passed through to jQuery sortable().

Hopefully those requirements are clear. Here is how to recreate the problem.

  • drag from the red tile on "Foo 1", over to the body of "Bar 2" to draw a new relationship between the two tables
  • drag the foos table around (by its title) to see that #4 is working properly (all the lines redraw properly)
  • now drag "Foo 1" down in the list of items inside "All my Foos", so that instead of being the top-most item in the list, it is the second or third item. So far so good, as you drag, jsPlumb continues to update the connecting line properly
  • the problem arises when you then drag the Foos table to move it around. Once you do that, suddenly the line connecting "Foo 1" to "Bar 2" is in the wrong place. Unfortunately I don't have enough reputation on stackoverflow to post an image. But try it and you'll see the line jumps to the wrong spot.

Stranger still is that if you drag the other table (the "All my Bars" table), the connecting line jumps back to the correct position on both ends. It's only when you drag the table that was on the "source" side of the jsPlumb connection that it gets confused and draws the line in the wrong place.


Solution

  • The problem is that jsPlumb caches the offsets of child elements of some draggable. So after you sort you need to tell jsPlumb to recalculate those offsets, as in this fiddle:

    http://jsfiddle.net/S7gVa/17/

    notice the line:

            jsPlumb.recalculateOffsets($(ui.item).parents(".draggable"));
    

    in the sortable's stop callback.