PDA

View Full Version : Scope Problem for Popup Form



Mark Sisson
1 May 2009, 8:48 AM
I think I have a scoping problem but I just can't figure it out on my own. Thanks in advance for any help.

Here the situation: I have a grid that shows users from aN ajax fetch. I have a modal form that displays the details about each user. I have two ways for the user to display this popup: (1) right-click a row and select "edit", and (2) double-click on the row. My goal is to reuse this form so that each time the user wants to view a rows worth of data I just re-populate the form and show it modally.

The problem is, the first time I display the popup I see no data. The second time, and all subsequent times, it works perfectly. At the bottom of my code sample I have a function called showWindow() to load the form with data and display it. I call this function from my context menu and from my double-click event.

What's wierd to me is that when I step into the frmMain.getForm().loadRecord() method (going into ext-all-debug.js) I notice that the "this" variable is different the first time compared to subsequent times. So my novice question is WHY?????

Maybe I need some advice on best practice for organizing my functions and where and how to define them. I'm still very confused by scope in JS and I'm hoping I can learn some best-practices here.

Code Sample
(sorry, it's not pared down. I can do that if it would help but I have a hunch that an ExtJS pro will see a fundamental flaw quickly)

var ctxMenuRow;
Ext.onReady(function() {
Ext.QuickTips.init();

var dsStates = new Ext.data.SimpleStore({
fields: ['abbr', 'state', 'nick'],
data: Ext.exampledata.states // from states.js
});

var dsUsers = new Ext.data.Store({
url: 'rest/user/',
method: 'GET',
stripeRows: true,
reader: new Ext.data.JsonReader({
root: 'data',
id: 'UserID'
}, [
'UserID',
'UserName',
'FirstName',
'LastName',
'Password',
'Email',
'Address1',
'Address2',
'City',
'State',
'Zip',
'HomePhone',
'MobilePhone',
{ name: 'PasswordSetDate', type: 'date' },
{ name: 'PasswordChangeRequired', type: 'boolean' }
])
});

dsUsers.load();

var popupMenu = new Ext.menu.Menu({
items: [
{ text: 'Edit',
iconCls: 'edit',
handler: function() {
var selected = grid.selModel.getSelected();
var rec = dsUsers.getById(selected.id);
ctxMenuRecord = rec;
showWindow();
}
},
{ text: 'Delete', iconCls: 'del', disabled: false, handler: function() {
winMain.show();
}
}
]
});

var sm = new Ext.grid.CheckboxSelectionModel();

var grid = new Ext.grid.GridPanel({
renderTo: 'divMain',
tbar: [
{
xtype: 'tbbutton',
cls: 'x-btn-text-icon',
icon: '/icons/add.png',
text: 'Add New User',
handler: function() {
frmMain.getForm().reset();
winMain.show();
}
}, '-',
{
xtype: 'tbbutton',
cls: 'x-btn-text-icon',
icon: '/icons/find.png',
text: 'Search',
handler: function() {
Ext.Msg.prompt("Search", "Find:", function(btn, text) {
if (btn == 'ok')
alert('search for ' + text);
else
alert('no search');
});
}
}, '-',
{
xtype: 'tbbutton',
cls: 'x-btn-text-icon',
icon: '/icons/delete.png',
text: 'Delete Selected',
handler: function() {
}
}
],
sm: sm,
frame: false,
store: dsUsers,
height: 400,
width: 750,
cm: new Ext.grid.ColumnModel([
sm,
{ width: 90, sortable: true, header: "First", dataIndex: "FirstName" },
{ width: 90, sortable: true, header: "Last", dataIndex: "LastName" },
{ width: 90, sortable: true, header: "UserName", dataIndex: "UserName" },
{ width: 250, sortable: true, header: "Email", dataIndex: "Email" },
{ width: 90, hidden: true, sortable: true, header: "Address1", dataIndex: "Address1" },
{ width: 90, hidden: true, sortable: true, header: "Address2", dataIndex: "Address2" },
{ width: 90, hidden: true, sortable: true, header: "City", dataIndex: "City" },
{ width: 30, hidden: true, sortable: true, header: "State", dataIndex: "State" },
{ width: 30, hidden: true, sortable: true, header: "City", dataIndex: "City" },
{ width: 90, sortable: true, header: "HomePhone", dataIndex: "HomePhone" },
{ width: 90, sortable: true, header: "MobilePhone", dataIndex: "MobilePhone" },
{ width: 90, hidden: true, type: "date", sortable: true, header: "PasswordSetDate", dataIndex: "PasswordSetDate" },
{ width: 30, hidden: true, sortable: true, header: "PasswordChangeRequired", dataIndex: "PasswordChangeRequired", type: "boolean" }
])
});

grid.on('rowcontextmenu', function(grid, rowIndex, e) {
e.stopEvent(); // Stops the browser context menu from showing.
//if (ctxRow) {
// Ext.fly(ctxRow).removeClass('x-node-ctx');
// ctxRow = null;
//}
ctxMenuRow = grid.getView().getRow(rowIndex);
ctxMenuRecord = dsUsers.getAt(rowIndex);

name = ctxMenuRecord.data.FirstName + ' ' + ctxMenuRecord.data.LastName;
popupMenu.items.items[0].text = "Edit '" + name + "'";
popupMenu.items.items[1].text = "Delete '" + name + "'";
popupMenu.showAt(e.getXY());
});

grid.on('rowdblclick', function(grid, rowIndex, e) {
ctxMenuRow = grid.getView().getRow(rowIndex);
ctxMenuRecord = dsUsers.getAt(rowIndex);
showWindow();
});


frmMain = new Ext.FormPanel({
labelWidth: 100,
url: '/rest/user',
action: 'post',
monitorValid: true,
frame: true,
defaults: { width: 180 },
items: [
{ allowBlank: false, xtype: 'textfield', fieldLabel: "First", dataIndex: "FirstName", required: true },
{ allowBlank: false, xtype: 'textfield', fieldLabel: "Last", dataIndex: "LastName" },
{ allowBlank: false, xtype: 'textfield', fieldLabel: "UserName", dataIndex: "UserName" },
{ xtype: 'textfield', fieldLabel: "Email", vtype: 'email', dataIndex: "Email" },
{ xtype: 'textfield', fieldLabel: "Address1", dataIndex: "Address1" },
{ xtype: 'textfield', fieldLabel: "Address2", dataIndex: "Address2" },
{ xtype: 'textfield', fieldLabel: "City", dataIndex: "City" },
{ xtype: 'combo', mode: 'local', store: dsStates, displayField: 'state', fieldLabel: "State", dataIndex: "State" },
{ xtype: 'textfield', fieldLabel: "City", dataIndex: "City" },
{ xtype: 'textfield', fieldLabel: "HomePhone", dataIndex: "HomePhone" },
{ xtype: 'textfield', fieldLabel: "MobilePhone", dataIndex: "MobilePhone" },
{ xtype: 'datefield', fieldLabel: "PasswordSetDate", width: 100, dataIndex: "PasswordSetDate" },
{ xtype: 'checkbox', fieldLabel: "PasswordChangeRequired", dataIndex: "PasswordChangeRequired" }
],
buttons: [
{ text: 'OK',
cls: 'x-btn-text-icon',
icon: '/icons/accept.png',
formBind: true,
minWidth: 75,
handler: function() { winMain.hide() }
},
{ text: 'Cancel',
minWidth: 75,
cls: 'x-btn-text-icon',
icon: '/icons/cancel.png',
handler: function() {
winMain.hide()
}
}]
});

winMain = new Ext.Window({
height: 440,
width: 320,
border: false,
closable: true,
closeAction: 'hide',
modal: true,
title: 'User',
layout: 'fit',
items: frmMain
});

var showWindow = function() {
var g = 1;
frmMain.getForm().loadRecord(ctxMenuRecord);
winMain.show();
}
});

Mark Sisson
1 May 2009, 11:42 PM
Okay, I think I found the problem. When I was using the FormPanel, I was loading it, then showing it:


frmMain.getForm().loadRecord(ctxMenuRecord);
winMain.show();

The problem is that the FormPanel object does not instantiate its internal form object until it is rendered the first time. So, calling loadRecord() before the FormPanel had been rendered the first time has no form variables to fill in.

The solution was simple, just make sure the FormPanel is rendered before using the loadRecord() function:


winMain.show();
frmMain.getForm().loadRecord(ctxMenuRecord);

I'd be interested to hear why the architects did this. I'm guessing their thought was to instantiate resources as late as possible for performance sake but then how would users of this class know that they couldn't use loadRecord() yet? It makes me wonder (and worry) if this type of pattern is found throughout the code base: how many other objects will have functions and behavior break because objects aren't "ready to go"?

I WOULD RECOMMEND THAT AN ANNOTATION BE ADDED TO THE ONLINE DOCUMENTATION FOR THE loadRecord() FUNCTION TO WARN THAT IT IS INEFECTIVE UNLESS THE FORMPANEL HAS BEEN RENDERED.

I hope this helps others.

Condor
1 May 2009, 11:52 PM
You could use:

winMain.render(Ext.getBody());
frmMain.getForm().loadRecord(ctxMenuRecord);
...
winMain.show();

Mark Sisson
3 May 2009, 7:16 PM
Condor, thanks for the reply. Is there a moral to the story here that I should be learning? Should I go away from this thread thinking that Ext controls can't be expected to be ready for action until they've been rendered?

What do seasoned ExtJs developers do? Do they throw tons of render() statements all over the place before interacting with objects? Or is this an isolated case that might be a bug in this component? I'm trying to figure out if I should expect to see this as a reoccurring pattern throughout the library.

thx

Condor
3 May 2009, 8:33 PM
There are some things you can't do before you render a component, but in most cases it's not needed.

A form is the biggest exception, because forms rely on input elements to store the names and values (which don't exist before rendering). There are several workarounds, but they all don't work with file inputs.