Results 1 to 8 of 8

Thread: Grid performance issues when rendering Widgets

  1. #1
    Sencha Premium Member
    Join Date
    Oct 2011
    Posts
    16

    Default Grid performance issues when rendering Widgets

    In our application there are data records that contain any number of fields. When these data records are displayed in an editable Grid (GXT 2.2.6) some fields are rendered with Widgets. The Widgets are either a Gwt PushButton wrapped by an AdapterField, or a LabelField. The reason they are rendered as Widgets is to support certain edit capabilities for the field.

    When rendering Grids with a lot of columns, say > 20, and a number of the Widgets being rendered, it is possible for the Grid to incorrectly render the Widgets or to completely not render them, depending on how much scrolling the user does.

    We are already using the BufferView as it seems to perform the best. But the cleanup and rendering "threads" can overlap and cause rendering issues. I've attached a couple of screen shots to show what we see.

    I've seen requests for buffering the column rendering, but haven't seen or heard whether this capability has been implemented. We've removed the cleanup totally and that caused memory and browser performance issues/crashes. Other than "don't use Widgets", are there other suggestions on what can be done to improve the rendering performance and prevent the render/cleanup from causing issues?
    Attached Images Attached Images

  2. #2
    Sencha Premium Member
    Join Date
    Oct 2011
    Posts
    16

    Default

    After additional investigation it has been determined that the issue appears to be caused by the way the Widgets are rendered separate from the rest of the cells. In the doUpdate() method, there is a call

    Code:
    if (!isRowRendered(i)) {
       // render row
    }
    Then in isRowRendered( int row ) the determination of whether or not a row is rendered is if it is not null and if it has children. The problem is, the row may be "rendered" -- it has children, which are the default cell contents such as text/html -- but the Widgets have been removed from the widgetList (unrendered) due to either a scroll or clean. So isRowRendered() doesn't take into account any Widgets that are supposed to be in the row.

    We are trying to modify the doUpdate() to work around this, but it seems like the isRendered() should be a little more precise in determining whether or not a row really is rendered, or a better definition of what it means to be "rendered" is needed that takes the Widgets into consideration.

    Again, any additional help or insight would be appreciated.

  3. #3
    Sencha Premium Member
    Join Date
    Sep 2007
    Posts
    13,976

    Default

    The problem is that the widgets are real DOM elements. doRender however works on a String.

    This is way we first need to render all row rows using doRender and than add the widgets to the cells. Those are all small dom changes which are not fast. This behaviour changed in GXT3, there we are using the Cell pattern which fixes the problem. There is not much we can change here for GXT 2.


    However, do you really require a widget here? It seems you are only rendering a checkbox. What is the reason you require a real widget?

  4. #4
    Sencha Premium Member
    Join Date
    Oct 2011
    Posts
    16

    Default

    Thanks for the reply and explanation. The reason it is rendered as a Widget is because (1) it can be a tri-state button, which the default checkbox doesn't support, and (2) there is an underlying editor associated with it that makes edits to the data field on the server Object via RPC. When an edit is made to a data field, we send that edit to the server via RPC to ensure the user is able to edit the record (no one else is currently editing the record). Rather than making a lock request and then allowing the user to edit the record, we do a lock check upon the edit action.

    We do have a different version of these Grids using GXT 3.0. However, we couldn't find an efficient way to render the Grids. The BufferView isn't there anymore and the LiveGridView doesn't have the performance we need. So we decided to stick with GXT 2 for now. We may end up revisiting it at some later time, though.

  5. #5

    Question

    I am running into a similar problem. How did you workaround this in doUpdate()?
    Here is the description of my problem:
    http://www.sencha.com/forum/showthread.php?270113

  6. #6
    Sencha Premium Member
    Join Date
    Oct 2011
    Posts
    16

    Default

    The best solution we found was to increase the scroll delay using
    Code:
    setScrollDelay( 200 );
    This makes rendering of the table choppy as there is a delay in rendering as you scroll, but it results in all cells being properly rendered.

    It sounds like your issue is different, though.

    We do also have custom selection handling using our own CellSelectionModel. Our class maintains the selections even if the row becomes unrendered and then rerendered later by scrolling. By creating a subclass of
    HTML Code:
    CellSelectionModel
    you can track the selected rows yourself and render them as such.

  7. #7

    Default

    Thanks eprice.

    How do you maintain the selections in your custom CellSelectionModel? Can you please elaborate?

  8. #8
    Sencha Premium Member
    Join Date
    Oct 2011
    Posts
    16

    Default

    For our needs, we modifed the CellSelectionModel available here: http://www.sencha.com/forum/showthread.php?112256

    because we needed to be able to track both cell selections and row selections. We also had to override some methods in the GridSelectionModel superclass since the CellSelectionModel doesn't normally support the ability to handle row selections.

    Code:
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    import com.extjs.gxt.ui.client.GXT;
    import com.extjs.gxt.ui.client.Style.SelectionMode;
    import com.extjs.gxt.ui.client.aria.FocusFrame;
    import com.extjs.gxt.ui.client.event.BaseEvent;
    import com.extjs.gxt.ui.client.event.DomEvent;
    import com.extjs.gxt.ui.client.event.EventType;
    import com.extjs.gxt.ui.client.event.Events;
    import com.extjs.gxt.ui.client.event.GridEvent;
    import com.extjs.gxt.ui.client.widget.grid.CellEditor;
    import com.extjs.gxt.ui.client.widget.grid.CellSelectionModel;
    import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
    import com.extjs.gxt.ui.client.widget.grid.EditorGrid;
    import com.extjs.gxt.ui.client.widget.grid.Grid;
    import com.google.gwt.event.dom.client.KeyCodes;
    import com.google.gwt.user.client.rpc.AsyncCallback;
    
    /**
     * This class as been modified based on the <code>MultiCellSelectionModel</code> 
     * originally created by Sencha user haotten and available for free use from
     * the Sencha website.
     * 
     * @author Eric Price
     * Feb 15, 2012
     * 
     */
    public class EditableGridSelectionModel extends CellSelectionModel<DataRecordModel> {
    
        private Callback callback = new Callback(this);
        //Allows us to support multiple cell selections.
        protected ArrayList<CellSelection> cellSelections = new ArrayList<CellSelection>();
        protected boolean isInMultiSelect = false;
        protected boolean isShiftSelected = false;
        protected boolean isCtrlSelected = false;
        
        protected ClientContext context = ClientContext.getInstance();
        
        
        public EditableGridSelectionModel() {
            this.setMoveEditorOnEnter( true );
            this.setSelectionMode( SelectionMode.MULTI );
        }
        
        
        private void setFlags (boolean ctrlSelected, boolean shiftSelected) {
            isShiftSelected = shiftSelected;
            isCtrlSelected = ctrlSelected;
            isInMultiSelect = (shiftSelected || ctrlSelected || context.isShiftKeyForced() || context.isControlKeyForced());
        }
        
        
        private void clearFlags() {
            isShiftSelected = context.isControlKeyForced();
            isCtrlSelected = context.isShiftKeyForced();
            isInMultiSelect = (isShiftSelected || isCtrlSelected);
        }
        
        
        private CellSelection createCell(int row, int col) {
            DataRecordModel m = listStore.getAt(row);
            return new CellSelection(m, row, col);
        }
        
        
        @SuppressWarnings("rawtypes")
        public void bindGrid(Grid grid) {
            if (this.grid != null) {
                this.grid.removeListener(Events.RowMouseDown, this);
                this.grid.removeListener(Events.RowClick, this);
    
            }
    
            if (grid != null) {
                grid.addListener(Events.RowMouseDown, this);
                grid.addListener(Events.RowClick, this);
            }
    
            super.bindGrid( grid );
        }
    
        
        @SuppressWarnings("unchecked")
        public void handleEvent(BaseEvent e) {
            EventType type = e.getType();
            if ((e instanceof GridEvent) && ((GridEvent<DataRecordModel>)e).getColIndex() == 0) {
                if (type == Events.RowMouseDown) {
                    this.mockSuperMouseDown((GridEvent<DataRecordModel>) e);
                } 
                else if (type == Events.RowClick) {
                    // clear all the cell selections
                    this.deselectAllSelections( true );
                    handleMouseClick((GridEvent<DataRecordModel>) e);
                }
            }
            else {
                super.handleEvent( e );
            }
        }
        
        
        protected void doShiftSelect() {
            // Assume the last one on the list was shift selected.
            CellSelection cell = (cellSelections.size() > 0) ? cellSelections.get( cellSelections.size() - 1 ) : null;
            CellSelection anchor = (cellSelections.size() > 0) ? cellSelections.get( 0 ) : null;
    
            if (cell == null) {
                return;
            }
    
            ArrayList<CellSelection> newCellSelections = new ArrayList<CellSelectionModel<DataRecordModel>.CellSelection>();
    
            // NOTE: This algorithm makes sure that the anchor is the first one
            // written to the list, and the last cell is the last one written to the
            // list.
            if (anchor.row < cell.row) {
                for (int r = anchor.row; r <= cell.row; r++) {
                    if (anchor.cell <= cell.cell) { // anchor col < cell col
                        for (int c = anchor.cell; c <= cell.cell; c++) {
                            if (this.isCellMultiSelectable( r, c )) {
                                newCellSelections.add( createCell( r, c ) );
                            }
                        }
                    }
                    else { // anchor col > cell col
                        for (int c = anchor.cell; c >= cell.cell; c--) {
                            if (this.isCellMultiSelectable( r, c )) {
                                newCellSelections.add( createCell( r, c ) );
                            }
                        }
                    }
                }
            }
            else { // anchor row > cell row.
                for (int r = anchor.row; r >= cell.row; r--) {
                    if (anchor.cell <= cell.cell) { // anchor col < cell col
                        for (int c = anchor.cell; c <= cell.cell; c++) {
                            if (this.isCellMultiSelectable( r, c )) {
                                newCellSelections.add( createCell( r, c ) );
                            }
                        }
                    }
                    else { // anchor col > cell coll
                        for (int c = anchor.cell; c >= cell.cell; c--) {
                            if (this.isCellMultiSelectable( r, c )) {
                                newCellSelections.add( createCell( r, c ) );
                            }
                        }
                    }
                }
            }
    
            // This should probably calculate a merge, to minimize redraws.
            deselectAllSelections( false );
            cellSelections = newCellSelections;
            selection = cellSelections.get( cellSelections.size() - 1 );
            selectAllSelections();
        }
        
        
        protected void doCtrlSelect() {
            // Assume the last one on the cellSelection list was ctrl -
            // selected......
            CellSelection cell = (cellSelections.size() > 0) ? cellSelections.get( cellSelections.size() - 1 ) : null;
            if (cell == null) { // shouldn't happen.
                return;
            }
    
            // See if the cell is already in cell selection list, if so need to
            // unselect it.
            int index = findCell( cell.row, cell.cell, cellSelections );
            if (index != (cellSelections.size() - 1)) { // we need to "Unselect this
                                                        // cell;
                if (grid.isViewReady()) {
                    ((DataRecordGridView) grid.getView()).onCellDeselect( cell.row, cell.cell );
                }
                cellSelections.remove( cellSelections.size() - 1 );
                cellSelections.remove( index );
            }
        }
        
        
        @Override
        public void deselectAll() {
            if (!isInMultiSelect) {
                super.deselectAll();
                deselectAllSelections( true );
            }
        }
    
        @Override
        public void selectCell( int row, int cell ) {        
            if (!isInMultiSelect) {
                deselectAllSelections( true );
            }
            else if ((selection != null) && (cellSelections.size() == 0)) { 
                // need to push the current selection on the stack.
                if (!cellSelections.contains( selection )) {
                    cellSelections.add( selection );
                }
            }
    
            DataRecordModel m = listStore.getAt( row );
            if (GXT.isAriaEnabled() && selectedHeader != null) {
                selectedHeader = null;
                FocusFrame.get().frame( grid );
            }
    
            /*
             * Prevent selection of cell if the underlying field is non-editable
             * or has dependent data fields.
             */
            if (isInMultiSelect && (!isCellMultiSelectable( row, cell ))) {
                selection = null;
                return;
            }
            
            selection = new CellSelection( m, row, cell );
            
            if (grid.isViewReady()) {
                ((DataRecordGridView) grid.getView()).onCellSelect( row, cell );
                grid.getView().focusCell( row, cell, true );
                
                if (cell > 0) {
                    doDeselect(new ArrayList<DataRecordModel>(selected), false);
                }
            }
            
            if (isInMultiSelect) {
                // Push the current cell on the selection stack.
                if (!cellSelections.contains( selection )) {
                    cellSelections.add( selection ); 
                }
            
                if (isCtrlSelected) {
                    doCtrlSelect();
                }
                else {
                    doShiftSelect();
                }
            }
        }
        
        
        /*
         * This was added in order to prevent cells containing fields with edit
         * blocks, such as dependent field values or not having an associated
         * cell editor, from being selected during multi-select actions.
         */
        protected boolean isCellMultiSelectable( int row, int cell ) {
            return true;
        }
    
        
        /**
         * Loop through all cellselections on the stack and unselect them, then pop
         * them off the stack.
         * 
         * If clearCurrent is set to true, then the current selection is also
         * cleared.
         */
        protected void deselectAllSelections( boolean clearCurrent ) {
            for (CellSelection cell : cellSelections) {
                // we will deal with the current selection separately.
                if ((cell == selection) && (!clearCurrent)) { 
                    continue;
                }
                
                int row = listStore.indexOf( cell.model );
                if (grid.isViewReady()) {
                    ((DataRecordGridView) grid.getView()).onCellDeselect( row, cell.cell );
                }
            }
            cellSelections.clear();
    
            if ((clearCurrent) && (selection != null)) {
                int row = listStore.indexOf( selection.model );
                if (grid.isViewReady()) {
                    ((DataRecordGridView) grid.getView()).onCellDeselect( row, selection.cell );
                }
                selection = null;
            }
        }
        
        
        protected void selectAllSelections() {
            for (CellSelection cell : cellSelections) {
                if (grid.isViewReady()) {
                    ((DataRecordGridView)grid.getView()).onCellSelect(cell.row, cell.cell);
                }
            }
        }
        
        
        private int findCell(int row, int col, ArrayList<CellSelection> cells) {
            for (int i=0; i < cells.size(); i++) {
                CellSelection cell = cells.get(i);
                if ((cell.row == row) && (cell.cell == col)) {
                    return i;
                }
            }
            return -1;
        }
        
        
        @Override
        protected void onKeyDown(GridEvent<DataRecordModel> e) {
            if (!e.isControlKey() && selected.size() == 0 && getLastFocused() == null && selection == null) {
                select(0, false);
            } else {
                int idx = listStore.indexOf(getLastFocused());
                if (idx >= 0) {
                    if (e.isControlKey() || (e.isShiftKey() && isSelected(listStore.getAt(idx + 1)))) {
                        if (!e.isControlKey()) {
                            deselect(idx);
                        }
            
                        DataRecordModel lF = listStore.getAt(idx + 1);
                        if (lF != null) {
                            setLastFocused(lF);
                            grid.getView().focusCell(idx + 1, 0, false);
                        }
            
                    } else {
                        if (e.isShiftKey() && lastSelected != getLastFocused()) {
                            select(listStore.indexOf(lastSelected), idx + 1, true);
                            grid.getView().focusCell(idx + 1, 0, false);
                        } else {
                            if (idx + 1 < listStore.getCount()) {
                                selectNext(e.isShiftKey());
                                grid.getView().focusCell(idx + 1, 0, false);
                            }
                        }
                    }
                }
            }
            
            e.preventDefault();
        }
        
        
        protected void onKeyUp(GridEvent<DataRecordModel> e) {
            int idx = listStore.indexOf(getLastFocused());
            if (idx >= 0) {
              if (e.isControlKey() || (e.isShiftKey() && isSelected(listStore.getAt(idx - 1)))) {
                if (!e.isControlKey()) {
                  deselect(idx);
                }
    
                DataRecordModel lF = listStore.getAt(idx - 1);
                if (lF != null) {
                  setLastFocused(lF);
                  grid.getView().focusCell(idx - 1, 0, false);
                }
    
              } else {
    
                if (e.isShiftKey() && lastSelected != getLastFocused()) {
                  select(listStore.indexOf(lastSelected), idx - 1, true);
                  grid.getView().focusCell(idx - 1, 0, false);
                } else {
                  if (idx > 0) {
                    selectPrevious(e.isShiftKey());
                    grid.getView().focusCell(idx - 1, 0, false);
                  }
                }
    
              }
            }
    
            e.preventDefault();
        }
        
        
        @Override
        protected void onKeyPress(GridEvent<DataRecordModel> e) {
            setFlags(e.isControlKey(), e.isShiftKey());
            if (e.getColIndex() > 0) {
                doDeselect(new ArrayList<DataRecordModel>(selected), false);
            }
            super.onKeyPress(e);
            clearFlags();
        }
    
        
        @Override
        protected void handleMouseDown(GridEvent<DataRecordModel> e) {
            CellEditor editor = ((EditorGrid<DataRecordModel>)grid).getActiveEditor();
            if (editor != null && editor.isVisible() && (!editor.getField().isValid( true ))) {
                e.cancelBubble();
                return;
            }
            else {
                ColumnConfig cc = grid.getColumnModel().getColumn( e.getColIndex() );
                if ((cc instanceof DataFieldDefinitionColumn) && 
                        (((DataFieldDefinitionColumn)cc).getDataFieldDefinition() instanceof ActionFieldDefinitionDto)) {
                    e.cancelBubble();
                    return;
                }
                else if ("_EditColumn".equals( cc.getId() )) {
                    e.cancelBubble();
                    return;
                }
            }
            
            if (e.getColIndex() > 0) {
                setFlags(context.isControlKeyForced() || e.isControlKey(), context.isShiftKeyForced() || e.isShiftKey());
                // simulate deselection of all rows
                doDeselect(new ArrayList<DataRecordModel>(selected), false);
                super.handleMouseDown(e);
                clearFlags();
            }
            else {
                this.clearFlags();
                this.deselectAll();
                mockSuperMouseDown( e );
            }
        }
        
        
        /*
         * Since row selection support is required and the CellSelectionModel
         * doesn't call super.mouseDown(), this duplicates the mouseDown()
         * call in the GridSelectionModel.
         */
        protected void mockSuperMouseDown(GridEvent<DataRecordModel> e) {
    
            if (e.getRowIndex() == -1 || isLocked() || isInput(e.getTarget()) || e.getColIndex() > 0) {
                return;
            }
            if (e.isRightClick()) {
                if (selectionMode != SelectionMode.SINGLE && isSelected(listStore.getAt(e.getRowIndex()))) {
                    return;
                }
                select(e.getRowIndex(), false);
                grid.getView().focusCell(e.getRowIndex(), e.getColIndex(), false);
            } else {
                DataRecordModel sel = listStore.getAt(e.getRowIndex());
                if (selectionMode == SelectionMode.SIMPLE) {
                    if (!isSelected(sel)) {
                        select(sel, true);
                        grid.getView().focusCell(e.getRowIndex(), e.getColIndex(), false);
                    }
    
                } else if (selectionMode == SelectionMode.SINGLE) {
                    if ((e.isControlKey() || context.isControlKeyForced()) && isSelected(sel)) {
                        deselect(sel);
                    } else if (!isSelected(sel)) {
                        select(sel, false);
                        grid.getView().focusCell(e.getRowIndex(), e.getColIndex(), false);
                    }
                } else if (!e.isControlKey() && !context.isControlKeyForced()) {
                    if ((e.isShiftKey() || context.isShiftKeyForced()) && lastSelected != null) {
                        int last = listStore.indexOf(lastSelected);
                        int index = e.getRowIndex();
                        select(last, index, e.isControlKey() || context.isControlKeyForced());
                        grid.getView().focusCell(index, e.getColIndex(), false);
                    } else if (!isSelected(sel)) {
                        doSelect(Arrays.asList(sel), false, false);
                        grid.getView().focusCell(e.getRowIndex(), e.getColIndex(), false);
                    }
                }
            }
        }
        
        
        @Override
        public void onEditorKey(DomEvent e) {
            int k = e.getKeyCode();
            Cell newCell = null;
            CellEditor editor = ((EditorGrid<DataRecordModel>)grid).getActiveEditor();
    
            switch (k) {
                case KeyCodes.KEY_ENTER:
                    e.stopEvent();
                    if (editor != null) {
                        if (this.isMoveEditorOnEnter()) {
                            if (e.isShiftKey()) {
                                //newCell = simulateGridWalkCells(editor.row, editor.col - 1, -1, callback, true);
                                newCell = simulateGridWalkCells(editor.row - 1, editor.col, -1, callback, true);
                            } else {
                                //newCell = simulateGridWalkCells(editor.row, editor.col + 1, 1, callback, true);
                                newCell = simulateGridWalkCells(editor.row + 1, editor.col, 1, callback, true);
                            }
                        }
                        
                        if (editor instanceof DataFieldCellEditor) {
                            final Cell nextCell = newCell;
                            final DataFieldCellEditor dfCellEditor = (DataFieldCellEditor)editor;
                            AsyncCallback<Boolean> editCallback = null;
                            
                            editCallback = new AsyncCallback<Boolean>() {
    
                                @Override
                                public void onFailure( Throwable caught ) {
                                    ExceptionNotifier.showOnFailureMessage( caught );
                                }
    
                                @Override
                                public void onSuccess( Boolean result ) {
                                    if (result) {
                                        if (nextCell != null) {
                                            if (!isInMultiSelect) {
                                                ((EditorGrid<DataRecordModel>)grid).startEditing( nextCell.row, nextCell.cell );
                                            }
                                        }
                                        else {
                                            grid.getView().focusCell( dfCellEditor.row, dfCellEditor.col, false );
                                        }
                                    }
                                }
                                
                            };
                            
                            dfCellEditor.completeEdit( editCallback );
                        }
                        else {
                            editor.completeEdit();
                        }
                    }
                    break;
                    
                case KeyCodes.KEY_TAB:
                    e.stopEvent();
                    if (editor != null) {
                        if (e.isShiftKey()) {
                            newCell = simulateGridWalkCells(editor.row, editor.col - 1, -1, callback, true);
                        } else {
                            newCell = simulateGridWalkCells(editor.row, editor.col + 1, 1, callback, true);
                        }
                        
                        if (editor instanceof DataFieldCellEditor) {
                            final Cell nextCell = newCell;
                            final DataFieldCellEditor dfCellEditor = (DataFieldCellEditor)editor;
                            AsyncCallback<Boolean> editCallback = null;
                            
                            editCallback = new AsyncCallback<Boolean>() {
    
                                @Override
                                public void onFailure( Throwable caught ) {
                                    ExceptionNotifier.showOnFailureMessage( caught );
                                }
    
                                @Override
                                public void onSuccess( Boolean result ) {
                                    if (result) {
                                        if (nextCell != null) {
                                            if (!isInMultiSelect) {
                                                ((EditorGrid<DataRecordModel>)grid).startEditing( nextCell.row, nextCell.cell );
                                            } 
                                        }
                                        else {
                                            grid.getView().focusCell( dfCellEditor.row, dfCellEditor.col, false );
                                        }
                                    }
                                }
                                
                            };
                            
                            dfCellEditor.completeEdit( editCallback );
                        }
                        else {
                            editor.completeEdit();
                        }
                    }
    
                    break;
                    
                case KeyCodes.KEY_ESCAPE:
                    if (editor != null) {
                        editor.cancelEdit();
                    }
                    break;
            }
            
            if ((!(editor instanceof DataFieldCellEditor)) || (k == KeyCodes.KEY_ESCAPE)) {
                if (newCell != null) {
                    if (!isInMultiSelect) {
                        ((EditorGrid<DataRecordModel>)grid).startEditing(newCell.row, newCell.cell);
                    }
                    //grid.getView().focusCell(editor.row, editor.col, true);
                } 
                else {
                    if ((k == KeyCodes.KEY_ENTER || k == KeyCodes.KEY_TAB || k == KeyCodes.KEY_ESCAPE) && editor != null) {
                        grid.getView().focusCell(editor.row, editor.col, false);
                    }
                }
            }
        }
    
    
        /*
         * Simulate the <code>grid.walkCells</code> call in order to get the location
         * of the next edit grid cell.
         */
        protected Cell simulateGridWalkCells( int row, int col, int step, Callback callback, boolean acceptNavs ) {
            boolean first = true;
            int clen = grid.getColumnModel().getColumnCount();
            int rlen = grid.getStore().getCount();
            if (step < 0) {
                if (col < 0) {
                    if (GXT.isFocusManagerEnabled()) {
                        return new Cell(row, 0);
                    }
                    row--;
                    first = false;
                }
                while (row >= 0) {
                    if (!first) {
                        col = clen - 1;
                    }
                    first = false;
                    while (col >= 0) {
                        if (callback.isSelectable(row, col, acceptNavs)) {
                            return new Cell(row, col);
                        }
                        col--;
                    }
                    row--;
                }
            } 
            else {
                if (col == clen && GXT.isFocusManagerEnabled()) {
                    return new Cell(row, col - 1);
                }
                
                if (col >= clen) {
                    row++;
                    first = false;
                }
                
                while (row < rlen) {
                    if (!first) {
                        col = 0;
                    }
                    first = false;
                    while (col < clen) {
                        if (callback.isSelectable(row, col, acceptNavs)) {
                            return new Cell(row, col);
                        }
                        col++;
                    }
                    row++;
                }
            }
            
            return null;
        }
        
        
        public ArrayList<CellSelection> getSelections() {
            return cellSelections;
        }
        
        
        public List<DataRecordModel> getSelectedItems() {
            ArrayList<DataRecordModel> selections = new ArrayList<DataRecordModel>();
    
            /*
             * combine both the row selections and cell selections in order to
             * get the correct value since CellSelectionModel and AbstractStoreSelectionModel
             * handle this method differently
             */
            selections.addAll( selected );
            if ((selection != null) && (!selections.contains( selection.model ))) {
                selections.add( selection.model );
            }
            return selections;
        }
        
        
        /**
         * This implementation is copied from the GridSelectionModel superclass.  The only modification is to look for 
         * the forced control and shift keys.
         */
        @Override
        protected void handleMouseClick(GridEvent<DataRecordModel> e) {
            if (isLocked() || isInput(e.getTarget())) {
                return;
            }
            if (e.getRowIndex() == -1) {
                deselectAll();
                return;
            }
            if (selectionMode == SelectionMode.MULTI) {
                DataRecordModel sel = listStore.getAt(e.getRowIndex());
                if ((e.isControlKey() || context.isControlKeyForced()) && isSelected(sel)) {
                    doDeselect(Arrays.asList(sel), false);
                } else if (e.isControlKey() || context.isControlKeyForced()) {
                    doSelect(Arrays.asList(sel), true, false);
                    grid.getView().focusCell(e.getRowIndex(), e.getColIndex(), false);
                } else if (isSelected(sel) && !e.isShiftKey() && !e.isControlKey() && 
                           !context.isShiftKeyForced() && !context.isControlKeyForced() && selected.size() > 1) {
                    doSelect(Arrays.asList(sel), false, false);
                    grid.getView().focusCell(e.getRowIndex(), e.getColIndex(), false);
                }
                
                if (selected.size() > 0 && selection == null) {
                    selection = new CellSelection( sel, e.getRowIndex(), e.getColIndex() );
                }
            }
        }
    }
    You can ignore the ClientContext calls and the use of special cell editors we have in our code. DataRecordGridView is a subclass of BufferView, so this will work with the BufferView. DataRecordModel is the class we used in the Grid, so just replace that with the class you're displaying in your Grid.

    Also, you will need to subclass BufferView and override these two methods:

    Code:
        /*
         * make this public so it's available to the EditableGridSelectionModel
         * 
         * (non-Javadoc)
         * @see com.extjs.gxt.ui.client.widget.grid.GridView#onCellDeselect(int, int)
         */
        public void onCellDeselect( int row, int col ) {
            super.onCellDeselect( row, col );
        }
        
        
        /*
         * make this public so it's available to the EditableGridSelectionModel
         * 
         * (non-Javadoc)
         * @see com.extjs.gxt.ui.client.widget.grid.GridView#onCellSelect(int, int)
         */
        public void onCellSelect(int row, int col) {
            super.onCellSelect( row, col );
        }

Posting Permissions

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