yuki
8 Mar 2009, 6:17 AM
フォーラムをあちこち探したのですが、なかったので作ってみました:)
・03/09[追記] サンプルがIEで動かなかったのを修正しました
Ext.ux.JsonpTreeLoader
コメントつきソースコードはこちらから (http://code.google.com/p/extjssamples-jp/source/browse/trunk/js/Ext.ux.JsonpTreeLoader.js)
デモページはこちら (http://extjssamples-jp.googlecode.com/svn/trunk/tree/extuxjsonptreeloaderdemo.html)
Ext.ux.JsonpTreeLoader = Ext.extend(Ext.tree.TreeLoader, function(){
return {
timeout : 30000,
callbackParam : "callback",
requestData : function(node, callback){
if(this.fireEvent("beforeload", this, node, callback) !== false){
this.scriptRequest(node,callback);
}else{
if(typeof callback == "function"){
callback();
}
}
},
// private
// retrieves data from cross-domain servers by dynamically creating a script tag
// referred to the "Cross-domain Ext.Ajax/Ext.data.Connection" by Animal
// url: http://extjs.com/forum/showthread.php?t=1769
scriptRequest : function(node, cb) {
var transId = ++Ext.data.ScriptTagProxy.TRANS_ID;
var trans = {
id : transId,
cb : "stcCallback"+transId,
scriptId : "stcScript"+transId,
argument: {node: node, callback: cb}
};
var url = this.dataUrl || this.url;
url += (url.indexOf("?") != -1 ? "&" : "?") + this.getParams(node) + String.format("&{0}={1}", this.callbackParam, trans.cb);
var conn = this;
window[trans.cb] = function(o){
conn.handleResponse({responseObject: o },trans);
};
trans.timeoutId = this.handleScriptFailure.defer(this.timeout, this, [trans]);
var script = document.createElement("script");
script.setAttribute("src", url);
script.setAttribute("type", "text/javascript");
script.setAttribute("id", trans.scriptId);
document.getElementsByTagName("head")[0].appendChild(script);
return trans;
},
// private
// called inside requestData as a callback with response data from remote server
handleResponse : function(response,trans){
var a = trans.argument;
this.processResponse(response, a.node, a.callback, trans);
this.fireEvent("load", this, a.node, response);
},
// private
// tries to create tree nodes from retrieved data
// referred to the "Cross-domain Ext.Ajax/Ext.data.Connection" by Animal and Ext.data.JsonReader
// url: http://extjs.com/forum/showthread.php?t=1769
processResponse : function(response, node, callback, trans){
var o = response.responseObject;
this.transId = false;
this.destroyScriptTrans(trans, true);
if(this.totalProperty) {
this.getTotal = this.getJsonAccessor(this.totalProperty);
}
this.getRoot = this.root ? this.getJsonAccessor(this.root) : function(p){return p;};
var root = this.getRoot(o), c = root.length, totalRecords = c;
if(this.totalProperty){
var v = parseInt(this.getTotal(o), 10);
if(!isNaN(v)){
totalRecords = v;
}
}
node.attributes.totalRecords = totalRecords;
try {
var rootName = this.root.split('.').pop();
node.beginUpdate();
node.appendChild(this.parseJson(root,rootName));
node.endUpdate();
if(typeof callback == "function"){
callback(this, node);
}
}catch(e){
this.handleFailure(trans);
}
},
isNodeProperty: function(property){
return this.nodeProperty
? this.nodeProperty.indexOf(property) !== -1
: true;
},
// private
// parses and creates tree node from retrieved JSON data
parseJson : function(o, name){
if(typeof o !== 'object') return;
else if(name && !this.isNodeProperty(name)) return;
else if(o instanceof Array) return this.parseArray(o, name);
var nodes = [];
for(var i in o){
if(o.hasOwnProperty(i) && this.isNodeProperty(i)){
if(typeof o[i] == 'object'){
var treeNode = this.createNode(o[i],i);
var child = null;
child = this.parseJson(o[i],i);
if(child && child.length >= 1){
treeNode.appendChild(child);
}
nodes.push(treeNode);
}
}
}
return nodes;
},
// private
// parses array and creates tree nodes
// Assumption: Direct member of array must not be an array
// Assumption: Array members must be objects, not primitives
parseArray : function(o, name){
if(!(o instanceof Array)) return;
else if(name && !this.isNodeProperty(name)) return;
var nodes = [];
Ext.each(o, function(n){
if(typeof n == 'object'){
var treeNode = this.createNode(n,name);
var child = this.parseJson(n,name);
if(child && child.length >= 1){
treeNode.appendChild(child);
}
nodes.push(treeNode);
}
},this);
return nodes;
},
createNode : function(node,name){
var attr = {
propName: name
};
for(var i in node){
if(node.hasOwnProperty(i) && (typeof node[i] !== 'object' || i == 'nodeLoader')){
attr[i] = node[i];
}
}
this.processAttributes(attr, node);
var n = Ext.ux.JsonpTreeLoader.superclass.createNode.call(this, attr);
// set loader after superclass call (because n.loader is set to "this" inside superclass)
if(attr.nodeLoader){
n.loader = attr.nodeLoader;
}
return n;
},
// private
// used inside createNode method to process the attributes of the node
processAttributes: function(attr, n){
attr.text = attr.text || attr.propName;
if(this.mappingFn && this.mappingFn[attr.propName]){
this.mappingFn[attr.propName](attr, n);
}else{
attr.loaded = typeof attr.loaded !== 'undefined' ? attr.loaded : true;
}
},
// private
// copied from Ext.data.JsonReader
getJsonAccessor: function(){
var re = /[\[\.]/;
return function(expr) {
try {
return(re.test(expr))
? new Function("obj", "return obj." + expr)
: function(obj){
return obj[expr];
};
} catch(e){}
return Ext.emptyFn;
};
}(),
// private
// fires "loadexception" event
// referred to the "Cross-domain Ext.Ajax/Ext.data.Connection" by Animal
// url: http://extjs.com/forum/showthread.php?t=1769
handleScriptFailure: function(trans) {
this.transId = false;
this.destroyScriptTrans(trans, false);
var response = {
argument: trans.argument,
status: 500,
statusText: 'Server failed to respond',
responseText: ''
};
var node = trans.argument.node;
this.fireEvent("loadexception", this, node, response);
},
// private
// remove scrip tag which was used to retrieve data
// referred to the "Cross-domain Ext.Ajax/Ext.data.Connection" by Animal
// url: http://extjs.com/forum/showthread.php?t=1769
destroyScriptTrans : function(trans, isLoaded){
document.getElementsByTagName("head")[0].removeChild(document.getElementById(trans.scriptId));
clearTimeout(trans.timeoutId);
if(isLoaded){
window[trans.cb] = undefined;
try{
delete window[trans.cb];
}catch(e){}
}else{
// if hasn't been loaded, wait for load to remove it to prevent script error
window[trans.cb] = function(){
window[trans.cb] = undefined;
try{
delete window[trans.cb];
}catch(e){}
};
}
}
}; // end return
}()
);
その名の通り、JSONPで提供されているWeb APIからデータを取得してきてExt.data.TreeNode形式にしてくれるクラスです(サンプルではPanoramioのAPIを使っています)。
SDKのサンプルに含まれているExt.ux.XmlTreeLoader (http://extjs.cachefly.net/ext-2.2.1/examples/tree/XmlTreeLoader.js)と、このスレッド (http://extjs.com/forum/showthread.php?t=17691)(英語)、そしてExt.data.ScriptTagProxyとExt.data.JsonReaderからいくつかメソッドを参考&コピペして作ってみました。
もともとのExt.tree.TreeLoaderは、
同一ドメインからのデータ取得のみ
決まった形のJSONデータのみ
と、ちょっと使い勝手がよくなかったので、クロスドメイン対応と、あとExt.data.JsonReaderのようなマッピング機能も追加してみました(このマッピング機能は、上記XmlTreeLoaderや既存のTreeLoaderにも簡単にポーティング可能です)。
バグ報告、突っ込み、大歓迎ですので、お試しください:D
・03/09[追記] サンプルがIEで動かなかったのを修正しました
Ext.ux.JsonpTreeLoader
コメントつきソースコードはこちらから (http://code.google.com/p/extjssamples-jp/source/browse/trunk/js/Ext.ux.JsonpTreeLoader.js)
デモページはこちら (http://extjssamples-jp.googlecode.com/svn/trunk/tree/extuxjsonptreeloaderdemo.html)
Ext.ux.JsonpTreeLoader = Ext.extend(Ext.tree.TreeLoader, function(){
return {
timeout : 30000,
callbackParam : "callback",
requestData : function(node, callback){
if(this.fireEvent("beforeload", this, node, callback) !== false){
this.scriptRequest(node,callback);
}else{
if(typeof callback == "function"){
callback();
}
}
},
// private
// retrieves data from cross-domain servers by dynamically creating a script tag
// referred to the "Cross-domain Ext.Ajax/Ext.data.Connection" by Animal
// url: http://extjs.com/forum/showthread.php?t=1769
scriptRequest : function(node, cb) {
var transId = ++Ext.data.ScriptTagProxy.TRANS_ID;
var trans = {
id : transId,
cb : "stcCallback"+transId,
scriptId : "stcScript"+transId,
argument: {node: node, callback: cb}
};
var url = this.dataUrl || this.url;
url += (url.indexOf("?") != -1 ? "&" : "?") + this.getParams(node) + String.format("&{0}={1}", this.callbackParam, trans.cb);
var conn = this;
window[trans.cb] = function(o){
conn.handleResponse({responseObject: o },trans);
};
trans.timeoutId = this.handleScriptFailure.defer(this.timeout, this, [trans]);
var script = document.createElement("script");
script.setAttribute("src", url);
script.setAttribute("type", "text/javascript");
script.setAttribute("id", trans.scriptId);
document.getElementsByTagName("head")[0].appendChild(script);
return trans;
},
// private
// called inside requestData as a callback with response data from remote server
handleResponse : function(response,trans){
var a = trans.argument;
this.processResponse(response, a.node, a.callback, trans);
this.fireEvent("load", this, a.node, response);
},
// private
// tries to create tree nodes from retrieved data
// referred to the "Cross-domain Ext.Ajax/Ext.data.Connection" by Animal and Ext.data.JsonReader
// url: http://extjs.com/forum/showthread.php?t=1769
processResponse : function(response, node, callback, trans){
var o = response.responseObject;
this.transId = false;
this.destroyScriptTrans(trans, true);
if(this.totalProperty) {
this.getTotal = this.getJsonAccessor(this.totalProperty);
}
this.getRoot = this.root ? this.getJsonAccessor(this.root) : function(p){return p;};
var root = this.getRoot(o), c = root.length, totalRecords = c;
if(this.totalProperty){
var v = parseInt(this.getTotal(o), 10);
if(!isNaN(v)){
totalRecords = v;
}
}
node.attributes.totalRecords = totalRecords;
try {
var rootName = this.root.split('.').pop();
node.beginUpdate();
node.appendChild(this.parseJson(root,rootName));
node.endUpdate();
if(typeof callback == "function"){
callback(this, node);
}
}catch(e){
this.handleFailure(trans);
}
},
isNodeProperty: function(property){
return this.nodeProperty
? this.nodeProperty.indexOf(property) !== -1
: true;
},
// private
// parses and creates tree node from retrieved JSON data
parseJson : function(o, name){
if(typeof o !== 'object') return;
else if(name && !this.isNodeProperty(name)) return;
else if(o instanceof Array) return this.parseArray(o, name);
var nodes = [];
for(var i in o){
if(o.hasOwnProperty(i) && this.isNodeProperty(i)){
if(typeof o[i] == 'object'){
var treeNode = this.createNode(o[i],i);
var child = null;
child = this.parseJson(o[i],i);
if(child && child.length >= 1){
treeNode.appendChild(child);
}
nodes.push(treeNode);
}
}
}
return nodes;
},
// private
// parses array and creates tree nodes
// Assumption: Direct member of array must not be an array
// Assumption: Array members must be objects, not primitives
parseArray : function(o, name){
if(!(o instanceof Array)) return;
else if(name && !this.isNodeProperty(name)) return;
var nodes = [];
Ext.each(o, function(n){
if(typeof n == 'object'){
var treeNode = this.createNode(n,name);
var child = this.parseJson(n,name);
if(child && child.length >= 1){
treeNode.appendChild(child);
}
nodes.push(treeNode);
}
},this);
return nodes;
},
createNode : function(node,name){
var attr = {
propName: name
};
for(var i in node){
if(node.hasOwnProperty(i) && (typeof node[i] !== 'object' || i == 'nodeLoader')){
attr[i] = node[i];
}
}
this.processAttributes(attr, node);
var n = Ext.ux.JsonpTreeLoader.superclass.createNode.call(this, attr);
// set loader after superclass call (because n.loader is set to "this" inside superclass)
if(attr.nodeLoader){
n.loader = attr.nodeLoader;
}
return n;
},
// private
// used inside createNode method to process the attributes of the node
processAttributes: function(attr, n){
attr.text = attr.text || attr.propName;
if(this.mappingFn && this.mappingFn[attr.propName]){
this.mappingFn[attr.propName](attr, n);
}else{
attr.loaded = typeof attr.loaded !== 'undefined' ? attr.loaded : true;
}
},
// private
// copied from Ext.data.JsonReader
getJsonAccessor: function(){
var re = /[\[\.]/;
return function(expr) {
try {
return(re.test(expr))
? new Function("obj", "return obj." + expr)
: function(obj){
return obj[expr];
};
} catch(e){}
return Ext.emptyFn;
};
}(),
// private
// fires "loadexception" event
// referred to the "Cross-domain Ext.Ajax/Ext.data.Connection" by Animal
// url: http://extjs.com/forum/showthread.php?t=1769
handleScriptFailure: function(trans) {
this.transId = false;
this.destroyScriptTrans(trans, false);
var response = {
argument: trans.argument,
status: 500,
statusText: 'Server failed to respond',
responseText: ''
};
var node = trans.argument.node;
this.fireEvent("loadexception", this, node, response);
},
// private
// remove scrip tag which was used to retrieve data
// referred to the "Cross-domain Ext.Ajax/Ext.data.Connection" by Animal
// url: http://extjs.com/forum/showthread.php?t=1769
destroyScriptTrans : function(trans, isLoaded){
document.getElementsByTagName("head")[0].removeChild(document.getElementById(trans.scriptId));
clearTimeout(trans.timeoutId);
if(isLoaded){
window[trans.cb] = undefined;
try{
delete window[trans.cb];
}catch(e){}
}else{
// if hasn't been loaded, wait for load to remove it to prevent script error
window[trans.cb] = function(){
window[trans.cb] = undefined;
try{
delete window[trans.cb];
}catch(e){}
};
}
}
}; // end return
}()
);
その名の通り、JSONPで提供されているWeb APIからデータを取得してきてExt.data.TreeNode形式にしてくれるクラスです(サンプルではPanoramioのAPIを使っています)。
SDKのサンプルに含まれているExt.ux.XmlTreeLoader (http://extjs.cachefly.net/ext-2.2.1/examples/tree/XmlTreeLoader.js)と、このスレッド (http://extjs.com/forum/showthread.php?t=17691)(英語)、そしてExt.data.ScriptTagProxyとExt.data.JsonReaderからいくつかメソッドを参考&コピペして作ってみました。
もともとのExt.tree.TreeLoaderは、
同一ドメインからのデータ取得のみ
決まった形のJSONデータのみ
と、ちょっと使い勝手がよくなかったので、クロスドメイン対応と、あとExt.data.JsonReaderのようなマッピング機能も追加してみました(このマッピング機能は、上記XmlTreeLoaderや既存のTreeLoaderにも簡単にポーティング可能です)。
バグ報告、突っ込み、大歓迎ですので、お試しください:D