PDA

View Full Version : Name collisions in form loadRecord



mikepeat
14 Jul 2010, 3:16 AM
Hi

I suspect I am being dim, but I've encountered a problem that has lots of possible solutions, but all that I can see are nasty and messy - I wonder if I am missing the nice, elegant (and maintainable!) solution.

Briefly, I am using forms (Ext.form.FormPanel subclassed), feeding off stores. In the stores I don't want to do field mapping (one of the nasty fixes), but just use the returned column names as the data names. The application will have many windows (Ext.Window subclassed). When I load a record into a form using this.getForm().loadRecord(rec) it not only loads the values into the appropriately named fields in that form, but also into any other fields in the entire application which have the same name. Since column names are not unique across the database, and indeed I will want to have the same column from the same table occurring in different, independent windows, this is a big problem.

The reason seems to be that loadData is using the DOM name property (rather than, say, the ID property) to set the values.

My question is, do I have to code around this, or is there simply a better way? I can't see one in the documentation, but the list of things I can't see in the documentation (but which later turn out to be there) is slightly larger than Wikipedia! ;)

Or am I perhaps just getting something else wrong and seeing a problem where there is none?

TIA

Mike Peat
Unicorn InterGlobal

Animal
14 Jul 2010, 3:25 AM
I don't understand the problem.

Field names must be unique within a form.

And (data) Field names must be unique within a Record.

So they map together OK don't they?

Condor
14 Jul 2010, 3:35 AM
What you describe shouldn't happen! Are you sure you are assigning unique ids to every fields on every window?

ps. Field names don't have to be unique (e.g. multiple radio buttons with the same name).

mikepeat
14 Jul 2010, 3:36 AM
Hi Animal - thanks for replying (and so quickly!).

Your "not understanding" makes me think I am getting something stupid wrong, but here is what seems to be happening.

I have two tables called "Module" and "User". Both have a column called "Name". ATM I have a window, with a form, for the maintenance of each. Each has its own store, with a fields property as a simple array of column names. In the forms each has an item something like: { name: "Name", fieldLabel: .... }. When loadRecord is actioned on the Module form, the module name also appears in the name field of the User form. Not quite what we want! :(

Mike

Animal
14 Jul 2010, 3:41 AM
Oh, both Records, a Module and a User are being loaded into one form?

Mmm... that won't work as it stands.

Animal
14 Jul 2010, 3:44 AM
One concept I am thinking about suggesting for a Feature Request is that management of form Fields (distribution and collection of values, checking mass validty, marking invalid etc) be moved to Ext.Container.

So any level of Containment owns a set of Fields.

That way, you could have a "Module" FieldSet and a "User" Fieldset both within one Form which could each be loaded from a separate Record.

I don't have any code done yet, just the concept!

mikepeat
14 Jul 2010, 3:51 AM
Animal

No... Window A (the Module Maintenance screen) has a form and a store (and a list). Window B (the User Maintenance screen) also has a form and a store (and a list). But when I action loadRecord on the Module form, passing it a module record (as selected in the list in fact), as well as populating the module form, the module name appears in the "Name" field in the user form in the User Maintenance window. Two forms, in two different windows, but each having an item with name: "Name".

Maybe I'm doing something else wrong though.

Mike

Condor
14 Jul 2010, 3:56 AM
And each window contains a form and you only load that form? That shouldn't cause a field in another form to update!

Unless these two fields have the same id!!!

Animal
14 Jul 2010, 4:02 AM
Yes, that behaviour does sound like an ID clash.

beegeedee
14 Jul 2010, 4:04 AM
Definitely sounds like an ID problem. Inherited a project where the grid toolbar buttons all had the same ID even though they might appear 3 or 4 times on any form. I got random updates and buttons doing strange things until I made sure they were all unique. Try that first.
It's worth mentioning that this didn't happen until upgrading to 3.2 - we were still working with 3.0 and it was fine.

mikepeat
14 Jul 2010, 4:18 AM
Well, looking in Firebug , the Name field of the Module form has id="ext-comp-1078" and the Name field of the User form has id="ext-comp-1068" (it depends on the order I open the windows from the menu, but that's what they have right now). The forms have different ids as well (assigned by me: "ModuleForm" and "UserForm"). I tried assigning ids to the items manually (which would count as a "nasty" work-around in my book, requiring too much unencapsulated programmer knowledge) - id: "moduleMaintName" and id: "userMaintName" - but that didn't help.

Mike

beegeedee
14 Jul 2010, 4:22 AM
By forms, are we talking fields on the forms such as a text box or radio button or whatever? If so, it would be these that also need unique names. the ext-comp- ids are generated and will always be unique but you have to watch where you specify them yourself so they are unique. If the forms are generated based on metaData in JSON you need to ensure that the id for each field is unique too, you mentioned a name item - try ids of moduleFormName and userFormName as an example.

mikepeat
14 Jul 2010, 4:32 AM
The form items in question are both xtype "textfield" BTW.

Animal
14 Jul 2010, 4:35 AM
We're going to need to see code.

beegeedee
14 Jul 2010, 4:37 AM
So, just to clarify the ID of the textfields must be unique to access them.

mikepeat
14 Jul 2010, 4:59 AM
Code - as it currently stands (this is with me setting ids by hand, which I don't want to do if I can avoid it):

First the module maintenance code:


var menuStore = new IAS.data.Store({
storeId : 'modMenuStore',
fields : [ "MenuID", "Descr" ],
idProperty : "MenuID",
proxy : new IAS.data.Proxy({ url: 'menu.php' }),
baseParams : {
columns: '["MenuID", "Descr"]'
}
});

var moduleStore = new IAS.data.Store({
storeId : 'moduleStore',
fields : ["ModuleID", "Name", "Menu", "MenuDesc", "IconClass",
"ToolTip", "Sequence", "AllUsers", "Source", "NonMenu" ],
idProperty : "ModuleID",
writer : new IAS.data.Writer(),
proxy : new IAS.data.Proxy({ url: 'modules2.php' })
});

var moduleList = new IAS.list.List({
id : "moduleList",
store : moduleStore,
columns : [
{ header: "ID", dataIndex: "ModuleID", width: .2, align: "right" },
{ header: "Module", dataIndex: "Name" }
]
});

var moduleForm = {
id : "moduleForm",
xtype : "iasform",
title : "Module",
idInTitle : true,
tbarType : "maint",
confirmSave : true,
validationFn: function (vals) { return true; },
store : moduleStore,
items : [
{ id: "moduleMaintName", name: "Name", fieldLabel: "Module name", width: 300 }, // This is problem field 1
{ name: "Menu", fieldLabel: "Menu", xtype: "combo",
store: menuStore, mode: "local", displayField: "Descr",
valueField: "MenuID", triggerAction: "all", hiddenName: "Menu",
hiddenId: "moduleMaintMenuID", forceSelection: true, width: 300
},
{ name: "MenuDesc", fieldLabel: "Menu display", width: 300 },
{ name: "IconClass", fieldLabel: "Icon", width: 200 },
{ name: "ToolTip", fieldLabel: "Tip", width: 500 },
{ name: "Sequence", fieldLabel: "Sequence (within menu)",
xtype: "numberfield", width: 120 },
{ name: "AllUsers", fieldLabel: "For all users", xtype: "checkbox" },
{ name: "Source", fieldLabel: "JavaScript source", width: 500 },
{ name: "NonMenu", fieldLabel: "Non menu item", xtype: "checkbox" }
]
};


Now the user maintenance code:


var userUGroupStore = new IAS.data.Store({
storeID : 'userUGroupStore',
fields : [ "GroupID", "Name" ],
proxy : new IAS.data.Proxy({ url: 'usergroup.php' }),
baseParams : {
columns: '["GroupID", "Name"]'
}
});

var userStore = new IAS.data.Store({
storeId : 'userStore',
fields : ["UserID", "Login", "UserGroup", "Name"],
idProperty : "UserID",
writer : new IAS.data.Writer(),
proxy : new IAS.data.Proxy({ url: 'users.php' })
});

var userList = new IAS.list.List({
id : "userList",
store : userStore,
columns : [
{ header: "ID", dataIndex: "UserID", width: .2, align: "right" },
{ header: "Name", dataIndex: "Name" }
]
});

var userForm = {
id : "userForm",
xtype : "iasform",
title : "User",
idInTitle : true,
tbarType : "maint",
confirmSave : true,
validationFn: function (vals) { return true; },
store : userStore,
items : [
{ id: "userMaintName", name: "Name", fieldLabel: "User name", width: 300 }, // This is problem field 2
{ name: "Login", fieldLabel: "Login", width: 300, cls: "textTransform: 'uppercase'" },
{ name: "Group", fieldLabel: "Group", xtype: "combo",
store: userUGroupStore, mode: "local", displayField: "Name",
valueField: "GroupID", triggerAction: "all", hiddenName: "Group",
hiddenId: "userMaintGroupID", forceSelection: true, width: 300
}
]
};



Each is in a different Window. I've attached a JPEG of what it looks like.

Mike

Animal
14 Jul 2010, 5:06 AM
So iasform extends Ext.form.FormPanel, and what you are loading is the getForm() of that?

Condor
14 Jul 2010, 5:11 AM
Can't see much wrong with this. You even made sure you were using unique hiddenIds.

ps. Does cls: "textTransform: 'uppercase'" really work?

mikepeat
14 Jul 2010, 5:18 AM
Yes, sorry - the actual loadRecord code is in the IAS.form.Form code, which is registered as xtype "iasform" for lazy instantiation. It is triggered by selection (on("click"...) ) in the list (also in its subclass), but passed through the store subclass (which is acting to orchestrate the various data-entry components) - the list tells the store that it has selected a record (via click), which in turn notifies all attached data entry components of the selected record.

And, no, textTransform: 'uppercase' does not work - it was just something I was messing around with. ;)

beegeedee
14 Jul 2010, 5:22 AM
Ok, for unique names for controls within IAS.form.Form, when you are processing the items, tag the id of the form instance onto the start of the controls. This way, even if the form id is allocated automatically, it generates unique names for the individual textfields...
something like new Ext.form.TextField({id: this.id+this.items[x].id}) as an example.
And as long as you access the content using this name/id you'll be set. I've used this a few times now and works brilliantly.

Condor
14 Jul 2010, 5:23 AM
Are you sure your notification system is correct?

ps. Shouldn't it be the selection model that notifies the form and not the store?

mikepeat
14 Jul 2010, 5:33 AM
Guys - I've just seen where I'm going wrong. Thank you for your help - it was your questions that caused me to look into where the problem was actually happening.

For some reason (as yet undiagnosed) the user form is receiving my "newSelectedRecord" message from the module store, being passed the module record and doing exactly the right thing with it (just in the wrong place). I don't yet know where my mistake is (maybe an inadvertent global or a class - rather than instance - property, or something), but now I know what is going wrong I'll be able to crack it.

There was no chance of you spotting it, because the problem lies somewhere in my already extensive sub-class layer!

Thanks for all your your help!

Mike

Animal
14 Jul 2010, 5:37 AM
style: 'text-transform:uppercase' will change the APPEARANCE of the text field to uppercase, but will still need a physical conversion on blur.

I'm beginning to suspect a problem in the iasform subclass such that they share too much via the prototype. We need to see that class.

mikepeat
14 Jul 2010, 6:03 AM
Animal

While I have not solved it yet, I've have tracked it down. Both stores were seeing all four UI components (both forms and both lists) as clients and sending them the appropriate (or inappropriate in this case) notifications.

I think my problem is that I don't have a clear enough understanding of Ext.extend... I'd set up the store client list as an array property (member) in the Ext.extend, but I now suspect that is giving me a class property, rather than an instance variable. (Doh!) Should I be creating it as a var in the constructor instead? I'm going to try with that anyway.

"...share too much..." is spot-on as a diagnosis though.

Mike

Animal
14 Jul 2010, 6:30 AM
Yes, everything in Ext.extend is shared across all instances.

That's why only methods, and default values and constants are usually placed there.

mikepeat
14 Jul 2010, 6:36 AM
OK... I've worked it out now (maybe :-?).

In my sub-class, I was creating a property "clients", thus:



clients: [],

addClient: fuction (client) {
this.clients.push(client);
},


However due to JavaScript's... idiosyncratic?... approach to object properties - if reading, use class version; if writing, use (creating if required) object version - this was only creating an initial class variable. No instance variable was created - I think - because it was never written to, just manipulated by reference, with things - UI objects - being added into the array.

Changing my code so that I only create the member "this.clients = [];" if it doesn't currently exist forces the creation of an instance (object) variable:



//clients: [],

addClient: function (client) {

if (!this.clients) {
this.clients = [];
}

this.clients.push(client);
},


solves the problem.

Whew! Took me a bit of head-scratching to suss that out!

I think that is the right interpretation of what was happening... if not maybe somebody more versed in the peculiarities of JavaScript's pseudo-classical inheritance model can provide a better explanation.

Thanks again for all the help! :)

Mike

mikepeat
14 Jul 2010, 6:50 AM
Now, in answer to Condor's question about who should be notifying who, as an inadequate "thank-you" for your help, I'll explain how I see it.

I may have, in a given window, many UI components all relating to something I'm doing. There may be multiple forms, lists, grids, etc, all using one store as their data supplier. There may even be (usually will be in more complex modules) other data stores dealing with child records, with their own UI component clients.

My approach is to make the store the master. Have the UI components report stuff to it and have it notify all the other UI components (and other stores if required) of what is going on.

This data-centric approach means that List A doesn't need to know about Form B or Grid C - they all just register as clients of the controlling store and it handles keeping them all on the same page. This means I can abstract much more of the functionality out into my sub-class layer, with each sub-class having its own implementation of the common interface and dealing with the various messages appropriately for its type.

Mike

Animal
14 Jul 2010, 6:59 AM
Ext.extend places properties into the new class's shared prototype.

mikepeat
14 Jul 2010, 8:27 AM
Animal

Yes... it has very much to do with the prototype, but is not specific to just Ext.extend.

Consider the following trivial JavaScript code code which illustrates the behaviour:



<html>
<body>
<script>
function MyClass() {}

MyClass.prototype.simpleProperty = "1,2,3";
MyClass.prototype.arrayProperty = [1,2,3];

MyClass.prototype.stringArray = function () {
return this.arrayProperty.join(",");
};

var obj1 = new MyClass();
var obj2 = new MyClass();

obj1.simpleProperty = "4,5,6";
obj1.arrayProperty[0] = 4;
obj1.arrayProperty[1] = 5;
obj1.arrayProperty[2] = 6;

document.write("Object 1 - simple: " + obj1.simpleProperty + "<br>");
document.write("Object 2 - simple: " + obj2.simpleProperty + "<br>");
document.write("Object 1 - array : " + obj1.stringArray() + "<br>");
document.write("Object 2 - array : " + obj2.stringArray() + "<br>");
</script>
<body>
</html>


This outputs:

Object 1 - simple: 4,5,6
Object 2 - simple: 1,2,3
Object 1 - array : 4,5,6
Object 2 - array : 4,5,6

Now the simple string behaves as an OO programmer (me! ;)) intuitively expects: each instance appears (!) to have it's own version of the property (data member) - change it in one and the other one remains unchanged.

With the array, changing one changes the other!!! Aaaaarg! Not what Mr Stupid here expected at all!

With the array property, we are not actually writing to the data member at all - that is just a reference to an array object, which (unless you actually overwrite the reference in the object, say with: obj1.arrayProperty = [4,5,6];) remains a property of the prototype (see JavaScript: The Definitive Guide, 5th Edition, pp 153-154, for JavaScript's asymmetric handling of inherited properties under read/write operations). What we are doing is using that reference to write to elements of the array... which itself remains shared. I'm almost certain that this would work the same way if we used an object with properties rather than an array with elements (I'll just test it... yes, it does).

This is the only way I've found to make this spooky thing happen. All the other ways I've tried of creating objects from a class give them independent properties. It must (I think) be the way that Ext.extend does it (which makes logical sense, if you think about it - how else could it do it?).

Apologies if I am teaching my grandmother to suck eggs here - I find trying to explain them clearly to someone else helps enormously in clarifying my own grasp of tricky issues. ;)

Mike