Results 1 to 4 of 4

Thread: columnmove event in grid toIdx has wrong value moving columns to the right

    You found a bug! We've classified it as EXTJS-7083 . We encourage you to continue the discussion and to find an acceptable workaround while we work on a permanent fix.
  1. #1
    Sencha Premium Member
    Join Date
    Jan 2012
    Posts
    96

    Default columnmove event in grid toIdx has wrong value moving columns to the right

    I've got a grid with a columnmove listener on it.

    If I move a column from position 4 to position 2 the columnmove listener gets a fromIdx of 3 and a toIdx of 1 (basically the array positions since its base-0), this is as I expect.

    If I move a column from position 2 to position 4 the columnmove listener gets a fromIdx of 1, and a toIdx of *4* ... the container itself has the column in the right index in the gridDataColumns variable, but this toIdx appears to be incorrect.

    This is with extjs 4.1.1 RC2

  2. #2
    Sencha Premium User mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    40,449

    Default

    Thanks for the report!

    Looks like the toIdx is one more than it should be.

  3. #3
    Sencha User
    Join Date
    Oct 2012
    Location
    France
    Posts
    9

    Default

    This bug has been open for 2+ years now and is still present in Ext 5.1

    Here's a quick override that fixed it for me. Only took me a couple of minutes to find it. Sencha, it's getting tiresome to keep having to fix your bugs for you.

    I've colored the changes in red for clarity.

    Code:
    Ext.define('My.override.grid.header.DropZone',{
      override: 'Ext.grid.header.DropZone',
      
      /**
       * Fixes an off-by-one bug on the toIdx param of the 'columnmove' grid event.
       * (see http://www.sencha.com/forum/showthread.php?238808-columnmove-event-in-grid-toIdx-has-wrong-value-moving-columns-to-the-right)
       */
      onNodeDrop: function(node, dragZone, e, data) {
          // Note that dropLocation.pos refers to whether the header is dropped before or after the target node!
          if (!this.valid) {
              return;
          }
    
    
          var me = this,
              dragHeader = data.header,
              dropLocation = data.dropLocation,
              dropPosition = dropLocation.pos,
              targetHeader = dropLocation.header,
              fromCt = dragHeader.ownerCt,
              toCt = targetHeader.ownerCt,
              // Use the full column manager here, the indices we want are for moving the actual items in the container.
              // The HeaderContainer translates this to visible columns for informing the view and firing events.
              visibleColumnManager = me.headerCt.visibleColumnManager,
              visibleFromIdx = visibleColumnManager.getHeaderIndex(dragHeader),
              visibleToIdx, colsToMove, moveMethod, scrollerOwner, savedWidth;
    
    
          // If we are dragging in between two HeaderContainers that have had the lockable mixin injected we will lock/unlock
          // headers in between sections, and then continue with another execution of onNodeDrop to ensure the header is
          // dropped into the correct group.
          if (data.isLock || data.isUnlock) {
              scrollerOwner = fromCt.up('[scrollerOwner]');
              visibleToIdx = toCt.items.indexOf(targetHeader);
    
    
              if (dropPosition === 'after') {
                  visibleToIdx++;
              }
    
    
              if (data.isLock) {
                  scrollerOwner.lock(dragHeader, visibleToIdx, toCt);
              } else {
                  scrollerOwner.unlock(dragHeader, visibleToIdx, toCt);
              }
          }
          // This is a drop within the same HeaderContainer.
          else {
              // For the after position, we need to update the visibleToIdx index. In case it's nested in one or more
              // grouped headers, we need to get the last header (or the first, depending on the dropPosition) in the
              // items collection for the most deeply-nested header, whether it be first or last in the collection.
              // This will yield the header index in the visibleColumnManager, which will correctly maintain a list
              // of all the headers.
              // <start edit>
              if (dropPosition === 'after') {
                  // Get the last header in the most deeply-nested header group.
                  visibleToIdx = visibleColumnManager.getHeaderIndex(me.getNestedHeader(targetHeader, 1));
                  if (visibleToIdx < visibleFromIdx) {
                    // The column is moving leftwards and being dropped to the right of the drop point
                    visibleToIdx++;
                  }
                  // else: no need to add to the index as the drop point will be shifting rightwards
              }
              else {
                  // Get the first header in the most deeply-nested header group.
                  visibleToIdx = visibleColumnManager.getHeaderIndex(me.getNestedHeader(targetHeader, 0));
              }
              // </edit>
    
              me.invalidateDrop();
              // Cache the width here, we need to get it before we removed it from the DOM
              savedWidth = dragHeader.getWidth();
    
    
              // Suspend layouts while we sort all this out.
              Ext.suspendLayouts();
    
    
              // When removing and then adding, the owning gridpanel will be informed of column mutation twice
              // Both remove and add handling inform the owning grid.
              // The isDDMoveInGrid flag will prevent the remove operation from doing this.
              // See Ext.grid.header.Container#onRemove.
              fromCt.isDDMoveInGrid = toCt.isDDMoveInGrid = !data.crossPanel;
    
    
              // ***Move the headers***
              //
              // If both drag and target headers are groupHeaders, we have to check and see if they are nested, i.e.,
              // there are multiple stacked group headers with only subheaders at the lowest level:
              //
              //           +-----------------------------------+
              //           |               Group 1             |
              //           |-----------------------------------|
              //           |               Group 2             |
              //   other   |-----------------------------------|   other
              //  headers  |               Group 3             |  headers
              //           |-----------------------------------|
              //           | Field3 | Field4 | Field5 | Field6 |
              //           |===================================|
              //           |               view                |
              //           +-----------------------------------+
              //
              // In these cases, we need to mark the groupHeader that is the ownerCt of the targetHeader and then only
              // remove the headers up until that (removal of headers is recursive and assumes that any header with no
              // children can be safely removed, which is not a safe assumption).
              // See Ext.grid.header.Container#onRemove.
              if (dragHeader.isGroupHeader && targetHeader.isGroupHeader) {
                  dragHeader.setNestedParent(targetHeader);
              }
    
    
              // We only need to be concerned with moving the dragHeader component before or after the targetHeader
              // component rather than trying to pass indices, which is too ambiguous and could refer to any
              // collection at any level of (grouped) header containers.
              if (dropPosition === 'before') {
                  targetHeader.insertNestedHeader(dragHeader);
              } else {
                  // Capitalize the first letter. This will call either ct.moveAfter() or ct.moveBefore().
                  moveMethod = 'move' + dropPosition.charAt(0).toUpperCase() + dropPosition.substr(1);
                  toCt[moveMethod](dragHeader, targetHeader);
              }
    
    
              // ***Move the view data columns***
              // Refresh the view if it's not the last header in a group. If it is the last header, we don't need
              // to refresh the view as the headers and the corrresponding data columns will already be correctly
              // aligned (think of the group header sitting directly atop the last header in the group).
              // Also, it's not necessary to refresh the view if the indices are the same.
              if (visibleToIdx >= 0 && !(targetHeader.isGroupHeader && !targetHeader.items.length) && visibleFromIdx !== visibleToIdx) {
                  colsToMove = dragHeader.isGroupHeader ?
                      dragHeader.query(':not([hidden]):not([isGroupHeader])').length :
                      1;
    
    
                  // We need to adjust the visibleToIdx when both of the following conditions are met:
                  //   1. The drag is forward, i.e., the dragHeader is being dragged to the right.
                  //   2. There is more than one column being dragged, i.e., an entire group.
                  if ((visibleFromIdx <= visibleToIdx) && colsToMove > 1) {
                      visibleToIdx -= colsToMove;
                  }
    
    
                  // It's necessary to lookup the ancestor grid of the grouped header b/c the header could be
                  // nested at any level.
                  toCt.getRootHeaderCt().grid.view.moveColumn(visibleFromIdx, visibleToIdx, colsToMove);
              }
    
    
              // We need to always fire a columnmove event. Check for an .ownerCt first in case this is a
              // grouped header.
              (fromCt.ownerCt || fromCt).fireEvent('columnmove', fromCt, dragHeader, visibleFromIdx, visibleToIdx);
    
    
              fromCt.isDDMoveInGrid = toCt.isDDMoveInGrid = false;
    
    
              // Group headers skrinkwrap their child headers.
              // Therefore a child header may not flex; it must contribute a fixed width.
              // But we restore the flex value when moving back into the main header container
              //
              // Note that we don't need to save the flex if coming from another group header b/c it couldn't
              // have had one!
              if (toCt.isGroupHeader && !fromCt.isGroupHeader) {
                  // Adjust the width of the "to" group header only if we dragged in from somewhere else.
                  // If not within the same container.
                  if (fromCt !== toCt) {
                      dragHeader.savedFlex = dragHeader.flex;
                      delete dragHeader.flex;
                      dragHeader.width = savedWidth;
                  }
              } else if (!fromCt.isGroupHeader) {
                  if (dragHeader.savedFlex) {
                      dragHeader.flex = dragHeader.savedFlex;
                      delete dragHeader.width;
                  }
              }
    
    
              Ext.resumeLayouts(true);
              // Ext.grid.header.Container will handle the removal of empty groups, don't handle it here.
          }
      }
    });

  4. #4

    Default

    how would i make this work for extjs 4? Never mind -
    Code:
    Ext.define('My.override.grid.header.DropZone',{
       override: 'Ext.grid.header.DropZone',
    
       onNodeDrop: function(node, dragZone, e, data) {
           // Note that dropLocation.pos refers to whether the header is dropped before or after the target node!
           if (this.valid) {
               var me = this,
                   dragHeader = data.header,
                   dropLocation = data.dropLocation,
                   dropPosition = dropLocation.pos,
                   targetHeader = dropLocation.header,
                   fromCt = dragHeader.ownerCt,
                   toCt = targetHeader.ownerCt,
                   visibleColumnManager = me.headerCt.visibleColumnManager,
                   visibleFromIdx = visibleColumnManager.getHeaderIndex(dragHeader),
                   visibleToIdx, colsToMove, moveMethod, scrollerOwner, savedWidth;
               if (data.isLock || data.isUnlock) {
                   scrollerOwner = fromCt.up('[scrollerOwner]');
                   visibleToIdx = toCt.items.indexOf(targetHeader);
                   if (dropPosition === 'after') {
                       visibleToIdx++;
                   }
                   if (data.isLock) {
                       scrollerOwner.lock(dragHeader, visibleToIdx, toCt);
                   } else {
                       scrollerOwner.unlock(dragHeader, visibleToIdx, toCt);
                   }
               } else // This is a drop within the same HeaderContainer.
               {
                  // <start edit>
                  if (dropPosition === 'after') {
                      // Get the last header in the most deeply-nested header group.
                      visibleToIdx = visibleColumnManager.getHeaderIndex(me.getNestedHeader(targetHeader, 1));
                      if (visibleToIdx < visibleFromIdx) {
                        // The column is moving leftwards and being dropped to the right of the drop point
                        visibleToIdx++;
                      }
                      // else: no need to add to the index as the drop point will be shifting rightwards
                  }
                  else {
                      visibleToIdx = visibleColumnManager.getHeaderIndex(me.getNestedHeader(targetHeader, 0));
                  }
                  // </edit>
                   visibleToIdx = dropPosition === 'after' ? // Get the last header in the most deeply-nested header group and add one.
                   visibleColumnManager.getHeaderIndex(me.getNestedHeader(targetHeader, 1)) + 1 : // Get the first header in the most deeply-nested header group.
                   visibleColumnManager.getHeaderIndex(me.getNestedHeader(targetHeader, 0)) , me.invalidateDrop();
                   savedWidth = dragHeader.getWidth();
                   Ext.suspendLayouts();
                   fromCt.isDDMoveInGrid = toCt.isDDMoveInGrid = !data.crossPanel;
                   moveMethod = 'move' + dropPosition.charAt(0).toUpperCase() + dropPosition.substr(1);
                   toCt[moveMethod](dragHeader, targetHeader);
                   if (visibleToIdx >= 0 && !(targetHeader.isGroupHeader && !targetHeader.items.length) && visibleFromIdx !== visibleToIdx) {
                       colsToMove = dragHeader.isGroupHeader ? dragHeader.query(':not([hidden]):not([isGroupHeader])').length : 1;
                       if ((visibleFromIdx <= visibleToIdx) && colsToMove > 1) {
                           visibleToIdx -= colsToMove;
                       }
                       toCt.up('grid').view.moveColumn(visibleFromIdx, visibleToIdx, colsToMove);
                   }
                   (fromCt.ownerCt || fromCt).fireEvent('columnmove', fromCt, dragHeader, visibleFromIdx, visibleToIdx);
                   fromCt.isDDMoveInGrid = toCt.isDDMoveInGrid = false;
                   if (toCt.isGroupHeader && !fromCt.isGroupHeader) {
                       if (fromCt !== toCt) {
                           dragHeader.savedFlex = dragHeader.flex;
                           delete dragHeader.flex;
                           dragHeader.width = savedWidth;
                       }
                   } else if (!fromCt.isGroupHeader) {
                       if (dragHeader.savedFlex) {
                           dragHeader.flex = dragHeader.savedFlex;
                           delete dragHeader.width;
                       }
                   }
                   Ext.resumeLayouts(true);
               }
           }
       }
     });

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •