Im working on a project using Ext JS4. In some of our classes we are declaring functions inside the initComponent function which may then be set as handlers to a control. I will include an example of this below. Ignore most of what is in the class, the key details are the fact that the Handlers are declared within initComponent and set as handlers to the buttons.
Now, this actually WORKS - the qestion here is WHY this works. I'm fairly new to JavaScript, but I thought that any vars or functions declared within a function were destroyed once that function had completed. Is this incorrect? I appreciate that there may be a better coding style to this, but I would really like to get my head around this before I consider changing loads of classes. The class is as follows... Some comments identify the key areas.
Ext.onReady(function () {
Ext.application({
name: 'Form2',
thisForm: {},
launch: function() {
thisForm = Ext.create('Form2', {});
}
});
});
Ext.define('Form2', {
extend: 'Ext.form.Panel',
layout:'border',
config: {
controlManager: {},
formVariables: {},
dataHelper: {}
},
constructor: function () {
var me = this;
...
...
// Initialize the form - I know, this might not be the be best coding style here.
me.initComponent();
},
initComponent: function() {
Ext.QuickTips.init();
var ButtonControl1 = this.controlManager.createButton('ButtonControl1');
var ButtonControl2 = this.controlManager.createButton('ButtonControl2');
...
...
// Handler for Btton1 - **I'm not using the var keyword in this declaration**
function Handler1() {
alert('This Works!');
};
// Handler for Btton2 - **I'm using the var keyword in this example**
var Handler2 = function () {
alert('This Works also!');
};
// THIS IS THE KEY PART OF THIS QUESTION - even though the handler functions are declared
// locally (above), clicking the buttons will still execute these. Do the functions
// still exist by chance, and will be garbage collected at some later time, or are they
// actually quaranteed to be there. I'm confused!
ButtonControl1.onClickEventHandler = function () {Handler1();};
ButtonControl2.onClickEventHandler = function () {Handler2();};
// Don't need to worry about this part.
Ext.create('Ext.container.Viewport', {
layout:'border',
style: { position:'relative' },
defaults: {
collapsible: true,
split: true,
bodyStyle: 'padding:0px'
},
items: [
{
collapsible: false,
split: false,
region: 'north',
height: 50,
margins: '0 2 0 2',
bbar: '',
items: [ ]
},
{
collapsible: false,
split: false,
region:'west',
margins: '0 0 0 0',
cmargins: '0 2 0 2',
width: 0,
lbar: [ ]
},
{
collapsible: false,
region:'center',
margins: '0 2 0 2',
layout: {
align: 'stretch',
type: 'hbox'
},
items: [
{
xtype: 'form',
fileUpload: true,
layout: {
align: 'stretch',
type: 'vbox'
},
flex: 1,
items: [
{
xtype: 'container',
height: 550,
layout: {
align: 'stretch',
type: 'hbox'
},
items: [
{
xtype: 'container',
width: 570,
layout: 'vbox',
padding: '5 0 0 0',
style:'background-color:rgb(255, 255, 255);',
items: [
ButtonControl1, ButtonControl2
]
}
]
}
]
}
]
}
]
});
}
});
For primitive variables (like string or int), when the function finished all its local variables are distroyed.
For non-primitive variables, the object created locally (such as array or Ext object) will not be distroyed while there is any other object references to it.
In your example ButtonControl1 and ButtonControl2 are declared outside initComponent.
Inside initComponent function, onClickEventHandler is a handler references to Handler1 and Handler2 functions.
When initComponent run finishes, because ButtonControl1 and ButtonControl2 are not in the scope of initComponent (but in the scope of onReady function), they remain to be alive and therefore also all of the objects they are reference to.
var ButtonControl1 = ....; // this global variable object
var ButtonControl2 = ....; // this global variable object
initComponent: function() {
function Handler1() {
...
};
var Handler2 = function () {
...
};
// ButtonControl1 and ButtonControl2 are declared outside of initComponent.
// Unless these variables will be distroyed, they keep onClickEventHandler references to Handler1 and Handler2 objects and therefore these objects will alive outside the scope of initComponent.
ButtonControl1.onClickEventHandler = function () {Handler1();};
ButtonControl2.onClickEventHandler = function () {Handler2();};
}
Consider the last function in the scope of onReady is initComponent (i.e. there is no any other event handler defined).
So why all these objects will remain be alive after initComponent finishes?
The answer is created 'Ext.container.Viewport' object, which is rendered to document's page and therefore all attached objects and reference objects are alive as well.
Related
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();
}
});
Side notes
Ext.apply
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 = [...];
Changing the scope of your listeners
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
}
External sources
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).
I was wondering if it's possible to not to allow the user to close the item again when it's open, meaning to let it close only when i click on another item in the accordion.
hope it's clear enough.
I'm adding the code:
Ext.define('application.view.SystemHealth', {
extend: 'Ext.Container',
alias: 'widget.globalSynchronization',
requires: ['infra.view.views.BoxHeader',
'application.view.SystemStatusHeader',
'application.model.SystemHealth',
'application.store.SystemHealth',
'infra.utils.pvs.SyncJobRunningStep',
'application.view.CostCalculation',
'application.view.SystemStatusHeader',
'application.view.DataCollector',
'application.view.PublicCloudConnection',
'Ext.layout.container.Accordion'
],
layout:{
type:'vbox',
align:'stretch'
},
header: false,
cls: ['global-synchronization'],
syncJobStatus: null,
initComponent: function() {
var me = this;
me.store = Ext.create('itfm.application.store.SystemHealth');
me.store.load({scope: me, callback: me.onLoadDone});
Ext.apply(me, {
items: [
{
xtype: 'boxHeader',
width: '100%',
title: itfm.application.T.GlobalSynchronizationTitle
},
{
xtype: 'label',
width: '90%',
html: itfm.application.T.GlobalSynchronizationDescription,
margin: "0 0 30 10"
}
]
});
me.callParent(arguments);
},
onLoadDone: function(records, operation, success){
var me =this;
var accordionItemsMargin = '0 0 30 0';
me.accordion = Ext.create('Ext.panel.Panel', {
margin: "0 0 30 10",
defaults:[{
layout:'fit',
height:'100%',
width:'100%'
}] ,
layout: {
type: 'accordion',
titleCollapse: false,
animate: true,
activeOnTop: true,
fill : true,
collapseFirst :true
},
items: [
{
height: 180,
xtype: 'dataCollector',
autoScroll:true,
margins: accordionItemsMargin,
store: records[0].dcModule()
}
,
{
xtype: 'costCalculation',
margins: accordionItemsMargin,
store: records[0].ccModule()
},
{
xtype: 'publicCloudConnection',
margins: accordionItemsMargin,
store: records[0].pcModule()
}
]
});
me.add( me.accordion);
}
});
thanks for the help
What you want is that nothing happens when the user click on the currently expanded panel, instead of collapsing and expanding the next one, is that right?
Unfortunately, there's no built-in option for that... If you really want it, you'll have to override Ext.layout.container.Accordion and implement it yourself.
Edit
In fact most of the collapsing/expanding code lies in the Ext.panel.Panel class.
This simple override seems to be enough to do what you want. Apparently this method is used only for collapse listeners, so it shouldn't have any adverse effect (unless the method is also used somewhere in your code...).
Ext.define('Ext.ux.Panel.JQueryStyleAccordion', {
override: 'Ext.panel.Panel'
,toggleCollapse: function() {
if (this.collapsed || this.floatedFromCollapse) {
this.callParent();
}
}
});
See this fiddle.
I was able to override the accordion with the following
Ext.define('itfm.application.view.SystemHealthAccordion', {
extend: 'Ext.layout.container.Accordion',
alias: ['layout.systemHealthAccordion'] ,
constructor: function() {
var me = this;
me.callParent(arguments);
},
onComponentExpand: function(comp) {
var me = this;
me.callParent(arguments);
var button = comp.down('tool');
button.setDisabled(true);
},
onComponentCollapse: function(comp) {
var me = this;
me.callParent(arguments);
var button = comp.down('tool');
button.setDisabled(false);
}
});
inside the class of the first item of the accordion, there is a need to do the following:
me.on('boxready', me.disableTools);
},
// disable the click on the item in case it's open
disableTools: function(){
var me = this;
var button = me.getHeader().down('tool');
button.setDisabled(true);
}
so when the first item is open it will have the same behaviour
I´m using ExtJs4.1, here is my example. The "calendarioChange" is not called in this way:
Ext.define('App.view.atendimento.Agenda', {
extend: 'Ext.window.Window',
maximized: true,
bodyPadding: 4,
constrain: true,
layout: 'hbox',
items:[
{
xtype: 'container',
width: 300,
layout: 'vbox',
items: [
{
xtype: 'datepicker',
handler: this.calendarioChange
}
]
}
],
calendarioChange: function(picker, date){
alert('changed');
}
});
but in thsi way works:
xtype: 'datepicker',
handler: function(picker, date){
alert('changed');
}
What I´m missing in the first case?
Thanks.
the problem is you haven't taken in account the scope of your handle. Every time you nest a {} constructor you change the 'this' pointer. In your case:
this.calendarioChange
Can not work because 'this' is pointing to the datepicker not the Window. You could solve it by adding a function in the event that locate the window and then call to the appropiate method:
items: [
{
xtype: 'datepicker',
handler: function(picker, date) {
var window = this.up('window');
window.calendarioChange(picker, date);
}
}
...
Because the method you define as an event handler is not in this scope. For example you can declare the method outside Ext.define() call and then refer it just by name.
I want to achive lazy loading of tree branches in an MVC application with extjs4.1 where the braches are located on different urls. I have come quite some ways and hit quite some walls, right now it does not branch.
Here is where I fail:
Ext.define('OI.view.tree.Tree' ,{
extend: 'Ext.tree.Panel',
alias : 'widget.treepanel',
store: 'TreeStore',
collapsible: true,
rootVisible: false,
viewConfig: {
plugins: [{
ptype: 'treeviewdragdrop'
}]
},
height: 350,
width: 400,
title: 'Directory Listing',
initComponent: function() {
this.store = Ext.data.StoreManager.lookup(this.store);
this.store.getProxy().url = 'data/level1.json'; // <-- init loading
this.store.load();
this.callParent(arguments);
},
listeners: {
itemclick: function(view, record) {
console.info('ID: '+record.get('id'));
console.info('TEXT: '+record.get('text'));
console.info('PrimType: '+record.get('primaryType'));
console.info(record.fields.getCount());
console.info('JCRPATH: '+record.get('jcrPath'));
var newBranchStore = Ext.create('Ext.data.TreeStore', {
model: 'OI.model.Branch',
autoLoad: true,
proxy: {
type: 'ajax',
reader: {
url: 'data/'+ record.get('jcrPath') +'/level1.json', //<-- load json at the level of the url path returned by the model
type: 'json'
}
},
folderSort: false
});
newBranchStore.load({url: 'data/'+ record.get('jcrPath') +'/level1.json',
callback: function(){
console.log('loaded');
var mynode = newBranchStore.getNodeById('content_mytest');
console.info(mynode.get('id'));
this.store.getNodeById(record.get('id')).appendChild(mynode).expand(); // <-- this does not work
},
scope: this
});
}
}
});
The first level is loaded correctly, however when I trigger a click on the node, I am getting the right json returned from the server, in this case a static json file for debugging, and I try to statically fetch a node and append it to the one that has been clicked. But it is never injected.
What I eventually want to achive is that I can append all children returned by the json file to the node that has been clicked.
Further I am a bit confused about treestores ...
Am I correct when I say that there can be only ONE treestore per tree, right? So I need to attach the new nodes to the original treestore ... I am slightly confused and could need all the pointers I can get.
You are way overcomplicating this, use this approach instead (basically just swap out the url of your store before it loads to the correct url):
Ext.define('OI.view.tree.Tree' ,{
extend: 'Ext.tree.Panel',
alias : 'widget.treepanel',
store: 'TreeStore',
collapsible: true,
rootVisible: false,
viewConfig: {
plugins: [{
ptype: 'treeviewdragdrop'
}]
},
height: 350,
width: 400,
title: 'Directory Listing',
initComponent: function() {
this.store = Ext.data.StoreManager.lookup(this.store);
this.store.getProxy().url = 'data/level1.json'; // <-- init loading
this.store.load();
this.callParent(arguments);
},
listeners: {
beforeload: function(store, operation) {
store.getProxy().url = 'data/'+ operation.node.get('jcrPath') +'/level1.json';
}
}
});
I have following problem. I have grid with tbar. Inside tbar I have number of Ext.form.field.Trigger.
When the user click on trigger button I want to filter the store using function that is provided with grid. I want to define functionality of triggerclick inside defined class, so I can reuse this component with different grid.
So, in short I want to find the panel where clicked component is placed and call panel function, or pass reference of panel to triggerclick, or fire an event with some parameter that will calculated based on where the button was clicked, or maybe there is a better method to accomplish this.
The code (FilterField -> extension of trigger):
Ext.define('GSIP.core.components.FilterField', {
extend: 'Ext.form.field.Trigger',
alias: 'widget.filterfield',
initComponent: function() {
this.addEvents('filterclick');
this.callParent(arguments);
},
onTriggerClick: function(e, t) {
//Ext.getCmp('gsip_plan_list').filterList(); - working but dont want this
//this.fireEvent('filterclick'); - controller cant see it,
//this.filterList; - is it possible to pass scope to panel or reference to panel
//e.getSomething() - is it possible to get panel via EventObject? smth like e.getEl().up(panel)
}
});
code of panel:
Ext.define('GSIP.view.plans.PlanReqList', {
extend: 'Ext.grid.Panel',
alias: 'widget.gsip_devplan_list',
id: 'gsip_plan_list',
title: i18n.getMsg('gsip.view.PlanReqList.title'),
layout: 'fit',
initComponent: function() {
this.store = 'DevPlan';
this.tbar = [{
xtype: 'filterfield',
id: 'filter_login',
triggerCls: 'icon-user',
//scope:this - how to pass scope to panel without defining onTriggerClick here
// onTriggerClick: function() {
// this.fireEvent('filterclick'); //working event is fired but controller cant see it
// this.filterList; //this is working but i dont want to put this code in every filterfield
// },
// listeners : {
// filterclick: function(btn, e, eOpts) { //this is working
// }
// },
}];
this.columns = [{
id: 'id',
header: "Id",
dataIndex: "id",
width: 50,
sortable: true,
filterable: true
}, {
header: "Name",
dataIndex: "name",
width: 150,
sortable: true,
filterable: true
}, {
header: "Author",
dataIndex: "author",
sortable: true,
renderer: this.renderLogin,
filterable: true
}];
this.callParent(arguments);
},
filterList: function() {
this.store.clearFilter();
this.store.filter({
property: 'id',
value: this.down("#filter_id").getValue()
}, {
property: 'name',
value: this.down("#filter_name").getValue()
});
},
renderLogin: function(value, metadata, record) {
return value.login;
}
});
part of code of Controller:
init: function() {
this.control({
'attachments': {
filesaved: this.scanSaved,
}
}, {
'scan': {
filesaved: this.attachmentSaved
}
}, {
'#filter_login': {
filterclick: this.filterStore //this is not listened
}
});
},
filterStore: function() {
console.log('filtering store');
this.getPlanListInstance().filter();
},
Controller can listen to anything. Just need to specify exactly what to. But I would fire events on the panel level - add this into your trigger handler:
this.up('panel').fireEvent('triggerclicked');