PDA

View Full Version : AutoRef Question



froamer
28 May 2010, 6:24 AM
Hi, I found myself getting far more excited about autoRef than a grow-up should do, but my enthusiasm died when I couldn't get it to work so I wonder if it is "pilot error".

Am I right in thinking I can give any component in Ext Designer an AutoRef (ensuring it does not conflict with a reserved name or another AutoRef value) and then simply reference it as this.myAutoRefName in the exported classname.js?

If so I can’t get it to work on a Radio button. It is in this hierarchy...


MyForm (jsClass="MemberForm")
MyTabPanel
MyContainer
MyFieldSet
MyCompositeField
MyRadioButton (AutoRef="fieldSmeYes")If I look at the generated code its ref is '../../../../fieldSmeYes' which I think looks right.

However, my MembersForm.js looks like this...

MemberForm = Ext.extend(MemberFormUi, {
backgroundColor: '#F0EDED', //background color of the form tabs
initComponent: function() {
MemberForm.superclass.initComponent.call(this);
this.tabs.bodyStyle = 'background-color:'+this.backgroundColor;
this.contactsGrid.selModel = new Ext.grid.RowSelectionModel();
this.contactsGrid.clicksToEdit = 1;
this.industryApprovalsGrid.selModel = new Ext.grid.RowSelectionModel();
this.industryApprovalsGrid.clicksToEdit = 1;
this.customerApprovalsGrid.selModel = new Ext.grid.RowSelectionModel();
this.customerApprovalsGrid.clicksToEdit = 1;
this.capabilitiesGrid.selModel = new Ext.grid.RowSelectionModel();
this.capabilitiesGrid.clicksToEdit = 1;
console.log(this.fieldSmeYes);
}
});but the last line: console.log(this.fieldSmeYes); reports undefined in FireBug. The other AutoRefs to the grids work fine.

Is this a bug or have I misunderstood? I have attached the xds project file in case it helps.

j-joey
28 May 2010, 6:50 AM
i have the same issue, for my toolbars.... i think this isn't a designer bug. should be an ext bug. yesterday i just asked to ext help forum...

jarrednicholls
28 May 2010, 7:07 AM
This is a bug in Ext JS for buttons inside of menus attached to buttons...and it looks like for fields inside of a CompositeField as well.

Here is a link to a post on the subject, and a monkey patch to Ext JS for the button-in-menu-attached-to-button issue: http://www.extjs.com/forum/showthread.php?99895-MenuItem-autoRef&p=470296#post470296



This code only works if the menu is configured as an xtype object (such as the above case), it won't help menus configured as an array of items. The Designer doesn't generate the latter...


Ext.Button.prototype.initComponent = Ext.Button.prototype.initComponent.createInterceptor(function(){
if (this.menu){
this.menu.ownerCt = this;
}
});



I'll post back on here for a monkey patch to CompositeField. As I did for the button issue, I'll put a ticket in to Ext JS proper for CompositeField.

EDIT:

OK I have no idea if this will work, it's untested, but here's an alpha monkey patch for CompositeField. Changes are in bold for the technically curious...



Ext.override(Ext.form.CompositeField, {
initComponent: Ext.form.CompositeField.prototype.initComponent.createInterceptor(function(){
var items = this.items || [];
for (var i = 0; i < items.length; i++){
var item = items[i];
if (!Ext.isEmpty(item.ref)){
item.ref = '../' + item.ref;
}
}
}),

onRender: function(ct, position) {
if (!this.el) {

var innerCt = this.innerCt = new Ext.Container({
layout : 'hbox',
renderTo: ct,
items : this.items,
cls : 'x-form-composite',
defaultMargins: '0 3 0 0',
ownerCt: this
});

innerCt.ownerCt = undefined;
this.el = innerCt.getEl();

var fields = innerCt.findBy(function(c) {
return c.isFormField;
}, this);


this.items = new Ext.util.MixedCollection();
this.items.addAll(fields);

//if we're combining subfield errors into a single message, override the markInvalid and clearInvalid
//methods of each subfield and show them at the Composite level instead
if (this.combineErrors) {
this.eachItem(function(field) {
Ext.apply(field, {
markInvalid : this.onFieldMarkInvalid.createDelegate(this, [field], 0),
clearInvalid: this.onFieldClearInvalid.createDelegate(this, [field], 0)
});
});
}

//set the label 'for' to the first item
var l = this.el.parent().parent().child('label', true);
if (l) {
l.setAttribute('for', this.items.items[0].id);
}
}

Ext.form.CompositeField.superclass.onRender.apply(this, arguments);
}
});

froamer
28 May 2010, 8:00 AM
Hi Jarred,

I've applied the monkey patch, but it doesn't seem to make a difference - sorry.

jarrednicholls
28 May 2010, 8:20 AM
Ok I'll actually test it and see what I can get working :-)

jarrednicholls
28 May 2010, 9:03 AM
Hey froamer,

Actually the patch works great...given the circumstances. You see, CompositeField sets up an inner Ext.Container in the render stage ("onRender"), which happens after the initComponent chain has been called. Thus, the items inside of the CompositeField that have "ref" configured won't actually be available until after the CompositeField has rendered.

So for example, you can add a "ref" to the CompositeField directly, and on initComponent, attach to its render event:


this.compositeField.on('render', function(){
console.log(this.fieldSmeYes); // works now
}, this);


It's just the way CompositeField works at the moment.... :-( It can't render its inner Ext.Container until it knows where it needs to render itself, and that happens in "onRender"...and thus the child Fields aren't instantiated until "onRender". Hope that makes sense! Let me know if you still have trouble.

NOTE: I did add one extra line to the patch, which un-sets the ownerCt for the innerCt after it's instantiated...because the CompositeField is not a true Ext.Container, and it may affect how the elements are destroyed, etc...

froamer
28 May 2010, 9:23 AM
Many thanks Jarred,

That solves my problem.

Perfect!

TheCatWhisperer
20 Oct 2010, 9:29 AM
Hi Jarred,

Not sure if it is something else we've done in our code, but the Monkey Patch you created no longer works with 3.3.0. We get an error on "ownerCt : this"... (comp.getItemId is not a function)

Any Ideas?

jarrednicholls
20 Oct 2010, 9:33 AM
Is this in regards to Buttons or CompositeFields?

TheCatWhisperer
20 Oct 2010, 11:07 AM
composite fields... we have a composite with 2 combos (a parent & child).

TheCatWhisperer
21 Oct 2010, 3:47 AM
We are noticing the issue anywhere we are using ref to obtain a reference to a field in a composite.

e.g. for the setup:



{
xtype : 'container',
flex : 1,
layout : 'form',
labelAlign : 'top',
margins : '0 10 0 0',
items : [
{
xtype : 'compositefield',
ref : '../../../phoneNumberComposite',
fieldLabel : 'Direct Line',
anchor : '100%',
items : [
{
xtype : 'numberfield',
flex : 0.25,
fieldLabel : 'Label',
allowDecimals : false,
allowNegative : false,
name : 'countryCode',
ref : '../../../../phoneCountryField'
},
{
xtype : 'textfield',
flex : 1,
fieldLabel : 'Label',
name : 'phone',
ref : '../../../../phoneNumberField'
},
{
xtype : 'numberfield',
flex : 0.3,
fieldLabel : 'Label',
name : 'extension',
ref : '../../../../phoneExtensionField'
}
]
}
]
}


We later use this.phoneNumberField to get the phone number to do extra work on.

If we remove your "monkey patch", we get the expected {someObj}.phoneNumberField is undefined error. When the patch is in we get "comp.getItemId is not a function" whenever we attempt to open a dialog (... Ext.Window().show)...

jarrednicholls
21 Oct 2010, 7:09 AM
Got it. It looks like the CompositeField code was altered in 3.3 and moved the creation of the inner Ext.Container to the initComponent function instead of the onReader function. So my previous monkey patch doesn't quite work anymore. This is untested, but try this instead:



Ext.override(Ext.form.CompositeField, {
initComponent: function() {
var labels = [],
items = this.items,
item;

for (var i=0, j = items.length; i < j; i++) {
item = items[i];

if (!Ext.isEmpty(item.ref)){
item.ref = '../' + item.ref;
}

labels.push(item.fieldLabel);

//apply any defaults
Ext.applyIf(item, this.defaults);

//apply default margins to each item except the last
if (!(i == j - 1 && this.skipLastItemMargin)) {
Ext.applyIf(item, {margins: this.defaultMargins});
}
}

this.fieldLabel = this.fieldLabel || this.buildLabel(labels);

this.fieldErrors = new Ext.util.MixedCollection(true, function(item) {
return item.field;
});

this.fieldErrors.on({
scope : this,
add : this.updateInvalidMark,
remove : this.updateInvalidMark,
replace: this.updateInvalidMark
});

Ext.form.CompositeField.superclass.initComponent.apply(this, arguments);

this.innerCt = new Ext.Container({
layout : 'hbox',
items : this.items,
cls : 'x-form-composite',
defaultMargins: '0 3 0 0',
ownerCt: this
});
this.innerCt.ownerCt = undefined;

var fields = this.innerCt.findBy(function(c) {
return c.isFormField;
}, this);

this.items = new Ext.util.MixedCollection();
this.items.addAll(fields);
}
});


Good News: You no longer have to wait until the CompositeField has been rendered before referencing fields within it, now that the innerCt is created when the CompositeField is instantiated as opposed to when it is rendered.

Bad News: The reference bug still exists! :-( But hopefully this new patch does the trick for you!

TheCatWhisperer
21 Oct 2010, 9:29 AM
Jarred,

You are our hero! Seems to work great!

jarrednicholls
29 Nov 2010, 11:38 AM
this will be permanently resolved in 3.3.1

bbimber
17 Mar 2011, 9:23 AM
hello,

i have a related question. in initComponent, you explicitly set the ownerCt of innerCt to undefined.

this.innerCt = new Ext.Container({
layout : 'hbox',
items : this.items,
cls : 'x-form-composite',
defaultMargins: '0 3 0 0',
ownerCt: this
});
this.innerCt.ownerCt = undefined;


the problem is that findParent() does not work for children of the CompositeField. Is there a reason you delete ownerCt? Is there a different mechanism that we're supposed to work on the container hierarchy?

thanks.

jarrednicholls
18 Mar 2011, 10:37 AM
ownerCt is cleared because the CompositeField is not a true subclass of Ext.Container. There might be some destruction code that cleans up references to ownerCt, believing it to be an Ext.Container with items, etc. That is why it's cleared out.

But I see your dilemma. Is there anything preventing you from speaking with the CompositeField item directly (which has an items collection)? In other words, start from there, instead of starting from a Field within CompositeField?

bbimber
18 Mar 2011, 11:05 AM
thanks for the reply. the answer is maybe. let me describe the scenario:

i have an Ext.FormPanel. That panel has many fields, including a CompositeField. The CompositeField has two children, one of which is a Combo.

The Combo has a change listener. I'd like to make this function work up the container hierarchy, find the parent form, then find a field in that form by name (basically find a sibling of the Combo, except it's technically not a sibling). I then was to set the value on this field.

In other situations, I just use findParent() to get the FormPanel, then find the field by name from the BasicForm.

This Combo and it's listener are designed to be reusable. Some of the time the field will be a child of a composite field and some of the time it will be a child of the FormPanel. It's much better if the same code can be used in both cases.

The other idea I have would be to abandon CompositeField and just use a nested hbox layout in my formpanel. Essentially I'd manually do what compositefield does.

jarrednicholls
18 Mar 2011, 11:44 AM
I think the easiest thing to do is to use the HBox Container. That's honestly what I would do instead.

Or, you can inspect the ComboBox's inner "input" DOM node, and use the DOM to get the form (combo.el.dom.form). You can use that form's ID to lookup the FormPanel.

bbimber
18 Mar 2011, 2:08 PM
ok, i'll use hbox. based on this, it seems like compositefield is appropriate for situations when you want multiple inputs to have a single value (like the phone number example). that's sort of akin to a checkboxgroup or radiogroup. for situations where the purpose it simply to arrange differentfields in a different layout, compositefield is probably not the right solution.

thanks for the help.