annotate misc/tabledrag.js @ 20:e3d20ebd63d1 tip

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