[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/sites/all/modules/jquery_update/replace/ -> tabledrag.js (source)

   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">&nbsp;</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">&nbsp;</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  };


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7