Disclaimer: I am relatively new to ExtJS (version 5.01). I am hoping to reach some ExtJS experts to point me in the right direction:
I am getting an error when specifying an initComponent method within an items config. The code below generates the error:
"Uncaught TypeError: Cannot read property 'items' of undefined"
The error disappears when the 'initComponent' function of the north-child panel is commented out. I have the feeling I missed something on initialization order.
Q: How can I specify an initComponent method of a child item within the items configuration?
Ext.define('MyApp.view.TestView', {
extend: 'Ext.panel.Panel',
title: 'Parent',
height: 300,
layout: 'border',
items: [{
xtype: 'panel',
region: 'north',
title: 'North Child',
/* Problematic function: If commented, it works */
initComponent: function(){
console.log("test north child");
this.callParent(arguments);
}
}],
initComponent: function(){
console.log("Test parent");
this.callParent(arguments);
}
});
Short answer: You can't define initComponent on a child, because you can't do anything there that can't be done anywhere else.
InitComponent is executed when an instance of the component 'MyApp.view.TestView' is created (you only defined it here, using Ext.define). It can be created using Ext.create('MyApp.view.TestView',{, or by creating another view that has this component added as an item, or by deriving another component (extend:'MyApp.view.TestView').
All the child components are also created when 'MyApp.view.TestView' is created, so the initComponent function on the child would be superfluous, because the child cannot be created without the parent, so the initComponent of the parent can be used for everything that you want to do in the child's initComponent.
If you need sth. to be calculated before the items can be addded, you would proceed as follows:
Ext.define('MyApp.view.TestView', {
extend: 'Ext.panel.Panel',
title: 'Parent',
height: 300,
layout: 'border',
initComponent: function(){
var me = this,
tf = Ext.getCmp("someTextField"),
myTitle = (tf?tf.getValue():'');
Ext.applyIf(me,{
items: [{
xtype: 'panel',
region: 'north',
title: myTitle,
}]
});
this.callParent(arguments);
}
});
Please refer to the docs what exactly Ext.applyIf does (and how it differs from Ext.apply, because that function also comes handy sometimes).
Related
Ext JS - v 6.2.1
I'm interested in reusing a main component developed in ExtJs which i've written in different tabs of the tabpanel. The main component is composed of two child components and each of these components have their respective controllers and the child components interact among themselves. Since the event listeners are added in the controller domain the events fired in one instance of tab affects the other tabs as well.
Pseudo code of the scenario explained
*********** Views ***********
Ext.define('MainApp.view.main.Main', {
extend: 'Ext.tab.Panel',
xtype: 'mainapp-main',
controller: 'main',
...
items: [{
title: 'Main Component Instance 1',
closable: true,
items: [{
xtype: 'mainapp-maincomponent'
}]
}, {
title: 'Main Component Instance 2',
closable: true,
items: [{
xtype: 'mainapp-maincomponent'
}]
}]
});
Ext.define('MainApp.view.maincomponent.MainComponent', {
extend: 'Ext.panel.Panel',
xtype: 'mainapp-maincomponent',
controller: 'maincomponent',
...
items: [{
xtype: 'mainapp-component1'
},{
xtype: 'mainapp-component2'
}]
});
Ext.define('MainApp.view.component1.Component1', {
extend: 'Ext.panel.Panel',
xtype: 'mainapp-component1',
controller: 'component1',
...
items: [{
xtype: 'button',
cls: 'contactBtn',
scale: 'large',
text: 'Fire Event',
handler: 'onComponentButtonTapped'
}]
});
Ext.define('MainApp.view.component2.Component2', {
extend: 'Ext.panel.Panel',
xtype: 'mainapp-component2',
controller: 'component2',
...
items: [{
xtype: 'textfield',
value: 'Button is not clicked yet',
width: 500,
readOnly: true
}]
});
*********** Controllers ***********
Ext.define('MainApp.view.component1.Component1Controller', {
extend: 'Ext.app.ViewController',
alias: 'controller.component1',
onComponentButtonTapped: function (btn, eventArgs) {
this.fireEvent('component1ButtonTapped', btn, eventArgs);
}
});
Ext.define('MainApp.view.component2.Component2Controller', {
extend: 'Ext.app.ViewController',
alias: 'controller.component2',
listen: {
controller: {
'component1': {
component1ButtonTapped: 'onComponent1ButtonTapped'
}
}
},
onComponent1ButtonTapped: function(){
this.getView().down('textfield').setValue(Ext.String.format('Button tapped at {0}', Ext.Date.format(new Date(), 'Y-m-d H:i:s')));
}
});
Can somebody please suggest the correct way of addressing this used case.
More Details
The tab panel is loaded in viewport
Tab 1 has first instance of Main Component - M1
Tab 2 has second instance of Main Component - M2
Every instance of Main Component has two child components
Component1 - M1C1 > M1C1 View and M1C1 Controller
Component2 - M1C2 > M1C2 View and M1C2 Controller
Similarly for the second instance of Main Component
Component1 - M2C1 > M2C1 View and M2C1 Controller
Component2 - M2C2 > M2C2 View and M2C2 Controller
Requirement here is to restrict Actions done on M1C1 view should be processed by M1C2 Controller only.
Issue is that with the code above M2C2 Controller also listens to the event
Change handler: 'onComponentButtonTapped' to
{
xtype: 'button',
cls: 'contactBtn',
scale: 'large',
text: 'Fire Event',
listeners: {
click: 'onComponentButtonTapped',
scope: SCOPE-YOU-WANT
}
}
First, I believe you are overnesting in your MainApp.view.main.Main component and you don't use a layout for your overnesting either. That can lead to unnecessary consequences like extra layout runs and bloated DOM affecting performance.
Ok, onto your question! I don't think scope is what you're actually after here. I see it as how to properly architect your ViewControllers. While understanding this isn't your real code, I will say that in this example you don't need that Component1Controller controller. Get rid of it and the button's handler will get resolved up to the maincomponent controller which is where you can control both child items. I only say this fully knowing that your Component1Controller is likely doing other things so it's not going anywhere just to say that not all containers need controllers. It also serves what I would do in this case if Component1Controller is to stick around. Instead of firing an event on the Component1Controller instance and use the event domain to get to Component2Controller, I would fire a view event on Component1 and add a listener on your config object so that maincomponent controller gets the event to do it's thing.
That sounds messy and hard to follow so I created this fiddle.
A component definition:
Ext.define('Retroplanner.view.dimension.DimensionMapping', {
alias: 'widget.dimensionMapping',
extend: 'Ext.form.Panel',
...
items: [{
xtype: 'combo'
}, ...
]
A 'select' handler of the child item must create a widget and add this widget to the items array of its parent.
Inside of this child item, it its 'select' handler, I can find its parent by some search techniques. But I would like to avoid it if it is possible. I do not have a reference variable to the parent neither.
A better approach would be - to create function in the parent, and attach it somehow to the child item:
Ext.define('Retroplanner.view.dimension.DimensionMapping', {
...
onSiRemoteCombo: function(cmb, rec, idx) {
alert("select handler");
var newItem = Ext.widget('somexType');
this.items.add(newItem);
}
The question, how to attach onSiRemoteCombo?
I've found a similar solution here: How to create listener for child component's custom event
First, it does not work for me. I can give a full example that I tried to use.
2nd, I would like to create items via the most common way/in the common place, not via initComponent method. I would like to have something like:
Ext.define('Retroplanner.view.dimension.DimensionMapping', {
...
afterRender: function() {
var me = this;
//exception here
//Uncaught TypeError: Cannot read property 'on' of undefined
me.items[0].on('select', onSiRemoteCombo, this);
},
items: [{
xtype: 'combo'
}, ...
],
onSiRemoteCombo: function(cmb, rec, idx) {
alert("Ttt");
var dimensionMapping = Ext.widget('propGrid');
this.getParent().add(dimensionMapping);
}
But I get an exception:
//exception here
//Uncaught TypeError: Cannot read property 'on' of undefined
me.items[0].on('select', onSiRemoteCombo, this);
And also, attach a listener after each rendering, really is a bad idea.
Are there any best practices for such use cases? Ideally, if it will work in different versions of Ext JS, at least in 5.x and 6.x
Attach a handler in a child and access its parent? A child should not depend on its parent. Only parent should know, what to do.
One way to solve this is by wrapping the combo item component initialization into form's initComponent method. This way when setting a listener for the combo, you can use this.formMethod to reference a form method. Here is some code:
Ext.define('Fiddle.view.FirstForm', {
extend: 'Ext.form.Panel',
bodyPadding: 15,
initComponent: function () {
Ext.apply(this, {
items: [{
xtype: 'combo',
fieldLabel: 'First Combo',
store: ['first', 'second'],
listeners: {
'select': this.onComboSelect
}
}]
});
this.callParent();
},
onComboSelect: function () {
alert('I am a first form method');
}
});
The second approach by using a string listener on the combo, and by setting defaultListenerScope to true on the form. This way the listener function will be resolved to the form's method. Again, some code:
Ext.define('Fiddle.view.SecondForm', {
extend: 'Ext.form.Panel',
bodyPadding: 15,
defaultListenerScope: true,
items: [{
xtype: 'combo',
fieldLabel: 'Second Combo',
store: ['first', 'second'],
listeners: {
'select': 'onComboSelect'
}
}],
onComboSelect: function () {
alert('I am a second form method');
}
});
And here is a working fiddle with both approaches: https://fiddle.sencha.com/#view/editor&fiddle/27un
I am trying to reference a store in my app for the purpose of adding a paging tool bar at the bottom of my gird. In most examples I have studied the store is referenced by variable, ex: store: someStore. However, by I have build my app a little differently and did create a reference variable to the store. I have
tried assigning an id but this did not work.
Here is what I have:
In my view Grid.js:
Ext.define('myApp.view.user.Grid', {
extend: 'Ext.grid.Panel',
viewModel: {
type: 'user-grid'
},
bind: {
store: '{users}',
},
columns: {...},
//my paging tool bar
dockedItems: [{
xtype: 'pagingtoolbar',
dock: 'bottom',
store: 'girdStore'
//store: {users} -> did not work
}],
...
});
In my view model GridModel.js:
Ext.define('myApp.view.user.GridModel', {
extend: 'Ext.app.ViewModel',
requires: [
'myApp.model.User'
],
stores: {
users: {
model: 'myApp.model.User',
storeId: 'gridStore',
autoLoad: true
}
},
formulas: {...}
});
When I try to reference the {users} store by id 'gridStore' I get this error:
Uncaught TypeError: Cannot read property 'on' of undefined
What is the best way to proceed without completely refactoring my model?
When you have a reference to your grid, you could get the store by calling the getStore function. See the ExtJs 6.2.1 documentation.
var grid; // reference to your grid
var store = grid.getStore();
You can create the store in initComponent and then attach it to the dockedItems, so both will share the same store.
initComponent: function () {
var store = Ext.create('Ext.data.Store', {
model: 'myApp.model.User',
storeId: 'gridStore',
autoLoad: true
});
this.store = store;
this.dockedItems = [{
xtype: 'pagingtoolbar',
dock: 'bottom',
store:store
}];
this.callParent(arguments);
}
The initComponent is called once when a new instance of the class is created, see the description in the documentation.
...It is intended to be implemented by each subclass of
Ext.Component to provide any needed constructor logic. The
initComponent method of the class being created is called first, with
each initComponent method up the hierarchy to Ext.Component being
called thereafter. This makes it easy to implement and, if needed,
override the constructor logic of the Component at any step in the
hierarchy. The initComponent method must contain a call to callParent
in order to ensure that the parent class' initComponent method is also
called...
The view with the initComponent function.
Ext.define('myApp.view.user.Grid', {
extend: 'Ext.grid.Panel',
viewModel: {
type: 'user-grid'
},
initComponent: function () {
var store = Ext.create('Ext.data.Store', {
model: 'myApp.model.User',
storeId: 'gridStore',
autoLoad: true
});
this.store = store;
this.dockedItems = [{
xtype: 'pagingtoolbar',
dock: 'bottom',
store: store
}];
this.callParent(arguments);
},
columns: {...},
...
});
I added a loadmask to a panel and I want the spinner to be displayed each time stores associated with the loadmask are loaded. The panel is a large tooltip and the stores are loaded each time a point is visited in a line chart. Thus, when I hover over a point, I'm expecting a load message to appear for a short period of time before I see the contents in the panel. What I'm getting is an empty panel however. If I remove the code that I have which adds the load mask (the initComponent function), it works (without the load message though). How would I use the loadmask in this manner as opposed to explicitly calling the setLoading() method for each panel?
Here's the code:
tips:
{
...
items:{
xtype: 'panel',
initComponent: function(){
var loadMask = new Ext.LoadMask(this, {
store: Ext.getStore('CompanyContext')
});
},
id: 'bar-tip-panel',
width: 700,
height: 700,
layout: {
type: 'accordion',
align : 'stretch',
padding: '5 5 5 5'
},
items:...
}
}
config object isn't the proper place to override initComponent method. What you should do is to define a subclass of Panel, and override the method there.
Ext.define('MyPanel', {
extend: 'Ext.panel.Panel',
xtype: 'mypanel',
initComponent: function() {
this.callParent(arguments); // MUST call parent's method
var loadMask = new Ext.LoadMask(this, {
store: ...
});
},
});
Then, you can use xtype mypanel in your tips configuration.
simple question for you today...
This works:
var carousel = Ext.create('Ext.Carousel', {
fullscreen: 'true',
//load in views view clean instantiation using
// the widget.alias's defined in each view... yea
// For some reason, putting flex on these components... oh...
// Have to call directly in by just the xtype since these are just
// references..
items: [
{
xtype: 'Main'
},
{
xtype: 'CommentList'
}
]
This does NOT work:
var tabpanel = Ext.create('Ext.TabPanel', {
fullscreen: 'true',
tabBarPosition: 'bottom',
defaults: {
styleHtmlContent: true
},
//load in views view clean instantiation using
// the widget.alias's defined in each view... yea
// For some reason, putting flex on these components... oh...
// Have to call directly in by just the xtype since these are just
// references..
items: [
{
xtype: 'Main',
title: 'The Main',
iconCls: 'user'
},
{
xtype: 'CommentList',
title: 'Comments',
iconCls: 'user'
}
]
});
As you can see, they are pretty much the same except one is a TapPanel (with the required default configs added) and the other is a carousel.
Everything else is exactly the same.... This is in the app.js of my Sencha Touch 2.0 app designed following the MVC architecture.
The result of the not-working TabPanel is that I only see the first view (Main) and no tab-bar appears in the bottom of the screen.
Any ideas what my problem might be?
I am not sure if this is an issue but in my code the line is:
Ext.create("Ext.tab.Panel", {
Not:
Ext.create('Ext.TabPanel', {
Fullscreen should be fullscreen: true instead of fullscreen: 'true'. You could also add this code to make them switch:
cardSwitchAnimation: {type: "fade", duration: 1000},
layout: "card",
Didn't test it, but it worked for me (got it from a snippet of my own code)