Tabledrag with locked children We use Drupal's tabledrag component in one of our interfaces and we've got this requirement we don't know how to solve.

Basically, we have a tree structure with elements of 2 types - let's call them A and B.

An element of type A can be moved to any location in the tree (we use weight and parent-child relationships).
An element of type B is always a child of an A, and cannot be moved to another A. It is locked under it's parent A. However, it can be sorted with sibling As.

So for a tree like:

A1
\- A2
 - B1
 - A3
   \- B2
    - A4
    - B3
    - B4


We can move A3 up to be a child of A2, and all it's children (B2..B4) will move along (like normal).
However, B1 can only be moved up and down (in regards to A2 and A3), as we can change it's weight, but cannot change it's parent: it's locked under A1.

I have searched for a while, but have not found any examples of this. Is this even possible with Drupal's core tabledrag ?
Ok, found it.

If one includes this code, it will prevent all items with a class "tabledrag-parent-locked" from being dragged into another group, while still being orderable with their siblings.

This code uses a CSS class of 'parent' to target the parent relation field. The actual CSS class would obviously depend on what CSS class you pass to drupal_add_tabledrag().

/**
 * @file
 * Override some Tabledrag functionality.
 */

/**
 * If the dragged row has a class of 'tabledrag-parent-locked', check the rows with which it might get swapped.
 * If the row it gets swapped with has a different  parent, block the swap by returning false.
 * This will prevent the locked child from being dragged to another parent.
 *
 * @override Drupal.tableDrag.row.isValidSwap
 */
// Keep the original implementation - we still need it.
Drupal.tableDrag.prototype.row.prototype._isValidSwap = Drupal.tableDrag.prototype.row.prototype.isValidSwap;
Drupal.tableDrag.prototype.row.prototype.isValidSwap = function(row) {
  if (this.indentEnabled &amp;&amp; $(this.element).hasClass('tabledrag-parent-locked')) {
    var nextRow;
    if (this.direction == 'down') {
      nextRow = $(row).next('tr').get(0);
    }
    else {
      nextRow = row;
    }

    if (nextRow &amp;&amp; $('.parent', this.element).val() !== $('.parent', nextRow).val()) {
      return false;
    }
  }

  // Return the original result.
  return this._isValidSwap(row);
}

/**
 * If the dragged row has a class of 'tabledrag-parent-locked', disable the indentation for the duration of
 * the drag. Store the old indentation setting in _indentEnabled.
 *
 * @override Drupal.tableDrag.dragRow
 */
// Keep the original implementation - we still need it.
Drupal.tableDrag.prototype._dragRow = Drupal.tableDrag.prototype.dragRow;
Drupal.tableDrag.prototype.dragRow = function(event, self) {
  if (self.rowObject &amp;&amp; $(self.rowObject.element).hasClass('tabledrag-parent-locked')) {
    if (self.indentEnabled) {
      self._indentEnabled = true;
      self.indentEnabled = false;
    }
  }

  return self._dragRow(event, self);
}

/**
 * Restore the original indentation setting, if needed.
 *
 * @override Drupal.tableDrag.dragRow
 */
// Keep the original implementation - we still need it.
Drupal.tableDrag.prototype._dropRow = Drupal.tableDrag.prototype.dropRow;
Drupal.tableDrag.prototype.dropRow = function(event, self) {
  if (self._indentEnabled !== null) {
    self.indentEnabled = true;
    self._indentEnabled = null;
  }

  return self._dropRow(event, self);
}