PDA

View Full Version : I need smth like Ext.data.Record with optional fields!



natts
8 Jul 2009, 8:17 AM
I'll explain that insanity scope:

I'm writing rather big app, the server side is based on Ruby On Rails, and my domain object model has these:

Custoemer It's really like a company, it has many employees. Customer has principal, accountant, primary_contact and other contacts. They all are CustomerEmployees.
CustomerEmployee it a person, a customer's representative.


On the client side i use a set of classes, extended from Ext.data.*, including store class, reader and writer classes (yeap, it's restful), proxy and record.
In the customers grid, primary customer contact's name and phone should be renderd, but customer is valid even without primary person or any contact persons at all.

Here's my Record definition:


Ext.ns("Delivery.data");
dd = Delivery.data;

// record
dd.CustomerRecord = Ext.data.Record.create([

/*
* After servers-side refactoring, we will need to work with that data in a strange and different way %)
* A lot of stuff is stored in a separate stores, something is stored here, but in nested models.
* Lets go..
*/

// customer id
{ name: "id", type: "int"},

// customer agreement number. This one is required to create a customer.
{ name: "agreement_number" },

// customer common name
{ name: "name" },

// legal customer name
{ name: "legal_name" },

// legal crap
{ name: "tax_identifier" },
{ name: "kpp" },
{ name: "primary_state_registration_number" },

// addresses. Yes, they still do look ugly.
{ name: "legal_address" },
{ name: "real_address" },
{ name: "post_address" },

// phone and fax for the customer. Still stored here.
{ name: "phone_number" },
{ name: "fax_number" },

// we store principal AND accountant IDs to access them via customer_employees datastore.
//
{ name: "principal_id", mapping: "principal.id" },
{ name: "accountant_id", mapping: "accountant.id" },

// but we cash them in that store for better speed
{ name: "principal_name", mapping: "principal.full_name" },
{ name: "accountant_name", mapping: "accountant.full_name" },

// bank information.
{ name: "bank_name" },
{ name: "bank_account" },
{ name: "correspondent_bank_account" },
{ name: "bank_identifier_code" },

// ADD CONTACTS
{ name: 'primary_contact_name', mapping: 'primary_contact.full_name' },
{ name: 'primary_contact_phone_number', mapping: 'primary_contact.phone_number' },
{ name: 'primary_contact_mobile_phone_number', mapping: 'primary_contact.mobile_phone_number' },

// email and url are stored for the customer as strings
{ name: "email" },
{ name: "url" },

// our comment
{ name: "comment" },

// is it in archive?
{ name: "archived" },

// what customer type is it?
{ name: "customer_type" }

// ADD TYPE ID
]);


As you can see, i use mapping primary_contact.full name and other. My server-side app renders principal, accountant, primary_contact and contacts as a nested objects, or an array of nested objects if they exist.

In the situation, the customer has no principal yet (no accountant or no primary contact), primary_person field will be null, which leads us to Store exception.

I need another behaviour: i want the record to return nulls if it's impossible to read mapped property of json response.

How can i accomplish that? should i render :primary_contact => nil from my server? that will not create primary_contact after JSON.decode anyway.

Or, maybe, i should extend Reader class not to throw exceptions?

Any suggestions?

natts
8 Jul 2009, 8:25 AM
I thought of using more dataStores: i could create a separate store for employees (contacts ) and get the data i need by primary_contact_id rendered from server. But that means, i will implement all the domain object model on the client side, which is completely insane, and i will need more server-side controllers to work with employees.

natts
8 Jul 2009, 8:45 AM
I am an *****.
If anyone else experiences that issue:

http://extjs.com/forum/showthread.php?p=185253#post185253

From the FAQ. Sorry for wasting your time.

natts
8 Jul 2009, 9:42 AM
I still have a question.

I have a loadException listener for my _store_ object, which fires and says: TypeError: obj.principal is undefined.
But in that situation there's nothing i can do to handle that.

I don't really understand the code posted on the thread i referenced earlier. What method should i override? is it JsonReader.readRecords?

Animal
8 Jul 2009, 9:45 AM
Use a convert function to pull the data from the row object. Then you can test if it's there.

natts
8 Jul 2009, 10:54 AM
Animal, thanks for your answer.

Here's what i figured for now: If i could override reader.readRecords, it could handle such a records. Okay, i've done the next thing:


/*
* Included a fix for JsonReader to correctly handle
* Json data with optional nested json objects in it.
*
*/
Ext.override( Ext.data.JsonReader, {
readRecords : function(o){
/**
* After any data loads, the raw JSON data is available for further custom processing. If no data is
* loaded or there is a load exception this property will be undefined.
* @type Object
*/
this.jsonData = o;
if(o.metaData){
delete this.ef;
this.meta = o.metaData;
this.recordType = Ext.data.Record.create(o.metaData.fields);
this.onMetaChange(this.meta, this.recordType, o);
}
var s = this.meta, Record = this.recordType,
f = Record.prototype.fields, fi = f.items, fl = f.length, v;
console.log(this);
var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;
if(s.totalProperty){
v = parseInt(this.getTotal(o), 10);
if(!isNaN(v)){
totalRecords = v;
}
}

if(s.successProperty){
var v = this.getSuccess(o);
if(v === false || v === 'false'){
success = false;
}
}
var params = {i: 0, records: [], j: 0, values: null};
var records = null;
do {
try {
records = this.getRecords(c, root, Record, fl, fi, params);
} catch(e) {
if (e instanceof TypeError)
++params.j;
else throw(e);
}
} while (records == null);
return {
success : success,
records : records,
totalRecords : totalRecords
};
},

getRecords : function(c, root, Record, fl, fi, params) {
for(; params.i < c; params.i++){
var n = root[params.i];
if (!params.values) params.values = {};
var id = this.getId(n);

for(; params.j < fl; params.j++){
f = fi[params.j];
var v = this.ef[params.j](n);
params.values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, n);
}

var record = new Record(params.values, id);
params.j = 0;
params.values = null;
record.json = n;
params.records[params.i] = record;
}

return params.records;
}
});


That's included after ext-all-debug and all other extensions are loaded, but before my app's frontend is. Anyway, i get "this.getRoot() is not a function". That means, the method is executed in the wrong scope. Console.log(this) says "undefined". Reading ext documentation about Ext.override doesn't give me any clue on how to provide scope to the method correctly.

I know, it's a trivial issue and i'm familiar with javascript oo model and functions and scopes, but i just didn't use Ext.override before, or i'm just too tired and missing something.
How can i fix that?

Animal
8 Jul 2009, 11:14 AM
Why overcomplicate? Just use a convert function.

natts
8 Jul 2009, 3:36 PM
Animal, thank you once more ;) I solved the issue, just like you said, i used a set of convert functions. It's more flexible since i can set my own default values per-field.

But, really, where did i messed up that Ext.override call?

Animal
8 Jul 2009, 11:33 PM
I don't know. You could easily have debugged it by simply breaking at the line with the error and examining the state of all variables.

mjlecomte
9 Jul 2009, 4:51 AM
There's an example in the docs by the way:
http://extjs.com/deploy/ext-3.0.0/docs/?class=Ext.data.Field&member=convert

That said, there maybe should be some outside reference to funnel people into that config property. Maybe at the top of:
http://extjs.com/deploy/ext-3.0.0/docs/?class=Ext.data.Record

Or within the create method:
http://extjs.com/deploy/ext-3.0.0/docs/?class=Ext.data.Record&member=create

@OP: would you have noticed a link to convert in either of the above locations and followed it?