I am trying to mod the Portlet example and port it into our program. The code looks like this:
Ext.create('Ext.container.Viewport',{
id: 'app-viewport', //creates viewport
layout: {
type: 'border',
padding: '0 5 5 5' // pad the layout from the window edges
},
onPortletClose: function(portlet) {
this.showMsg('"' + portlet.title + '" was removed');
},
showMsg: function(msg) {
var el = Ext.get('app-msg'),
msgId = Ext.id();
this.msgId = msgId;
el.update(msg).show();
Ext.defer(this.clearMsg, 3000, this, [msgId]);
},
clearMsg: function(msgId) {
if (msgId === this.msgId) {
Ext.get('app-msg').hide();
}
},
items: [{
id: 'app-header',
xtype: 'box',
region: 'north',
height: 40,
html: 'Ext Welcome'
},{
xtype: 'container',
region: 'center',
layout: 'border',
items: [{
id: 'app-options', //Creates the Options panel on the left
title: 'Options',
region: 'west',
animCollapse: true,
width: 200,
minWidth: 150,
maxWidth: 400,
split: true,
collapsible: true,
layout:{
type: 'accordion',
animate: true
},
items: [{
html: content,
title:'Navigation',
autoScroll: true,
border: false,
iconCls: 'nav'
},{
title:'Settings',
html: content,
border: false,
autoScroll: true,
iconCls: 'settings'
}]
},{
id: 'app-portal', //Creates the panel where the portal drop zones are.
xtype: 'mainpanel',
region: 'center',
items: [{
id: 'col-1', //Each column represents a drop zone column. If we add more there are more created and width gets adjusted accordingly
items: [{
id: 'portlet-1',
title: 'Portlet 1',
html: content,
listeners: {
'close': Ext.bind(this.onPortletClose, this)
},{
id: 'portlet-2',
title: 'Portlet 2',
html: content,
listeners: {
'close': Ext.bind(this.onPortletClose, this)
}
}]
},{
id: 'col-2',
items: [{
id: 'portlet-3',
title: 'Portlet 3',
html: content,
listeners: {
'close': Ext.bind(this.onPortletClose, this)
}
}]
},{
id: 'col-3',
items: [{
id: 'portlet-4',
title: 'Portlet 4',
html: content,
listeners: {
'close': Ext.bind(this.onPortletClose, this)
}
}]
}]
}]
}]
});
The problem is that Ext.bind cannot read the onPortletClose
function and the browser gives me a:
Uncaught TypeError: Cannot read property 'apply' of undefined
error. I checked the stack and essentially in the Ext.bind(fn,scope) the fn
is undefined and thus it cannot read the handler function. The difference between my application and in the example is that I add this directly into a JSP's Ext.onReady()
whereas in the example all of this is added through a Ext.apply(this, {...})
. I'm really confused. I tried all kinds of gimmicks to force the scope on the viewport but it seems that whatever is inside Ext.bind()
loses contact with outside or something. I've used Ext.bind()
before and it went fine although it was inside an initComponent
configuration. Is that mandatory? If no then what is the problem?
It is important to understand the meaning of this
here (or in general when working with JavaScript or ExtJS).
In a global context, this
refers to the global object, also known as the global window
variable. This is the case inside your Ext.onReady
function:
Ext.onReady(function() {
console.log(this === window);
// -> true
// because this is the global object
});
In an object context, by default this
refers to the owner object of the function (there are ways around it and actually this is exactly want you want do achieve using Ext.bind
):
var obj = {
prop: 'My text property',
fn: function() {
console.log(this.prop);
// -> My text property
// because "this" refers to our object "obj"
}
};
Now, this is exactly what makes the difference between your case and the one from the examples.
When using the approach with Ext.onReady
this
- as pointed out above - will refer to the global object, which of course does not have a function onPortletClose
.
When using the approach from the examples, this
is accessed from inside of initComponent
, which is a member function of your derived viewport class, and therefore this
refers to the component instance (= the owning object), which allows you to access your function.
Ext.define('MyViewport', {
extend: 'Ext.container.Viewport',
onPortletClose: function(portlet) {
this.showMsg('"' + portlet.title + '" was removed');
},
initComponent: function() {
console.log(this);
// -> the instance of your viewport class
Ext.apply(this, {
items:[{
xtype: 'panel',
title: 'Portlet 1',
listeners: {
'close': Ext.bind(this.onPortletClose, this)
}
}]
});
this.callParent();
}
});
Ext.apply
really just copies all properties from one object to another, so in this case it is just a convenient way of applying all attributes of that object to this
with one function call. You could as well just use:
this.items = [...];
You do not need to use Ext.bind
when adding listeners to a component, you can simply provide a scope
configuration in your listeners
object:
listeners: {
close: function() {...},
scope: this
}
If you want to dive in a little deeper (which I would recommend), you might want to read more about this at the MDN (Mozilla Developer Network).