annotate misc/tabledrag.js @ 7:fff6d4c8c043 6.3

Drupal 6.3
author Franck Deroche <webmaster@defr.org>
date Tue, 23 Dec 2008 14:30:28 +0100
parents c1f4ac30525a
children 589fb7c02327
rev   line source
webmaster@7 1 // $Id: tabledrag.js,v 1.13.2.3 2008/06/12 19:13:25 dries Exp $
webmaster@1 2
webmaster@1 3 /**
webmaster@1 4 * Drag and drop table rows with field manipulation.
webmaster@1 5 *
webmaster@1 6 * Using the drupal_add_tabledrag() function, any table with weights or parent
webmaster@1 7 * relationships may be made into draggable tables. Columns containing a field
webmaster@1 8 * may optionally be hidden, providing a better user experience.
webmaster@1 9 *
webmaster@1 10 * Created tableDrag instances may be modified with custom behaviors by
webmaster@1 11 * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods.
webmaster@1 12 * See blocks.js for an example of adding additional functionality to tableDrag.
webmaster@1 13 */
webmaster@1 14 Drupal.behaviors.tableDrag = function(context) {
webmaster@1 15 for (var base in Drupal.settings.tableDrag) {
webmaster@1 16 if (!$('#' + base + '.tabledrag-processed', context).size()) {
webmaster@1 17 var tableSettings = Drupal.settings.tableDrag[base];
webmaster@1 18
webmaster@1 19 $('#' + base).filter(':not(.tabledrag-processed)').each(function() {
webmaster@1 20 // Create the new tableDrag instance. Save in the Drupal variable
webmaster@1 21 // to allow other scripts access to the object.
webmaster@1 22 Drupal.tableDrag[base] = new Drupal.tableDrag(this, tableSettings);
webmaster@1 23 });
webmaster@1 24
webmaster@1 25 $('#' + base).addClass('tabledrag-processed');
webmaster@1 26 }
webmaster@1 27 }
webmaster@1 28 };
webmaster@1 29
webmaster@1 30 /**
webmaster@1 31 * Constructor for the tableDrag object. Provides table and field manipulation.
webmaster@1 32 *
webmaster@1 33 * @param table
webmaster@1 34 * DOM object for the table to be made draggable.
webmaster@1 35 * @param tableSettings
webmaster@1 36 * Settings for the table added via drupal_add_dragtable().
webmaster@1 37 */
webmaster@1 38 Drupal.tableDrag = function(table, tableSettings) {
webmaster@1 39 var self = this;
webmaster@1 40
webmaster@1 41 // Required object variables.
webmaster@1 42 this.table = table;
webmaster@1 43 this.tableSettings = tableSettings;
webmaster@1 44 this.dragObject = null; // Used to hold information about a current drag operation.
webmaster@1 45 this.rowObject = null; // Provides operations for row manipulation.
webmaster@1 46 this.oldRowElement = null; // Remember the previous element.
webmaster@1 47 this.oldY = 0; // Used to determine up or down direction from last mouse move.
webmaster@1 48 this.changed = false; // Whether anything in the entire table has changed.
webmaster@1 49 this.maxDepth = 0; // Maximum amount of allowed parenting.
webmaster@1 50 this.rtl = $(this.table).css('direction') == 'rtl' ? -1 : 1; // Direction of the table.
webmaster@1 51
webmaster@1 52 // Configure the scroll settings.
webmaster@1 53 this.scrollSettings = { amount: 4, interval: 50, trigger: 70 };
webmaster@1 54 this.scrollInterval = null;
webmaster@1 55 this.scrollY = 0;
webmaster@1 56 this.windowHeight = 0;
webmaster@1 57
webmaster@1 58 // Check this table's settings to see if there are parent relationships in
webmaster@1 59 // this table. For efficiency, large sections of code can be skipped if we
webmaster@1 60 // don't need to track horizontal movement and indentations.
webmaster@1 61 this.indentEnabled = false;
webmaster@1 62 for (group in tableSettings) {
webmaster@1 63 for (n in tableSettings[group]) {
webmaster@1 64 if (tableSettings[group][n]['relationship'] == 'parent') {
webmaster@1 65 this.indentEnabled = true;
webmaster@1 66 }
webmaster@1 67 if (tableSettings[group][n]['limit'] > 0) {
webmaster@1 68 this.maxDepth = tableSettings[group][n]['limit'];
webmaster@1 69 }
webmaster@1 70 }
webmaster@1 71 }
webmaster@1 72 if (this.indentEnabled) {
webmaster@1 73 this.indentCount = 1; // Total width of indents, set in makeDraggable.
webmaster@1 74 // Find the width of indentations to measure mouse movements against.
webmaster@1 75 // Because the table doesn't need to start with any indentations, we
webmaster@1 76 // manually create an empty div, check it's width, then remove.
webmaster@1 77 var indent = $(Drupal.theme('tableDragIndentation')).appendTo('body');
webmaster@1 78 this.indentAmount = parseInt(indent.css('width'));
webmaster@1 79 indent.remove();
webmaster@1 80 }
webmaster@1 81
webmaster@1 82 // Make each applicable row draggable.
webmaster@1 83 $('tr.draggable', table).each(function() { self.makeDraggable(this); });
webmaster@1 84
webmaster@1 85 // Hide columns containing affected form elements.
webmaster@1 86 this.hideColumns();
webmaster@1 87
webmaster@1 88 // Add mouse bindings to the document. The self variable is passed along
webmaster@1 89 // as event handlers do not have direct access to the tableDrag object.
webmaster@7 90 $(document).bind('mousemove', function(event) { return self.dragRow(event, self); });
webmaster@7 91 $(document).bind('mouseup', function(event) { return self.dropRow(event, self); });
webmaster@1 92 };
webmaster@1 93
webmaster@1 94 /**
webmaster@1 95 * Hide the columns containing form elements according to the settings for
webmaster@1 96 * this tableDrag instance.
webmaster@1 97 */
webmaster@7 98 Drupal.tableDrag.prototype.hideColumns = function(){
webmaster@1 99 for (var group in this.tableSettings) {
webmaster@1 100 // Find the first field in this group.
webmaster@1 101 for (var d in this.tableSettings[group]) {
webmaster@7 102 var field = $('.' + this.tableSettings[group][d]['target'] + ':first', this.table);
webmaster@7 103 if (field.size() && this.tableSettings[group][d]['hidden']) {
webmaster@1 104 var hidden = this.tableSettings[group][d]['hidden'];
webmaster@7 105 var cell = field.parents('td:first');
webmaster@1 106 break;
webmaster@1 107 }
webmaster@1 108 }
webmaster@7 109
webmaster@1 110 // Hide the column containing this field.
webmaster@1 111 if (hidden && cell[0] && cell.css('display') != 'none') {
webmaster@1 112 // Add 1 to our indexes. The nth-child selector is 1 based, not 0 based.
webmaster@7 113 var columnIndex = $('td', cell.parent()).index(cell.get(0)) + 1;
webmaster@7 114 var headerIndex = $('td:not(:hidden)', cell.parent()).index(cell.get(0)) + 1;
webmaster@7 115 $('tr', this.table).each(function(){
webmaster@7 116 var row = $(this);
webmaster@7 117 var parentTag = row.parent().get(0).tagName.toLowerCase();
webmaster@7 118 var index = (parentTag == 'thead') ? headerIndex : columnIndex;
webmaster@7 119
webmaster@7 120 // Adjust the index to take into account colspans.
webmaster@7 121 row.children().each(function(n) {
webmaster@7 122 if (n < index) {
webmaster@7 123 index -= (this.colSpan && this.colSpan > 1) ? this.colSpan - 1 : 0;
webmaster@7 124 }
webmaster@7 125 });
webmaster@7 126 if (index > 0) {
webmaster@7 127 cell = row.children(':nth-child(' + index + ')');
webmaster@7 128 if (cell[0].colSpan > 1) {
webmaster@7 129 // If this cell has a colspan, simply reduce it.
webmaster@7 130 cell[0].colSpan = cell[0].colSpan - 1;
webmaster@7 131 }
webmaster@7 132 else {
webmaster@7 133 // Hide table body cells, but remove table header cells entirely
webmaster@7 134 // (Safari doesn't hide properly).
webmaster@7 135 parentTag == 'thead' ? cell.remove() : cell.css('display', 'none');
webmaster@7 136 }
webmaster@1 137 }
webmaster@1 138 });
webmaster@1 139 }
webmaster@1 140 }
webmaster@1 141 };
webmaster@1 142
webmaster@1 143 /**
webmaster@1 144 * Find the target used within a particular row and group.
webmaster@1 145 */
webmaster@1 146 Drupal.tableDrag.prototype.rowSettings = function(group, row) {
webmaster@1 147 var field = $('.' + group, row);
webmaster@1 148 for (delta in this.tableSettings[group]) {
webmaster@1 149 var targetClass = this.tableSettings[group][delta]['target'];
webmaster@1 150 if (field.is('.' + targetClass)) {
webmaster@1 151 // Return a copy of the row settings.
webmaster@1 152 var rowSettings = new Object();
webmaster@1 153 for (var n in this.tableSettings[group][delta]) {
webmaster@1 154 rowSettings[n] = this.tableSettings[group][delta][n];
webmaster@1 155 }
webmaster@1 156 return rowSettings;
webmaster@1 157 }
webmaster@1 158 }
webmaster@1 159 };
webmaster@1 160
webmaster@1 161 /**
webmaster@1 162 * Take an item and add event handlers to make it become draggable.
webmaster@1 163 */
webmaster@1 164 Drupal.tableDrag.prototype.makeDraggable = function(item) {
webmaster@1 165 var self = this;
webmaster@1 166
webmaster@1 167 // Create the handle.
webmaster@1 168 var handle = $('<a href="#" class="tabledrag-handle"><div class="handle">&nbsp;</div></a>').attr('title', Drupal.t('Drag to re-order'));
webmaster@1 169 // Insert the handle after indentations (if any).
webmaster@1 170 if ($('td:first .indentation:last', item).after(handle).size()) {
webmaster@1 171 // Update the total width of indentation in this entire table.
webmaster@1 172 self.indentCount = Math.max($('.indentation', item).size(), self.indentCount);
webmaster@1 173 }
webmaster@1 174 else {
webmaster@1 175 $('td:first', item).prepend(handle);
webmaster@1 176 }
webmaster@1 177
webmaster@1 178 // Add hover action for the handle.
webmaster@1 179 handle.hover(function() {
webmaster@1 180 self.dragObject == null ? $(this).addClass('tabledrag-handle-hover') : null;
webmaster@1 181 }, function() {
webmaster@1 182 self.dragObject == null ? $(this).removeClass('tabledrag-handle-hover') : null;
webmaster@1 183 });
webmaster@1 184
webmaster@1 185 // Add the mousedown action for the handle.
webmaster@1 186 handle.mousedown(function(event) {
webmaster@1 187 // Create a new dragObject recording the event information.
webmaster@1 188 self.dragObject = new Object();
webmaster@1 189 self.dragObject.initMouseOffset = self.getMouseOffset(item, event);
webmaster@1 190 self.dragObject.initMouseCoords = self.mouseCoords(event);
webmaster@1 191 if (self.indentEnabled) {
webmaster@1 192 self.dragObject.indentMousePos = self.dragObject.initMouseCoords;
webmaster@1 193 }
webmaster@1 194
webmaster@1 195 // If there's a lingering row object from the keyboard, remove its focus.
webmaster@1 196 if (self.rowObject) {
webmaster@1 197 $('a.tabledrag-handle', self.rowObject.element).blur();
webmaster@1 198 }
webmaster@1 199
webmaster@1 200 // Create a new rowObject for manipulation of this row.
webmaster@1 201 self.rowObject = new self.row(item, 'mouse', self.indentEnabled, self.maxDepth, true);
webmaster@1 202
webmaster@1 203 // Save the position of the table.
webmaster@1 204 self.table.topY = self.getPosition(self.table).y;
webmaster@1 205 self.table.bottomY = self.table.topY + self.table.offsetHeight;
webmaster@1 206
webmaster@1 207 // Add classes to the handle and row.
webmaster@1 208 $(this).addClass('tabledrag-handle-hover');
webmaster@1 209 $(item).addClass('drag');
webmaster@1 210
webmaster@1 211 // Set the document to use the move cursor during drag.
webmaster@1 212 $('body').addClass('drag');
webmaster@1 213 if (self.oldRowElement) {
webmaster@1 214 $(self.oldRowElement).removeClass('drag-previous');
webmaster@1 215 }
webmaster@1 216
webmaster@1 217 // Hack for IE6 that flickers uncontrollably if select lists are moved.
webmaster@1 218 if (navigator.userAgent.indexOf('MSIE 6.') != -1) {
webmaster@1 219 $('select', this.table).css('display', 'none');
webmaster@1 220 }
webmaster@1 221
webmaster@1 222 // Hack for Konqueror, prevent the blur handler from firing.
webmaster@1 223 // Konqueror always gives links focus, even after returning false on mousedown.
webmaster@1 224 self.safeBlur = false;
webmaster@1 225
webmaster@1 226 // Call optional placeholder function.
webmaster@1 227 self.onDrag();
webmaster@1 228 return false;
webmaster@1 229 });
webmaster@1 230
webmaster@1 231 // Prevent the anchor tag from jumping us to the top of the page.
webmaster@1 232 handle.click(function() {
webmaster@1 233 return false;
webmaster@1 234 });
webmaster@1 235
webmaster@1 236 // Similar to the hover event, add a class when the handle is focused.
webmaster@1 237 handle.focus(function() {
webmaster@1 238 $(this).addClass('tabledrag-handle-hover');
webmaster@1 239 self.safeBlur = true;
webmaster@1 240 });
webmaster@1 241
webmaster@1 242 // Remove the handle class on blur and fire the same function as a mouseup.
webmaster@1 243 handle.blur(function(event) {
webmaster@1 244 $(this).removeClass('tabledrag-handle-hover');
webmaster@1 245 if (self.rowObject && self.safeBlur) {
webmaster@1 246 self.dropRow(event, self);
webmaster@1 247 }
webmaster@1 248 });
webmaster@1 249
webmaster@1 250 // Add arrow-key support to the handle.
webmaster@1 251 handle.keydown(function(event) {
webmaster@1 252 // If a rowObject doesn't yet exist and this isn't the tab key.
webmaster@1 253 if (event.keyCode != 9 && !self.rowObject) {
webmaster@1 254 self.rowObject = new self.row(item, 'keyboard', self.indentEnabled, self.maxDepth, true);
webmaster@1 255 }
webmaster@1 256
webmaster@1 257 var keyChange = false;
webmaster@1 258 switch (event.keyCode) {
webmaster@1 259 case 37: // Left arrow.
webmaster@1 260 case 63234: // Safari left arrow.
webmaster@1 261 keyChange = true;
webmaster@1 262 self.rowObject.indent(-1 * self.rtl);
webmaster@1 263 break;
webmaster@1 264 case 38: // Up arrow.
webmaster@1 265 case 63232: // Safari up arrow.
webmaster@1 266 var previousRow = $(self.rowObject.element).prev('tr').get(0);
webmaster@1 267 while (previousRow && $(previousRow).is(':hidden')) {
webmaster@1 268 previousRow = $(previousRow).prev('tr').get(0);
webmaster@1 269 }
webmaster@1 270 if (previousRow) {
webmaster@1 271 self.safeBlur = false; // Do not allow the onBlur cleanup.
webmaster@1 272 self.rowObject.direction = 'up';
webmaster@1 273 keyChange = true;
webmaster@1 274
webmaster@1 275 if ($(item).is('.tabledrag-root')) {
webmaster@1 276 // Swap with the previous top-level row..
webmaster@1 277 var groupHeight = 0;
webmaster@1 278 while (previousRow && $('.indentation', previousRow).size()) {
webmaster@1 279 previousRow = $(previousRow).prev('tr').get(0);
webmaster@1 280 groupHeight += $(previousRow).is(':hidden') ? 0 : previousRow.offsetHeight;
webmaster@1 281 }
webmaster@1 282 if (previousRow) {
webmaster@1 283 self.rowObject.swap('before', previousRow);
webmaster@1 284 // No need to check for indentation, 0 is the only valid one.
webmaster@1 285 window.scrollBy(0, -groupHeight);
webmaster@1 286 }
webmaster@1 287 }
webmaster@1 288 else if (self.table.tBodies[0].rows[0] != previousRow || $(previousRow).is('.draggable')) {
webmaster@1 289 // Swap with the previous row (unless previous row is the first one
webmaster@1 290 // and undraggable).
webmaster@1 291 self.rowObject.swap('before', previousRow);
webmaster@1 292 self.rowObject.interval = null;
webmaster@1 293 self.rowObject.indent(0);
webmaster@1 294 window.scrollBy(0, -parseInt(item.offsetHeight));
webmaster@1 295 }
webmaster@1 296 handle.get(0).focus(); // Regain focus after the DOM manipulation.
webmaster@1 297 }
webmaster@1 298 break;
webmaster@1 299 case 39: // Right arrow.
webmaster@1 300 case 63235: // Safari right arrow.
webmaster@1 301 keyChange = true;
webmaster@1 302 self.rowObject.indent(1 * self.rtl);
webmaster@1 303 break;
webmaster@1 304 case 40: // Down arrow.
webmaster@1 305 case 63233: // Safari down arrow.
webmaster@1 306 var nextRow = $(self.rowObject.group).filter(':last').next('tr').get(0);
webmaster@1 307 while (nextRow && $(nextRow).is(':hidden')) {
webmaster@1 308 nextRow = $(nextRow).next('tr').get(0);
webmaster@1 309 }
webmaster@1 310 if (nextRow) {
webmaster@1 311 self.safeBlur = false; // Do not allow the onBlur cleanup.
webmaster@1 312 self.rowObject.direction = 'down';
webmaster@1 313 keyChange = true;
webmaster@1 314
webmaster@1 315 if ($(item).is('.tabledrag-root')) {
webmaster@1 316 // Swap with the next group (necessarily a top-level one).
webmaster@1 317 var groupHeight = 0;
webmaster@1 318 nextGroup = new self.row(nextRow, 'keyboard', self.indentEnabled, self.maxDepth, false);
webmaster@1 319 if (nextGroup) {
webmaster@1 320 $(nextGroup.group).each(function () {groupHeight += $(this).is(':hidden') ? 0 : this.offsetHeight});
webmaster@1 321 nextGroupRow = $(nextGroup.group).filter(':last').get(0);
webmaster@1 322 self.rowObject.swap('after', nextGroupRow);
webmaster@1 323 // No need to check for indentation, 0 is the only valid one.
webmaster@1 324 window.scrollBy(0, parseInt(groupHeight));
webmaster@1 325 }
webmaster@1 326 }
webmaster@1 327 else {
webmaster@1 328 // Swap with the next row.
webmaster@1 329 self.rowObject.swap('after', nextRow);
webmaster@1 330 self.rowObject.interval = null;
webmaster@1 331 self.rowObject.indent(0);
webmaster@1 332 window.scrollBy(0, parseInt(item.offsetHeight));
webmaster@1 333 }
webmaster@1 334 handle.get(0).focus(); // Regain focus after the DOM manipulation.
webmaster@1 335 }
webmaster@1 336 break;
webmaster@1 337 }
webmaster@1 338
webmaster@1 339 if (self.rowObject && self.rowObject.changed == true) {
webmaster@1 340 $(item).addClass('drag');
webmaster@1 341 if (self.oldRowElement) {
webmaster@1 342 $(self.oldRowElement).removeClass('drag-previous');
webmaster@1 343 }
webmaster@1 344 self.oldRowElement = item;
webmaster@1 345 self.restripeTable();
webmaster@1 346 self.onDrag();
webmaster@1 347 }
webmaster@1 348
webmaster@1 349 // Returning false if we have an arrow key to prevent scrolling.
webmaster@1 350 if (keyChange) {
webmaster@1 351 return false;
webmaster@1 352 }
webmaster@1 353 });
webmaster@1 354
webmaster@1 355 // Compatibility addition, return false on keypress to prevent unwanted scrolling.
webmaster@1 356 // IE and Safari will supress scrolling on keydown, but all other browsers
webmaster@1 357 // need to return false on keypress. http://www.quirksmode.org/js/keys.html
webmaster@1 358 handle.keypress(function(event) {
webmaster@1 359 switch (event.keyCode) {
webmaster@1 360 case 37: // Left arrow.
webmaster@1 361 case 38: // Up arrow.
webmaster@1 362 case 39: // Right arrow.
webmaster@1 363 case 40: // Down arrow.
webmaster@1 364 return false;
webmaster@1 365 }
webmaster@1 366 });
webmaster@1 367 };
webmaster@1 368
webmaster@1 369 /**
webmaster@1 370 * Mousemove event handler, bound to document.
webmaster@1 371 */
webmaster@1 372 Drupal.tableDrag.prototype.dragRow = function(event, self) {
webmaster@1 373 if (self.dragObject) {
webmaster@1 374 self.currentMouseCoords = self.mouseCoords(event);
webmaster@1 375
webmaster@1 376 var y = self.currentMouseCoords.y - self.dragObject.initMouseOffset.y;
webmaster@1 377 var x = self.currentMouseCoords.x - self.dragObject.initMouseOffset.x;
webmaster@1 378
webmaster@1 379 // Check for row swapping and vertical scrolling.
webmaster@1 380 if (y != self.oldY) {
webmaster@1 381 self.rowObject.direction = y > self.oldY ? 'down' : 'up';
webmaster@1 382 self.oldY = y; // Update the old value.
webmaster@1 383
webmaster@1 384 // Check if the window should be scrolled (and how fast).
webmaster@1 385 var scrollAmount = self.checkScroll(self.currentMouseCoords.y);
webmaster@1 386 // Stop any current scrolling.
webmaster@1 387 clearInterval(self.scrollInterval);
webmaster@1 388 // Continue scrolling if the mouse has moved in the scroll direction.
webmaster@1 389 if (scrollAmount > 0 && self.rowObject.direction == 'down' || scrollAmount < 0 && self.rowObject.direction == 'up') {
webmaster@1 390 self.setScroll(scrollAmount);
webmaster@1 391 }
webmaster@1 392
webmaster@1 393 // If we have a valid target, perform the swap and restripe the table.
webmaster@1 394 var currentRow = self.findDropTargetRow(x, y);
webmaster@1 395 if (currentRow) {
webmaster@1 396 if (self.rowObject.direction == 'down') {
webmaster@1 397 self.rowObject.swap('after', currentRow, self);
webmaster@1 398 }
webmaster@1 399 else {
webmaster@1 400 self.rowObject.swap('before', currentRow, self);
webmaster@1 401 }
webmaster@1 402 self.restripeTable();
webmaster@1 403 }
webmaster@1 404 }
webmaster@1 405
webmaster@1 406 // Similar to row swapping, handle indentations.
webmaster@1 407 if (self.indentEnabled) {
webmaster@1 408 var xDiff = self.currentMouseCoords.x - self.dragObject.indentMousePos.x;
webmaster@1 409 // Set the number of indentations the mouse has been moved left or right.
webmaster@1 410 var indentDiff = parseInt(xDiff / self.indentAmount * self.rtl);
webmaster@1 411 // Indent the row with our estimated diff, which may be further
webmaster@1 412 // restricted according to the rows around this row.
webmaster@1 413 var indentChange = self.rowObject.indent(indentDiff);
webmaster@1 414 // Update table and mouse indentations.
webmaster@1 415 self.dragObject.indentMousePos.x += self.indentAmount * indentChange * self.rtl;
webmaster@1 416 self.indentCount = Math.max(self.indentCount, self.rowObject.indents);
webmaster@1 417 }
webmaster@7 418
webmaster@7 419 return false;
webmaster@1 420 }
webmaster@1 421 };
webmaster@1 422
webmaster@1 423 /**
webmaster@1 424 * Mouseup event handler, bound to document.
webmaster@1 425 * Blur event handler, bound to drag handle for keyboard support.
webmaster@1 426 */
webmaster@1 427 Drupal.tableDrag.prototype.dropRow = function(event, self) {
webmaster@1 428 // Drop row functionality shared between mouseup and blur events.
webmaster@1 429 if (self.rowObject != null) {
webmaster@1 430 var droppedRow = self.rowObject.element;
webmaster@1 431 // The row is already in the right place so we just release it.
webmaster@1 432 if (self.rowObject.changed == true) {
webmaster@1 433 // Update the fields in the dropped row.
webmaster@1 434 self.updateFields(droppedRow);
webmaster@1 435
webmaster@1 436 // If a setting exists for affecting the entire group, update all the
webmaster@1 437 // fields in the entire dragged group.
webmaster@1 438 for (var group in self.tableSettings) {
webmaster@1 439 var rowSettings = self.rowSettings(group, droppedRow);
webmaster@1 440 if (rowSettings.relationship == 'group') {
webmaster@1 441 for (n in self.rowObject.children) {
webmaster@1 442 self.updateField(self.rowObject.children[n], group);
webmaster@1 443 }
webmaster@1 444 }
webmaster@1 445 }
webmaster@1 446
webmaster@1 447 self.rowObject.markChanged();
webmaster@1 448 if (self.changed == false) {
webmaster@1 449 $(Drupal.theme('tableDragChangedWarning')).insertAfter(self.table).hide().fadeIn('slow');
webmaster@1 450 self.changed = true;
webmaster@1 451 }
webmaster@1 452 }
webmaster@1 453
webmaster@1 454 if (self.indentEnabled) {
webmaster@1 455 self.rowObject.removeIndentClasses();
webmaster@1 456 }
webmaster@1 457 if (self.oldRowElement) {
webmaster@1 458 $(self.oldRowElement).removeClass('drag-previous');
webmaster@1 459 }
webmaster@1 460 $(droppedRow).removeClass('drag').addClass('drag-previous');
webmaster@1 461 self.oldRowElement = droppedRow;
webmaster@1 462 self.onDrop();
webmaster@1 463 self.rowObject = null;
webmaster@1 464 }
webmaster@1 465
webmaster@1 466 // Functionality specific only to mouseup event.
webmaster@1 467 if (self.dragObject != null) {
webmaster@1 468 $('.tabledrag-handle', droppedRow).removeClass('tabledrag-handle-hover');
webmaster@1 469
webmaster@1 470 self.dragObject = null;
webmaster@1 471 $('body').removeClass('drag');
webmaster@1 472 clearInterval(self.scrollInterval);
webmaster@1 473
webmaster@1 474 // Hack for IE6 that flickers uncontrollably if select lists are moved.
webmaster@1 475 if (navigator.userAgent.indexOf('MSIE 6.') != -1) {
webmaster@1 476 $('select', this.table).css('display', 'block');
webmaster@1 477 }
webmaster@1 478 }
webmaster@1 479 };
webmaster@1 480
webmaster@1 481 /**
webmaster@1 482 * Get the position of an element by adding up parent offsets in the DOM tree.
webmaster@1 483 */
webmaster@1 484 Drupal.tableDrag.prototype.getPosition = function(element){
webmaster@1 485 var left = 0;
webmaster@1 486 var top = 0;
webmaster@1 487 // Because Safari doesn't report offsetHeight on table rows, but does on table
webmaster@1 488 // cells, grab the firstChild of the row and use that instead.
webmaster@1 489 // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari
webmaster@1 490 if (element.offsetHeight == 0) {
webmaster@1 491 element = element.firstChild; // a table cell
webmaster@1 492 }
webmaster@1 493
webmaster@1 494 while (element.offsetParent){
webmaster@1 495 left += element.offsetLeft;
webmaster@1 496 top += element.offsetTop;
webmaster@1 497 element = element.offsetParent;
webmaster@1 498 }
webmaster@1 499
webmaster@1 500 left += element.offsetLeft;
webmaster@1 501 top += element.offsetTop;
webmaster@1 502
webmaster@1 503 return {x:left, y:top};
webmaster@1 504 };
webmaster@1 505
webmaster@1 506 /**
webmaster@1 507 * Get the mouse coordinates from the event (allowing for browser differences).
webmaster@1 508 */
webmaster@1 509 Drupal.tableDrag.prototype.mouseCoords = function(event){
webmaster@1 510 if (event.pageX || event.pageY) {
webmaster@1 511 return {x:event.pageX, y:event.pageY};
webmaster@1 512 }
webmaster@1 513 return {
webmaster@1 514 x:event.clientX + document.body.scrollLeft - document.body.clientLeft,
webmaster@1 515 y:event.clientY + document.body.scrollTop - document.body.clientTop
webmaster@1 516 };
webmaster@1 517 };
webmaster@1 518
webmaster@1 519 /**
webmaster@1 520 * Given a target element and a mouse event, get the mouse offset from that
webmaster@1 521 * element. To do this we need the element's position and the mouse position.
webmaster@1 522 */
webmaster@1 523 Drupal.tableDrag.prototype.getMouseOffset = function(target, event) {
webmaster@1 524 var docPos = this.getPosition(target);
webmaster@1 525 var mousePos = this.mouseCoords(event);
webmaster@1 526 return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
webmaster@1 527 };
webmaster@1 528
webmaster@1 529 /**
webmaster@1 530 * Find the row the mouse is currently over. This row is then taken and swapped
webmaster@1 531 * with the one being dragged.
webmaster@1 532 *
webmaster@1 533 * @param x
webmaster@1 534 * The x coordinate of the mouse on the page (not the screen).
webmaster@1 535 * @param y
webmaster@1 536 * The y coordinate of the mouse on the page (not the screen).
webmaster@1 537 */
webmaster@1 538 Drupal.tableDrag.prototype.findDropTargetRow = function(x, y) {
webmaster@1 539 var rows = this.table.tBodies[0].rows;
webmaster@1 540 for (var n=0; n<rows.length; n++) {
webmaster@1 541 var row = rows[n];
webmaster@1 542 var indentDiff = 0;
webmaster@1 543 // Safari fix see Drupal.tableDrag.prototype.getPosition()
webmaster@1 544 if (row.offsetHeight == 0) {
webmaster@1 545 var rowY = this.getPosition(row.firstChild).y;
webmaster@1 546 var rowHeight = parseInt(row.firstChild.offsetHeight)/2;
webmaster@1 547 }
webmaster@1 548 // Other browsers.
webmaster@1 549 else {
webmaster@1 550 var rowY = this.getPosition(row).y;
webmaster@1 551 var rowHeight = parseInt(row.offsetHeight)/2;
webmaster@1 552 }
webmaster@1 553
webmaster@1 554 // Because we always insert before, we need to offset the height a bit.
webmaster@1 555 if ((y > (rowY - rowHeight)) && (y < (rowY + rowHeight))) {
webmaster@1 556 if (this.indentEnabled) {
webmaster@1 557 // Check that this row is not a child of the row being dragged.
webmaster@1 558 for (n in this.rowObject.group) {
webmaster@1 559 if (this.rowObject.group[n] == row) {
webmaster@1 560 return null;
webmaster@1 561 }
webmaster@1 562 }
webmaster@1 563 }
webmaster@1 564 // Check that swapping with this row is allowed.
webmaster@1 565 if (!this.rowObject.isValidSwap(row)) {
webmaster@1 566 return null;
webmaster@1 567 }
webmaster@1 568
webmaster@1 569 // We may have found the row the mouse just passed over, but it doesn't
webmaster@1 570 // take into account hidden rows. Skip backwards until we find a draggable
webmaster@1 571 // row.
webmaster@1 572 while ($(row).is(':hidden') && $(row).prev('tr').is(':hidden')) {
webmaster@1 573 row = $(row).prev('tr').get(0);
webmaster@1 574 }
webmaster@1 575 return row;
webmaster@1 576 }
webmaster@1 577 }
webmaster@1 578 return null;
webmaster@1 579 };
webmaster@1 580
webmaster@1 581 /**
webmaster@1 582 * After the row is dropped, update the table fields according to the settings
webmaster@1 583 * set for this table.
webmaster@1 584 *
webmaster@1 585 * @param changedRow
webmaster@1 586 * DOM object for the row that was just dropped.
webmaster@1 587 */
webmaster@1 588 Drupal.tableDrag.prototype.updateFields = function(changedRow) {
webmaster@1 589 for (var group in this.tableSettings) {
webmaster@1 590 // Each group may have a different setting for relationship, so we find
webmaster@1 591 // the source rows for each seperately.
webmaster@1 592 this.updateField(changedRow, group);
webmaster@1 593 }
webmaster@1 594 };
webmaster@1 595
webmaster@1 596 /**
webmaster@1 597 * After the row is dropped, update a single table field according to specific
webmaster@1 598 * settings.
webmaster@1 599 *
webmaster@1 600 * @param changedRow
webmaster@1 601 * DOM object for the row that was just dropped.
webmaster@1 602 * @param group
webmaster@1 603 * The settings group on which field updates will occur.
webmaster@1 604 */
webmaster@1 605 Drupal.tableDrag.prototype.updateField = function(changedRow, group) {
webmaster@1 606 var rowSettings = this.rowSettings(group, changedRow);
webmaster@1 607
webmaster@1 608 // Set the row as it's own target.
webmaster@1 609 if (rowSettings.relationship == 'self' || rowSettings.relationship == 'group') {
webmaster@1 610 var sourceRow = changedRow;
webmaster@1 611 }
webmaster@1 612 // Siblings are easy, check previous and next rows.
webmaster@1 613 else if (rowSettings.relationship == 'sibling') {
webmaster@1 614 var previousRow = $(changedRow).prev('tr').get(0);
webmaster@1 615 var nextRow = $(changedRow).next('tr').get(0);
webmaster@1 616 var sourceRow = changedRow;
webmaster@1 617 if ($(previousRow).is('.draggable') && $('.' + group, previousRow).length) {
webmaster@1 618 if (this.indentEnabled) {
webmaster@1 619 if ($('.indentations', previousRow).size() == $('.indentations', changedRow)) {
webmaster@1 620 sourceRow = previousRow;
webmaster@1 621 }
webmaster@1 622 }
webmaster@1 623 else {
webmaster@1 624 sourceRow = previousRow;
webmaster@1 625 }
webmaster@1 626 }
webmaster@1 627 else if ($(nextRow).is('.draggable') && $('.' + group, nextRow).length) {
webmaster@1 628 if (this.indentEnabled) {
webmaster@1 629 if ($('.indentations', nextRow).size() == $('.indentations', changedRow)) {
webmaster@1 630 sourceRow = nextRow;
webmaster@1 631 }
webmaster@1 632 }
webmaster@1 633 else {
webmaster@1 634 sourceRow = nextRow;
webmaster@1 635 }
webmaster@1 636 }
webmaster@1 637 }
webmaster@1 638 // Parents, look up the tree until we find a field not in this group.
webmaster@1 639 // Go up as many parents as indentations in the changed row.
webmaster@1 640 else if (rowSettings.relationship == 'parent') {
webmaster@1 641 var previousRow = $(changedRow).prev('tr');
webmaster@1 642 while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) {
webmaster@1 643 previousRow = previousRow.prev('tr');
webmaster@1 644 }
webmaster@1 645 // If we found a row.
webmaster@1 646 if (previousRow.length) {
webmaster@1 647 sourceRow = previousRow[0];
webmaster@1 648 }
webmaster@1 649 // Otherwise we went all the way to the left of the table without finding
webmaster@1 650 // a parent, meaning this item has been placed at the root level.
webmaster@1 651 else {
webmaster@1 652 // Use the first row in the table as source, because it's garanteed to
webmaster@1 653 // be at the root level. Find the first item, then compare this row
webmaster@1 654 // against it as a sibling.
webmaster@1 655 sourceRow = $('tr.draggable:first').get(0);
webmaster@1 656 if (sourceRow == this.rowObject.element) {
webmaster@1 657 sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
webmaster@1 658 }
webmaster@1 659 var useSibling = true;
webmaster@1 660 }
webmaster@1 661 }
webmaster@1 662
webmaster@1 663 // Because we may have moved the row from one category to another,
webmaster@1 664 // take a look at our sibling and borrow its sources and targets.
webmaster@1 665 this.copyDragClasses(sourceRow, changedRow, group);
webmaster@1 666 rowSettings = this.rowSettings(group, changedRow);
webmaster@1 667
webmaster@1 668 // In the case that we're looking for a parent, but the row is at the top
webmaster@1 669 // of the tree, copy our sibling's values.
webmaster@1 670 if (useSibling) {
webmaster@1 671 rowSettings.relationship = 'sibling';
webmaster@1 672 rowSettings.source = rowSettings.target;
webmaster@1 673 }
webmaster@1 674
webmaster@1 675 var targetClass = '.' + rowSettings.target;
webmaster@1 676 var targetElement = $(targetClass, changedRow).get(0);
webmaster@1 677
webmaster@1 678 // Check if a target element exists in this row.
webmaster@1 679 if (targetElement) {
webmaster@1 680 var sourceClass = '.' + rowSettings.source;
webmaster@1 681 var sourceElement = $(sourceClass, sourceRow).get(0);
webmaster@1 682 switch (rowSettings.action) {
webmaster@1 683 case 'depth':
webmaster@1 684 // Get the depth of the target row.
webmaster@1 685 targetElement.value = $('.indentation', $(sourceElement).parents('tr:first')).size();
webmaster@1 686 break;
webmaster@1 687 case 'match':
webmaster@1 688 // Update the value.
webmaster@1 689 targetElement.value = sourceElement.value;
webmaster@1 690 break;
webmaster@1 691 case 'order':
webmaster@1 692 var siblings = this.rowObject.findSiblings(rowSettings);
webmaster@1 693 if ($(targetElement).is('select')) {
webmaster@1 694 // Get a list of acceptable values.
webmaster@1 695 var values = new Array();
webmaster@1 696 $('option', targetElement).each(function() {
webmaster@1 697 values.push(this.value);
webmaster@1 698 });
webmaster@1 699 var maxVal = values[values.length - 1];
webmaster@1 700 // Populate the values in the siblings.
webmaster@1 701 $(targetClass, siblings).each(function() {
webmaster@1 702 // If there are more items than possible values, assign the maximum value to the row.
webmaster@1 703 if (values.length > 0) {
webmaster@1 704 this.value = values.shift();
webmaster@1 705 }
webmaster@1 706 else {
webmaster@1 707 this.value = maxVal;
webmaster@1 708 }
webmaster@1 709 });
webmaster@1 710 }
webmaster@1 711 else {
webmaster@1 712 // Assume a numeric input field.
webmaster@1 713 var weight = parseInt($(targetClass, siblings[0]).val()) || 0;
webmaster@1 714 $(targetClass, siblings).each(function() {
webmaster@1 715 this.value = weight;
webmaster@1 716 weight++;
webmaster@1 717 });
webmaster@1 718 }
webmaster@1 719 break;
webmaster@1 720 }
webmaster@1 721 }
webmaster@1 722 };
webmaster@1 723
webmaster@1 724 /**
webmaster@1 725 * Copy all special tableDrag classes from one row's form elements to a
webmaster@1 726 * different one, removing any special classes that the destination row
webmaster@1 727 * may have had.
webmaster@1 728 */
webmaster@1 729 Drupal.tableDrag.prototype.copyDragClasses = function(sourceRow, targetRow, group) {
webmaster@1 730 var sourceElement = $('.' + group, sourceRow);
webmaster@1 731 var targetElement = $('.' + group, targetRow);
webmaster@1 732 if (sourceElement.length && targetElement.length) {
webmaster@1 733 targetElement[0].className = sourceElement[0].className;
webmaster@1 734 }
webmaster@1 735 };
webmaster@1 736
webmaster@1 737 Drupal.tableDrag.prototype.checkScroll = function(cursorY) {
webmaster@1 738 var de = document.documentElement;
webmaster@1 739 var b = document.body;
webmaster@1 740
webmaster@1 741 var windowHeight = this.windowHeight = window.innerHeight || (de.clientHeight && de.clientWidth != 0 ? de.clientHeight : b.offsetHeight);
webmaster@1 742 var scrollY = this.scrollY = (document.all ? (!de.scrollTop ? b.scrollTop : de.scrollTop) : (window.pageYOffset ? window.pageYOffset : window.scrollY));
webmaster@1 743 var trigger = this.scrollSettings.trigger;
webmaster@1 744 var delta = 0;
webmaster@1 745
webmaster@1 746 // Return a scroll speed relative to the edge of the screen.
webmaster@1 747 if (cursorY - scrollY > windowHeight - trigger) {
webmaster@1 748 delta = trigger / (windowHeight + scrollY - cursorY);
webmaster@1 749 delta = (delta > 0 && delta < trigger) ? delta : trigger;
webmaster@1 750 return delta * this.scrollSettings.amount;
webmaster@1 751 }
webmaster@1 752 else if (cursorY - scrollY < trigger) {
webmaster@1 753 delta = trigger / (cursorY - scrollY);
webmaster@1 754 delta = (delta > 0 && delta < trigger) ? delta : trigger;
webmaster@1 755 return -delta * this.scrollSettings.amount;
webmaster@1 756 }
webmaster@1 757 };
webmaster@1 758
webmaster@1 759 Drupal.tableDrag.prototype.setScroll = function(scrollAmount) {
webmaster@1 760 var self = this;
webmaster@1 761
webmaster@1 762 this.scrollInterval = setInterval(function() {
webmaster@1 763 // Update the scroll values stored in the object.
webmaster@1 764 self.checkScroll(self.currentMouseCoords.y);
webmaster@1 765 var aboveTable = self.scrollY > self.table.topY;
webmaster@1 766 var belowTable = self.scrollY + self.windowHeight < self.table.bottomY;
webmaster@1 767 if (scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable) {
webmaster@1 768 window.scrollBy(0, scrollAmount);
webmaster@1 769 }
webmaster@1 770 }, this.scrollSettings.interval);
webmaster@1 771 };
webmaster@1 772
webmaster@1 773 Drupal.tableDrag.prototype.restripeTable = function() {
webmaster@1 774 // :even and :odd are reversed because jquery counts from 0 and
webmaster@1 775 // we count from 1, so we're out of sync.
webmaster@1 776 $('tr.draggable', this.table)
webmaster@1 777 .filter(':odd').filter('.odd')
webmaster@1 778 .removeClass('odd').addClass('even')
webmaster@1 779 .end().end()
webmaster@1 780 .filter(':even').filter('.even')
webmaster@1 781 .removeClass('even').addClass('odd');
webmaster@1 782 };
webmaster@1 783
webmaster@1 784 /**
webmaster@1 785 * Stub function. Allows a custom handler when a row begins dragging.
webmaster@1 786 */
webmaster@1 787 Drupal.tableDrag.prototype.onDrag = function() {
webmaster@1 788 return null;
webmaster@1 789 };
webmaster@1 790
webmaster@1 791 /**
webmaster@1 792 * Stub function. Allows a custom handler when a row is dropped.
webmaster@1 793 */
webmaster@1 794 Drupal.tableDrag.prototype.onDrop = function() {
webmaster@1 795 return null;
webmaster@1 796 };
webmaster@1 797
webmaster@1 798 /**
webmaster@1 799 * Constructor to make a new object to manipulate a table row.
webmaster@1 800 *
webmaster@1 801 * @param tableRow
webmaster@1 802 * The DOM element for the table row we will be manipulating.
webmaster@1 803 * @param method
webmaster@1 804 * The method in which this row is being moved. Either 'keyboard' or 'mouse'.
webmaster@1 805 * @param indentEnabled
webmaster@1 806 * Whether the containing table uses indentations. Used for optimizations.
webmaster@1 807 * @param maxDepth
webmaster@1 808 * The maximum amount of indentations this row may contain.
webmaster@1 809 * @param addClasses
webmaster@1 810 * Whether we want to add classes to this row to indicate child relationships.
webmaster@1 811 */
webmaster@1 812 Drupal.tableDrag.prototype.row = function(tableRow, method, indentEnabled, maxDepth, addClasses) {
webmaster@1 813 this.element = tableRow;
webmaster@1 814 this.method = method;
webmaster@1 815 this.group = new Array(tableRow);
webmaster@1 816 this.groupDepth = $('.indentation', tableRow).size();
webmaster@1 817 this.changed = false;
webmaster@1 818 this.table = $(tableRow).parents('table:first').get(0);
webmaster@1 819 this.indentEnabled = indentEnabled;
webmaster@1 820 this.maxDepth = maxDepth;
webmaster@1 821 this.direction = ''; // Direction the row is being moved.
webmaster@1 822
webmaster@1 823 if (this.indentEnabled) {
webmaster@1 824 this.indents = $('.indentation', tableRow).size();
webmaster@1 825 this.children = this.findChildren(addClasses);
webmaster@1 826 this.group = $.merge(this.group, this.children);
webmaster@1 827 // Find the depth of this entire group.
webmaster@1 828 for (var n = 0; n < this.group.length; n++) {
webmaster@1 829 this.groupDepth = Math.max($('.indentation', this.group[n]).size(), this.groupDepth);
webmaster@1 830 }
webmaster@1 831 }
webmaster@1 832 };
webmaster@1 833
webmaster@1 834 /**
webmaster@1 835 * Find all children of rowObject by indentation.
webmaster@1 836 *
webmaster@1 837 * @param addClasses
webmaster@1 838 * Whether we want to add classes to this row to indicate child relationships.
webmaster@1 839 */
webmaster@1 840 Drupal.tableDrag.prototype.row.prototype.findChildren = function(addClasses) {
webmaster@1 841 var parentIndentation = this.indents;
webmaster@1 842 var currentRow = $(this.element, this.table).next('tr.draggable');
webmaster@1 843 var rows = new Array();
webmaster@1 844 var child = 0;
webmaster@1 845 while (currentRow.length) {
webmaster@1 846 var rowIndentation = $('.indentation', currentRow).length;
webmaster@1 847 // A greater indentation indicates this is a child.
webmaster@1 848 if (rowIndentation > parentIndentation) {
webmaster@1 849 child++;
webmaster@1 850 rows.push(currentRow[0]);
webmaster@1 851 if (addClasses) {
webmaster@1 852 $('.indentation', currentRow).each(function(indentNum) {
webmaster@1 853 if (child == 1 && (indentNum == parentIndentation)) {
webmaster@1 854 $(this).addClass('tree-child-first');
webmaster@1 855 }
webmaster@1 856 if (indentNum == parentIndentation) {
webmaster@1 857 $(this).addClass('tree-child');
webmaster@1 858 }
webmaster@1 859 else if (indentNum > parentIndentation) {
webmaster@1 860 $(this).addClass('tree-child-horizontal');
webmaster@1 861 }
webmaster@1 862 });
webmaster@1 863 }
webmaster@1 864 }
webmaster@1 865 else {
webmaster@1 866 break;
webmaster@1 867 }
webmaster@1 868 currentRow = currentRow.next('tr.draggable');
webmaster@1 869 }
webmaster@1 870 if (addClasses && rows.length) {
webmaster@1 871 $('.indentation:nth-child(' + (parentIndentation + 1) + ')', rows[rows.length - 1]).addClass('tree-child-last');
webmaster@1 872 }
webmaster@1 873 return rows;
webmaster@1 874 };
webmaster@1 875
webmaster@1 876 /**
webmaster@1 877 * Ensure that two rows are allowed to be swapped.
webmaster@1 878 *
webmaster@1 879 * @param row
webmaster@1 880 * DOM object for the row being considered for swapping.
webmaster@1 881 */
webmaster@1 882 Drupal.tableDrag.prototype.row.prototype.isValidSwap = function(row) {
webmaster@1 883 if (this.indentEnabled) {
webmaster@1 884 var prevRow, nextRow;
webmaster@1 885 if (this.direction == 'down') {
webmaster@1 886 prevRow = row;
webmaster@1 887 nextRow = $(row).next('tr').get(0);
webmaster@1 888 }
webmaster@1 889 else {
webmaster@1 890 prevRow = $(row).prev('tr').get(0);
webmaster@1 891 nextRow = row;
webmaster@1 892 }
webmaster@1 893 this.interval = this.validIndentInterval(prevRow, nextRow);
webmaster@1 894
webmaster@1 895 // We have an invalid swap if the valid indentations interval is empty.
webmaster@1 896 if (this.interval.min > this.interval.max) {
webmaster@1 897 return false;
webmaster@1 898 }
webmaster@1 899 }
webmaster@1 900
webmaster@1 901 // Do not let an un-draggable first row have anything put before it.
webmaster@1 902 if (this.table.tBodies[0].rows[0] == row && $(row).is(':not(.draggable)')) {
webmaster@1 903 return false;
webmaster@1 904 }
webmaster@1 905
webmaster@1 906 return true;
webmaster@1 907 };
webmaster@1 908
webmaster@1 909 /**
webmaster@1 910 * Perform the swap between two rows.
webmaster@1 911 *
webmaster@1 912 * @param position
webmaster@1 913 * Whether the swap will occur 'before' or 'after' the given row.
webmaster@1 914 * @param row
webmaster@1 915 * DOM element what will be swapped with the row group.
webmaster@1 916 */
webmaster@1 917 Drupal.tableDrag.prototype.row.prototype.swap = function(position, row) {
webmaster@1 918 $(row)[position](this.group);
webmaster@1 919 this.changed = true;
webmaster@1 920 this.onSwap(row);
webmaster@1 921 };
webmaster@1 922
webmaster@1 923 /**
webmaster@1 924 * Determine the valid indentations interval for the row at a given position
webmaster@1 925 * in the table.
webmaster@1 926 *
webmaster@1 927 * @param prevRow
webmaster@1 928 * DOM object for the row before the tested position
webmaster@1 929 * (or null for first position in the table).
webmaster@1 930 * @param nextRow
webmaster@1 931 * DOM object for the row after the tested position
webmaster@1 932 * (or null for last position in the table).
webmaster@1 933 */
webmaster@1 934 Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) {
webmaster@1 935 var minIndent, maxIndent;
webmaster@1 936
webmaster@1 937 // Minimum indentation:
webmaster@1 938 // Do not orphan the next row.
webmaster@1 939 minIndent = nextRow ? $('.indentation', nextRow).size() : 0;
webmaster@1 940
webmaster@1 941 // Maximum indentation:
webmaster@1 942 if (!prevRow || $(this.element).is('.tabledrag-root')) {
webmaster@1 943 // Do not indent the first row in the table or 'root' rows..
webmaster@1 944 maxIndent = 0;
webmaster@1 945 }
webmaster@1 946 else {
webmaster@1 947 // Do not go deeper than as a child of the previous row.
webmaster@1 948 maxIndent = $('.indentation', prevRow).size() + ($(prevRow).is('.tabledrag-leaf') ? 0 : 1);
webmaster@1 949 // Limit by the maximum allowed depth for the table.
webmaster@1 950 if (this.maxDepth) {
webmaster@1 951 maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents));
webmaster@1 952 }
webmaster@1 953 }
webmaster@1 954
webmaster@1 955 return {'min':minIndent, 'max':maxIndent};
webmaster@1 956 }
webmaster@1 957
webmaster@1 958 /**
webmaster@1 959 * Indent a row within the legal bounds of the table.
webmaster@1 960 *
webmaster@1 961 * @param indentDiff
webmaster@1 962 * The number of additional indentations proposed for the row (can be
webmaster@1 963 * positive or negative). This number will be adjusted to nearest valid
webmaster@1 964 * indentation level for the row.
webmaster@1 965 */
webmaster@1 966 Drupal.tableDrag.prototype.row.prototype.indent = function(indentDiff) {
webmaster@1 967 // Determine the valid indentations interval if not available yet.
webmaster@1 968 if (!this.interval) {
webmaster@1 969 prevRow = $(this.element).prev('tr').get(0);
webmaster@1 970 nextRow = $(this.group).filter(':last').next('tr').get(0);
webmaster@1 971 this.interval = this.validIndentInterval(prevRow, nextRow);
webmaster@1 972 }
webmaster@1 973
webmaster@1 974 // Adjust to the nearest valid indentation.
webmaster@1 975 var indent = this.indents + indentDiff;
webmaster@1 976 indent = Math.max(indent, this.interval.min);
webmaster@1 977 indent = Math.min(indent, this.interval.max);
webmaster@1 978 indentDiff = indent - this.indents;
webmaster@1 979
webmaster@1 980 for (var n = 1; n <= Math.abs(indentDiff); n++) {
webmaster@1 981 // Add or remove indentations.
webmaster@1 982 if (indentDiff < 0) {
webmaster@1 983 $('.indentation:first', this.group).remove();
webmaster@1 984 this.indents--;
webmaster@1 985 }
webmaster@1 986 else {
webmaster@1 987 $('td:first', this.group).prepend(Drupal.theme('tableDragIndentation'));
webmaster@1 988 this.indents++;
webmaster@1 989 }
webmaster@1 990 }
webmaster@1 991 if (indentDiff) {
webmaster@1 992 // Update indentation for this row.
webmaster@1 993 this.changed = true;
webmaster@1 994 this.groupDepth += indentDiff;
webmaster@1 995 this.onIndent();
webmaster@1 996 }
webmaster@1 997
webmaster@1 998 return indentDiff;
webmaster@1 999 };
webmaster@1 1000
webmaster@1 1001 /**
webmaster@1 1002 * Find all siblings for a row, either according to its subgroup or indentation.
webmaster@1 1003 * Note that the passed in row is included in the list of siblings.
webmaster@1 1004 *
webmaster@1 1005 * @param settings
webmaster@1 1006 * The field settings we're using to identify what constitutes a sibling.
webmaster@1 1007 */
webmaster@1 1008 Drupal.tableDrag.prototype.row.prototype.findSiblings = function(rowSettings) {
webmaster@1 1009 var siblings = new Array();
webmaster@1 1010 var directions = new Array('prev', 'next');
webmaster@1 1011 var rowIndentation = this.indents;
webmaster@1 1012 for (var d in directions) {
webmaster@1 1013 var checkRow = $(this.element)[directions[d]]();
webmaster@1 1014 while (checkRow.length) {
webmaster@1 1015 // Check that the sibling contains a similar target field.
webmaster@1 1016 if ($('.' + rowSettings.target, checkRow)) {
webmaster@1 1017 // Either add immediately if this is a flat table, or check to ensure
webmaster@1 1018 // that this row has the same level of indentaiton.
webmaster@1 1019 if (this.indentEnabled) {
webmaster@1 1020 var checkRowIndentation = $('.indentation', checkRow).length
webmaster@1 1021 }
webmaster@1 1022
webmaster@1 1023 if (!(this.indentEnabled) || (checkRowIndentation == rowIndentation)) {
webmaster@1 1024 siblings.push(checkRow[0]);
webmaster@1 1025 }
webmaster@1 1026 else if (checkRowIndentation < rowIndentation) {
webmaster@1 1027 // No need to keep looking for siblings when we get to a parent.
webmaster@1 1028 break;
webmaster@1 1029 }
webmaster@1 1030 }
webmaster@1 1031 else {
webmaster@1 1032 break;
webmaster@1 1033 }
webmaster@1 1034 checkRow = $(checkRow)[directions[d]]();
webmaster@1 1035 }
webmaster@1 1036 // Since siblings are added in reverse order for previous, reverse the
webmaster@1 1037 // completed list of previous siblings. Add the current row and continue.
webmaster@1 1038 if (directions[d] == 'prev') {
webmaster@1 1039 siblings.reverse();
webmaster@1 1040 siblings.push(this.element);
webmaster@1 1041 }
webmaster@1 1042 }
webmaster@1 1043 return siblings;
webmaster@1 1044 };
webmaster@1 1045
webmaster@1 1046 /**
webmaster@1 1047 * Remove indentation helper classes from the current row group.
webmaster@1 1048 */
webmaster@1 1049 Drupal.tableDrag.prototype.row.prototype.removeIndentClasses = function() {
webmaster@1 1050 for (n in this.children) {
webmaster@1 1051 $('.indentation', this.children[n])
webmaster@1 1052 .removeClass('tree-child')
webmaster@1 1053 .removeClass('tree-child-first')
webmaster@1 1054 .removeClass('tree-child-last')
webmaster@1 1055 .removeClass('tree-child-horizontal');
webmaster@1 1056 }
webmaster@1 1057 };
webmaster@1 1058
webmaster@1 1059 /**
webmaster@1 1060 * Add an asterisk or other marker to the changed row.
webmaster@1 1061 */
webmaster@1 1062 Drupal.tableDrag.prototype.row.prototype.markChanged = function() {
webmaster@1 1063 var marker = Drupal.theme('tableDragChangedMarker');
webmaster@1 1064 var cell = $('td:first', this.element);
webmaster@1 1065 if ($('span.tabledrag-changed', cell).length == 0) {
webmaster@1 1066 cell.append(marker);
webmaster@1 1067 }
webmaster@1 1068 };
webmaster@1 1069
webmaster@1 1070 /**
webmaster@1 1071 * Stub function. Allows a custom handler when a row is indented.
webmaster@1 1072 */
webmaster@1 1073 Drupal.tableDrag.prototype.row.prototype.onIndent = function() {
webmaster@1 1074 return null;
webmaster@1 1075 };
webmaster@1 1076
webmaster@1 1077 /**
webmaster@1 1078 * Stub function. Allows a custom handler when a row is swapped.
webmaster@1 1079 */
webmaster@1 1080 Drupal.tableDrag.prototype.row.prototype.onSwap = function(swappedRow) {
webmaster@1 1081 return null;
webmaster@1 1082 };
webmaster@1 1083
webmaster@1 1084 Drupal.theme.prototype.tableDragChangedMarker = function () {
webmaster@1 1085 return '<span class="warning tabledrag-changed">*</span>';
webmaster@1 1086 };
webmaster@1 1087
webmaster@1 1088 Drupal.theme.prototype.tableDragIndentation = function () {
webmaster@1 1089 return '<div class="indentation">&nbsp;</div>';
webmaster@1 1090 };
webmaster@1 1091
webmaster@1 1092 Drupal.theme.prototype.tableDragChangedWarning = function () {
webmaster@1 1093 return '<div class="warning">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t("Changes made in this table will not be saved until the form is submitted.") + '</div>';
webmaster@1 1094 };