Benjamin Brownlee
Benjamin Brownlee

Reputation: 454

jQuery use right click to rotate div while dragging

I am using a drag and drop interface for a game I am developing. I am currently using jQuery draggable and droppable to manage my reverting, limitations, grid snap, etc, but I also want to make make it such that if a user right clicks while dragging, the game piece rotates but does not stop dragging. However, the right click of the mouse automatically ends the drag event, even if I put an event listener with event.stopPropagation() and event.preventDefault() on right click. Any ideas how to achieve this?

I have a half working snippet here, where a right click does rotate the game piece but causes the drag to stop. Note that copying it from my source messed with some of the scaling and offsetting but functionality for this demos's purpose is still intact.

var board = function(parentDIV, size, options = {}) {
    this.ship = this.ship.bind(this);
    this.flip = this.flip.bind(this);
    this.update = this.update.bind(this);
    this.ajaxExport = this.ajaxExport.bind(this);
    this.scale = this.scale.bind(this);
    
    this.size = size / 10;
    this.callback = {
        onvalid: options.onvalid,
        oninvalid: options.oninvalid,
        ondrag: options.ondrag,
        onstart: options.onstart,
        ondrop: options.ondrop
    };
    
    this.containerDIV = $("<div>").css({"position": "relative", "padding": "-1px"}).attr("id", "board-container");
    this.boardDIV = $("<div>").css({"display": "table", "border-collapse": "collapse"}).on("contextmenu", function() { event.stopPropagation; event.preventDefault(); });
    var rowDIV = $("<div>").css({"display": "table-row"});
    var cellDIV = $("<div>").css({"display": "table-cell", "border": "2px #000 solid", "padding": "-1px", "width": this.size.toString() + "px", "height": this.size.toString() + "px"})
    for(var i = 0; i < 10; i++) {
        var cloneRow = rowDIV.clone();
        for(var ii = 0; ii < 10; ii++) cloneRow.append(cellDIV.clone());
        this.boardDIV.append(cloneRow);
    }
    
    this.ships = [
        this.ship("carrier", 1, 5, 1, 1),
        this.ship("battleship", 4, 1, 4, 3),
        this.ship("cruiser", 1, 3, 8, 6),
        this.ship("submarine", 3, 1, 2, 8),
        this.ship("destroyer", 1, 2, 0, 8)
    ];
    

    this.containerDIV.append(this.boardDIV);
    $(parentDIV).append(this.containerDIV);
    

    this.update();
};
board.prototype.update = function() {
    this.layout = [
        [0,0,0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0,0,0]
    ];
    
    for(var i = 0; i < this.ships.length; i++) {
        var width = depx(this.ships[i].css("width")) / this.size;
        var height = depx(this.ships[i].css("height")) / this.size;
        var left = depx(this.ships[i].css("left")) / this.size;
        var top = depx(this.ships[i].css("top")) / this.size;
        
        if(width == 1) for(var ii = top; ii < top + height; ii++) this.layout[ii][left] = i + 1;
        if(height == 1) for(var ii = left; ii < left + width; ii++) this.layout[top][ii] = i + 1;
    }
    
    var pattern = new RegExp(/(?=^[^1]*(?:1,1,1,1,1|1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1)[^1]*$)(?=^[^2]*(?:2,2,2,2|2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2)[^2]*$)(?=^[^3]*(?:3,3,3|3\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+3\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+3)[^3]*$)(?=^[^4]*(?:4,4,4|4\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+4\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+4)[^4]*$)(?=^[^5]*(?:5,5|5\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+5)[^5]*$)^\[\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\]\]$/);
    
    if(pattern.test(JSON.stringify(this.layout))) if(this.callback.onvalid) this.callback.onvalid();
    else if(this.callback.oninvalid) this.callback.oninvalid();
    
    
    function depx(str) {
        return Number(str.substring(0, str.length - 2));
    }
};
board.prototype.ajaxExport = function() {
    var inputs = {
        type: "uploadGameBoard",
        game: null,
        board: this.layout
    };
    AJAXrequest("GET", inputs, function(response) {
        if(response == "success") {
            
        }
    });
};
board.prototype.random = function() {
    
};
board.prototype.default = function() {
    
};
board.prototype.scale = function() {
    console.log(this.containerDIV.parent());
};
board.prototype.set = function() {
    
};
board.prototype.ship = function(name, width, height, left, top) {
    return $("<div>")
        .attr("id", name)
        .addClass("ship")
        .draggable({
            containment: "parent",
            preventCollision: true,
            grid: [this.size, this.size], 
            cursor: "none", 
            start: function() { },
            stop: this.update,
            drag: this.validate })
        .css({
            "position": "absolute", 
            "width": (width * this.size).toString() + "px", 
            "height": (height * this.size).toString() + "px", 
            "background-color": "#000", 
            "top": (top * this.size).toString() + "px", 
            "left": (left * this.size).toString() + "px"})
        .on("contextmenu", function() { event.stopPropagation(); event.preventDefault(); })
        .on("mousedown", this.flip)
        .appendTo(this.boardDIV);
};
board.prototype.validate = function() {

};
board.prototype.flip = function() {
    if(event.which == 3) { 
        var ship = $(event.target); 
        var width = ship.css("height"); 
        var height = ship.css("width");
        var left = ship.css("left");
        var top = ship.css("top");
        
        ship.css({"width": width, "height": height}); 
        
        var offsetx = depx(width) + depx(left);
        var offsety = depx(height) + depx(top);
        var size = this.size * 10;
        
        if(offsetx > size) ship.css("left", String(size - depx(width)) + "px");
        else if(offsety > size) ship.css("top", String(size - depx(height)) + "px");
        
        this.update();
    }
    
    function depx(str) {
        return Number(str.substring(0, str.length - 2));
    }
};
board.prototype.disable = function() {
    for(var i = 0; i < this.ships.length; i++) this.ships[i].draggable( "option", "disabled", true );
};
board.prototype.enable = function() {
    for(var i = 0; i < this.ships.length; i++) this.ships[i].draggable( "option", "enable", true );
};

window.onload = function() {new board(document.body, 500); };
<!doctype html>
<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
    <style>
      body {
        margin 0;
       }
    </style>
  </head>
  <body>
  </body>
</html>

Upvotes: 3

Views: 498

Answers (1)

Twisty
Twisty

Reputation: 30893

I got it to work... I'm not sure what I changed exactly, but when I switched the .on() to this, it started working as expected for the most part:

  .on({
    contextmenu: function(event) {
      console.log("EVENT: " + event.type, event.target.id);
      event.preventDefault();
      event.stopImmediatePropagation();
    },
    mousedown: me.flip,
    mouseup: function(event) {
      console.log("EVENT: " + event.type, event.target.id);
      console.log(event);
      if (event.which === 3) {
        return false;
      }
    }
  })

Basically, what I could see happening was that events like mousedown, contextmenu, and mouseup would trigger dragstop. It made sense with mouseup since draggable might be looking for that event to trigger dragstop.

Working Test: https://jsfiddle.net/Twisty/ue2qpp2z/6/

JavaScript

function Board(parentDIV, size, options) {
  if (options == "undefined") {
    options = {};
  }

  var me = this;
  /**
  // Define Functions
  ***/
  this.ship = function(name, width, height, left, top) {
    console.log("Creating Ship: " + name, width, height, left, top);
    return $("<div>", {
        id: name,
        class: "ship"
      })
      .draggable({
        containment: "parent",
        preventCollision: true,
        grid: [me.size, me.size],
        cursor: "none",
        start: function(event, ui) {
          console.log("EVENT: " + event.type, event.target.id);
          console.log(event);
          me.dragging = true;
        },
        stop: function(event, ui) {
          console.log("EVENT: " + event.type, event.target.id);
          me.dragging = false;
          me.update.apply(me);
        },
        drag: function(event, ui) {
          console.log("EVENT: " + event.type, event.target.id);;
          //me.validate();
        }
      })
      .css({
        position: "absolute",
        width: (width * me.size).toString() + "px",
        height: (height * me.size).toString() + "px",
        "background-color": "#000",
        top: (top * me.size).toString() + "px",
        left: (left * me.size).toString() + "px"
      })
      .on({
        contextmenu: function(event) {
          console.log("EVENT: " + event.type, event.target.id);
          event.preventDefault();
          event.stopImmediatePropagation();
        },
        mousedown: me.flip,
        mouseup: function(event) {
          console.log("EVENT: " + event.type, event.target.id);
          console.log(event);
          if (event.which === 3) {
            return false;
          }
        }
      })
      .appendTo(me.boardDIV);
  };

  this.flip = function(event) {
    if (event.which == 3) {
      console.log("EVENT: " + event.type, event.target.id);
      console.log("Performing Flip: ", event.target.id);
      var ship = $(event.target);
      var width = ship.css("height");
      var height = ship.css("width");
      var left = ship.css("left");
      var top = ship.css("top");

      ship.css({
        "width": width,
        "height": height
      });

      var offsetx = depx(width) + depx(left);
      var offsety = depx(height) + depx(top);
      var size = me.size * 10;

      if (offsetx > size) ship.css("left", String(size - depx(width)) + "px");
      else if (offsety > size) ship.css("top", String(size - depx(height)) + "px");
      if ($(event.target).hasClass("ui-draggable-dragging")) {
        // Restart Drag Event
        console.log("drag & mousedown, triggering `drag` again");
        $(event.target).trigger(jQuery.Event("drag"));
      }
      me.update(event);
    }

    function depx(str) {
      return Number(str.substring(0, str.length - 2));
    }
  };

  this.update = function(event) {
    console.log("Board Update");
    me.layout = [
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ];

    for (var i = 0; i < me.ships.length; i++) {
      var width = depx(me.ships[i].css("width")) / me.size;
      var height = depx(me.ships[i].css("height")) / me.size;
      var left = depx(me.ships[i].css("left")) / me.size;
      var top = depx(me.ships[i].css("top")) / me.size;
      var ii;

      if (width == 1)
        for (ii = top; ii < top + height; ii++) me.layout[ii][left] = i + 1;
      if (height == 1)
        for (ii = left; ii < left + width; ii++) me.layout[top][ii] = i + 1;
    }

    var pattern = new RegExp(/(?=^[^1]*(?:1,1,1,1,1|1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1)[^1]*$)(?=^[^2]*(?:2,2,2,2|2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2)[^2]*$)(?=^[^3]*(?:3,3,3|3\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+3\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+3)[^3]*$)(?=^[^4]*(?:4,4,4|4\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+4\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+4)[^4]*$)(?=^[^5]*(?:5,5|5\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+5)[^5]*$)^\[\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\]\]$/);

    if (pattern.test(JSON.stringify(me.layout)))
      console.log("Update: Calling Callbacks");
    if (me.callback.onvalid) {
      console.log("Uopdate: Calling 'onvalid'");
      me.callback.onvalid.apply(me);
    } else if (me.callback.oninvalid) {
      console.log("Update: Calling 'oninvalid'");
      me.callback.oninvalid.apply(me);
    }

    function depx(str) {
      return Number(str.substring(0, str.length - 2));
    }

    if (event) {
      console.log("Update: Event found: " + event.type);
      if (event.type == "mousedown" && me.dragging) {
        $(event.target).trigger(jQuery.Event("dragstart"));
        event.stopImmediatePropagation();
      }
    }
  };

  this.scale = function() {
    console.log(me.containerDIV.parent());
  };

  this.disable = function() {
    console.log("Disable");
    for (var i = 0; i < me.ships.length; i++) me.ships[i].draggable("option", "disabled", true);
  };

  this.enable = function() {
    console.log("Enable");
    for (var i = 0; i < me.ships.length; i++) me.ships[i].draggable("option", "enable", true);
  };

  /***
  // Define Variables
  ***/

  this.size = size / 10;
  this.callback = {
    onvalid: options.onvalid,
    oninvalid: options.oninvalid,
    ondrag: options.ondrag,
    onstart: options.onstart,
    ondrop: options.ondrop
  };
  this.dragging = false;

  this.containerDIV = $("<div>").css({
    "position": "relative",
    "padding": "-1px"
  }).attr("id", "board-container");

  this.boardDIV = $("<div>").css({
    "display": "table",
    "border-collapse": "collapse"
  }).on("contextmenu", function(event) {
    console.log("EVENT: " + event.type, event.target.id);
    if ($(event.target).not(".ui-draggable-dragging")) {
      event.stopPropagation();
      event.preventDefault();
    }
  });

  var rowDIV = $("<div>").css({
    "display": "table-row"
  });

  var cellDIV = $("<div>").css({
    "display": "table-cell",
    "border": "2px #000 solid",
    "padding": "-1px",
    "width": this.size.toString() + "px",
    "height": this.size.toString() + "px"
  });

  for (var i = 0; i < 10; i++) {
    var cloneRow = rowDIV.clone();
    for (var ii = 0; ii < 10; ii++) cloneRow.append(cellDIV.clone());
    this.boardDIV.append(cloneRow);
  }

  this.ships = [
    this.ship("carrier", 1, 5, 1, 1),
    this.ship("battleship", 4, 1, 4, 3),
    this.ship("cruiser", 1, 3, 8, 6),
    this.ship("submarine", 3, 1, 2, 8),
    this.ship("destroyer", 1, 2, 0, 8)
  ];

  this.containerDIV.append(this.boardDIV);
  $(parentDIV).append(this.containerDIV);

  this.update();
}

$(function() {
  Board(document.body, 500, {});
});

Hope that helps.

Update

I think I know where the issue lands, related to the event x and y. When the element is flipped, in some cases, the mouse is no longer over the element and I think this is causing the dragstop to fire. I will see if I can dig into jQuery UI to see if I can find how draggable handles this scenario.

Upvotes: 1

Related Questions