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