0

jquery ui draggable does not allow to use arrow keys in keyboard to move elements. Only mouse can used.

For this keyboard event handler is created using

$(".designer-panel-body").keydown(function(e) {
    switch(e.which) {
        case 37: // left
           $(".ui-selected").each(function() {
             var $this = $(this);
                $this.css({
                  left: $this.left -2
                });
              });
            break;

        case 38: // up
           $(".ui-selected").each(function() {
             var $this = $(this);
                $this.css({
                  top: $this.top -2
                });
              });
        break;

        case 39: // right
           $(".ui-selected").each(function() {
             var $this = $(this);
                $this.css({
                  left: $this.left +2
                });
              });
        break;

        case 40: // down
            $(".ui-selected").each(function() {
             var $this = $(this);
                $this.css({
                  top: $this.top +2
                });
              });
        break;

        default: return; // exit this handler for other keys
    }
    e.preventDefault(); // prevent the default action (scroll / move caret)
});

But keydown event does not occur. To reproduse, run code below, select elements and try to use arrows to drag. Arrow keys are ignored. How to fix this so that array keys can used to drag pixel by pixel ?

Fiddle: https://jsfiddle.net/bgx8gpwc/2/

<!DOCTYPE html>
<html>

<head>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <link rel="stylesheet" href="http://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
  <style>
    .designer-panel-body {
      min-height: 1px;
      overflow: hidden;
      margin: 0;
      padding: 0;
    }
    .panel-footer {
      background-color: inherit;
    }
    .designer-panel,
    .designer-resetmargins {
      margin: 0;
      padding: 0;
    }
    .designer-verticalline,
    .designer-horizontalline,
    .designer-rectangle {
      font-size: 1pt;
      border: 1px solid #000000;
    }
    .designer-field {
      border: 1px solid lightgray;
      white-space: pre;
      overflow: hidden;
    }
    .ui-selecting {
      background-color: lightskyblue;
      color: white;
    }
    .ui-selected {
      background-color: lightskyblue;
      border-color: darkblue;
      color: white;
    }
    .designer-label {
      white-space: pre;
      /*overflow: hidden;*/
    }
    .designer-field,
    .designer-label {
      font-family: "Times New Roman";
      font-size: 10pt;
      z-index: 2;
    }
    .designer-verticalline,
    .designer-horizontalline,
    .designer-rectangle,
    .designer-field,
    .designer-image,
    .designer-label {
      position: absolute;
    }
  </style>
  <script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
  <script src="http://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>

  <script>
    function getpos(e) {
      return {
        X: e.pageX,
        Y: e.pageY
      };
    }

    function Rect(start, stop) {
      this.left = Math.min(start.X, stop.X);
      this.top = Math.min(start.Y, stop.Y);
      this.width = Math.abs(stop.X - start.X);
      this.height = Math.abs(stop.Y - start.Y);
    }

    $(function() {
      var startpos;
      var selected = $([]),
        offset = {
          top: 0,
          left: 0
        };
      $(".designer-verticalline, .designer-rectangle, .designer-field, .designer-image").resizable();

      // http://stackoverflow.com/questions/705250/is-there-a-jquery-plugin-which-combines-draggable-and-selectable#8643716
      // teha: seal on ka mousedown mis andis viga, kaseda kasutada
      var $liigutatavad = $(".designer-verticalline, .designer-horizontalline, .designer-rectangle, .designer-field, .designer-image, .designer-label");
      $liigutatavad.draggable({
        start: function(event, ui) {
          var $this = $(this);

          if ($this.hasClass("ui-selected")) {
            // if this is selected, attach current offset
            // of each selected element to that element
            selected = $(".ui-selected").each(function() {
              var el = $(this);
              el.data("offset", el.offset());
            });
          } else {
            // if this is not selected, clear current selection
            selected = $([]);
            $liigutatavad.removeClass("ui-selected");
          }
          offset = $this.offset();
        },

        drag: function(event, ui) {
          // drag all selected elements simultaneously
          var dt = ui.position.top - offset.top,
            dl = ui.position.left - offset.left;
          selected.not(this).each(function() {
            var $this = $(this);
            var elOffset = $this.data("offset");
            $this.css({
              top: elOffset.top + dt,
              left: elOffset.left + dl
            });
          });

          // this does not fix the issue:
          //$(".designer-verticalline, .designer-rectangle, .designer-field, .designer-image").resizable();
        }
      });

      // ...but manually implement selection to prevent interference from draggable()
      $(".designer-panel-body").on("click", "div", function(e) {
        if ( /*!e.metaKey &&*/ !e.shiftKey && !e.ctrlKey) {
          // deselect other elements if meta/shift not held down
          $(".designer-panel-body").removeClass("ui-selected");
          $(this).addClass("ui-selected");
        } else {
          if ($(this).hasClass("ui-selected")) {
            $(this).removeClass("ui-selected");
          } else {
            $(this).addClass("ui-selected");
          }
        }

        //var selectable = $("#container").data("selectable");
        //selectable.refresh();
        //$( ".designer-panel-body" ).data("selectable")._mouseStop(null);
      });

      $(".designer-panel-body").selectable({});


$(".designer-panel-body").keydown(function(e) {
switch(e.which) {
    case 37: // left
       $(".ui-selected").each(function() {
         var $this = $(this);
            $this.css({
              left: $this.left -2
            });
          });
        break;

    case 38: // up
       $(".ui-selected").each(function() {
         var $this = $(this);
            $this.css({
              top: $this.top -2
            });
          });
    break;

    case 39: // right
       $(".ui-selected").each(function() {
         var $this = $(this);
            $this.css({
              left: $this.left +2
            });
          });
    break;

    case 40: // down
        $(".ui-selected").each(function() {
         var $this = $(this);
            $this.css({
              top: $this.top +2
            });
          });
    break;

    default: return; // exit this handler for other keys
}
e.preventDefault(); // prevent the default action (scroll / move caret)
});


    });
  </script>

</head>

<body>
  <div class='panel designer-panel'>
    <div class='panel-body designer-panel-body panel-warning' style='height:9.37cm'>

      <div class='designer-field' contenteditable='true' style='top:2.30cm;left:5.84cm;width:10.24cm;height:0.63cm;font-family:Arial;font-size:14pt;font-weight:bold;'>vnimi+&#39; &#39;+dok.tasudok</div>
      <div class='designer-field' contenteditable='true' style='top:2.30cm;left:16.37cm;width:2.68cm;height:0.61cm;font-size:14pt;'>DOK.kuupaev</div>
      <div class='rectangle' style='border-width: 1px;background-color:#FFFFFF;top:2.99cm;left:1.34cm;width:18.05cm;height:5.29cm'></div>
      <div class='designer-field' contenteditable='true' style='top:3.01cm;left:1.53cm;width:9.71cm;height:0.55cm;font-size:12pt;'>m.FIRMA</div>
      <div class='designer-field' contenteditable='true' style='top:3.01cm;left:12.13cm;width:3.13cm;height:0.53cm;font-size:12pt;'>ise.telefon</div>
      <div class='designer-field' contenteditable='true' style='top:3.01cm;left:17.11cm;width:1.89cm;height:0.55cm;font-size:12pt;text-align:right;'>ise.regnr</div>
      <div class='designer-label' contenteditable='true' style='top:3.04cm;left:11.39cm;text-align:right;font-size:12pt;'>Tel.</div>
      <div class='designer-label' contenteditable='true' style='top:3.04cm;left:15.71cm;font-size:12pt;'>Reg.Nr</div>
      <div class='designer-field' contenteditable='true' style='top:3.62cm;left:1.55cm;width:9.45cm;height:0.55cm;font-size:12pt;'>ise.tanav</div>
      <div class='designer-field' contenteditable='true' style='top:3.70cm;left:15.16cm;width:3.37cm;height:0.55cm;font-size:12pt;'>ise.vatpayno</div>
      <div class='designer-label' contenteditable='true' style='top:3.72cm;left:12.89cm;text-align:right;font-size:12pt;'>KMKR nr</div>
      <div class='designer-field' contenteditable='true' style='top:4.30cm;left:1.58cm;width:9.08cm;height:0.55cm;font-size:12pt;'>rtri(ise.postiindek)+&#39; &#39;+rtri(ise.piirkond)</div>
      <div class='designer-field' contenteditable='true' style='top:4.30cm;left:14.66cm;width:4.34cm;height:0.55cm;font-size:12pt;text-align:right;'>aarve(dok.arvekonto, &#39;konto.arveldusar&#39;)</div>
      <div class='designer-label' contenteditable='true' style='top:4.33cm;left:13.89cm;font-size:12pt;'>A/A</div>
      <div class='designer-horizontalline' style='border-width: 1px;top:4.96cm;left:1.34cm;width:18.03cm;height:0.00cm'></div>
      <div class='designer-field' contenteditable='true' style='top:5.04cm;left:17.13cm;width:1.89cm;height:0.55cm;font-size:12pt;text-align:right;'>klient.regnr</div>
      <div class='designer-field' contenteditable='true' style='top:5.06cm;left:4.18cm;width:12.71cm;height:0.55cm;font-size:12pt;'>klient.nimi</div>
      <div class='designer-label' contenteditable='true' style='top:5.06cm;left:15.74cm;font-size:12pt;'>Reg.Nr</div>
      <div class='designer-label' contenteditable='true' style='top:5.09cm;left:1.63cm;font-size:12pt;'>Maksja</div>
      <div class='designer-field' contenteditable='true' style='top:5.72cm;left:1.53cm;width:11.68cm;height:0.55cm;font-size:12pt;'>klient.tanav</div>
      <div class='designer-field' contenteditable='true' style='top:5.72cm;left:15.18cm;width:3.37cm;height:0.55cm;font-size:12pt;'>klient.vatpayno</div>
      <div class='designer-label' contenteditable='true' style='top:5.75cm;left:12.92cm;text-align:right;font-size:12pt;'>KMKR nr</div>
      <div class='designer-field' contenteditable='true' style='top:6.38cm;left:1.53cm;width:11.84cm;height:0.55cm;font-size:12pt;'>rtri(klient.postiindek)+&#39; &#39; +rtri(klient.piirkond)</div>
      <div class='designer-field' contenteditable='true' style='top:6.38cm;left:13.47cm;width:3.37cm;height:0.55cm;font-size:12pt;'>sql(&quot;sele transfld(&#39;nimetus&#39;, &#39;riik&#39;, rapopref()) from riik where kood=klient.riik2&quot;, &#39;&#39; )</div>
      <div class='designer-field' contenteditable='true' style='top:6.99cm;left:3.71cm;width:12.16cm;height:1.16cm;font-size:12pt;'>klient.aadress</div>
      <div class='designer-label' contenteditable='true' style='top:7.01cm;left:1.45cm;text-align:right;font-size:12pt;'>Postiaadress</div>
      <div class='designer-field' contenteditable='true' style='top:8.33cm;left:3.95cm;width:2.11cm;height:0.55cm;font-size:12pt;'>dok.tasukuup</div>
      <div class='designer-field' contenteditable='true' style='top:8.33cm;left:6.08cm;width:8.05cm;height:0.55cm;font-size:12pt;'>eval( &#39;maksetin.&#39; +left(rapopref()+&#39;tingimus&#39;,10))</div>
      <div class='designer-label' contenteditable='true' style='top:8.35cm;left:1.45cm;font-size:12pt;'>Makset&#228;htaeg</div>
      <div class='designer-field' contenteditable='true' style='top:8.91cm;left:1.45cm;width:13.66cm;height:0.45cm;'>iif(!empty(dok.saaja), IR(&quot;Saaja: &quot;)+sql(&#39;sele rtri(nimi)+&quot; &quot;+rtri(tanav)+&quot; &quot;+rtri(piirkond)+&quot; &quot;+rtri(postiindek) from klient where kood=dok.saaja&#39;,&#39;&#39;),&#39;&#39;)</div>
    </div>
    <div class='bg-warning'>
      <div class='panel-footer'><i class='glyphicon glyphicon-chevron-up'></i> GroupHeader 1: str(dokumnr)+str(koopia,2)</div>
    </div>
  </div>
</body>

</html>
Andrus
  • 26,339
  • 60
  • 204
  • 378
  • 1
    "Dragging" an element with arrow keys is called "Nudging" You have to make a new action on each arrow key on keydown to Nudge the element pixel by pixel – ntgCleaner Jan 09 '16 at 22:49
  • Should drag method in code executed in keydown event for each arrow key? – Andrus Jan 09 '16 at 23:01
  • You won't be able to "drag" with arrow keys. You say you want to move an element pixel by pixel. You'll probably need to forget about using `draggable` for your purpose and make new functions for each arrow key. – ntgCleaner Jan 09 '16 at 23:04
  • Check here: http://stackoverflow.com/questions/1402698/binding-arrow-keys-in-js-jquery – ntgCleaner Jan 09 '16 at 23:05
  • @Andrus like ntg mentioned, clicking arrow keys != dragging, the generated event object won't have information such as the mouse positions, which jquery ui makes use of. *"How to fix this"* - Did jQuery ui team tell you that draggable has such functionality..? I guess not. Nothing is broken here. – T J Jan 10 '16 at 04:49
  • @ntgCleaner I tried your suggestion in https://jsfiddle.net/bgx8gpwc/2/ but keydown event does not fire. I replaced `dragging` with `move elements` in question – Andrus Jan 10 '16 at 09:17

1 Answers1

1

This is an example of how to "drag" DOM elements with arrow key's without using jQuery UI draggable.

In this example jQuery .before() and .after() functions are used to place elements to a new location.

$('.el').click(function(){     
   $('.el').removeClass('active'); 
      $(this).addClass('active');  
});

$('body').on('keyup',function(e){

switch(e.which) {
        case 37: // left
          var tmp_id = $('.el.active').prev().attr('id'); 
          $('#' + tmp_id).animate({ opacity: 0 }, 100, function(){
            $('.el.active').after($('.el.active').prev());  
          });         
          $('#' + tmp_id).animate({ opacity: 1 }, 300);
        break;

        case 38: // up
          var active_index = $('.el.active').index();
          var index_min = $('.el.active').parent().children().first().index();          
          var index_max = $('.el.active').parent().children().last().index();   
          var next_element = $('.el.active').parent().prev().children(':eq('+active_index+')');
          
          if(index_min === active_index){
           var prev_el = next_element.next();
            $('.el.active').before(next_element);
           $(prev_el).before($('.el.active'));
          }
          
          if(index_max === active_index || (active_index > index_min && active_index < index_max )){
           var prev_el = next_element.prev(); 
            $('.el.active').before(next_element);
            $(prev_el).after($('.el.active'));
          }
                              
        break;

        case 39: // right
          var tmp_id = $('.el.active').next().attr('id');
          $('#' + tmp_id).animate({ opacity: 0 }, 100, function(){
            $('.el.active').before($('.el.active').next());
          });                   
          $('#' + tmp_id).animate({ opacity: 1 }, 300);                   
        break;

        case 40: // down
          var active_index = $('.el.active').index();
          var index_min = $('.el.active').parent().children().first().index();          
          var index_max = $('.el.active').parent().children().last().index();          
       var next_element = $('.el.active').parent().next().children(':eq('+active_index+')');
          var prev_el = next_element.prev(); 
          
          if(index_min === active_index){
           var prev_el = next_element.next();
            $('.el.active').before(next_element);
           $(prev_el).before($('.el.active'));
          }
          
          if(index_max === active_index || (active_index > index_min && active_index < index_max )){
           var prev_el = next_element.prev(); 
            $('.el.active').before(next_element);
           $(prev_el).after($('.el.active'));
          }
                                   
        break;

        default: return; // exit this handler for other keys
    }
    e.preventDefault();

});
.container{
  width: 100%;    
}

div[id^="el-wr"]{  
  width: 280px;  
  margin: 0 auto;  
}

.el{
  width: 60px;
  height: 60px;
  background-color: #aaa; 
  margin: 5px;
  float: left; 
  cursor: pointer;   
  text-align: center;
  line-height: 60px;
  font-size: 28px;
  color: white;
}

.active{
  background-color: #525252;   
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<div class="container">

  <div id="el-wr0">

          <div class="el" id="index1">1</div>

          <div class="el" id="index2">2</div>

          <div class="el" id="index3">3</div>
          
          <div class="el" id="index4">4</div>

  </div>
  
  <div id="el-wr1">

          <div class="el" id="index5">5</div>

          <div class="el" id="index6">6</div>

          <div class="el" id="index7">7</div>
          
          <div class="el" id="index8">8</div>

  </div>
  
  <div id="el-wr2">

          <div class="el" id="index9">9</div>

          <div class="el" id="index10">10</div>

          <div class="el" id="index11">11</div>
          
          <div class="el" id="index12">12</div>

  </div>

  


</div>
JuZer
  • 775
  • 2
  • 7
  • 14
  • This moves only single element and puts it to same place as original elemen. Code in question allowa to select multiple elements. How to move multiple selected elements together pixel by pixel using arrows ? – Andrus Jan 10 '16 at 09:02
  • 1
    @Andrus, not realy. It's possible to move multiple elements with it. Only it needs a small modification. This example will show You how to select and drag multiple elements by 2px as in your example by using arrow keys. https://jsfiddle.net/zejur/bgx8gpwc/8/ – JuZer Jan 10 '16 at 15:56
  • Thank you. I used position(), for example for left arrow `$this.css({ left: $this.position().left - 1 });` Will this produce same result as in your code which uses offset()? – Andrus Jan 10 '16 at 16:51
  • @Andrus, You are welcome! I think yes, it will produce the same result. `position` will set the positon of element relative to the parent, but `offset` relative to the document. In your case it will do the same thing but with different numbers. – JuZer Jan 10 '16 at 17:34