Page 1 of 2 12 LastLast
Results 1 to 10 of 15

Thread: Classic (Ext 6.0.0) Cell Editor issue with grid buffered rendering.

    Success! Looks like we've fixed this one. According to our records the fix was applied for EXTJS-19675 in 6.0.2.437.
  1. #1
    Sencha User
    Join Date
    Sep 2015
    Posts
    1

    Default Classic (Ext 6.0.0) Cell Editor issue with grid buffered rendering.

    When using the Cell Editor plugin on a grid with many rows, eventually the editor fails to render after clicking and scrolling to different rows.

    Error thrown is:

    Uncaught TypeError: Failed to execute 'insertBefore' on 'Node': parameter 1 is not of type 'Node'

    on this line in the activateCell function of the Ext.grid.plugin.CellEditing class:

    PHP Code:
                // It will show if it begins editing.            // And will then be found in the tabbable children of the activating cell            if (!editor.rendered) {                editor.hidden = true;                editor.render(cell, 0);            } else {                if (editor.container !== cell) {                    editor.container = cell;                    cell.insertBefore(editor.el.dom, cell.firstChild); <-----                }                editor.hide();             } 
    I think the error has to do with the editor DOM element being destroyed when the grid is scrolled too far and the grid buffering functionality removes the row where the editor was last rendered. A work around that works for me is to set bufferedRenderer to false on the grid, which of course impacts performance.

  2. #2
    Sencha - Support Team bjdurham85's Avatar
    Join Date
    Mar 2014
    Posts
    962

    Default

    Hi jday3,

    Have you tested this in our latest (6.0.1) release?


    Regards,
    Bryan

  3. #3
    Sencha User
    Join Date
    Jun 2014
    Location
    Murmansk, Russia
    Posts
    273

    Default

    Quote Originally Posted by bjdurham85 View Post
    Have you tested this in our latest (6.0.1) release?
    6.0.1 also exposes this bug: https://s3.amazonaws.com/donnicky_sc...9-23_13-48.png

  4. #4
    Sencha Premium Member
    Join Date
    Oct 2010
    Posts
    99

    Default

    Any update on this? I am having exactly same problem. Occurs at least on Chrome and Safari.

  5. #5
    Sencha User
    Join Date
    Jun 2014
    Location
    Murmansk, Russia
    Posts
    273

    Default

    This fix can help:
    Code:
    Ext.define('Dbn.override.grid.CellEditor', {
        override: 'Ext.grid.CellEditor',
        compatibility: '6.0.1.250',
    
        beforeItemUpdate: function(record, recordIndex, oldItemDom, columnsToUpdate) {
            if(Ext.getDetachedBody().isAncestor(this.el)) return;
            this.callParent([record, recordIndex, oldItemDom, columnsToUpdate]);
        }
    });

  6. #6
    Sencha - Ext JS Dev Team nohuhu's Avatar
    Join Date
    Jun 2011
    Location
    Redwood coast
    Posts
    401

    Default

    Thanks for the report! I have opened a bug in our bug tracker.

  7. #7
    Sencha User
    Join Date
    Jun 2014
    Location
    Murmansk, Russia
    Posts
    273

    Default

    Quote Originally Posted by nohuhu View Post
    Thanks for the report! I have opened a bug in our bug tracker.
    Isn't it the same as EXTJS-19652?

  8. #8
    Sencha - Ext JS Dev Team nohuhu's Avatar
    Join Date
    Jun 2011
    Location
    Redwood coast
    Posts
    401

    Default

    Quote Originally Posted by nikolay.bobrovskiy View Post
    Isn't it the same as EXTJS-19652?
    Quite possibly so but you can never tell without investigating it first. Thanks for pointing out the other ticket though.
    Regards,
    Alex.

  9. #9
    Sencha User
    Join Date
    Mar 2007
    Location
    Bédoin/Nottingham
    Posts
    30,890

    Default

    The fix is something along these lines:

    Code:
    Ext.define('Ext.overrides.grid.plugin.BufferedRenderer', {
        override: 'Ext.grid.plugin.BufferedRenderer',
    
        onRangeFetched: function(range, start, end, options, fromLockingPartner) {
            var me = this,
                view = me.view,
                viewEl = view.el,
                oldStart,
                rows = view.all,
                removeCount,
                increment = 0,
                calculatedTop, newTop,
                lockingPartner = (view.lockingPartner && !fromLockingPartner && !me.doNotMirror) && view.lockingPartner.bufferedRenderer,
                newRows, partnerNewRows, topAdditionSize, topBufferZone, i,
                variableRowHeight = me.variableRowHeight,
                activeEl, containsFocus, pos, newFocus;
    
            // View may have been destroyed since the DelayedTask was kicked off.
            if (view.destroyed) {
                return;
            }
            // If called as a callback from the Store, the range will be passed, if called from renderRange, it won't
            if (range) {
                // Re-cache the scrollTop if there has been an asynchronous call to the server.
                me.scrollTop = me.view.getScrollY();
            } else {
                range = me.store.getRange(start, end);
                // Store may have been cleared since the DelayedTask was kicked off.
                if (!range) {
                    return;
                }
            }
            // If we contain focus now, but do not when we have rendered the new rows, we must focus the view el.
            activeEl = Ext.Element.getActiveElement();
            containsFocus = viewEl.contains(activeEl);
            // Best guess rendered block position is start row index * row height.
            calculatedTop = start * me.rowHeight;
            // The new range encompasses the current range. Refresh and keep the scroll position stable
            if (start < rows.startIndex && end > rows.endIndex) {
                // How many rows will be added at top. So that we can reposition the table to maintain scroll position
                topAdditionSize = rows.startIndex - start;
                // MUST use View method so that itemremove events are fired so widgets can be recycled.
                view.clearViewEl(true);
                newRows = view.doAdd(range, start);
                view.fireEvent('itemadd', range, start, newRows);
                for (i = 0; i < topAdditionSize; i++) {
                    increment -= newRows[i].offsetHeight;
                }
                // We've just added a bunch of rows to the top of our range, so move upwards to keep the row appearance stable
                newTop = me.bodyTop + increment;
            } else {
                // No overlapping nodes, we'll need to render the whole range
                // teleported flag is set in getFirstVisibleRowIndex/getLastVisibleRowIndex if
                // the table body has moved outside the viewport bounds
                if (me.teleported || start > rows.endIndex || end < rows.startIndex) {
                    newTop = calculatedTop;
                    // If we teleport with variable row height, the best thing is to try to render the block
                    // <bufferzone> pixels above the scrollTop so that the rendered block encompasses the
                    // viewport. Only do that if the start is more than <bufferzone> down the dataset.
                    if (variableRowHeight) {
                        topBufferZone = me.scrollTop < me.position ? me.leadingBufferZone : me.trailingBufferZone;
                        if (start > topBufferZone) {
                            newTop = me.scrollTop - me.rowHeight * topBufferZone;
                        }
                    }
                    // MUST use View method so that itemremove events are fired so widgets can be recycled.
                    view.clearViewEl(true);
                    me.teleported = false;
                }
                if (!rows.getCount()) {
                    newRows = view.doAdd(range, start);
                    view.fireEvent('itemadd', range, start, newRows);
                }
                // Moved down the dataset (content moved up): remove rows from top, add to end
                else if (end > rows.endIndex) {
                    removeCount = Math.max(start - rows.startIndex, 0);
                    // We only have to bump the table down by the height of removed rows if rows are not a standard size
                    if (variableRowHeight) {
                        increment = rows.item(rows.startIndex + removeCount, true).offsetTop;
                    }
                    newRows = rows.scroll(Ext.Array.slice(range, rows.endIndex + 1 - start), 1, removeCount);
                    // We only have to bump the table down by the height of removed rows if rows are not a standard size
                    if (variableRowHeight) {
                        // Bump the table downwards by the height scraped off the top
                        newTop = me.bodyTop + increment;
                    } else {
                        newTop = calculatedTop;
                    }
                } else // Moved up the dataset: remove rows from end, add to top
                {
                    removeCount = Math.max(rows.endIndex - end, 0);
                    oldStart = rows.startIndex;
                    newRows = rows.scroll(Ext.Array.slice(range, 0, rows.startIndex - start), -1, removeCount);
                    // We only have to bump the table up by the height of top-added rows if rows are not a standard size
                    if (variableRowHeight) {
                        // Bump the table upwards by the height added to the top
                        newTop = me.bodyTop - rows.item(oldStart, true).offsetTop;
                        // We've arrived at row zero...
                        if (!rows.startIndex) {
                            // But the calculated top position is out. It must be zero at this point
                            // We adjust the scroll position to keep visual position of table the same.
                            if (newTop) {
                                view.setScrollY(me.position = (me.scrollTop -= newTop));
                                newTop = 0;
                            }
                        }
                        // Not at zero yet, but the position has moved into negative range
                        else if (newTop < 0) {
                            increment = rows.startIndex * me.rowHeight;
                            view.setScrollY(me.position = (me.scrollTop += increment));
                            newTop = me.bodyTop + increment;
                        }
                    } else {
                        newTop = calculatedTop;
                    }
                }
                // The position property is the scrollTop value *at which the table was last correct*
                // MUST be set at table render/adjustment time
                me.position = me.scrollTop;
            }
            // We contained focus at the start, but that activeEl has been derendered.
            // Focus the cell's column header.
            if (containsFocus && !viewEl.contains(activeEl)) {
                pos = view.actionableMode ? view.actionPosition : view.lastFocused;
                if (pos && pos.column) {
                    view.onFocusLeave({});
    
                    //////////////////////////////////////////////
                    //
                    // The Fix/
                    // ========
                    // Try to focus the contextual column header.
                    // Failing that, look inside it for a tabbable element.
                    // Failing that, focus the view.
                    // Focus MUST NOT just silently die due to DOM removal
                    if (pos.column.focusable) {
    	                newFocus = pos.column;
                    } else {
                        newFocus = pos.column.el.findTabbableElements()[0];
                    }
                    if (!newFocus) {
                        newFocus = view.el;
                    }
                    newFocus.focus();
                }
            }
            // Position the item container.
            newTop = Math.max(Math.floor(newTop), 0);
            if (view.positionBody) {
                me.setBodyTop(newTop);
            }
            // Sync the other side to exactly the same range from the dataset.
            // Then ensure that we are still at exactly the same scroll position.
            if (newRows && lockingPartner && !lockingPartner.disabled) {
                // Set the pointers of the partner so that its onRangeFetched believes it is at the correct position.
                lockingPartner.scrollTop = lockingPartner.position = me.scrollTop;
                if (lockingPartner.view.ownerCt.isVisible()) {
                    partnerNewRows = lockingPartner.onRangeFetched(null, start, end, options, true);
                    // Sync the row heights if configured to do so, or if one side has variableRowHeight but the other doesn't.
                    // variableRowHeight is just a flag for the buffered rendering to know how to measure row height and
                    // calculate firstVisibleRow and lastVisibleRow. It does not *necessarily* mean that row heights are going
                    // to be asymmetric between sides. For example grouping causes variableRowHeight. But the row heights
                    // each side will be symmetric.
                    // But if one side has variableRowHeight (eg, a cellWrap: true column), and the other does not, that
                    // means there could be asymmetric row heights.
                    if (view.ownerGrid.syncRowHeight || (lockingPartner.variableRowHeight !== variableRowHeight)) {
                        me.syncRowHeights(newRows, partnerNewRows);
                        // body height might have changed with change of rows, and possible syncRowHeights call.
                        me.bodyHeight = view.body.dom.offsetHeight;
                    }
                }
                if (lockingPartner.bodyTop !== newTop) {
                    lockingPartner.setBodyTop(newTop);
                }
                // Set the real scrollY position after the correct data has been rendered there.
                // It will not handle a scroll because the scrollTop and position have been preset.
                lockingPartner.view.setScrollY(me.scrollTop);
            }
            return newRows;
        }
    });
    Basically, if a focused element is just removed from the DOM which is what happens when the buffered renderer scrolls rows out of the view, the browser does not fire blur events. So we must handle this by programatically focusing the contextual column header.

    In this case it's only if you have the editor open in the "Name" field that this occurs because that column header cannot be focused. This fix falls back to other focus targets to force a blur (and therefore correct cleanup of any editors)

  10. #10
    Sencha Premium Member
    Join Date
    Oct 2012
    Posts
    16

    Default

    I'm facing a similar issue: I've used a combo editor, and in the change event I'm calling field.blur()/grid.editingPlugin.completeEdit() both resulting in the same exception. And I'm also setting values for the edited record for other fields in the change event.

Page 1 of 2 12 LastLast

Similar Threads

  1. Cell editing, sync, and buffered rendering issue?
    By miroperez in forum Ext: Q&A
    Replies: 1
    Last Post: 31 Aug 2015, 11:35 AM
  2. Buffered rendering issue in tree grid
    By ajin in forum Ext 5: Bugs
    Replies: 4
    Last Post: 31 Mar 2015, 11:05 AM
  3. [OPEN] Sorted grid with buffered rendering enabled breaks row editor
    By andersonjf in forum Ext 5: Bugs
    Replies: 3
    Last Post: 8 Oct 2014, 11:01 AM
  4. Editor Grid Cell Calculation Issue
    By poojagarg89 in forum Ext 3.x: Help & Discussion
    Replies: 4
    Last Post: 11 Jun 2012, 11:34 PM
  5. Combobox in editor grid IE rendering issue
    By UGA_Zimma in forum Sencha Ext JS Q&A
    Replies: 5
    Last Post: 22 Jun 2010, 12:58 PM

Posting Permissions

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