PDA

View Full Version : XMLDataModel schema fields XPath support?



sjivan
4 Dec 2006, 10:45 PM
Presently it seems like the XMLDataModel navigates the XML tree to find the first element name that matches the schema field name. This is less flexible and error prone compared to using XPath.

For example if the server sends the following data


<companies>
<company>
<name>foo</name>
<location>
<name>bar</name>
<address>add1</address>
</location>
</company>
</companies>


Presently it does not seem possible to display a grid that has company name and location name in it, right? I could change the tag names but I normally convert my data beans to XML using XStream which goes by the bean property name (It does support aliases, but that too is not very clean as we have to verify that no element name collisions exist).

I think IE does not support XPath natively while Firefox does which might pose a problem. (Selenium uses an XPath js lib for IE).

Just thinking out loud.. I'll probably use aliases for now when serializing to XML.

Sanjiv

jack.slocum
5 Dec 2006, 5:48 AM
I could probably create an extension that leverages cssQuery for this. It supports working with XML documents. That would actually be really nice.

Can you please bump this thread on Friday?

sjivan
6 Dec 2006, 12:48 PM
Cool. I just checked out cssQuery's ability to query an external xml document, pretty neat.

I'll bump this thread on friday but here's a thought that I did not want to forget.

It would be nice if the select editor could also have the option of being configured to an XPath / CSS selector. This would allow a couple of things :

1. Have the drop down options provided in the data model instead of in the DOM content and more importantly
2. The ability to have a per row drop down select editor and not just a static dropdown for all rows.

Sanjiv

sjivan
9 Dec 2006, 11:54 AM
Jack,
I tried using the cssQuery selector on external XML and its not quite working consistently on IE and Firefox.

For example for the XML node below, the selector "name" returned the company name node. However to get the location name, I was only able to get it working on IE using "location/name" and this selector does not work on Firefox.



<company>
<id>4</id>
<name>foo</name>
<location>
<name>bar</name>
<address>add1</address>
</location>
</company>


I investigated the reason for the XPath js library for IE (written by google). Its because IE doesnt support XPath on HTML and this library allows that. However IE (and Firefox) does support XPath on XML documents natively which is what we need so maybe allowing XPath is a better option that the cssQuery XML selectors.

I'd like to bring up a related thread "enhance DefaultColumnModel to support column aliases"

http://www.yui-ext.com/forum/viewtopic.php?t=1254

I did some thinking about the XMLDataModel class, and I think that it presently blurs the distinction between field data and metadata.

The schema for the XMLDataModel presently accepts a list of fields which helps locate the data in the XML while not truly representing the metadata of the field (ie the schema).

I propose that the XMLDataModel should be enhanced to allow the user to specify both, the locator for the data and the field metadata. Here's an example of how the schema for XMLDataModel can be enhanced without breaking old code.



var schema = {
tagName: ['company, '/companies/company],
id: ['id', 'id],
fields: [['name', 'name'], ['location.name', 'location/name']]
};


The first element of the array is the field property name and the second element is the XPath locator for the data.

The XMLDataModel code can check to see if the fields are of type String and if so, preserve the old behavior else if array, use the locator to locate the data. A user can then have the ability to translate the column index to field metaname by overriding the createParams function.



dataModel.createParams = function(){
this.baseParams['sortColumn'] = this.schema.fields[this.sortColumn][0];
this.baseParams['_action'] = 'view';
return YAHOO.ext.grid.LoadableDataModel.prototype.createParams.apply(this, arguments);
};



Sanjiv

brian.moeskau
9 Dec 2006, 1:00 PM
Or maybe something like this ?


var schema = {
tagName: { name: 'company', xpath: '/companies/company]\' },
id: { name: 'id', xpath: 'id' },
fields: [
{ name: 'name', xpath: 'name' },
{ name: 'location.name', xpath: 'location/name'} ]
};

jack.slocum
9 Dec 2006, 1:14 PM
The problem is the way you are defining the query. Here's a class for you (it is in the bottom of XMLDataModel.js in svn.


YAHOO.ext.grid.XMLQueryDataModel = function(){
YAHOO.ext.grid.XMLQueryDataModel.superclass.constructor.apply(this, arguments);
};
YAHOO.extendX(YAHOO.ext.grid.XMLQueryDataModel, YAHOO.ext.grid.XMLDataModel, {
getNamedValue: function(node, name, defaultValue){
if(!node || !name){
return defaultValue;
}
var nodeValue = defaultValue;
var childNode = cssQuery(name, node);
if(childNode && childNode[0]) {
nodeValue = childNode[0].firstChild.nodeValue;
}
return nodeValue;
}
});


You will want to look at the docs for cssQuery (or the css3 spec) on how to select child nodes. Instead of location/name, what you are looking for is location > name.

sjivan
9 Dec 2006, 5:08 PM
The problem is the way you are defining the query. Here's a class for you (it is in the bottom of XMLDataModel.js in svn.


YAHOO.ext.grid.XMLQueryDataModel = function(){
YAHOO.ext.grid.XMLQueryDataModel.superclass.constructor.apply(this, arguments);
};
YAHOO.extendX(YAHOO.ext.grid.XMLQueryDataModel, YAHOO.ext.grid.XMLDataModel, {
getNamedValue: function(node, name, defaultValue){
if(!node || !name){
return defaultValue;
}
var nodeValue = defaultValue;
var childNode = cssQuery(name, node);
if(childNode && childNode[0]) {
nodeValue = childNode[0].firstChild.nodeValue;
}
return nodeValue;
}
});


You will want to look at the docs for cssQuery (or the css3 spec) on how to select child nodes. Instead of location/name, what you are looking for is location > name.

I'll try it out but location > name was the first thing I tried. Basically I set a break point in XMLDataSource and ran various CSS selector queries using cssQuery(selector, node) and the CSS selector syntax was the first thing I tried but it retuned nothing in IE. After trying different combinations, I gave location/name a shot and it returned the correct node in IE.

Will try using your code and see if it helps, although thats exactly the call I made too..

Sanjiv

sjivan
9 Dec 2006, 5:35 PM
I played around a little more. The css query selector "location > name" works in Firefox and not IE. I know that child selectors are not supported by IE but I thought the point of cssQuery was that it would work on IE too. The descendant selector "location name" works in IE but the descendant selector is error prone and could return the wrong "name" sub element.

btw do you have any thoughts on the separation of the field metadata and the field locator in the datamodel schema?

Thanks,
Sanjiv


edit : "location name" selector in IE returns a result but childNode[0].firstChild is null.

sjivan
10 Dec 2006, 3:02 PM
I wrote a class that supports field names and XPath locators. I tested it out with with grid-edit sample using nested xml elements. Modifications update the underlying XML document. As mentioned in the JS doc, you'll need to include jsQuery if you're using this class.

Some portions of loadData could be shared with XMLDataModel and requires simple refactoring in XMLDataModel like creating a protected method in XMLDataModel to get total record count etc.




/**
* @class YAHOO.ext.grid.XMLQueryDataModel
* This is an implementation of a DataModel used by the Grid. This implementation is an extension of XMLDataModel and provides
* more control by including field metadata and xpath locators in its schema.
*
Example schema
* <pre><code>
* var schema = {
* dataXPath: '/companies/company' ,
* totalCountXPath: '/companies/count',
* idXPath: 'id',
* fields: [
* { name: 'name', xpath: 'name' },
* { name: 'location.name', xpath: 'location/name'} ]
* };
* The dataXPath and and totalCountXPath locators are relative to the root document while the idXPath and the field xpath expressions are
* relative to the root data node (which represents a single row in the table).
* In addition to the paging and sorting parameters passed by LoadableDataModel, a parameter 'sortColumnName' is passed with the value
* of the field name as specified in the schema (eg : location.name ). Like XMLDataModel, this class also takes care of updating the underlying XML
* document when rows are created, modified or deleted.
* </code></pre>
* Note : You will need to include the jQuery (http://jquery.com/) javascript file for this class to work.
*
* @extends YAHOO.ext.grid.XMLDataModel
* @constructor
* @param {Object} schema The schema to use
* @param {XMLDocument} xml An XML document to load immediately
*/
YAHOO.ext.grid.XMLQueryDataModel = function(schema, xml){

YAHOO.ext.grid.XMLQueryDataModel.superclass.constructor.apply(this, arguments);
this.paramMap['sortColumnName'] = 'sortColumnName';

};

YAHOO.extendX(YAHOO.ext.grid.XMLQueryDataModel, YAHOO.ext.grid.XMLDataModel, {

/**
* Overrides loadData in LoadableDataModel to process XML
* @param {XMLDocument} doc The document to load
* @param {Function} callback (optional) callback to call when loading is complete
* @param {Boolean} keepExisting (optional) true to keep existing data
* @param {Number} insertIndex (optional) if present, loaded data is inserted at the specified index instead of overwriting existing data
*/
loadData: function(doc, callback, keepExisting, insertIndex){
this.xml = doc;
var idXPath = this.schema.idXPath;
var fields = this.schema.fields;
if(this.schema.totalCountXPath){
this.totalCount = null;
var countNode = jQuery(this.schema.totalCountXPath, doc);
if(countNode) {
var v = parseInt(countNode.text(), 10);
if(!isNaN(v)){
this.totalCount = v;
}
}
}
var rowData = [];
var nodes = jQuery(this.schema.dataXPath, doc);
for(var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var colData = [];
colData.node = node;

colData.id = this.getNamedValue(node, idXPath, String(++this.idSeed));

for(var j = 0; j < fields.length; j++) {
var val = this.getNamedValue(node, fields[j].xpath, "");
if(this.preprocessors[j]){
val = this.preprocessors[j](val);
}
colData.push(val);
}
rowData.push(colData);
}

if(!keepExisting){
YAHOO.ext.grid.XMLQueryDataModel.superclass.removeAll.call(this);
}
if(typeof insertIndex != 'number'){
insertIndex = this.getRowCount();
}
YAHOO.ext.grid.XMLDataModel.superclass.insertRows.call(this, insertIndex, rowData);
if(typeof callback == 'function'){
callback(this, true);
}
this.fireLoadEvent();
},


/**
* @private
* Convenience function to get the value from the underlying xml node
*/
getNamedValue: function(node, xpath, defaultValue){
if(!node || !xpath){
return defaultValue;
}
var nodeValue = defaultValue;
var result = jQuery(xpath, node);
if(result && result.length != 0) {
nodeValue = result.text();
}
return nodeValue;
},

/**
* @private
* Convenience function set a value in the underlying xml node.
*/
setNamedValue: function(node, field, value){
if(!node || !field){
return;
}
var xpath = field.xpath;

var valNode = jQuery(xpath, node);
if(valNode.length != 0 && valNode[0].firstChild) {
valNode[0].firstChild.nodeValue = value;
}
},

createParams: function(pageNum, sortColumnIndex, sortDir){
var params = YAHOO.ext.grid.LoadableDataModel.prototype.createParams.apply(this, arguments);
params[this.paramMap['sortColumnName']] = (typeof sortColumnIndex == 'undefined' ? '' : this.schema.fields[sortColumnIndex].name);
return params;
}
});

sjivan
15 Dec 2006, 11:36 AM
Jack,
Did you have a chance to take a look at the class I wrote? It works well and also allows more control when adding filters to the table for servers side filtering. cssQuery's selectors just didn't work on IE and Firefox with external XML nodes while jQuery's xpath support worked as expected and xpath IMO is a more suitable when working with XML documents like the XMLDataModel. If you do not want dependency on jQuery, then the xpath evaluation can probably done by us handcoding the xpath evaualtions doing if checks on browser type as firefox and IE use different XPath API's. Not sure of the support level of XPath in other browsers.

Would you consider adding this version of the XMLDataModel into yui-ext as it supports proper selectors and column meta information.

With the subclass I wrote, I am now able to create a server side sortable/ pageable and filterable table on any of my backend domain classes. I'm using Hibernate in the backend and have written generic library to sort / page / filter any domain class.

Thanks,
Sanjiv

jack.slocum
15 Dec 2006, 3:40 PM
I can't see embedding a class into yui-ext the requires jQuery. cssQuery is one thing (7kb) but jQuery is another animal.

The functionality is good, but is there another way to get xpath support?

sjivan
16 Dec 2006, 1:29 AM
I can't see embedding a class into yui-ext the requires jQuery. cssQuery is one thing (7kb) but jQuery is another animal.

The functionality is good, but is there another way to get xpath support?

jQuery is only around 15kb but since we only need XPath functionality, it should be fairly easy to write code to eval XPath for IE and Firefox (although both use different API's). Opera 9 also seems to support XPath so an xpath eval function can be written that uses the right API based on browser type. I'll try to write one up and post here. Is there an official or unofficial list of browers you'd like yui-ext to support?

Sanjiv

jack.slocum
16 Dec 2006, 10:00 AM
YUI Grade A browsers is preferred. This could be really cool!