PDA

View Full Version : OK, I admit it. I'm baffled.



Animal
20 Feb 2007, 3:06 AM
I want to upgrade to the new grid.

I've been reading source code, but going round in circles.

Using 0.33, I implemented my own custom DataModel which extended LoadableDataModel and used DWR to pull in this.data[][]. Dead easy!

And I had a custom ColumnModel (extends DefaultColumnModel) which pulled in a standard DefaultColumnModel config object down through DWR (The Java class GridColumn corresponds exactly to a column config object, so returning an array of these, I could just put the return value into this.config) Also dead easy.

Now, there's ext.data.Record, ext.data.DataField, ext.data.Store, ext.data.DataProxy.........

How does it all hang together?

I'm going to need to create my own extension of ext.data.Store (which is the new version of DataModel yes?) which provides the data by calling DWR. What is "this.data" a MixedCollection of? What does it provide? Using 0.33, it just provided cells from this.data[][] indexed by row and column.

The ext.data.Record looks just like the column model. Why is it there? Can I generate this from my column model like this:



// Create the data record based on column headers.
var recordEls = [];
for (var col in config) {
recordEls.push({name:col.header, mapping:col.header});
}
this.dataRecord = Ext.data.Record.create(recordEls);
:?:

jack.slocum
20 Feb 2007, 4:10 AM
Animal,

You don't need to touch DataStore. It was specifically designed to be independent of the source of the data. What you need is a DataProxy (proxy's data from sources) and a Reader (read DWR objects into data.Records). Both those interfaces are very simple. The structure was defined so that you don't need to extend Store to provide data from sources other than the default 3.

The flow is like this:

Store calls proxy passing info about what to load. Proxy gets the data from wherever and then uses a reader to read it into record objects. It then calls back to the store with the new data who then notifies observers of the new data.

It may seem overly complex, but there is a reason - trust me. :)

--

The record class is very different than a ColumnModel. It defines the data structure/fields, while the ColumnModel defines the column's in a grid. While much of the time they may be similar, their properties are entirely different. Records can and will be used by more than just the grid.

Animal
20 Feb 2007, 4:17 AM
OK then, looks like I need a DWRProxy, and a DWRReader. I'll look at existing ones to see what to do. Thanks!

OK, I get it about the records - you can have loads of fields in the record, but your ColumnModel might only use a few of them.

Animal
20 Feb 2007, 4:41 AM
What methods does my DWRProxy have to implement? I've copied HttpProxy, but obviously there can be no getConnection()

jack.slocum
20 Feb 2007, 5:33 AM
Currently the only one called is load(), but the other update ones will eventually be there to automatically marshall saves.

The whole Connection stuff is specific to HttpProxy so you can ignore it.

Animal
20 Feb 2007, 5:53 AM
OK, so "name" in Ext.data.Record.create() maps a record item onto the ColumnModel's "dataIndex" property?

And "mapping" in Ext.data.Record.create() is the index into the field collection? Because through DWR, I'm just returning a row as an Object[]

So I guess "mapping" will be 0, 1, 2....?

Animal
20 Feb 2007, 6:09 AM
So it's the "params" parameter passed to load() that specified page number and size etc?
[/code]

jack.slocum
20 Feb 2007, 6:51 AM
I guess. I'm not very familiar with DWR responses.

The column model (or later, form fields) can bind to fields in a record via dataIndex=>named field.

Incoming data can be mapped into fields via mapping => field. If you are using a default reader, you could skip the mapping stage, but that's up to you.

The params passed to load will contain start, limit and optionally any sorting information.

Animal
20 Feb 2007, 8:00 AM
OK, so what I send back from the DWR call getRange(int start, int limit) is this object:



public class ListRange
{
private long totalRows;
private Object[] data; // Each element, is in fact an Object[]
public ListRange() {
this.totalRows = 0;
this.data = new Object[]{};
}
public ListRange(long totalRows, Object[] data) {
this.totalRows = totalRows;
this.data = data;
}
public void setData(Object[] data) {
this.data = data;
}
public Object[] getData() {
return data;
}
public void setTotalRows(int totalRows) {
this.totalRows = totalRows;
}
public long getTotalRows() {
return totalRows;
}
}


DWR marshalls that into a javascript object, so the DWRProxy looks like this:



aspicio.DWRProxy = function(listerId) {
this.listerId = listerId;
aspicio.DWRProxy.superclass.constructor.call(this);
};

Ext.extend(aspicio.DWRProxy, Ext.data.DataProxy, {
load : function(params, reader, callback, scope, arg){
if(this.fireEvent("beforeload", this, params) !== false){
ListHelper.getRange(this.listerId, params.start, params.limit,
this.loadResponse.createDelegate(this, [reader, callback, scope, arg], 1));
}else{
callback.call(scope||this, null, arg, false);
}
},

loadResponse : function(listRange, reader, callback, scope, arg){
var result;
try {
result = reader.read(listRange);
}catch(e) {
this.fireEvent("loadexception", this, null, response, e);
callback.call(scope, null, arg, false);
return;
}
callback.call(scope, result, arg, true);
},

update : function(dataSet){

},

updateResponse : function(dataSet){

}
});


The reader looks like this:



aspicio.ListRangeReader = function(meta, recordType){
Ext.data.ListRangeReader.superclass.constructor.call(this, meta, recordType);
};
Ext.extend(aspicio.ListRangeReader, Ext.data.DataReader, {
readRecords : function(o){
var sid = this.meta ? this.meta.id : null;
var recordType = this.recordType, fields = recordType.prototype.fields;
var records = [];
var root = o.data;
for(var i = 0; i < root.length; i++){
var n = root[i];
var values = {};
var id = (sid && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
for(var j = 0, jlen = fields.length; j < jlen; j++){
var f = fields.items[j];
var k = f.mapping || j;
var v = n[k] !== undefined ? n[k] : f.defaultValue;
v = f.convert(v);
values[f.name] = v;
}
var record = new recordType(values, id);
records[records.length] = record;
}
return {
records : records,
totalRecords : o.totalRows
};
}
});


which is created like this:



// Create reader that can read a com.aspicio.util.ListRange
this.reader = new aspicio.ListRangeReader({ id: 0 }, this.columnModel.dataRecord);


Because the ColumnModel knows about the data, so it creates the Ext.data.Record:



// Create the data record which just uses numeric indices into an Object Array.
// Do not include the uuid field which is always present as the id. All other
// columns are valid data fields.
var recordEls = [];
var colIdx = 0;
for (var col in config) {
if (col.name != "uuid") { // Skip the uuid
recordEls.push({name:col.dataIndex, mapping:colIdx});
colIdx++;
}
}
this.dataRecord = Ext.data.Record.create(recordEls);


What can possibly go wrong? :lol: :lol: :lol: :lol: :lol: :lol:

jack.slocum
20 Feb 2007, 8:09 AM
Nothing obvious jumping out at me. Any errors?

FYI, I can't tell you how much you code has improved in the past few months. That's some clean looking code! Even your brackets are in the "right" spot. ;)

Animal
21 Feb 2007, 4:41 AM
I've been learning from the master! http://www.timetriallingforum.co.uk/style_emoticons/default/NotWorthy2.gif :lol:

jack.slocum
21 Feb 2007, 9:18 AM
So does that eman you have a working DWRProxy!?

amc
27 Feb 2007, 11:11 AM
Hi,
It would be really helpful to see a working DWR example if you have one.

Thanks.

Animal
27 Feb 2007, 12:26 PM
It all works fantastically. I'll post up a bit of code in the morning when I get to work.

The thing is, it won't be reusable because the client and server layers are tightly integrated. I have a specific ListProvider class in the server which exports over DWR the methods



public ListColumn[] getColumnModel();

public ListRange getBlock(int start, int limit);

public ListRange orderBy(String colName, String order, int start, int limit);


The ListColumn object is an exact analogue of a ColumnModel config element, so it just goes into the constructor of my ColumnModel.

The ColumnModel uses the data in that array to create an Ext.data.Record.

It hangs together very well.

The returned classes are used directly by my DWRProxy

amc
27 Feb 2007, 1:33 PM
Thanks, I look forward to seeing it. In my case, the DWR calls return json data already. So I have a javascript function that returns a json list. I want to plug that into the table. I'm not confident about how the datastore and reader hang together. My reader is effectively a dwr function call I think.

Sorry for my lack of insight - I'm hoping to understand that more than anything.

Anton

Animal
27 Feb 2007, 11:54 PM
Using DWR to return JSON seems a bit convoluted. DWR passes marshalled Object parameters out to the browser as JSON anyway, so you might as well use native DWR, and return yourself ready made, usable javascript objects.

DWR.XML fragment:



<allow>
<create creator="new" javascript="ListHelper">
<param name="class" value="com.aspicio.util.ListHelper"/>
</create>


<convert converter="bean" match="com.aspicio.util.ListRange"/>


<convert converter="bean" match="com.aspicio.util.ListColumn">

<param name="exclude" value="type"/>
</convert>
</allow>


Here are the main classes:

ListColumn is used on the server to store all columns wanted in a List-producing query, and generate Hibernate HQL, so it has a lot of stuff in it. But when returned through DWR, it gives you a perfect Ext.ColumnModel column config entry:



package com.aspicio.util;

import javax.persistence.Entity;

import com.aspicio.beanInfo.Property;

public class ListColumn
{
private String header;
private int width;
private boolean hidden;
private int visibleIndex;
private Property type;
private String HQLExpression = null;

public ListColumn()
{
}

public ListColumn(String header, Property type, int width, boolean hidden, int visibleIndex)
{
this(header, type, width, hidden, visibleIndex, null);
}

public ListColumn(String header, Property type, int width, boolean hidden, int visibleIndex,
String HQLExpression)
{
this.header = header;
this.type = type;
this.width = width;
this.hidden = hidden;
this.visibleIndex = visibleIndex;
this.HQLExpression = HQLExpression;
}

public void setHeader(String header)
{
this.header = header;
}
public String getHeader()
{
return header;
}
public String getDataIndex()
{
return getFieldName();
}
public void setWidth(int width)
{
this.width = width;
}
public int getWidth()
{
return width;
}
public void setHidden(boolean hidden)
{
this.hidden = hidden;
}
public boolean isHidden()
{
return hidden;
}
public void setVisibleIndex(int visibleIndex)
{
this.visibleIndex = visibleIndex;
}
public int getVisibleIndex()
{
return visibleIndex;
}
public void setType(Property type)
{
this.type = type;
}
public Property getType()
{
return type;
}
public String getFieldName()
{
return type.getFieldName();
}
public String getEntityName()
{
Class<?> c = type.getType();
return c.isAnnotationPresent(Entity.class) ? c.getSimpleName() : "";
}
public int getInputType()
{
return type == null ? Property.STRING : type.getInputType();
}
public String[] getSelectValues()
{
return type == null ? new String[] {} : type.getSelectValues();
}
public void setHQLExpression(String HQLExpression)
{
this.HQLExpression = HQLExpression;
}
public String getHQLExpression()
{
return (HQLExpression != null) ? HQLExpression : (HQLExpression = computeHQLExpression());
}
private String computeHQLExpression()
{
if ((type.getModifiers() & Property.FOREIGN_KEY) != 0)
{
return "_this." + getFieldName() + "." + type.getForeignDescriptionProperty();
}
else if (type.isEnumeration())
{
return "cast(" + getFieldName() + " as integer)";
}
else
{
return "_this." + getFieldName();
}
}
}

ListRange encapsulates a returned batch of rows. It is mapped into an Ext.data.Record by a ListRangeReader



package com.aspicio.util;

/**
*
* @author nigelw This clas encapsulates a range of data rows returned from a
* ListProvider.
*
*/
public class ListRange {
private long totalRows;
private Object[] data;
public ListRange() {
this.totalRows = 0;
this.data = new Object[] {};
}
public ListRange(long totalRows, Object[] data) {
this.totalRows = totalRows;
this.data = data;
}
public void setData(Object[] data) {
this.data = data;
}
public Object[] getData() {
return data;
}
public void setTotalRows(int totalRows) {
this.totalRows = totalRows;
}
public long getTotalRows() {
return totalRows;
}
}




I have a subclass of ColumnModel which is passed the array of ListColumn Objects as its config, and processes them in the code below.

The class which generates the query ALWAYS adds the database row id as column zero in the query, so column zero is removed from the column model



// Create the data Record which uses the HQLExpression as the dataIndex to map
// between ColumnModel and data Record
// Do not include the uuid field which is always present as the id.All other
// columns are valid data fields.
var recordEls = [];
for (var i = 0; i < config.length; i++) {
var col = config[i];
if (col.visibleIndex == -1) // Java doesn't have undefined...
delete col.visibleIndex;
col.sortable = true; // We use remote sorting
col.dataIndex = col.HQLExpression; // map between Column and Record field

// Create an entry for the Record creation call.
recordEls.push({name:col.HQLExpression});
}
this.recordType = Ext.data.Record.create(recordEls);

// Remove column 0. It's the database row id - we don't want to see it.
config.splice(0, 1);

// Call Ext.grid.ColumnModel constructor to do default initialization
aspicio.AspicioColumnModel.superclass.constructor.call(this, config);


So to create the Grid, I use DWR to get back an array of ListColumns and create my ColumnModel subclass by passing that into my constructor, then:



// Create reader that can read a com.aspicio.util.ListRange
// The id is column zero in the data record (which the ColumnModel created)
this.reader = new aspicio.ListRangeReader({ id: 0 }, this.columnModel.recordType);

// create the Data Store
this.dataStore = new Ext.data.Store({
proxy: new aspicio.DWRProxy(this.id),
reader: this.reader,
recordType: this.columnModel.recordType,
remoteSort: true
});

this.grid = new Ext.grid.Grid(this.container, {
ds: this.dataStore,
cm: this.columnModel,
sm: this.selModel = new Ext.grid.RowSelectionModel({singleSelect: this.singleSelect}),
autoSizeColumns: this.autoSizeColumns,
trackMouseOver: true
});


So, I need a DWRProxy:



aspicio.DWRProxy = function(listerId) {
this.listerId = listerId;
aspicio.DWRProxy.superclass.constructor.call(this);
};

Ext.extend(aspicio.DWRProxy, Ext.data.DataProxy, {
load : function(params, reader, callback, scope, arg){
if(this.fireEvent("beforeload", this, params) !== false){
if (params.sort && params.dir) {
ListHelper.orderBy(this.listerId, params.sort, params.dir, params.start, params.limit,
this.loadResponse.createDelegate(this, [reader, callback, scope, arg], 1));
} else {
ListHelper.getBlock(this.listerId, params.start, params.limit,
this.loadResponse.createDelegate(this, [reader, callback, scope, arg], 1));
}
}else{
callback.call(scope||this, null, arg, false);
}
},

loadResponse : function(listRange, reader, callback, scope, arg){
var result;
try {
result = reader.read(listRange);
}catch(e) {
this.fireEvent("loadexception", this, null, response, e);
callback.call(scope, null, arg, false);
return;
}
callback.call(scope, result, arg, true);
},

update : function(dataSet){

},

updateResponse : function(dataSet){

}
});


and a ListRangeReader:



aspicio.ListRangeReader = function(meta, recordType){
aspicio.ListRangeReader.superclass.constructor.call(this, meta, recordType);
};
Ext.extend(aspicio.ListRangeReader, Ext.data.DataReader, {
read : function(o){
var sid = this.meta ? this.meta.id : undefined;
var recordType = this.recordType, fields = recordType.prototype.fields;
var records = [];
var root = o.data;
for(var i = 0; i < root.length; i++){
var n = root[i];
var values = {};
var id = ((sid != undefined) && (n[sid] !== "") ? n[sid] : null);
for(var j = 0, jlen = fields.length; j < jlen; j++){
var f = fields.items[j];
var k = f.mapping || j;
var v = n[k] !== undefined ? n[k] : f.defaultValue;
v = f.convert(v);
values[f.name] = v;
}
var record = new recordType(values, id);
records[records.length] = record;
}
return {
records : records,
totalRecords : o.totalRows
};
}
});


OK, that should be enough for you to get hacking! I managed to "unbaffle" myself, and I'm not the brightest spark in the pack, so off you go! :D

amc
28 Feb 2007, 1:06 PM
Thanks so much ... I'll dig into it some more in the next couple of days, and let you know ...