Layout Renders Partially – How To Fix

Last year I wrote in-depth about ExtJS “heavy” JavaScript layout system in Sencha’s blog: http://www.sencha.com/blog/exploring-the-layout-system-in-ext-js-5-and-sencha-touch

Recently I discovered a rather interesting “issue” that all big ExtJS apps will hit eventually: the app will start “misbehaving” in a sense that very complex sections will sometimes render only partially and the UI will become unresponsive. This one will be extremely painful to figure out, as this is actually a result of a “silent failure” by the ExtJS layout system, due to a pre-emptive assumption of “if you’re running too much layout, you probably did something wrong” by the framework. Read on for an easy fix…

ExtJS JavaScript-based layout engine performs a lot of calculations. Naturally, the more “stuff” your app has, the more sections with ExtJS layouts you will have. This means more calculations for the layout engine to run. The framework architects deemed it necessary to silently abort the layout run when simply “too many” calculations are performed. This is very easy to reproduce by simply nesting 15+ panels inside one another:

https://fiddle.sencha.com/#fiddle/lam



Notice how after Panel 12 the rendering gets cut off?

This silent failure takes place in the following piece of code; pay attention to the “watchDog” variable:

http://docs.sencha.com/extjs/4.2.0/source/Context2.html#Ext-layout-Context-method-run

run: function () {
    var me = this,
        flushed = false,
        watchDog = 100;
 
    me.flushInvalidates();
 
    me.state = 1;
    me.totalCount = me.layoutQueue.getCount();
 
    // We may start with unflushed data placed by beginLayout calls. Since layouts may
    // use setProp as a convenience, even in a write phase, we don't want to transition
    // to a read phase with unflushed data since we can write it now "cheaply". Also,
    // these value could easily be needed in the DOM in order to really get going with
    // the calculations. In particular, fixed (configured) dimensions fall into this
    // category.
    me.flush();
 
    // While we have layouts that have not completed...
    while ((me.remainingLayouts || me.invalidQueue.length) && watchDog--) {
    	// ... layout logic
    }
 
    return me.runComplete();
}

So, the “while” loop silently aborts after 100 (default value) iterations. That is the silent failure. If you eliminate this check from code and make too huge of an app (or somehow force infinite loop here) – you will hang the browsers. I’m guessing the framework architects felt it’s easier to debug a silent failure than a hung browser?

Anyway, easiest fix is to simply bump the value with an override like this:

Ext.override(Ext.layout.Context, {
   	run: function () {
        var me = this,
            flushed = false,
            watchDog = 1000; // 100 default
 
        me.flushInvalidates();
 
        me.state = 1;
        me.totalCount = me.layoutQueue.getCount();
 
        // We may start with unflushed data placed by beginLayout calls. Since layouts may
        // use setProp as a convenience, even in a write phase, we don't want to transition
        // to a read phase with unflushed data since we can write it now "cheaply". Also,
        // these value could easily be needed in the DOM in order to really get going with
        // the calculations. In particular, fixed (configured) dimensions fall into this
        // category.
        me.flush();
 
        // While we have layouts that have not completed...
        while ((me.remainingLayouts || me.invalidQueue.length) && watchDog--) {
            if (me.invalidQueue.length) {
                me.flushInvalidates();
            }
 
            // if any of them can run right now, run them
            if (me.runCycle()) {
                flushed = false; // progress means we probably need to flush something
                // but not all progress appears in the flushQueue (e.g. 'contentHeight')
            } else if (!flushed) {
                // as long as we are making progress, flush updates to the DOM and see if
                // that triggers or unblocks any layouts...
                me.flush();
                flushed = true; // all flushed now, so more progress is required
 
                me.flushLayouts('completionQueue', 'completeLayout');
            } else if (!me.invalidQueue.length) {
                // after a flush, we must make progress or something is WRONG
                me.state = 2;
                break;
            }
 
            if (!(me.remainingLayouts || me.invalidQueue.length)) {
                me.flush();
                me.flushLayouts('completionQueue', 'completeLayout');
                me.flushLayouts('finalizeQueue', 'finalizeLayout');
            }
        }
 
        return me.runComplete();
    } 
});

A value of larger than a 1000 should account for any apps we build in the next century or so 🙂 Still, a better override would be to keep a value small, and to provide a meaningful console log when the watchDog is hit.

I’ve also heard rumors that Sencha’s making this watchDog a configurable property in the coming version of the framework.

VN:F [1.9.22_1171]
Rating: 7.8/10 (9 votes cast)
Layout Renders Partially - How To Fix, 7.8 out of 10 based on 9 ratings

Leave a Reply

Your email address will not be published. Required fields are marked *

* Copy This Password *

* Type Or Paste Password Here *