From a06e0c23631aba28cb163d055a2b4ddbb4549c01 Mon Sep 17 00:00:00 2001
From: Dries <dries@buytaert.net>
Date: Sat, 18 May 2013 08:13:17 -0700
Subject: [PATCH] Issue #1261002 by LewisNyman, nod_, blueshadow2911,
 sabsbrain, quicksketch, mgifford, jessebeach: Fixed Draggable tables do not
 work on touch screen devices.

---
 core/misc/tabledrag.js              | 170 +++++++++++++++-------------
 core/modules/system/system.base.css |  21 +++-
 2 files changed, 110 insertions(+), 81 deletions(-)

diff --git a/core/misc/tabledrag.js b/core/misc/tabledrag.js
index a803f66cfd18..99805ba61bfa 100644
--- a/core/misc/tabledrag.js
+++ b/core/misc/tabledrag.js
@@ -120,10 +120,17 @@ Drupal.tableDrag = function (table, tableSettings) {
   // manipulate form elements directly, rather than using drag-and-drop..
   self.initColumns();
 
-  // Add mouse bindings to the document. The self variable is passed along
+  // Add event bindings to the document. The self variable is passed along
   // as event handlers do not have direct access to the tableDrag object.
-  $(document).bind('mousemove', function (event) { return self.dragRow(event, self); });
-  $(document).bind('mouseup', function (event) { return self.dropRow(event, self); });
+  if (Modernizr.touch) {
+    $(document).bind('touchmove', function (event) { return self.dragRow(event.originalEvent.touches[0], self); });
+    $(document).bind('touchend', function (event) { return self.dropRow(event.originalEvent.touches[0], self); });
+  }
+  else {
+    $(document).bind('mousemove', function (event) { return self.dragRow(event, self); });
+    $(document).bind('mouseup', function (event) { return self.dropRow(event, self); });
+  }
+
   // React to localStorage event showing or hiding weight columns.
   $(window).bind('storage', $.proxy(function (e) {
     // Only react to 'Drupal.tableDrag.showWeight' value change.
@@ -302,7 +309,8 @@ Drupal.tableDrag.prototype.rowSettings = function (group, row) {
 Drupal.tableDrag.prototype.makeDraggable = function (item) {
   var self = this;
   var $item = $(item);
-
+  //Add a class to the title link
+  $item.find('td:first a').addClass('menu-item__link');
   // Create the handle.
   var handle = $('<a href="#" class="tabledrag-handle"><div class="handle">&nbsp;</div></a>').attr('title', Drupal.t('Drag to re-order'));
   // Insert the handle after indentations (if any).
@@ -316,68 +324,33 @@ Drupal.tableDrag.prototype.makeDraggable = function (item) {
     $item.find('td:first').prepend(handle);
   }
 
-  // Add hover action for the handle.
-  handle.hover(function () {
-    self.dragObject === null ? $(this).addClass('tabledrag-handle-hover') : null;
-  }, function () {
-    self.dragObject === null ? $(this).removeClass('tabledrag-handle-hover') : null;
-  });
-
-  // Add the mousedown action for the handle.
-  handle.mousedown(function (event) {
-    event.preventDefault();
-    // Create a new dragObject recording the event information.
-    self.dragObject = {};
-    self.dragObject.initMouseOffset = self.getMouseOffset(item, event);
-    self.dragObject.initMouseCoords = self.mouseCoords(event);
-    if (self.indentEnabled) {
-      self.dragObject.indentMousePos = self.dragObject.initMouseCoords;
-    }
-
-    // If there's a lingering row object from the keyboard, remove its focus.
-    if (self.rowObject) {
-      $(self.rowObject.element).find('a.tabledrag-handle').blur();
-    }
-
-    // Create a new rowObject for manipulation of this row.
-    self.rowObject = new self.row(item, 'mouse', self.indentEnabled, self.maxDepth, true);
-
-    // Save the position of the table.
-    self.table.topY = $(self.table).offset().top;
-    self.table.bottomY = self.table.topY + self.table.offsetHeight;
-
-    // Add classes to the handle and row.
-    $(this).addClass('tabledrag-handle-hover');
-    $(item).addClass('drag');
-
-    // Set the document to use the move cursor during drag.
-    $('body').addClass('drag');
-    if (self.oldRowElement) {
-      $(self.oldRowElement).removeClass('drag-previous');
-    }
-
-    // Hack for Konqueror, prevent the blur handler from firing.
-    // Konqueror always gives links focus, even after returning false on mousedown.
-    self.safeBlur = false;
-
-    // Call optional placeholder function.
-    self.onDrag();
-  });
+  if (Modernizr.touch) {
+    handle.on('touchstart', function (event) {
+      event.preventDefault();
+      event = event.originalEvent.touches[0];
+      self.dragStart(event, self, item);
+    });
+  }
+  else {
+    handle.mousedown(function (event) {
+      event.preventDefault();
+      self.dragStart(event, self, item);
+    });
+  }
 
   // Prevent the anchor tag from jumping us to the top of the page.
   handle.click(function (e) {
     e.preventDefault();
   });
 
-  // Similar to the hover event, add a class when the handle is focused.
+  // Set blur cleanup when a handle is focused.
   handle.focus(function () {
-    $(this).addClass('tabledrag-handle-hover');
     self.safeBlur = true;
   });
 
-  // Remove the handle class on blur and fire the same function as a mouseup.
+  // On blur, fire the same function as a touchend/mouseup. This is used to
+  // update values after a row has been moved through the keyboard support.
   handle.blur(function (event) {
-    $(this).removeClass('tabledrag-handle-hover');
     if (self.rowObject && self.safeBlur) {
       self.dropRow(event, self);
     }
@@ -511,14 +484,54 @@ Drupal.tableDrag.prototype.makeDraggable = function (item) {
 };
 
 /**
- * Mousemove event handler, bound to document.
+ * Pointer event initiator, creates drag object and information.
+ *
+ * @param jQuery.Event event
+ *   The event object that trigger the drag.
+ * @param Drupal.tableDrag self
+ *   The drag handle.
+ * @param DOM item
+ *   The item that that is being dragged.
+ */
+Drupal.tableDrag.prototype.dragStart = function (event, self, item) {
+  // Create a new dragObject recording the pointer information.
+  self.dragObject = {};
+  self.dragObject.initOffset = self.getPointerOffset(item, event);
+  self.dragObject.initPointerCoords = self.pointerCoords(event);
+  if (self.indentEnabled) {
+    self.dragObject.indentPointerPos = self.dragObject.initPointerCoords;
+  }
+
+  // If there's a lingering row object from the keyboard, remove its focus.
+  if (self.rowObject) {
+    $(self.rowObject.element).find('a.tabledrag-handle').blur();
+  }
+
+  // Create a new rowObject for manipulation of this row.
+  self.rowObject = new self.row(item, 'pointer', self.indentEnabled, self.maxDepth, true);
+
+  // Save the position of the table.
+  self.table.topY = $(self.table).offset().top;
+  self.table.bottomY = self.table.topY + self.table.offsetHeight;
+
+  // Add classes to the handle and row.
+  $(item).addClass('drag');
+
+  // Set the document to use the move cursor during drag.
+  $('body').addClass('drag');
+  if (self.oldRowElement) {
+    $(self.oldRowElement).removeClass('drag-previous');
+  }
+}
+
+/**
+ * Pointer movement handler, bound to document.
  */
 Drupal.tableDrag.prototype.dragRow = function (event, self) {
   if (self.dragObject) {
-    self.currentMouseCoords = self.mouseCoords(event);
-
-    var y = self.currentMouseCoords.y - self.dragObject.initMouseOffset.y;
-    var x = self.currentMouseCoords.x - self.dragObject.initMouseOffset.x;
+    self.currentPointerCoords = self.pointerCoords(event);
+    var y = self.currentPointerCoords.y - self.dragObject.initOffset.y;
+    var x = self.currentPointerCoords.x - self.dragObject.initOffset.x;
 
     // Check for row swapping and vertical scrolling.
     if (y !== self.oldY) {
@@ -526,7 +539,7 @@ Drupal.tableDrag.prototype.dragRow = function (event, self) {
       self.oldY = y; // Update the old value.
 
       // Check if the window should be scrolled (and how fast).
-      var scrollAmount = self.checkScroll(self.currentMouseCoords.y);
+      var scrollAmount = self.checkScroll(self.currentPointerCoords.y);
       // Stop any current scrolling.
       clearInterval(self.scrollInterval);
       // Continue scrolling if the mouse has moved in the scroll direction.
@@ -549,14 +562,14 @@ Drupal.tableDrag.prototype.dragRow = function (event, self) {
 
     // Similar to row swapping, handle indentations.
     if (self.indentEnabled) {
-      var xDiff = self.currentMouseCoords.x - self.dragObject.indentMousePos.x;
-      // Set the number of indentations the mouse has been moved left or right.
+      var xDiff = self.currentPointerCoords.x - self.dragObject.indentPointerPos.x;
+      // Set the number of indentations the pointer has been moved left or right.
       var indentDiff = Math.round(xDiff / self.indentAmount * self.rtl);
       // Indent the row with our estimated diff, which may be further
       // restricted according to the rows around this row.
       var indentChange = self.rowObject.indent(indentDiff);
-      // Update table and mouse indentations.
-      self.dragObject.indentMousePos.x += self.indentAmount * indentChange * self.rtl;
+      // Update table and pointer indentations.
+      self.dragObject.indentPointerPos.x += self.indentAmount * indentChange * self.rtl;
       self.indentCount = Math.max(self.indentCount, self.rowObject.indents);
     }
 
@@ -565,13 +578,12 @@ Drupal.tableDrag.prototype.dragRow = function (event, self) {
 };
 
 /**
- * Mouseup event handler, bound to document.
- * Blur event handler, bound to drag handle for keyboard support.
+ * Pointerup behaviour.
  */
 Drupal.tableDrag.prototype.dropRow = function (event, self) {
   var droppedRow, $droppedRow;
 
-  // Drop row functionality shared between mouseup and blur events.
+  // Drop row functionality.
   if (self.rowObject !== null) {
     droppedRow = self.rowObject.element;
     $droppedRow = $(droppedRow);
@@ -614,10 +626,8 @@ Drupal.tableDrag.prototype.dropRow = function (event, self) {
     self.rowObject = null;
   }
 
-  // Functionality specific only to mouseup event.
+  // Functionality specific only to pointerup events.
   if (self.dragObject !== null) {
-    $droppedRow.find('.tabledrag-handle').removeClass('tabledrag-handle-hover');
-
     self.dragObject = null;
     $('body').removeClass('drag');
     clearInterval(self.scrollInterval);
@@ -625,9 +635,9 @@ Drupal.tableDrag.prototype.dropRow = function (event, self) {
 };
 
 /**
- * Get the mouse coordinates from the event (allowing for browser differences).
+ * Get the coordinates from the event (allowing for browser differences).
  */
-Drupal.tableDrag.prototype.mouseCoords = function (event) {
+Drupal.tableDrag.prototype.pointerCoords = function (event) {
   if (event.pageX || event.pageY) {
     return { x: event.pageX, y: event.pageY };
   }
@@ -638,13 +648,13 @@ Drupal.tableDrag.prototype.mouseCoords = function (event) {
 };
 
 /**
- * Given a target element and a mouse event, get the mouse offset from that
- * element. To do this we need the element's position and the mouse position.
+ * Given a target element and a pointer event, get the event offset from that
+ * element. To do this we need the element's position and the target position.
  */
-Drupal.tableDrag.prototype.getMouseOffset = function (target, event) {
-  var docPos   = $(target).offset();
-  var mousePos = this.mouseCoords(event);
-  return { x: mousePos.x - docPos.left, y: mousePos.y - docPos.top };
+Drupal.tableDrag.prototype.getPointerOffset = function (target, event) {
+  var docPos = $(target).offset();
+  var pointerPos = this.pointerCoords(event);
+  return { x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top };
 };
 
 /**
@@ -903,7 +913,7 @@ Drupal.tableDrag.prototype.setScroll = function (scrollAmount) {
 
   this.scrollInterval = setInterval(function () {
     // Update the scroll values stored in the object.
-    self.checkScroll(self.currentMouseCoords.y);
+    self.checkScroll(self.currentPointerCoords.y);
     var aboveTable = self.scrollY > self.table.topY;
     var belowTable = self.scrollY + self.windowHeight < self.table.bottomY;
     if (scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable) {
diff --git a/core/modules/system/system.base.css b/core/modules/system/system.base.css
index 3f074c70f9a8..08ff66c8c860 100644
--- a/core/modules/system/system.base.css
+++ b/core/modules/system/system.base.css
@@ -101,7 +101,26 @@ a.tabledrag-handle .handle {
   padding: 0.42em 0.5em; /* LTR */
   width: 13px;
 }
-a.tabledrag-handle-hover .handle {
+.touch .draggable td {
+  padding: 0 10px;
+}
+.touch .draggable .menu-item__link {
+  display: inline-block;
+  padding: 10px 0;
+}
+.touch a.tabledrag-handle {
+  height: 44px;
+  width: 40px;
+}
+.touch a.tabledrag-handle .handle {
+  background-position: 40% 19px;
+  height: 21px;
+}
+.touch .draggable.drag a.tabledrag-handle .handle {
+  background-position: 50% -32px;
+}
+.no-touch a.tabledrag-handle:hover .handle,
+.no-touch a.tabledrag-handle:focus .handle {
   background-position: 6px -11px;
 }
 div.indentation {
-- 
GitLab