Prevent Rendering (hide and/or disable) of a Component at construction time

JJR picture JJR · Oct 6, 2013 · Viewed 12.7k times · Source

Background: Our app is always packed as a whole but through the users access some serverside actions may be restricted. We know which actions are allowed the time the app starts. We now want to hide all the views (panels, buttons, etc) from the user to which he lacks the access to.

For that we have written a plugin which can be applied to any Component. But here comes the problems:

Here is what we try to run against the plugin host:

if (cmp['setVisible']) cmp.setVisible(false); else cmp.hidden = true;
if (cmp['disable']) cmp.disable(); else cmp.disabled = true;
cmp.on('beforerender', function() { return false; })

First we thought the earlier we do this the better. So we tried to run it at construction time of the plugin. But that was not possible because the listeners (of the host) seems to be not ready yet (the component tries to fire the hide event). So we moved it into the init method of the plugin which does not throw a error but just worked partly. Only the beforerender event got really applied but it only aborted the rendering of the child. so we ended up with a broken looking component (the borders are there and the content not). If we commented the event registration out the host stayed untouched. We also tested the use of only the hidden:true and disabled:true with no luck.

So how can we prevent rendering of component in the correct way?

Edit:

The component should be flagged as disabled and hidden because we cannot prevent the creation of the component. The snipped I got from my colleague was wrong so the call of setVisible(false) worked, we guess disable() also. But the component get stilled rendered and we seem not really able to veto this without ending up with a half rendered component.

Answer by @AlexTokarev

I tried what @AlexTokarev suggested. For that I added the following lines into the Plugin-Constructor

cmp.hidden = true;
cmp.autoShow = false; // I know this may do nothing for non floating but I added it anyway
cmp.autoRender = true;

Based on debugging I know that the settings get applied really early (at the Ext.AbstractComponent.constructor), but I still ending up with a hidden and rendered component.

enter image description here

Comment by @sbgoran

In one Testcase we use a column-layout in which all containers extend from the same class. As soon as I add our plugin (with the beforerender event returning false configuration) to one of this extending containers (the plugin is directly added to class definition (as ptype)) all containers within this columns look broken (only borders are rendered and in the content a small grey box in the upper left corner.). So the aborted rendering affect all child items of the column when only one child item get the rendering canceled.

**Sample Code **

First I want to note that we are looking for a way to do this in general cause as far as we know the rendering in ExtJS is one thing. I can ask to setup a demo but I think this will not be that easy because we are using the Ext.app.portal.Panel for the failing example. but the plugin should work for any sort of Component. First I will add some demo code:

We have a view which is placed into a Viwport with border layout

Ext.define('MVC.view.Application',{
    extend:'Ext.tab.Panel',
    alias:'widget.appview',
    region: 'center',
    activeTab: 1
});

Within the Controller we fill this

var portal = this.portalRef = Ext.widget('portalpanel', {
    title: 'Employee',
    portalCols: 2
});
portal.addPortlet(0,['employee','employee2','employee3']);
portal.addPortlet(1,['employee4','employee5']);
app.appviewmain.add(portal);

Here is the portal panel

Ext.define('MVC.app.portal.PortalPanel', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.portalpanel',

    requires: [
        'Ext.layout.container.Column',
        'Ext.app.portal.PortalDropZone',
        'Ext.app.portal.PortalColumn'
    ],

    portalCols: 2,

    portalColCfg: {
        defaults: {
            closable: false,
            draggable: false,
            collapsible: false,
            header: false,
            bodyStyle: {
                background: '#fff',
                padding: '10px'
            }
        },
        items: []
    },

    addPortlet: function(idx, portlets) {
        if (idx > this.portalCols || idx < 0)
            return;
        var portalCol = this.items.getAt(idx);
        function insertPortlet(portlet) {
            if (Ext.isString(portlet)) {
                portlet = { xtype: portlet };
            }
            portalCol.add(portlet);
        };

        if (Ext.isArray(portlets)) {
            var len = portlets.length,
                i = 0;
            for(;i<len;i++) {
                insertPortlet(portlets[i]);
            }
        }  else  {
            insertPortlet(portlets);
        }

    },

    initPortal: function() {
        var cfg = this.portalColCfg,
            i = 0,
            cols = [];
        for (;i<this.portalCols;i++) {
            cols.push(Ext.clone(cfg));
        }
        this.items = cols;
    },

    cls: 'x-portal',
    bodyCls: 'x-portal-body',
    defaultType: 'portalcolumn',
    autoScroll: true,

    manageHeight: false,

    initComponent : function() {
        var me = this;
        // init only if nothing is defined
        if (!me.items)
            me.initPortal();

        // Implement a Container beforeLayout call from the layout to this Container
        me.layout = {
            type : 'column'
        };
        me.callParent();

        me.addEvents({
            validatedrop: true,
            beforedragover: true,
            dragover: true,
            beforedrop: true,
            drop: true
        });
    },

    // Set columnWidth, and set first and last column classes to allow exact CSS targeting.
    beforeLayout: function() {
        var items = this.layout.getLayoutItems(),
            len = items.length,
            firstAndLast = ['x-portal-column-first', 'x-portal-column-last'],
            i, item, last;

        for (i = 0; i < len; i++) {
            item = items[i];
            item.columnWidth = 1 / len;
            last = (i == len-1);

            if (!i) { // if (first)
                if (last) {
                    item.addCls(firstAndLast);
                } else {
                    item.addCls('x-portal-column-first');
                    item.removeCls('x-portal-column-last');
                }
            } else if (last) {
                item.addCls('x-portal-column-last');
                item.removeCls('x-portal-column-first');
            } else {
                item.removeCls(firstAndLast);
            }
        }

        return this.callParent(arguments);
    },

    // private
    initEvents : function(){
        this.callParent();
        this.dd = Ext.create('Ext.app.portal.PortalDropZone', this, this.dropConfig);
    },

    // private
    beforeDestroy : function() {
        if (this.dd) {
            this.dd.unreg();
        }
        this.callParent();
    }
});

And here is the Portlet

Ext.define('Ext.app.portal.Portlet', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.portlet',

    layout: 'fit',
    anchor: '100%',
    frame: true,
    closable: true,
    collapsible: true,
    animCollapse: true,
    draggable: {
        moveOnDrag: false    
    },
    cls: 'x-portlet',

    initComponent : function() {
        this.callParent();
    },

    // Override Panel's default doClose to provide a custom fade out effect
    // when a portlet is removed from the portal
    doClose: function() {
        if (!this.closing) {
            this.closing = true;
            this.el.animate({
                opacity: 0,
                callback: function(){
                    var closeAction = this.closeAction;
                    this.closing = false;
                    this.fireEvent('close', this);
                    this[closeAction]();
                    if (closeAction == 'hide') {
                        this.el.setOpacity(1);
                    }
                },
                scope: this
            });
        }
    }
});

Here is a sample view

Ext.define('MVC.view.employee.Employee',{
    extend:'Ext.app.portal.Portlet',
    alias:'widget.employee',
    plugins: [{ptype: 'directbound', accessRoute: 'Employee.Read'}],
    items: [
        /*A form with some fields*/
    ]
});

Here's the plugin

Ext.define('MVC.direct.plugins.DirectBound',{
    extend: 'Ext.AbstractPlugin',
    alternateClassName: ['MVC.direct.DirectBound'],
    alias: 'plugin.directbound',

    /**
     * @cfg {int} blockMode Indicates the way in which the Component gets blocked
     * options
     * 0 hide and disable
     * 1 disable
     */
    blockMode: 1,

    constructor: function(config) {
        var me = this,
            cmp = config['cmp'], 
            route;
        me.parseRoute(route);

        // check for access
        if (!me.checkAccess()) {
            if (me.blockMode === 0) {
                cmp.hidden = true;
                cmp.autoShow = false;
                cmp.autoRender = true;
            }
            me.diabled = true;
        }

        me.callParent(arguments);
    }

    /* some more methods */
});

Here's the column Layout

Ext.define('MVC.app.portal.PortalColumn', { extend: 'Ext.container.Container', alias: 'widget.portalcolumn',

requires: [
    'Ext.layout.container.Anchor',
    'MVC.app.portal.Portlet'
],

layout: 'anchor',
defaultType: 'portlet',
cls: 'x-portal-column'

// This is a class so that it could be easily extended
// if necessary to provide additional behavior.

});

Answer

Alex Tokarev picture Alex Tokarev · Oct 8, 2013

Have you tried to set autoRender: true in your optional components? Here's the doc: http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.AbstractComponent-cfg-autoRender