PDA

View Full Version : [FIXED-EXTJSIV-242] multiple HasMany association conflict in XTemplate



oe.elvik
18 Mar 2011, 5:53 AM
I have a Model with multiple HasMany association. I use this model in a XTemplate with the <tpl for="subModel">. When I'm only using one association it runs with no problem, but when I add another association the template for the first subModel generates zero rows. And if I add an other association i get "values is undefined eval(body); ext-all-debug.js (line 1248)"

oe.elvik
18 Mar 2011, 6:01 AM
Related to this bug, but sugested fix does not work.

http://www.sencha.com/forum/showthread.php?115383-Can-t-have-more-than-1-hasMany-association

oe.elvik
18 Mar 2011, 6:21 AM
I use the model "Contact" with hasMany association to Address, Phone, Email

Running:
Ext.getStore("Contact").getAt(0).data.Address
Ext.getStore("Contact").getAt(0).data.Phone
Ext.getStore("Contact").getAt(0).data.Email

Gives me a Collection of the records when only one association is defined,
but when multiple association is defined the first record in the Address collection is undefined, the phone collection is ok (no change), and the Email collection gets emptyed.

Strange

evant
20 Mar 2011, 9:15 PM
Please post a test case that demonstrates the issue.

oe.elvik
21 Mar 2011, 3:27 AM
in trying to create a simple testcase I realised that I maght not hawe used the correct way to render the record through a XTemplate. What i expected was that the submodel data would be accessible through the parent record.data

for example my model contact have a hasMany connection to phone I would expect to be able to access phones records throug: contact.data.phone, and therefore I would expect to be able to use xtemplate.overwrite(panel.body, contact.data); with xtemplate implementing <tpl for="phone"> ...

This turns out not to be the case in my testcase, I'm not able access the phone records under contact.data.phone. However in my huge app(not posting that here) I'm able. (I have no clue why)

Så my question is should i be able to use <tpl for="phone"> when phone is a submodel to contact.
If not, is it possible to access the sobmodel records in a xtemplate and if so, how?

Here is a little test case:

index.html:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="lib/ext/resources/css/ext-standard.css">

<title>Meso</title>
</head>
<body>

<script type="text/javascript" src="lib/ext/ext-core-debug.js"></script>
<script type="text/javascript" src="lib/ext/ext-all-debug.js"></script>

<script type="text/javascript" src="app.js"></script>

<div id="button"></div>
<div id="testcase"></div>
</body>
</html>

app.js:

Ext.regModel('Contact', {
fields: [
{name: 'id', type: 'int'},
{name: 'photo', type: 'string'},
{name: 'firstname', type: 'string'},
{name: 'midlename', type: 'string'},
{name: 'lastname', type: 'string'},
{name: 'tags', type: 'string'},
{name: 'bday', type: 'date', dateFormat: 'd.m.Y'},
{name: 'note', type: 'string'}
],

hasMany: [
{model: 'ElectronicAddress', name: 'electronicAddress'},
{model: 'Address', name: 'address'},
{model: 'Phone', name: 'phone'}
]
});

Ext.regModel('Address', {
fields: [
{name: 'id', type: 'int'},
{name: 'type', type: 'string'},
{name: 'pobox', type: 'string'},
{name: 'extendedAddress', type: 'string'},
{name: 'streetAddress', type: 'string'},
{name: 'locality', type: 'string'},
{name: 'region', type: 'string'},
{name: 'postalcode', type: 'string'},
{name: 'countryName', type: 'string'},
{name: 'ordering', type: 'int'}
],

belongsTo: 'Contact'
});

Ext.regModel('ElectronicAddress', {
fields: [
{name: 'id', type: 'int'},
{name: 'type', type: 'string'},
{name: 'description', type: 'string'},
{name: 'address', type: 'string'},
{name: 'ordering', type: 'int'}
],

belongsTo: 'Contact'
});

Ext.regModel('Phone', {
fields: [
{name: 'id', type: 'int'},
{name: 'type', type: 'string'},
{name: 'number', type: 'string'},
{name: 'ordering', type: 'int'}
],

belongsTo: 'Contact'
});

Ext.regStore('Contacts', {
model: 'Contact',
autoLoad: true,
proxy: {
type: 'ajax',
url : 'data.json',
reader: 'json'
}
});




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

var panel = new Ext.panel.Panel({
height: 350,
width: 600,
title: 'Contact',
renderTo: 'testcase'
});

var template = new Ext.XTemplate(
'<div class="photo"><span class="lable">Photo:</span><img src="{photo}" /></div>',
'<div class="name"><span class="lable">Name:</span>{firstname} {midlename} {lastname}</div>',
'<div class="tags"><span class="lable">Tags:</span>{tags}</div>',
'<div class="bday"><span class="lable">Birthday:</span>{bday}</div>',
'<div class="note"><span class="lable">Note:</span>{note}</div>',

'<div class="addresses">'
+ ' <span class="lable">Address:</span>'
+ ' <tpl for="address">'
+ ' <div class="address">'
+ ' <span class="type">{type}</span>'
+ ' <span class="streetAddress">{streetAddress}</span>'
+ ' <span class="extendedAddress">{extendedAddress}</span>'
+ ' <span class="pobox">{pobox}</span>'
+ ' <span class="postalcode">{postalcode}</span>'
+ ' <span class="locality">{locality}</span>'
+ ' <span class="region">{region}</span>'
+ ' <span class="countryName">{countryName}</span>'
+ ' </div>'
+ ' </tpl>'
+ '</div>',


'<div class="phones">'
+ ' <span class="lable">Phone:</span>'
+ ' <tpl for="phone">'
+ ' <div class="phone">'
+ ' <span class="type">{type}</span>'
+ ' <span class="number">{number}</span>'
+ ' </div>'
+ ' </tpl>'
+ '</div>',


'<div class="electronic_addresses">'
+ ' <span class="lable">Electronic address:</span>'
+ ' <tpl for="electronicAddress">'
+ ' <div class="electronic_address">'
+ ' <span class="type">{type}</span>'
+ ' <span class="address">{address}</span>'
+ ' <span class="description">{description}</span>'
+ ' </div>'
+ ' </tpl>'
+ '</div>'
);

var handler = function(){
var contact = Ext.getStore("Contacts").getAt(0);

template.overwrite(panel.body, contact.data);
}

var button = new Ext.button.Button({
scope: this,
handler: handler,
renderTo: "button",
text: 'Load'
});
});

data.json:

[
{
"id":1,
"firstname":"Ola",
"midlename":"",
"lastname":"Norman",
"note":"",
"photo":"photo.jpg",
"tags":"",
"address":
[{
"id":1,
"countryName":"Norway",
"locality":"Oslo",
"postalcode":"0123",
"region":"Oslo",
"streetAddress":"Karl Johansgate 3",
"type":"home"
}],
"electronicAddress":
[{
"address":"[email protected]",
"id":1,
"type":"email"
},{
"address":"http://www.example.com",
"id":2,
"type":"url"
},{
"address":"username",
"description":"skype",
"id":3,
"type":"im"
}],
"phone":
[{
"id":2,
"number":"12345678",
"type":"cell"
}]
}
]

oe.elvik
22 Mar 2011, 3:29 AM
It looks like the reason i can find the submodel records in my huge app is becose I use the store in a dataview running Ext.DataView.prototype.prepareData on the record.
When modifying the handler in my testcase to:


var handler = function(){
var contact = Ext.getStore("Contacts").getAt(0);

Ext.DataView.prototype.prepareData(contact.data, 0,contact);

template.overwrite(panel.body, contact.data);
}

I get the same error as in my huge app.
Everything works well while only one hasMany association, but throws:
values is undefined
[Break On This Error] eval(body);
ext-all-debug.js (line 1248)
When multiple hasMany associations is set.

Please thell me if there is a way to access multiple submodels in xtemplate or if this is a bug that will be addressed..

oe.elvik
22 Mar 2011, 3:42 AM
Looks like a bug in Ext.AbstractDataView.prepareData.
I tried to run this in sted of xtemplate.overwrite(..):


var dv = new Ext.DataView({store: 'Contacts',tpl: '<tpl for=".">{firstname}<tpl for="electronicAddress">{address}</tpl></tpl>', itemSelector: '.item-list', renderTo: 'button'});

It works with only one assosiation, but returns the same error as allways when multiple associations is used.

oe.elvik
22 Mar 2011, 4:30 AM
And finally i found the bug my selfe!

Heres the solution:

Ext.override(Ext.AbstractDataView, {
prepareAssociatedData: function(record, _ids) {
//we keep track of all of the internalIds of the models that we have loaded so far in here


var associations = record.associations.items,
associationCount = associations.length,
associationData = {},
associatedStore, associatedName, associatedRecords, associatedRecord,
associatedRecordCount, association, internalId, i, j;

for (i = 0; i < associationCount; i++) {
var ids = _ids || [];
association = associations[i];

//this is the hasMany store filled with the associated data
associatedStore = record[association.storeName];

//we will use this to contain each associated record's data
associationData[association.name] = [];

//if it's loaded, put it into the association data
if (associatedStore && associatedStore.data.length > 0) {
associatedRecords = associatedStore.data.items;
associatedRecordCount = associatedRecords.length;

//now we're finally iterating over the records in the association. We do this recursively
for (j = 0; j < associatedRecordCount; j++) {
associatedRecord = associatedRecords[j];
internalId = associatedRecord.internalId;

//when we load the associations for a specific model instance we add it to the set of loaded ids so that
//we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
if (Ext.Array.indexOf(ids, internalId) == -1) {
ids.push(internalId);

associationData[association.name][j] = associatedRecord.data;
Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids));
}
}
}
}

return associationData;
}
});

evant
22 Mar 2011, 4:46 PM
This will be changed in the next release so you don't have to call it on the View:



var handler = function(){
var contact = Ext.getStore("Contacts").getAt(0),
data = Ext.apply(contact.getAssociatedData(), contact.data);

template.overwrite(panel.body, data);
}


The bug with the aggregation of the data has also been resolved.

manuelhitz
13 Apr 2011, 4:47 AM
what annoys me is that the code in DataView.prepareAssociatedData seems to assume that Record ids are unique system-wide (i.e. across instances of different Models). This shouldn't be the case. For example, my app should be able to have a Post with id=1 and a Comment also with id=1, like that:


[{id:1,title:'hello',comments:[{id:1,text:'boring'},{id:2,text:'excellent'}]}]


I fixed it by using a map of ids instead of a list:



Ext.DataView.prototype.prepareAssociatedData = function(record, ids) {
//we keep track of all of the internalIds of the models that we have loaded so far in here
ids = ids || {}; // <======== CHANGE HERE

var associations = record.associations.items,
associationCount = associations.length,
associationData = {},
associatedStore, associatedName, associatedRecords, associatedRecord,
associatedRecordCount, association, internalId, i, j;

for (i = 0; i < associationCount; i++) {
association = associations[i];
associatedName = association.associatedName; // <======== CHANGE HERE

ids[associatedName] = ids[associatedName] || []; // <======== CHANGE HERE

//this is the hasMany store filled with the associated data
associatedStore = record[association.storeName];

//we will use this to contain each associated record's data
associationData[association.name] = [];

//if it's loaded, put it into the association data
if (associatedStore && associatedStore.data.length > 0) {
associatedRecords = associatedStore.data.items;
associatedRecordCount = associatedRecords.length;

//now we're finally iterating over the records in the association. We do this recursively
for (j = 0; j < associatedRecordCount; j++) {
associatedRecord = associatedRecords[j];
internalId = associatedRecord.internalId;

//when we load the associations for a specific model instance we add it to the set of loaded ids so that
//we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
if (ids[associatedName].indexOf(internalId) == -1) { // <======== CHANGE HERE
ids[associatedName].push(internalId); // <======== CHANGE HERE

associationData[association.name][j] = associatedRecord.data;
Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids));
}
}
}
}

return associationData;
};

evant
13 Apr 2011, 4:49 AM
You're using an old version, prepareAssociatedData has been changed a few releases ago.

manuelhitz
13 Apr 2011, 5:04 AM
oops... wrong forum! I'm talking about Sencha Touch 1.1.0

BTW I tried using ExtJS 4's prepareAssociatedData method in Sencha Touch 1.1.0, without success.

geniodella
19 Mar 2012, 10:33 AM
Is this bug resolved?
Is it possible now to use nested data in the dataviews??????????