PDA

View Full Version : Problem with dynamically adding a component to a viewport



nickweavers
15 Oct 2009, 5:38 PM
I have a viewport who's west region is a tree menu and center region a tab panel that will hold content determined by the choice of menu item. The code snippet defining these is as follows:


}, {
region: 'west',
collapsible: true,
title: 'Navigation',
id: 'west-panel',
xtype: 'treepanel',
width: 200,
autoScroll: true,
split: true,
loader: new Ext.tree.TreeLoader({
method: 'Get',
dataUrl : 'menu_tree.php',
}),
root: new Ext.tree.AsyncTreeNode(),
rootVisible: false,
listeners: {
click: function(tree_xhr) {
Ext.Ajax.request({
// Call a server side script to send a component configuration
url: 'grid_example_query.php',
params: { task: tree_xhr.attributes.task },
success: function(comp_xhr) {
var newComponent = eval(comp_xhr.responseText); // see discussion below
CenterTabPanel.add(newComponent); // add the component to the TabPanel
CenterTabPanel.setActiveTab(newComponent);
},
failure: function(response, options) {
Ext.Msg.alert("Grid create failed", "Server communication failure");
},
});
}
}
}, {
region: 'center',
layout: 'fit', // specify layout manager for items
items: new Ext.TabPanel({
deferredRender: true,
activeTab: 1, // first tab initially active
items: [
{
contentEl: 'help-tab',
title: 'Help',
closable: true,
autoScroll: true
},
{
contentEl: 'content-tab',
title: 'Main view',
layout: 'fit',
html: '<h1>Hello</h1>'
}
]
})
}


The server script looks like this:


<?php
////////////////////////////////////////////////////////
// DATABASE.PHP
////////////////////////////////////////////////////////
mysql_connect("localhost", "user", "pswd") or
die("Could not connect: " . mysql_error());
mysql_select_db("db");

$task = '';
if ( isset($_REQUEST['task'])){
$task = $_REQUEST['task']; // Get this from Ext
}
switch($task){
case "employee_grid": // Send the employee grid component
sendEmployeeGrid();
break;
case "employee_records": // Send the employee records
sendEmployeeRecords();
break;
default:
echo "{failure:true}"; // Simple 1-dim JSON array to tell Ext the request failed.
break;
}

function sendEmployeeGrid()
{
$script = "
(function() {

store = new Ext.data.Store({
url: 'grid_example_query.php',
baseParams: {
task: 'employee_records',
controller: '',
view: '',
format: ''
},
reader: new Ext.data.JsonReader({
record: 'transaction',
idProperty: 'id',
totalRecords: 'total'
}, [
{name: 'id_employee', type: 'string', mapping: 'id_employee'},
{name: 'id_person', type: 'string', mapping: 'id_person'},
{name: 'id_user', type: 'string', mapping: 'id_user'},
{name: 'FirstName', type: 'string', mapping: 'first_name'},
{name: 'Initials', type: 'string', mapping: 'initials'},
{name: 'LastName', type: 'string', mapping: 'last_name'},
{name: 'Title', type: 'string', mapping: 'title'},
{name: 'AddressLine1', type: 'string', mapping: 'address_line1'},
{name: 'AddressLine2', type: 'string', mapping: 'address_line2'},
{name: 'Town', type: 'string', mapping: 'town'},
{name: 'City', type: 'string', mapping: 'city'},
{name: 'County', type: 'string', mapping: 'county'},
{name: 'Postcode', type: 'string', mapping: 'postcode'},
{name: 'Telephone', type: 'string', mapping: 'telephone'},
{name: 'Mobile', type: 'string', mapping: 'mobile'}
])
});



var grid = Ext.grid.GridPanel({
title: 'Employees',
bbar: new Ext.PagingToolbar(store),
store: store,
columns: [ // fix up the column settings below
{header: 'id_employee', width: 250, dataIndex: 'id_employee', sortable: true},
{header: 'id_person', width: 250, dataIndex: 'id_person', hidden: true},
{header: 'id_user', width: 250, dataIndex: 'id_user', hidden: true},
{header: 'First Name', width: 250, dataIndex: 'FirstName', sortable: true},
{header: 'Ititials', width: 250, dataIndex: 'Initials', sortable: true},
{header: 'Last Name', width: 120, dataIndex: 'LastName', sortable: true},
{header: 'Title', width: 120, dataIndex: 'Title', sortable: true},
{header: 'Address Line1', width: 100, dataIndex: 'AddressLine1', sortable: true},
{header: 'Address Line2', width: 100, dataIndex: 'AddressLine2', hidden: true},
{header: 'Town', width: 100, dataIndex: 'Town', sortable: true},
{header: 'City', width: 100, dataIndex: 'City', sortable: true},
{header: 'County', width: 120, dataIndex: 'County', sortable: true},
{header: 'Postcode', width: 120, dataIndex: 'Postcode', sortable: true},
{header: 'Telephone', width: 120, dataIndex: 'Telephone'},
{header: 'Mobile', width: 120, dataIndex: 'Mobile'}
]
});

store.load();
return grid; // return instantiated component
})();
";
echo $script;
}

function sendEmployeeRecords()
{
$query = "
SELECT *
FROM jos_opsuk_employees AS employee
JOIN jos_opsuk_persons AS person ON employee.id_person = person.id_person
LIMIT 0 , 30
";
$result = mysql_query($query);
$nbrows = mysql_num_rows($result);
if($nbrows>0){
while($rec = mysql_fetch_array($result, MYSQL_ASSOC)){
$arr[] = $rec;
}
$jsonresult = JEncode($arr);
echo '({"total":"'.$nbrows.'","results":'.$jsonresult.'})';
} else {
echo '({"total":"0", "results":""})';
}
}

function JEncode($arr)
{
if (version_compare(PHP_VERSION,"5.2","<"))
{
require_once("./JSON.php"); //if php<5.2 need JSON class
$json = new Services_JSON(); //instantiate new json object
$data=$json->encode($arr); //encode the data in json format
} else
{
$data = json_encode($arr); //encode the data in json format
}
return $data;
}

?>



The problem I am having is that when the grid definition is received from the host and eval'ed, I get the following message in my Firebug console:


records[i].join is not a function
http://localhost:8080/opsuk/includes/js/ext_3.0/ext-all-debug.js
Line 30554

If I breakpoint the line
var grid = Ext.grid.GridPanel({and single step through the code, I see the cursor through code that looks like it is generating the toolbar in this section of ext-all-debug.js starting on line 30541:


/**
* Add Records to the Store and fires the {@link #add} event. To add Records
* to the store from a remote source use <code>{@link #load}({add:true})</code>.
* See also <code>{@link #recordType}</code> and <code>{@link #insert}</code>.
* @param {Ext.data.Record[]} records An Array of Ext.data.Record objects
* to add to the cache. See {@link #recordType}.
*/
add : function(records){
records = [].concat(records);
if(records.length < 1){
return;
}
for(var i = 0, len = records.length; i < len; i++){
records[i].join(this);
}

The value of records at the point it fails is:


[Object initialConfig=Object tooltip=FirstPage 0=Object]

Can anyone tell me what I am doing wrong? I'm not sure if it is something to do with the store not working properly. I noticed that if when I hit me first breakpoint, I typed

store.load()
I could see the http response of

({"total":"1","results":[{"id_employee":"1","id_person":"0","id_user":null,"first_name":"Fred","initials":"H","last_name":"Bloggs","title":"Mr","address_line1":"18 Brokendown Lane","address_line2":null,"town":"Totmarch","city":"Southwalls","county":"Hogshire","postcode":"PO40 3HZ","telephone":"02660 680765","mobile":"07386386639"}]}) But I could see no values in store->data->items but was only guessing that was where the data would probably be put.

I should add I am a complete newbie to this so please be gentle ;)

TIA

Animal
15 Oct 2009, 9:37 PM
You seem to have made a good start.

Does that error occur when the store.load() is called?

Because the JsonReader requires a root config in order to be able to grab the Array of row data: http://www.extjs.com/deploy/dev/docs/?class=Ext.data.JsonReader

nickweavers
16 Oct 2009, 12:13 AM
Hi, thanks for the quick reply.

The store.load() seems to run without producing any error messages. I tested this first by calling it from the firebug console command line when I hit the breakpoint I placed just before the "var grid = Ext.grid.GridPanel({". What I see when I do this is that the firebug console shows the http response that comes back from the async request, and in that request is valid data:


({"total":"1","results":[{"id_employee":"1","id_person":"0","id_user":null,"first_name":"Fred","initials":"H","last_name":"Bloggs","title":"Mr","address_line1":"18 Brokendown Lane","address_line2":null,"town":"Totmarch","city":"Southwalls","county":"Hogshire","postcode":"PO40 3HZ","telephone":"02660 680765","mobile":"07386386639"}]})

I think you are right, though. It looks like the problem is that the requested data is not finding its way into the array defined in the json reader

store = new Ext.data.Store({
url: 'grid_example_query.php',
baseParams: {
task: 'employee_records',
controller: '',
view: '',
format: ''
},
reader: new Ext.data.JsonReader({
record: 'transaction',
idProperty: 'id',
totalRecords: 'total'
}, [
{name: 'id_employee', type: 'string', mapping: 'id_employee'},
{name: 'id_person', type: 'string', mapping: 'id_person'},
{name: 'id_user', type: 'string', mapping: 'id_user'},
{name: 'FirstName', type: 'string', mapping: 'first_name'},
{name: 'Initials', type: 'string', mapping: 'initials'},
{name: 'LastName', type: 'string', mapping: 'last_name'},
{name: 'Title', type: 'string', mapping: 'title'},
{name: 'AddressLine1', type: 'string', mapping: 'address_line1'},
{name: 'AddressLine2', type: 'string', mapping: 'address_line2'},
{name: 'Town', type: 'string', mapping: 'town'},
{name: 'City', type: 'string', mapping: 'city'},
{name: 'County', type: 'string', mapping: 'county'},
{name: 'Postcode', type: 'string', mapping: 'postcode'},
{name: 'Telephone', type: 'string', mapping: 'telephone'},
{name: 'Mobile', type: 'string', mapping: 'mobile'}
])
});

I tried moving the store.load() ahead of the "var grid = Ext.grid.GridPanel({" and can now see that the error message
records[i].join is not a function
http://localhost:8080/opsuk/includes...t-all-debug.js
Line 30554 appears after it hits the line
bbar: new Ext.PagingToolbar(store), in the grid.GridPanel object. So I guess it is at the point where the grid object is trying to retrieve data from the store object that it hits problems.

Looking at the contents of store after the load, I see what is in the attached screenshot jpg from firebug. This shows that the data array is empty and I think this should not be the case. I will take a closer look at the need for the root config. It was not used the the examples I strung together to build what I have so far, but it could be that it was missing in the example :(

Animal
16 Oct 2009, 12:23 AM
I told you your solution.



reader: new Ext.data.JsonReader({
record: 'transaction',
idProperty: 'id',
totalRecords: 'total'
}


Then



({"total":"1","results":[{"id_employee":"1","id_person":"0","id_user":null,"first_name":"Fred","initials":"H","last_name":"Bloggs","title":"Mr","address_line1":"18 Brokendown Lane","address_line2":null,"town":"Totmarch","city":"Southwalls","county":"Hogshire","postcode":"PO40 3HZ","telephone":"02660 680765","mobile":"07386386639"}]})


How is it to know to use the "results" property as the Array of data?

Read that link.

nickweavers
16 Oct 2009, 1:03 AM
Okay, I now have

reader: new Ext.data.JsonReader({
//record: 'transaction',
idProperty: 'id',
totalRecords: 'total',
root: 'rows'
}

and json input of

({"total":"1","rows":[{"id_employee":"1","id_person":"0","id_user":null,"first_name":"Fred","initials":"H","last_name":"Bloggs","title":"Mr","address_line1":"18 Brokendown Lane","address_line2":null,"town":"Totmarch","city":"Southwalls","county":"Hogshire","postcode":"PO40 3HZ","telephone":"02660 680765","mobile":"07386386639"}]})

and still get the same error
records[i].join is not a function
http://localhost:8080/opsuk/includes/js/ext_3.0/ext-all-debug.js
Line 30554

I'll keep staring at it.

Animal
16 Oct 2009, 2:21 AM
OK, now it should be working.

Set Firebug to break on all errors, and see what records[i] is at that point.

Animal
16 Oct 2009, 2:21 AM
OK, now it should be working.

Set Firebug to break on all errors, and see what records[i] is at that point.

nickweavers
16 Oct 2009, 7:52 AM
Something is still not right. Firebug again stops at line 30553. For some reason the array being passed to add remains empty, so store is still not "seeing" its input. (In passing, I think it would be a good idea if this function did a basic validity check on it's input up front to check it is not being passed an empty array. Telling us that would be slightly more useful, but that's just my view.)


/**
30542 * Add Records to the Store and fires the {@link #add} event. To add Records
30543 * to the store from a remote source use <code>{@link #load}({add:true})</code>.
30544 * See also <code>{@link #recordType}</code> and <code>{@link #insert}</code>.
30545 * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects
30546 * to add to the cache. See {@link #recordType}.
30547 */
30548 add : function(records){
30549 records = [].concat(records);
30550 if(records.length < 1){
30551 return;
30552 }
30553 for(var i = 0, len = records.length; i < len; i++){
30554 records[i].join(this); <-- records[i].join is not a function
30555 }
30556 var index = this.data.length;
30557 this.data.addAll(records);
30558 if(this.snapshot){
30559 this.snapshot.addAll(records);
30560 }
30561 this.fireEvent('add', this, records, index);
30562 },

Animal
16 Oct 2009, 1:14 PM
Can't be empty otherwise that loop would never run. What is records[i]? And you can find out who passed it by going back through the call stack from that breakpoint.

nickweavers
16 Oct 2009, 1:54 PM
I switched to using a static data store to see if that would make any difference, and also used the simpler Ext.data.JsonStore rather than seperate Ext.data.Store and Ext.data.JsonReader, but I am still getting the same error in Firebug when it tries to process the bbar in the GridPanel.

(function() {

store = new Ext.data.JsonStore({
//url: 'grid_example_query.php',
baseParams: {
task: 'employee_records',
controller: '',
view: '',
format: ''
},
root: 'rows',
idProperty: 'id_employee',
fields: [
{name: 'id_employee', type: 'string', mapping: 'id_employee'},
{name: 'id_person', type: 'string', mapping: 'id_person'},
{name: 'id_user', type: 'string', mapping: 'id_user'},
{name: 'FirstName', type: 'string', mapping: 'first_name'},
{name: 'Initials', type: 'string', mapping: 'initials'},
{name: 'LastName', type: 'string', mapping: 'last_name'},
{name: 'Title', type: 'string', mapping: 'title'},
{name: 'AddressLine1', type: 'string', mapping: 'address_line1'},
{name: 'AddressLine2', type: 'string', mapping: 'address_line2'},
{name: 'Town', type: 'string', mapping: 'town'},
{name: 'City', type: 'string', mapping: 'city'},
{name: 'County', type: 'string', mapping: 'county'},
{name: 'Postcode', type: 'string', mapping: 'postcode'},
{name: 'Telephone', type: 'string', mapping: 'telephone'},
{name: 'Mobile', type: 'string', mapping: 'mobile'}
],
data: {
rows: [{
id_employee: '1',
id_person: '0',
id_user: null,
first_name: 'Fred',
initials: 'H',
last_name: 'Bloggs',
title: 'Sir',
address_line1: '18 Brokencrock Lane',
address_line2: null,
town: 'Totfield',
city: 'Sourbridge',
county: 'Hampdale',
postcode: 'SP40 2HW',
telephone: '07680 687864',
mobile: '07878786491'
}]
}
});

//store.load();

var grid = Ext.grid.GridPanel({
title: 'Employees',
bbar: new Ext.PagingToolbar(store),
//store: store,
columns: [ // fix up the column settings below
{header: 'id_employee', width: 250, dataIndex: 'id_employee', sortable: true},
{header: 'id_person', width: 250, dataIndex: 'id_person', hidden: true},
{header: 'id_user', width: 250, dataIndex: 'id_user', hidden: true},
{header: 'First Name', width: 250, dataIndex: 'FirstName', sortable: true},
{header: 'Ititials', width: 250, dataIndex: 'Initials', sortable: true},
{header: 'Last Name', width: 120, dataIndex: 'LastName', sortable: true},
{header: 'Title', width: 120, dataIndex: 'Title', sortable: true},
{header: 'Address Line1', width: 100, dataIndex: 'AddressLine1', sortable: true},
{header: 'Address Line2', width: 100, dataIndex: 'AddressLine2', hidden: true},
{header: 'Town', width: 100, dataIndex: 'Town', sortable: true},
{header: 'City', width: 100, dataIndex: 'City', sortable: true},
{header: 'County', width: 120, dataIndex: 'County', sortable: true},
{header: 'Postcode', width: 120, dataIndex: 'Postcode', sortable: true},
{header: 'Telephone', width: 120, dataIndex: 'Telephone'},
{header: 'Mobile', width: 120, dataIndex: 'Mobile'}
]
});

return grid; // return instantiated component
})();



records[i].join is not a function
http://localhost:8080/opsuk/includes/js/ext_3.0/ext-all-debug.js
Line 30554
If I single step and stop just before bbar line, though, I see that the store is there and now I have what looks like valid data in it See first thumbnail. If I remove the bbar I get a different error shown in the second thumbnail.

nickweavers
16 Oct 2009, 2:10 PM
Can't be empty otherwise that loop would never run. What is records[i]? And you can find out who passed it by going back through the call stack from that breakpoint.
The value of "i" is zero at that point. My mistake, you are right, records[0] is not empty (see thumb 1), but the object does not appear have a join method (see thumb 2, they are listed alphabetically). I guess this would normally be inherited?

nickweavers
17 Oct 2009, 6:53 AM
I tried sending a simpler component from the server.

(function() {
var cp = new Ext.ColorPalette([{title: 'Colour Picker'},{value:'993300'}]); // initial selected color
return cp;
})();
Now I can at least see something appear in the tab panel (thumbnail attached) which is heartening!

I think I must have a layout issue though as I am getting an error mesage


Ext.layout.FitLayout = Ext.extend(Ext.layout.ContainerLayout, {
// private
monitorResize:true,

// private
onLayout : function(ct, target){
Ext.layout.FitLayout.superclass.onLayout.call(this, ct, target);
if(!this.container.collapsed){
var sz = (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getViewSize() : target.getStyleSize();
this.setItemSize(this.activeItem || ct.items.itemAt(0), sz);
}
},

// private
setItemSize : function(item, size){
if(item && size.height > 0){ // display none?
item.setSize(size); <-- item.setSize is not a function
}
}
});


Can anyone point me at a good article describing layouts?

TIA

hendricd
17 Oct 2009, 8:58 AM
Try:

var grid = new Ext.grid.GridPanel({;)

nickweavers
17 Oct 2009, 1:22 PM
OMG... that was it!

How is it that you can stare at your own code for hours, no days sometimes, and still not spot these things!

Thank you so much @hendricd

I do have to comment out the bbar to make it work. But I think that is a separate problem to investigate.

Animal
18 Oct 2009, 2:44 AM
I tried sending a simpler component from the server.

(function() {
var cp = new Ext.ColorPalette([{title: 'Colour Picker'},{value:'993300'}]); // initial selected color
return cp;
})();
Now I can at least see something appear in the tab panel (thumbnail attached) which is heartening!

I think I must have a layout issue though as I am getting an error mesage


Ext.layout.FitLayout = Ext.extend(Ext.layout.ContainerLayout, {
// private
monitorResize:true,

// private
onLayout : function(ct, target){
Ext.layout.FitLayout.superclass.onLayout.call(this, ct, target);
if(!this.container.collapsed){
var sz = (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getViewSize() : target.getStyleSize();
this.setItemSize(this.activeItem || ct.items.itemAt(0), sz);
}
},

// private
setItemSize : function(item, size){
if(item && size.height > 0){ // display none?
item.setSize(size); <-- item.setSize is not a function
}
}
});


Can anyone point me at a good article describing layouts?

TIA

That error is just because ColorPalette is (for no good reason I can think of, and I've reported it to them) not a BoxComponent, but just a Component, and so cannot participate in box sizing layouts (like CardLayout which TabPanel uses)

Animal
18 Oct 2009, 2:45 AM
Info about layouts?

I put it all in the docs: http://www.extjs.com/deploy/dev/docs/?class=Ext.Container

All info I have ever explained on the forums is in the docs. It's just that people seem to like me to waste time copy/pasting it into a forum post for them for some reason.

nickweavers
18 Oct 2009, 10:27 AM
Info about layouts?

I put it all in the docs: http://www.extjs.com/deploy/dev/docs/?class=Ext.Container

All info I have ever explained on the forums is in the docs. It's just that people seem to like me to waste time copy/pasting it into a forum post for them for some reason.

Unfair remark. I think a lot of folks visiting the forums for help spend a huge amount of time trawling for answers before asking here. The problem is that there is so much info and it is hard to find some of the sign posts. I started looking under the Ext Layout (seemed intuitive to me at the time) section in the API Docs but did not uncover a good explanation there. I have no doubt that a lot of effort has been put into providing documentation, but it just isn't always where you might look for it.

Anyway, thanks for the link.