Search code examples
javascriptjqueryjquery-uijquery-ui-droppable

JQuery UI set up my own tolerance


I´m working on game for my little baby. I want to change default tolerance in droppable object. I know, that there are default values like touch, fit, intersect and pointer. But in my app it doesn't work correctly, because I want something between fit and intersect. When I use fit, it is too difficult to insert into correct position, and when I use intersect, it is too light. Here is my HTML source:

<!DOCTYPE html>
<html lang="sk">
    <head>
        <title>
            Puzzle Slovakia
        </title>
        <script src="jquery-2.1.3.min.js"></script>
        <script src="jquery-ui.js"></script>
        <script src="jquery.ui.touch-punch.js"></script>
        <script type="text/JavaScript" src="javas.js"></script>
        <link rel="stylesheet" type="text/css" href="style.css">
    </head>
    <body>
        <section>
            <div id="game">
                <div id="complete">
                    <img src="images/map.png">
                </div>
            </div>
        </section>
    </body>
</html>

And there is my JS code:

window.onload = function() {
    var hra = document.getElementById("game");
    hra.innerHTML+="<div class='place' id='BAp'></div><img class='puzzle' id='BA' src='images/puzzle/BA.png'>";
    hra.innerHTML+="<div class='place' id='TTp'></div><img class='puzzle' id='TT' src='images/puzzle/TT.png'>";
    hra.innerHTML+="<div class='place' id='TNp'></div><img class='puzzle' id='TN' src='images/puzzle/TN.png'>";
    hra.innerHTML+="<div class='place' id='ZAp'></div><img class='puzzle' id='ZA' src='images/puzzle/ZA.png'>";
    hra.innerHTML+="<div class='place' id='BBp'></div><img class='puzzle' id='BB' src='images/puzzle/BB.png'>";
    hra.innerHTML+="<div class='place' id='KEp'></div><img class='puzzle' id='KE' src='images/puzzle/KE.png'>";
    hra.innerHTML+="<div class='place' id='PPp'></div><img class='puzzle' id='PP' src='images/puzzle/PP.png'>";
    hra.innerHTML+="<div class='place' id='NRp'></div><img class='puzzle' id='NR' src='images/puzzle/NR.png'>"; 

    $('.puzzle').draggable( {
        containment:'parent',
        cursor:'move',
    });

    $('.place').droppable ( {
        accept: '.puzzle',
        drop: dropPicture,
        tolerance: 'intersect'
    });
}

function dropPicture( event, ui ) {
  if ( $(this).attr('id') == ui.draggable.attr('id') + "p" ) {
    ui.draggable.draggable( 'disable' );
    ui.draggable.attr('class','done');
    $(this).droppable( 'disable' );
    ui.draggable.position( { of: $(this), my: 'left top', at: 'left top' } );
  } 
}

Solution

  • You could modify intersect function of jquery-ui to allow this. Most flexible way would be to be able to add a custom function returning true when droppable should be activated, and false when not. This would give complete control on the tolerance. Something like this:

    $.ui.intersect = function(draggable, droppable, toleranceMode) {
    
      if (!droppable.offset) {
        return false;
      }
    
      var draggableLeft, draggableTop,
        x1 = (draggable.positionAbs || draggable.position.absolute).left,
        y1 = (draggable.positionAbs || draggable.position.absolute).top,
        x2 = x1 + draggable.helperProportions.width,
        y2 = y1 + draggable.helperProportions.height,
        l = droppable.offset.left,
        t = droppable.offset.top,
        r = l + droppable.proportions.width,
        b = t + droppable.proportions.height;
      
      // With next line this allows to set a function as toleranceMode
      if (typeof toleranceMode === "function") {
        return toleranceMode(draggable, droppable);
      } else {
        // If it's not a function, then normal code applies
        switch (toleranceMode) {
          case "custom":
            return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half
              x2 - (draggable.helperProportions.width / 2) < r && // Left Half
              t < y1 && // Bottom Half
              b > y1 + 15); // Top Half
          case "fit":
            return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
          case "intersect":
            return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half
              x2 - (draggable.helperProportions.width / 2) < r && // Left Half
              t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half
              y2 - (draggable.helperProportions.height / 2) < b); // Top Half
          case "pointer":
            draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left);
            draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top);
            return isOverAxis(draggableTop, t, droppable.proportions().height) && isOverAxis(draggableLeft, l, droppable.proportions().width);
          case "touch":
            return (
              (y1 >= t && y1 <= b) || // Top edge touching
              (y2 >= t && y2 <= b) || // Bottom edge touching
              (y1 < t && y2 > b) // Surrounded vertically
            ) && (
              (x1 >= l && x1 <= r) || // Left edge touching
              (x2 >= l && x2 <= r) || // Right edge touching
              (x1 < l && x2 > r) // Surrounded horizontally
            );
          default:
            return false;
        }
      }
    
    };
    
    $("#draggable").draggable();
    
    
    $("#droppable").droppable({
      hoverClass: "hover",
      tolerance: function(draggable, droppable) {
        // Calculations will be made on the small div 
        // inside the draggable. This is the equivalent 
        // of touch tolerance, but applied not to the draggable
        // itself, but to another div
        var toleranceDiv = $('#toleranceDiv');
        x1 = toleranceDiv.offset().left,
          y1 = toleranceDiv.offset().top,
          x2 = x1 + toleranceDiv.width(),
          y2 = y1 + toleranceDiv.height(),
          l = droppable.offset.left,
          t = droppable.offset.top,
          r = l + droppable.proportions.width,
          b = t + droppable.proportions.height;
        return (
          (y1 >= t && y1 <= b) || // Top edge touching
          (y2 >= t && y2 <= b) || // Bottom edge touching
          (y1 < t && y2 > b) // Surrounded vertically
        ) && (
          (x1 >= l && x1 <= r) || // Left edge touching
          (x2 >= l && x2 <= r) || // Right edge touching
          (x1 < l && x2 > r) // Surrounded horizontally
        );
      },
      over: function(event, ui) {
        $('#draggable').css('opacity', 0.8);
      },
      out: function(event, ui) {
        $('#draggable').css('opacity', 0.5);
      },
      drop: function(event, ui) {
        console.log('dropped');
      }
    });
    #draggable {
      width: 100px;
      height: 100px;
      background-color: blue;
      opacity: 0.5;
      position: absolute;
    }
    #toleranceDiv {
      width: 20px;
      height: 20px;
      top: 30px;
      left: 30px;
      background-color: yellow;
      position: absolute;
    }
    #droppable {
      width: 200px;
      height: 200px;
      background-color: green;
      opacity: 1;
      top: 250px;
      left: 250px;
      position: absolute;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
    <script type="text/javascript" src="//code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
    <div id="droppable"></div>
    <div id="draggable">
      <div id="toleranceDiv">
      </div>
    </div>