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

3

9

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.

});

Bellow answered 6/10, 2013 at 8:57 Comment(5)
What do you mean by the listeners seems to be not ready yet ? If you want to fire events from your plugin you will need to extend from observable. Maybe this is your error?Neglectful
@Neglectful I meant the listeners of the host.Bellow
I think that returning false inside beforerender callback should work. Maybe you have problem with container component that ends up without any child components but itself is visible (assumed this form your "the borders are there and the content not"). So you might need to test if items property for containable components are empty and set empty items components as not visible too.Griffie
@Griffie Well I doubt that. Because all containers within that column (in one testcase we use a column layout) are empty when using the event while only one container aborted the layout (all others not even have the plugin and yes the plugin is directly assigned to the container where all containers extend from the same class).Bellow
Could you provide us with some sample code that doesn't work, it would be even better to set up a test case in JSFiddle or similar and show us exactly what problem you have, I think that would speed up things and limit additional questions ;)Griffie
M
3

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

Marty answered 8/10, 2013 at 18:39 Comment(3)
This hint sounds most promising but it still get rendered :( I will edit my post to provide more informationBellow
Not sure I can recommend anything without seeing the code. autoRender does work generally but there may be something in your code that breaks that logic and forces the components to render.Marty
Your answer didn't solve the problem but it was by far the best so I decided to award the bounty to you. We are still on this issue and thought we may reopen this with more detailed information (and a higher bounty ;) )Bellow
E
1

You can try with hide and show functions and also try with "added" event listener, which will be called after adding the component to container.

Evaevacuant answered 8/10, 2013 at 11:46 Comment(1)
I guess I didn't made it that clear: We need to prevent the rendering because it will never be accessed. We already managed to get it hidden, but it still rendersBellow
G
1

Try something like this for your 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);

        // Try to define beforerender callback on component and return false if
        // component should not be visible
        cmp.on('beforerender', function() {
            if (!me.checkAccess()) {
                if (me.blockMode === 0) {
                    return false;
                }

                // Not sure what this do for you but it wont disable component
                // if you want your component disabled here try cmp.disable()
                me.diabled = true;
            }
        });

        // Maybe this code is not needed anymore
        // check for access
        if (!me.checkAccess()) {
            if (me.blockMode === 0) {
                cmp.hidden = true;
                cmp.autoShow = false;
                cmp.autoRender = true;
            }
            me.diabled = true;
        }

        // This should stay for sure
        me.callParent(arguments);
    }

    /* some more methods */
});
Griffie answered 10/10, 2013 at 8:14 Comment(3)
As I wrote in the question; If we do so the layout is broken, meaning the layout stops for all components in that column. We think this depends on the layout type somehow. Next is that the observable instance is not ready on the host. To work with events we will need to move into the init method cause when this is called the host has set it all up. So this will not work as we already tried using the 'beforerender' event. Anyway, thanks for your continuous help.Bellow
@Bellow Hmm, well, then you need to look at Ext.app.portal.PortalColumn component and see what needs to be done there so layout don't break when one of its items is not rendered. I don't quite get what you mean by "Next is that the observable instance is not ready on the host". If you don't want to render component then you should use init method and events (in my opinion) or else don't add components as items at all if they should not be visible. Is that possible solution in your case? maybe you could also call `remove' on component parent inside plugin somehow... JSFiddle would really help.Griffie
I added it to the question. Note that the portal is basically the same (addPortlet!) as shipped by sencha. What I meant with "Next ..." is that I can't do this in the plugin constructor because the host isn't ready at this time. Basically we are looking for a lightweight solution. Fact is we cannot stop the creation process of the component so at least we could prevent it from being rendered (at least we thought so). By now it seems that it is not possible to prevent the rendering of a component in a clean way. Remember that the the portal is shipped by sencha along with every ExtJS version.Bellow

© 2022 - 2024 — McMap. All rights reserved.