(rowY - rowHeight)) && (y < (rowY + rowHeight))) {
if (this.indentEnabled) {
// Check that this row is not a child of the row being dragged.
for (n in this.rowObject.group) {
if (this.rowObject.group[n] == row) {
return null;
}
}
// Check that moving this row will not cause invalid relationships.
var xDiff = this.currentMouseCoords.x - this.dragObject.initMouseCoords.x;
indentDiff = parseInt(xDiff / this.indentAmount);
}
if (!this.rowObject.isValidSwap(row, indentDiff)) {
return null;
}
// We've may have found the row the mouse just passed over, but it doesn't
// take into account hidden rows. Skip backwards until we find a draggable
// row.
while ($(row).is(':hidden') && $(row).prev('tr').is(':hidden')) {
row = $(row).prev('tr').get(0);
}
return row;
}
}
return null;
};
/**
* After the row is dropped, update the table fields according to the settings
* set for this table.
*
* @param changedRow
* DOM object for the row that was just dropped.
*/
Drupal.tableDrag.prototype.updateFields = function(changedRow) {
for (var group in this.tableSettings) {
// Each group may have a different setting for relationship, so we find
// the source rows for each seperately.
this.updateField(changedRow, group);
}
}
/**
* After the row is dropped, update a single table field according to specific
* settings.
*
* @param changedRow
* DOM object for the row that was just dropped.
* @param group
* The settings group on which field updates will occur.
*/
Drupal.tableDrag.prototype.updateField = function(changedRow, group) {
var rowSettings = this.rowSettings(group, changedRow);
// Set the row as it's own target.
if (rowSettings.relationship == 'self' || rowSettings.relationship == 'group') {
var sourceRow = changedRow;
}
// Siblings are easy, check previous and next rows.
else if (rowSettings.relationship == 'sibling') {
var previousRow = $(changedRow).prev('tr').get(0);
var nextRow = $(changedRow).next('tr').get(0);
var sourceRow = changedRow;
if ($(previousRow).is('.draggable') && $('.' + group, previousRow).length) {
if (this.indentEnabled) {
if ($('.indentations', previousRow).size() == $('.indentations', changedRow)) {
sourceRow = previousRow;
}
}
else {
sourceRow = previousRow;
}
}
else if ($(nextRow).is('.draggable') && $('.' + group, nextRow).length) {
if (this.indentEnabled) {
if ($('.indentations', nextRow).size() == $('.indentations', changedRow)) {
sourceRow = nextRow;
}
}
else {
sourceRow = nextRow;
}
}
}
// Parents, look up the tree until we find a field not in this group.
// Go up as many parents as indentations in the changed row.
else if (rowSettings.relationship == 'parent') {
var previousRow = $(changedRow).prev('tr');
while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) {
previousRow = previousRow.prev('tr');
}
// If we found a row.
if (previousRow.length) {
sourceRow = previousRow[0];
}
// Otherwise we went all the way to the left of the table without finding
// a parent, meaning this item has been placed at the root level.
else {
// Use the first row in the table as source, because it's garanteed to
// be at the root level. Find the first item, then compare this row
// against it as a sibling.
sourceRow = $('tr.draggable:first').get(0);
if (sourceRow == this.rowObject.element) {
sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
}
var useSibling = true;
}
}
// Because we may have moved the row from one category to another,
// take a look at our sibling and borrow its sources and targets.
this.copyDragClasses(sourceRow, changedRow, group);
rowSettings = this.rowSettings(group, changedRow);
// In the case that we're looking for a parent, but the row is at the top
// of the tree, copy our sibling's values.
if (useSibling) {
rowSettings.relationship = 'sibling';
rowSettings.source = rowSettings.target;
}
var targetClass = '.' + rowSettings.target;
var targetElement = $(targetClass, changedRow).get(0);
// Check if a target element exists in this row.
if (targetElement) {
var sourceClass = '.' + rowSettings.source;
var sourceElement = $(sourceClass, sourceRow).get(0);
switch (rowSettings.action) {
case 'depth':
// Get the depth of the target row.
targetElement.value = $('.indentation', $(sourceElement).parents('tr:first')).size();
break;
case 'match':
// Update the value.
targetElement.value = sourceElement.value;
break;
case 'order':
var siblings = this.rowObject.findSiblings(rowSettings);
if ($(targetElement).is('select')) {
// Get a list of acceptable values.
var values = new Array();
$('option', targetElement).each(function() {
values.push(this.value);
});
// Populate the values in the siblings.
$(targetClass, siblings).each(function() {
this.value = values.shift();
});
}
else {
// Assume a numeric input field.
var weight = parseInt($(targetClass, siblings[0]).val()) || 0;
$(targetClass, siblings).each(function() {
this.value = weight;
weight++;
});
}
break;
}
}
};
/**
* Copy all special tableDrag classes from one row's form elements to a
* different one, removing any special classes that the destination row
* may have had.
*/
Drupal.tableDrag.prototype.copyDragClasses = function(sourceRow, targetRow, group) {
var sourceElement = $('.' + group, sourceRow);
var targetElement = $('.' + group, targetRow);
if (sourceElement.length && targetElement.length) {
targetElement[0].className = sourceElement[0].className;
}
};
Drupal.tableDrag.prototype.checkScroll = function(cursorY) {
var de = document.documentElement;
var b = document.body;
var windowHeight = this.windowHeight = window.innerHeight || (de.clientHeight && de.clientWidth != 0 ? de.clientHeight : b.offsetHeight);
var scrollY = this.scrollY = (document.all ? (!de.scrollTop ? b.scrollTop : de.scrollTop) : (window.pageYOffset ? window.pageYOffset : window.scrollY));
var trigger = this.scrollSettings.trigger;
var delta = 0;
// Return a scroll speed relative to the edge of the screen.
if (cursorY - scrollY > windowHeight - trigger) {
delta = trigger / (windowHeight + scrollY - cursorY);
delta = (delta > 0 && delta < trigger) ? delta : trigger;
return delta * this.scrollSettings.amount;
}
else if (cursorY - scrollY < trigger) {
delta = trigger / (cursorY - scrollY);
delta = (delta > 0 && delta < trigger) ? delta : trigger;
return -delta * this.scrollSettings.amount;
}
};
Drupal.tableDrag.prototype.setScroll = function(scrollAmount) {
var self = this;
this.scrollInterval = setInterval(function() {
// Update the scroll values stored in the object.
self.checkScroll(self.currentMouseCoords.y);
var aboveTable = self.scrollY > self.table.topY;
var belowTable = self.scrollY + self.windowHeight < self.table.bottomY;
if (scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable) {
window.scrollBy(0, scrollAmount);
}
}, this.scrollSettings.interval);
};
Drupal.tableDrag.prototype.restripeTable = function() {
// :even and :odd are reversed because jquery counts from 0 and
// we count from 1, so we're out of sync.
$('tr.draggable', this.table)
.filter(':odd').filter('.odd')
.removeClass('odd').addClass('even')
.end().end()
.filter(':even').filter('.even')
.removeClass('even').addClass('odd');
};
/**
* Stub function. Allows a custom handler when a row begins dragging.
*/
Drupal.tableDrag.prototype.onDrag = function() {
return null;
};
/**
* Stub function. Allows a custom handler when a row is dropped.
*/
Drupal.tableDrag.prototype.onDrop = function() {
return null;
};
/**
* Constructor to make a new object to manipulate a table row.
*
* @param tableRow
* The DOM element for the table row we will be manipulating.
* @param method
* The method in which this row is being moved. Either 'keyboard' or 'mouse'.
* @param indentEnabled
* Whether the containing table uses indentations. Used for optimizations.
* @param maxDepth
* The maximum amount of indentations this row may contain.
* @param addClasses
* Whether we want to add classes to this row to indicate child relationships.
*/
Drupal.tableDrag.prototype.row = function(tableRow, method, indentEnabled, maxDepth, addClasses) {
this.element = tableRow;
this.method = method;
this.group = new Array(tableRow);
this.groupDepth = $('.indentation', tableRow).size();
this.changed = false;
this.table = $(tableRow).parents('table:first').get(0);
this.indentEnabled = indentEnabled;
this.maxDepth = maxDepth;
this.direction = ''; // Direction the row is being moved.
if (this.indentEnabled) {
this.indents = $('.indentation', tableRow).size();
this.children = this.findChildren(addClasses);
this.group = $.merge(this.group, this.children);
// Find the depth of this entire group.
for (var n = 0; n < this.group.length; n++) {
this.groupDepth = Math.max($('.indentation', this.group[n]).size(), this.groupDepth);
}
}
};
/**
* Find all children of rowObject by indentation.
*
* @param addClasses
* Whether we want to add classes to this row to indicate child relationships.
*/
Drupal.tableDrag.prototype.row.prototype.findChildren = function(addClasses) {
var parentIndentation = this.indents;
var currentRow = $(this.element, this.table).next('tr.draggable');
var rows = new Array();
var child = 0;
while (currentRow.length) {
var rowIndentation = $('.indentation', currentRow).length;
// A greater indentation indicates this is a child.
if (rowIndentation > parentIndentation) {
child++;
rows.push(currentRow[0]);
if (addClasses) {
$('.indentation', currentRow).each(function(indentNum) {
if (child == 1 && (indentNum == parentIndentation)) {
$(this).addClass('tree-child-first');
}
if (indentNum == parentIndentation) {
$(this).addClass('tree-child');
}
else if (indentNum > parentIndentation) {
$(this).addClass('tree-child-horizontal');
}
});
}
}
else {
break;
}
currentRow = currentRow.next('tr.draggable');
}
if (addClasses && rows.length) {
$('.indentation:nth-child(' + (parentIndentation + 1) + ')', rows[rows.length - 1]).addClass('tree-child-last');
}
return rows;
};
/**
* Ensure that two rows are allowed to be swapped. Returns false if swapping
* will cause invalid parent relationships.
*
* @param row
* The row being considered for swapping.
* @param indentDiff
* A horizontal difference in indendations.
*/
Drupal.tableDrag.prototype.row.prototype.isValidSwap = function(row, indentDiff) {
if (this.indentEnabled) {
var rowIndents = $('.indentation', row).size();
var prevIndents = $('.indentation', $(row).prev('tr')).size();
var nextIndents = $('.indentation', $(row).next('tr')).size();
if (
(this.direction == 'down') && (
// Prevent being able to drag a row downward with 2 indentations from a parent.
this.indents > rowIndents + 1 ||
// Prevent orphaning children when dragging into a parent.
this.indents < nextIndents - indentDiff
) ||
(this.direction == 'up') && (
// Prevent being able to drag a row upward with 2 indentations from a parent.
this.indents < rowIndents ||
// Prevent orphaning children when dragging between a child and parent.
this.indents > prevIndents + 1 - indentDiff
)
) {
return false;
}
}
// Restriction special cases for the first row in the table.
if (this.table.tBodies[0].rows[0] == row) {
// Do not let the first row contain indentations
// or let an un-draggable first row have anything put before it.
if ((this.indents + indentDiff) > 0 || $(row).is(':not(.draggable)')) {
return false;
}
}
return true;
};
/**
* Perform the swap between two rows.
*
* @param position
* Whether the swap will occur 'before' or 'after' the given row.
* @param row
* DOM element what will be swapped with the row group.
*/
Drupal.tableDrag.prototype.row.prototype.swap = function(position, row) {
$(row)[position](this.group);
this.changed = true;
this.onSwap(row);
};
/**
* Indent a row within the legal bounds of the table.
*
* @param indentDiff
* The number of indentations the row group should receive, can be negative or
* positive.
*/
Drupal.tableDrag.prototype.row.prototype.indent = function(indentDiff) {
if (indentDiff > 0) {
var prevRow = $(this.group).filter(':first').prev('tr').get(0);
if (prevRow) {
var prevIndent = $('.indentation', $(this.group).filter(':first').prev('tr')).size();
indentDiff = Math.min(prevIndent - this.indents + 1, indentDiff);
}
else {
indentDiff = 0; // First row may not contain indents.
}
}
else {
var nextIndent = $('.indentation', $(this.group).filter(':last').next('tr')).size();
indentDiff = Math.max(nextIndent - this.indents, indentDiff);
}
// Never allow indentation greater the set limit.
if (this.maxDepth && indentDiff + this.groupDepth > this.maxDepth) {
indentDiff = 0;
}
for (var n = 1; n <= Math.abs(indentDiff); n++) {
// Add or remove indentations.
if (indentDiff < 0) {
$('.indentation:first', this.group).remove();
this.indents--;
}
else {
$('td:first', this.group).prepend(Drupal.theme('tableDragIndentation'));
this.indents++;
}
}
if (indentDiff) {
this.changed = true;
}
// Update indentation for this row.
this.groupDepth += indentDiff;
this.onIndent();
return indentDiff;
};
/**
* Find all siblings for a row, either according to its subgroup or indentation.
* Note that the passed in row is included in the list of siblings.
*
* @param settings
* The field settings we're using to identify what constitutes a sibling.
*/
Drupal.tableDrag.prototype.row.prototype.findSiblings = function(rowSettings) {
var siblings = new Array();
var directions = new Array('prev', 'next');
var rowIndentation = this.indents;
for (var d in directions) {
var checkRow = $(this.element)[directions[d]]();
while (checkRow.length) {
// Check that the sibling contains a similar target field.
if ($('.' + rowSettings.target, checkRow)) {
// Either add immediately if this is a flat table, or check to ensure
// that this row has the same level of indentaiton.
if (this.indentEnabled) {
var checkRowIndentation = $('.indentation', checkRow).length
}
if (!(this.indentEnabled) || (checkRowIndentation == rowIndentation)) {
siblings.push(checkRow[0]);
}
else if (checkRowIndentation < rowIndentation) {
// No need to keep looking for siblings when we get to a parent.
break;
}
}
else {
break;
}
checkRow = $(checkRow)[directions[d]]();
}
// Since siblings are added in reverse order for previous, reverse the
// completed list of previous siblings. Add the current row and continue.
if (directions[d] == 'prev') {
siblings.reverse();
siblings.push(this.element);
}
}
return siblings;
};
/**
* Remove indentation helper classes from the current row group.
*/
Drupal.tableDrag.prototype.row.prototype.removeIndentClasses = function() {
for (n in this.children) {
$('.indentation', this.children[n])
.removeClass('tree-child')
.removeClass('tree-child-first')
.removeClass('tree-child-last')
.removeClass('tree-child-horizontal');
}
};
/**
* Add an asterisk or other marker to the changed row.
*/
Drupal.tableDrag.prototype.row.prototype.markChanged = function() {
var marker = Drupal.theme('tableDragChangedMarker');
var cell = $('td:first', this.element);
if ($('span.tabledrag-changed', cell).length == 0) {
cell.append(marker);
}
};
/**
* Stub function. Allows a custom handler when a row is indented.
*/
Drupal.tableDrag.prototype.row.prototype.onIndent = function() {
return null;
};
/**
* Stub function. Allows a custom handler when a row is swapped.
*/
Drupal.tableDrag.prototype.row.prototype.onSwap = function(swappedRow) {
return null;
};
Drupal.theme.prototype.tableDragChangedMarker = function () {
return '*';
};
Drupal.theme.prototype.tableDragIndentation = function () {
return '
';
};
Drupal.theme.prototype.tableDragChangedWarning = function () {
return '' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t("Changes made in this table will not be saved until the form is submitted.") + '
';
};