Search code examples
jqueryjquery-uidraggablejquery-ui-draggable

Jquery draggable snap only to outmost elements


I created a grid where each column has a number asigned. I want a draggable div to snap to the borders of each column and then basically display the range from the outmost columns my div is snapped to.

Example is here: https://jsfiddle.net/Cataras/dpdLLcft/

$(function() {
  $(".draggable").draggable({
    snap: ".hour-full, .hour-half",
    snapMode: 'both',
    stop: function(event, ui) {
      /* Get the possible snap targets: */
      var snapped = $(this).data('ui-draggable').snapElements;

      /* Pull out only the snap targets that are "snapping": */
      var snappedTo = $.map(snapped, function(element) {
        return element.snapping ? element.item : null;
      });
      /* Display the results: */
      var result = '';
      $.each(snappedTo, function(idx, item) {
        result += $(item).text() + ", ";
      });

      $("#results").html("Snapped to: " + (result === '' ? "Nothing!" : result));
    }
  });
});

Code taken from this question: How to find out about the "snapped to" element for jQuery UI draggable elements on snap

However, it doesn't display only the column number on the left and right side of the div but also all the columns that are in between. And sometimes also the next ones to the right which the red bar clearly isn't even touching. Any suggestions?


Solution

  • The snappedTo array is a bit greedy and I would use left positioning to determine which element the dragged item is essentially over.

    Here is a working example: https://jsfiddle.net/Twisty/dpdLLcft/5/

    jQuery

    $(function() {
      $(".draggable").draggable({
        snap: ".hour-full, .hour-half",
        snapMode: 'both',
        stop: function(event, ui) {
          console.log("Drag stopped at Left: " + ui.offset.left);
          /* Get the possible snap targets: */
          var snapped = $(this).data('ui-draggable').snapElements;
          console.log($(this).data('ui-draggable'));
    
          /* Pull out only the snap targets that are "snapping": */
          var snappedTo = $.map(snapped, function(element) {
            if (element.snapping) {
              console.log("Found snapped element: " + $(element.item).text() + ". Left: " + element.left + " Width: " + element.width);
              return element;
            }
          });
          /* Display the results: */
          var result = '';
          $.each(snappedTo, function(idx, item) {
            if (ui.offset.left == item.left) {
              console.log(item);
              result = $(item.item).text() + ", ";
              result += $(snappedTo[idx + 3].item).text();
            }
          });
    
          $("#results").html("Snapped to: " + (result === '' ? "Nothing!" : result));
        }
      });
    });
    

    First, using your loop, I just grabbed all data from the elements that had snapping as true.

    Second, we loop through those and compare the left edge of our draggable to the various elements. We see this in the console:

    Drag stopped at Left: 48
    Found snapped element. Left: 28 Width: 20
    Found snapped element. Left: 48 Width: 20
    Found snapped element. Left: 68 Width: 20
    Found snapped element. Left: 88 Width: 20
    Found snapped element. Left: 108 Width: 20
    Found snapped element. Left: 128 Width: 20
    

    We can then compare this and determine which element we start at.

    if (ui.offset.left == item.left) 
    

    When the left offset is 48 and the left of the element is 48 (48 == 48), we then update the result:

    result = item.item.innerHTML + ", ";
    result += snappedTo[idx + 3].item.innerHTML;
    

    Since we know that the number of columns the draggable covers, and we know the start, we just get the info from the other element by increasing the index.

    Snapped to: 2, 5
    

    I think this is what you were trying to accomplish from your description. If you want to get the outer ones, simple adjust the index:

    result = snappedTo[idx - 1].item.innerHTML + ", ";
    result += snappedTo[idx + 4].item.innerHTML;
    

    This should get you want you want one way or another. Let me know if you have questions.