Ext Js Attach Event Handler In Parent Container To Its Child Item - javascript

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

Related

Ext JS Remove GUI Item From Its Container By xtype

A panel contains of 3 items. The last item has an event handler attache. In the handler a new item (widget) is added to the parent panel. Before adding a new item, an old item of the same xtype should be deleted.
Here is an example that does not work:
Ext.define('Retroplanner.view.dimension.DimensionMapping', {
extend: 'Ext.form.Panel',
defaultListenerScope: true,
items: [{
xtype: 'component',
html: '<h3> Dimension Mappings</h3>',
},{
xtype: 'bm-separator'
},{
xtype: 'siRemoteCombo',
...
listeners: {
'select': 'onSiRemoteCombo'
}
}
],
onSiRemoteCombo: function(cmb, rec, idx) {
var item;
for(item in this.items){
//here item - is undefined,
//although this.items.length=3, as expected
//alert(item.xtype);
//isXType is not defined for undefined element:
if (item.isXType('propGrid')) {
this.remove(item);
break;
}
}
//the following code works as expected, if the previous is commented
var dimensionMapping = Ext.widget('propGrid');
this.items.add(dimensionMapping);
this.updateLayout();
}
});
I tried to use index, but it also does not work:
Ext.define('Retroplanner.view.dimension.DimensionMapping', {
...
defaultListenerScope: true,
items: [{
xtype: 'component',
...
},{
xtype: 'bm-separator'
},{
xtype: 'siRemoteCombo',
...
listeners: {
'select': 'onSiRemoteCombo'
}
}
],
onSiRemoteCombo: function(cmb, rec, idx) {
//the following code does not remove item in GUI interface.
if (this.items.length == 4)
this.remove(this.items[3], true);
var dimensionMapping = Ext.widget('propGrid');
this.items.add(dimensionMapping);
this.updateLayout();
}
});
I would like to be able to remove item by xtype, without any id or other types of references. But if it is not possible, which is the best way to do it? To remove GUI component from its container.
Check out component queries. It allows you to search for ExtJS components based on their attributes, globally or from within a container.
For easy access to queries based from a particular Container see the
Ext.container.Container#query, Ext.container.Container#down and
Ext.container.Container#child methods. Also see Ext.Component#up.
For your case down or child are appropriate. Something like this:
this.remove(this.down('propGrid'))
Here is a working fiddle: https://fiddle.sencha.com/#view/editor&fiddle/27vh. Just select a value from the combo, and the grid will be removed.
To remove an item with the prodGrid xtype, try this:
onSiRemoteCombo: function(cmb, rec, idx) {
if (this.down('prodGrid'))
this.remove(this.down('prodGrid'))
var dimensionMapping = Ext.widget('propGrid');
this.items.add(dimensionMapping);
this.updateLayout();
}

How can I make my controller call the correct view's component in extjs?

I defined a Ext.grid.Panel called JobList that has an Ext button with an itemId called myButton. JobList has a controller. In the controller I have the following code:
Ext.define('App.controller.JobList', {
extend: 'Ext.app.Controller',
refs: [
{ref: 'jobList', selector: '#jobList'},
{ref: 'myButton', selector: '#myButton'}
],
init: function(){
this.control({
'jobList': {
select: this.selectJob
}
});
},
selectJob: function(){
this.getMyButton().enable();
}
});
I then create two instances of jobList using Ext.create they have an id of jobList1 and jobList2. The problem is when I select a job in the list on jobList2 it will enable the myButton on jobList1 not jobList2. How do I correctly enable the myButton on each instance of jobList?
Try to avoid referencing by itemId, and use aliases instead:
// in App.view.JobList.js you should have
Ext.define('App.view.JobList', {
extend: 'Ext.grid.Panel',
alias: 'widget.job-list',
// ...
dockedItems: [{
xtype: 'button',
name: 'myButton',
text: 'My button',
}]
});
// and the in the App.controller.JobList.js:
// ...
'job-list': {
selectionchange: function(model, selected) {
var button = model.view.up('job-list').down('button[name=myButton]');
button.setDisabled(Ext.isEmpty(selected));
}
}
Check the example: https://fiddle.sencha.com/#fiddle/tq1
You're using global controller, so it catches events from all views that matching the query. Look at MVVM pattern in extjs5. Sencha did a great job, in MVVM each instance of view has their own instance of ViewController, so this situation will never happen. If you want to stick with MVC pattern, then you need to manually control this. Forget about refs, you can't use them if you have more than one instance of your view class. Get other components only by query from your current component. Something like:
Ext.define('App.controller.JobList', {
extend: 'Ext.app.Controller',
init: function() {
this.control({
'jobList': {
select: this.selectJob
}
});
},
selectJob: function(selectionModel){
//first of all you need to get a grid. We have only selectionModel in this event that linked somehow with our grid
var grid = selectionModel.view.ownerCt; //or if you find more ellegant way to get a grid from selectionModel, use it
var button = grid.down('#myButton');
button.enable();
}
});

EXTJS inline initComponent method within items config

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).

Defining items of an object inside the initialize function

I am trying to create items inside a component as it gets initialized, with a function.
Consider the following:
Ext.define('mobi.form.Login',{
extend:'Ext.form.Panel',
config:{
items: [{
xtype: 'textfield',
name: 'Name',
label: 'Name'
}]
});
Ext.application({
viewport: {
layout:'fit'
},
launch: function(){
Ext.Viewport.add(Ext.create('mobi.form.Login'));
}
})
I am trying to get The mobi.form.login to generate its config from a function that runs on initialize ( or whatever I can use to over write the config I specify ).
I know Sencha touch 2 has the constructor, and initialize function, but both of them seem to have arguments=[] ( eg an empty array )
This is more or less how it would look if I was doing it in ExtJS 4.x:
Ext.define('mobi.form.Login',{
extend:'Ext.form.Panel',
initComponent:function(config){
config=Ext.apply({}.config,{});//make sure config exists
config.items= [{
xtype: 'textfield',
name: 'Name',
label: 'Name'
}]
Ext.apply(this, config);
this.callParent(arguments);
}
});
If you ever wanted to do this, you could use constructor or initialize.
Constructor you would use for synchronous logic which will be fast and you want to happen before the component is initialized. You can access the configuration through the constructors first argument:
Ext.define('MyComponent', {
extend: 'Ext.Component',
constructor: function(config) {
console.log(config);
this.callParent([config]);
}
});
Ext.application({
launch: function(){
Ext.create('MyComponent', { test: 1 })
// Will log out:
// {
// test: 1
// }
}
});
Remember you will always need to callParent with the config/arguments within constructor.
In any other situation, you should use initialize which is called after all the config's have been... initialized. :) We use this a lot internally for adding listeners.
initialize: function() {
this.on({
...
});
}
you don't need to call initialize manually it is already done by constructor and when calling this function you can access items data using this.items and create panel items there
Ext.define('mobi.form.Login',{
extend:'Ext.form.Panel',
config: {
items: []
},
initialize : function()
{
this.items = [Ext.create({
xtype: 'textfield',
name: 'Name',
label: 'Name'
})];
this.callParent();
}
});
Ext.application({
viewport: {
layout:'fit'
},
launch: function(){
Ext.Viewport.add(Ext.create('mobi.form.Login'));
}
})
Use the following:
Ext.apply(this, {
items: [
....
]
});
Have you tried something similar to this? I'm just passing a config object to Ext.create, though I can't test it right now. See http://docs.sencha.com/touch/1-1/#!/api/Ext-method-create
Ext.Viewport.add(Ext.create(
{
xtype: 'mobi.form.Login',
items: [ /*A list of items*/ ]
}
));
You could stick this snippet in its own function as well, one that takes in items as a parameter. Hope this solves your problem!

Ext-JS remove component gotcha

I have a component acts like a table row, called flightLegComponent like so:
[ flight leg component ] [-] [+]
[ flight leg component] [-] [+]
...
when the [-] button is pressed, that component is meant to be removed from the parent panel.
I have added a listener to the [-] button, and in the listener, i call
this.remove(theFlightLegComponent);
where 'this' is the parent component.
This throws an exception, apparently, you can not remove components inside the event handler... What is the proper way to remove it? invoke a method after a delay?
New:
The panels are structured so:
_flightLegRow: function(removable) {
var flightLegInput = new xx.yy.zz.search.FlightLegInput({
columnWidth: .8
});
var legId = 'flightLeg-' + this.legs++;
var c = {
border: 0,
width: '90%',
layout: 'column',
id: legId,
items: [
flightLegInput,
{
columnWidth: .2,
margin: 10,
border: 0,
layout: {
type: 'column'
},
items: [{
xtype: 'button',
text: '-',
disabled: !removable,
listeners: {
click: Ext.Function.bind(function() {
//debugger;
this.remove(legId, true);
}, this)
}
},{
xtype: 'button',
text: '+',
listeners: {
click: Ext.Function.bind(function(){
this.add(this._flightLegRow(true));
}, this)
}
}]
}
]
};
return c;
}
You can remove components in event handlers you need to remember to pass the proper scope. If you are removing the component it may be invoking the parents autoDestroy config witch may delete it entirely and can cause null pointer exceptions. I'm guessing the button handler's function is being called in the scope of the button and it's throwing an exception this.remove is undefined. Any code or exception message would be helpful to pinpoint the problem.
new Ext.button.Button({
handler: function(){this.remove......},
scope: this
})
This is the code you shold be using for the button:
b.ownerCt will be theFlightLegComponent and its ownerCt will the panel that contains theFlightLegComponent, in this way you can remove it.
{
xtype: 'button',
text: '-',
disabled: !removable,
handler: function(b) {
b.ownerCt.ownerCt.remove(legId, true);
}
}

Categories