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.
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.
});
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With