crp_spaeth
9 Feb 2009, 4:51 AM
I' am developing an multi User Application and I needed a kind of Sync mechanism for the Tree. May two or more User working in the same Tree and delete add or update Treenodes.
In the Current Version of the Tree you can ask the tree to re-request the Rootnode.
But there is no Way to just update the Tree...
So I decided to implement my own Updatemechanism.
I decided to Extend the Standart Ext.tree.Treeloader and to override the requestData and proccessresponse functions to handle updates.
You may Take a look into the code Documentation for more details...
Ext.ns('Ext', 'Ext.ux', 'Ext.ux.tree');
/**
* @class Ext.ux.tree.SyncableTreeLoader
* @extends Ext.tree.TreeLoader
*
*
* A SyncableTreeLoader provides the same functionality as its Baseclass (Ext.tree.Treeloader) plus the
* possibility of lazy updating an existing Structure of TreeNodes from a specified Url.
*
* The response must be a JavaScript Object definition whose Members are (by default) "deleted", "updated", "added"
*
* "deleted" contains a simple Javascript Array definition of the TreeNode-Id that should be deleted
*
* "updated" contains a JavasScript Array defintion contaioning the Object definition of the updated
* TreeNodes with its new attribute properties
*
* "added" contains a JavaScript Array of definition containing the Object definition of the treeNodes
* that should get Added to the Tree. Cause the Tree needs to know where to insert/append the TreeNode,
* the definition of the Node needs to contain an Attribute with the Id of its Parentnode "Parentnode"
* and Optional its Presilbing.
*
* A possible Response looks like this:
* <pre><code>
{
"deleted" : ['nodeId123'],
"updated" : [{
"leaf" : false,
"id" : "nodeId342",
"children" : [{
"leaf" : false,
"id" : "node12314",
"children" : [],
"attr" : {
"preSilbing_id" : null,
"parent_id" : "nodeId342",
"Dokumente" : "0",
"leaf" : false,
"text" : "childs text"
}
}
],
"attr" : {
"parent_id" : "parentNodeId",
"preSilbing_id" : null,
"text" : "Node Text",
"leaf" : false
}
}],
"added" : [{
"leaf" : false,
"id" : "nodeId555",
"children" : [{
"leaf" : false,
"id" : "node12314",
"children" : [],
"attr" : {
"preSilbing_id" : null,
"parent_id" : "nodeId342",
"Dokumente" : "0",
"leaf" : false,
"text" : "childs text"
}
}
],
"attr" : {
"parent_id" : "RootNodeID",
"preSilbing_id" : "FirstChildsId",
"text" : "Node Text",
"leaf" : false
}]
}
</code></pre>
* @author Martin Spaeth
* @copyright (c) 2008, by Martin Spaeth
*/
Ext.ux.tree.SyncableTreeLoader = Ext.extend( Ext.tree.TreeLoader, {
/**
* @cfg {string} updateParamName
* Parametername for the requesttype
*/
updateParamName: 'request',
/**
* @cfg {string} updateParamValue
* value of the Parameter to show the server that this is an updaterequest
*/
updateParamValue: 'update',
/**
* @cfg {String} updatedName
* Name of the array containing the nodes that should get updateted
*/
updatedName: 'updated',
/**
* @cfg {String} deletedName
* Name of the array containing the Id's of the nodes that should get deleted
*/
deletedName: 'deleted',
/**
* @cfg {String} addedName
* Name of the array containing the nodes that should get added
*/
addedName: 'added',
/**
* @cfg {String} parentIDAttr
* name of the attribute containing the parentId of a node
*/
parentIDAttr: 'parentIdAttr',
/**
* @cfg {String} preSilbAttr
* name of the attribute containing the preSilbingId of a node
*/
preSilbAttr: 'preSilbingIdAttr',
clearOnLoad : true,
/**
* @cfg tree {Ext.tree.Tree} the Tree this Treeloader is instantiated for
*/
tree: undefined,
/**
* Create a String containing all needed Request Parameters
* @param {Ext.tree.TreeNode} node
* @param {boolean} update
* @return {String} Parameterstring for a Request z.b. node=432&updete=true
*/
getParams: function(node, update){
var buf = [], bp = this.baseParams;
for(var key in bp){
if(typeof bp[key] != "function"){
buf.push(encodeURIComponent(key), "=", encodeURIComponent(bp[key]), "&");
}
}
buf.push("node=", encodeURIComponent(node.id));
if(update){
// adding the updateparameter
buf.push("&"+this.updateParamName+"=", encodeURIComponent(this.updateParamValue));
}
return buf.join("");
},
/**
* Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.
* This is called automatically when a node is expanded, but may be used to reload
* a node (or append new children if the {@link #clearOnLoad} option is false.)
*
* it now has the possibility to Update a node by requesting the data as an updaterequest
* @param {Ext.tree.TreeNode} node
* @param {Function} callback
* @param {boolean} update should the Request handled as an Update request?
*/
load : function(node, callback, upload){
if(upload === true) {
// call the Request Function with the upload flag == true
this.requestData(node, callback, upload);
} else {
// just call the normal load Function (it will then call the Request function)
Ext.ux.tree.SyncableTreeLoader.superclass.load.apply(this, arguments);
}
},
/**
* RequestData Function with the possibility to fire an updaterequest
* @param {Ext.tree.TreeNode} node the Node for wicht the data should be requested
* @param {function} callback a callbackfunction
* @param {boolean} update is it an updaterequest
*/
requestData : function(node, callback, update){
if(this.fireEvent("beforeload", this, node, callback) !== false){
this.transId = Ext.Ajax.request({
update: update,
method:this.requestMethod,
url: this.dataUrl || this.url,
success: this.handleResponse,
failure: this.handleFailure,
scope: this,
argument: {callback: callback, node: node},
params: this.getParams(node, update)
});
}else{
// if the load is cancelled, make sure we notify
// the node that we are done
if(typeof callback == "function"){
callback();
}
}
},
/**
* the function that processes the Response an handles all the Update logic
* @param {Object} response from the Server
* @param {Ext.tree.TreeNode} node The Node the Response is for
* @param {function} callback
*/
processResponse : function(response, node, callback){
var json = response.responseText;
var update = response.options.update;
var tree = node.getOwnerTree();
try {
var o = eval("("+json+")");
// is the response part of an update request?
if(update){
// delete Nodes
var del = o[this.deletedName];
if(del && del.length){
for(var i = 0; del.length > i; i++){
var delNodeId = del[i];
tree.getNodeById(delNodeId).remove();
}
}
// Add all Nodes from the added Array Responsed by the server
var added = o[this.addedName];
if(added && added.length){
for(var i1 = 0; added.length > i1; i1++){
var addedNode = added[i1];
/**
* @type Ext.tree.TreeNode
*/
var parent = tree.getNodeById(addedNode.attr[this.parentIDAttr]);
var preSilbing = tree.getNodeById(addedNode.attr[this.preSilbAttr]);
parent.beginUpdate();
// Append the Node to the Parent or Insert it after the presilbing.
if(!preSilbing){
parent.appendChild(this.createNode(addedNode));
} else {
parent.insertBefore(this.createNode(addedNode), preSilbing.nextSibling);
}
parent.endUpdate();
}
}
// Update all the nodes the Update array contains
var updated = o[this.updatedName];
if(updated && updated.length){
for(var i2 = 0; updated.length > i2; i2++){
var updatedNode = updated[i2];
/**
* @type Ext.tree.TreeNode
*/
var toUpdate = tree.getNodeById(updatedNode.id);
toUpdate.beginUpdate();
// toUpdate.updateNode(updatedNode);
// update should only handle the attributes of a node nor the children
if(updateNode.children) {
delete updateNode.children;
}
Ext.apply(toUpdate, updateNode);
// update the text of the node
if(updateNode.text){
toUpdate.setText(updateNode.text);
}
// replace the Icon
if(updateNode.iconCls){
Ext.fly(toUpdate.getUI().getIconEl()).replaceClass(oldIconCls, updateNode.iconCls);
toUpdate.iconCls = updateNode.iconCls;
}
// replace the quicktip
if(updateNode.qtip) {
var ui = toUpdate.getUI();
if(ui.textNode.setAttributeNS){
ui.textNode.setAttributeNS("ext", "qtip", updateNode.qtip);
if(updateNode.qtipTitle){
ui.textNode.setAttributeNS("ext", "qtitle", updateNode.qtipTitle);
}
}else{
ui.textNode.setAttribute("ext:qtip", updateNode.qtip);
if(updateNode.qtipTitle){
ui.textNode.setAttribute("ext:qtitle", updateNode.qtipTitle);
}
}
}
toUpdate.endUpdate();
}
}
if(typeof callback == "function"){
callback(this, node);
}
}
else {
node.beginUpdate();
for(var i = 0, len = o.length; i < len; i++){
var n = this.createNode(o[i]);
if(n){
node.appendChild(n);
}
}
node.endUpdate();
if(typeof callback == "function"){
callback(this, node);
}
}
}catch(e){
this.handleFailure(response);
}
}
});
In the Current Version of the Tree you can ask the tree to re-request the Rootnode.
But there is no Way to just update the Tree...
So I decided to implement my own Updatemechanism.
I decided to Extend the Standart Ext.tree.Treeloader and to override the requestData and proccessresponse functions to handle updates.
You may Take a look into the code Documentation for more details...
Ext.ns('Ext', 'Ext.ux', 'Ext.ux.tree');
/**
* @class Ext.ux.tree.SyncableTreeLoader
* @extends Ext.tree.TreeLoader
*
*
* A SyncableTreeLoader provides the same functionality as its Baseclass (Ext.tree.Treeloader) plus the
* possibility of lazy updating an existing Structure of TreeNodes from a specified Url.
*
* The response must be a JavaScript Object definition whose Members are (by default) "deleted", "updated", "added"
*
* "deleted" contains a simple Javascript Array definition of the TreeNode-Id that should be deleted
*
* "updated" contains a JavasScript Array defintion contaioning the Object definition of the updated
* TreeNodes with its new attribute properties
*
* "added" contains a JavaScript Array of definition containing the Object definition of the treeNodes
* that should get Added to the Tree. Cause the Tree needs to know where to insert/append the TreeNode,
* the definition of the Node needs to contain an Attribute with the Id of its Parentnode "Parentnode"
* and Optional its Presilbing.
*
* A possible Response looks like this:
* <pre><code>
{
"deleted" : ['nodeId123'],
"updated" : [{
"leaf" : false,
"id" : "nodeId342",
"children" : [{
"leaf" : false,
"id" : "node12314",
"children" : [],
"attr" : {
"preSilbing_id" : null,
"parent_id" : "nodeId342",
"Dokumente" : "0",
"leaf" : false,
"text" : "childs text"
}
}
],
"attr" : {
"parent_id" : "parentNodeId",
"preSilbing_id" : null,
"text" : "Node Text",
"leaf" : false
}
}],
"added" : [{
"leaf" : false,
"id" : "nodeId555",
"children" : [{
"leaf" : false,
"id" : "node12314",
"children" : [],
"attr" : {
"preSilbing_id" : null,
"parent_id" : "nodeId342",
"Dokumente" : "0",
"leaf" : false,
"text" : "childs text"
}
}
],
"attr" : {
"parent_id" : "RootNodeID",
"preSilbing_id" : "FirstChildsId",
"text" : "Node Text",
"leaf" : false
}]
}
</code></pre>
* @author Martin Spaeth
* @copyright (c) 2008, by Martin Spaeth
*/
Ext.ux.tree.SyncableTreeLoader = Ext.extend( Ext.tree.TreeLoader, {
/**
* @cfg {string} updateParamName
* Parametername for the requesttype
*/
updateParamName: 'request',
/**
* @cfg {string} updateParamValue
* value of the Parameter to show the server that this is an updaterequest
*/
updateParamValue: 'update',
/**
* @cfg {String} updatedName
* Name of the array containing the nodes that should get updateted
*/
updatedName: 'updated',
/**
* @cfg {String} deletedName
* Name of the array containing the Id's of the nodes that should get deleted
*/
deletedName: 'deleted',
/**
* @cfg {String} addedName
* Name of the array containing the nodes that should get added
*/
addedName: 'added',
/**
* @cfg {String} parentIDAttr
* name of the attribute containing the parentId of a node
*/
parentIDAttr: 'parentIdAttr',
/**
* @cfg {String} preSilbAttr
* name of the attribute containing the preSilbingId of a node
*/
preSilbAttr: 'preSilbingIdAttr',
clearOnLoad : true,
/**
* @cfg tree {Ext.tree.Tree} the Tree this Treeloader is instantiated for
*/
tree: undefined,
/**
* Create a String containing all needed Request Parameters
* @param {Ext.tree.TreeNode} node
* @param {boolean} update
* @return {String} Parameterstring for a Request z.b. node=432&updete=true
*/
getParams: function(node, update){
var buf = [], bp = this.baseParams;
for(var key in bp){
if(typeof bp[key] != "function"){
buf.push(encodeURIComponent(key), "=", encodeURIComponent(bp[key]), "&");
}
}
buf.push("node=", encodeURIComponent(node.id));
if(update){
// adding the updateparameter
buf.push("&"+this.updateParamName+"=", encodeURIComponent(this.updateParamValue));
}
return buf.join("");
},
/**
* Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.
* This is called automatically when a node is expanded, but may be used to reload
* a node (or append new children if the {@link #clearOnLoad} option is false.)
*
* it now has the possibility to Update a node by requesting the data as an updaterequest
* @param {Ext.tree.TreeNode} node
* @param {Function} callback
* @param {boolean} update should the Request handled as an Update request?
*/
load : function(node, callback, upload){
if(upload === true) {
// call the Request Function with the upload flag == true
this.requestData(node, callback, upload);
} else {
// just call the normal load Function (it will then call the Request function)
Ext.ux.tree.SyncableTreeLoader.superclass.load.apply(this, arguments);
}
},
/**
* RequestData Function with the possibility to fire an updaterequest
* @param {Ext.tree.TreeNode} node the Node for wicht the data should be requested
* @param {function} callback a callbackfunction
* @param {boolean} update is it an updaterequest
*/
requestData : function(node, callback, update){
if(this.fireEvent("beforeload", this, node, callback) !== false){
this.transId = Ext.Ajax.request({
update: update,
method:this.requestMethod,
url: this.dataUrl || this.url,
success: this.handleResponse,
failure: this.handleFailure,
scope: this,
argument: {callback: callback, node: node},
params: this.getParams(node, update)
});
}else{
// if the load is cancelled, make sure we notify
// the node that we are done
if(typeof callback == "function"){
callback();
}
}
},
/**
* the function that processes the Response an handles all the Update logic
* @param {Object} response from the Server
* @param {Ext.tree.TreeNode} node The Node the Response is for
* @param {function} callback
*/
processResponse : function(response, node, callback){
var json = response.responseText;
var update = response.options.update;
var tree = node.getOwnerTree();
try {
var o = eval("("+json+")");
// is the response part of an update request?
if(update){
// delete Nodes
var del = o[this.deletedName];
if(del && del.length){
for(var i = 0; del.length > i; i++){
var delNodeId = del[i];
tree.getNodeById(delNodeId).remove();
}
}
// Add all Nodes from the added Array Responsed by the server
var added = o[this.addedName];
if(added && added.length){
for(var i1 = 0; added.length > i1; i1++){
var addedNode = added[i1];
/**
* @type Ext.tree.TreeNode
*/
var parent = tree.getNodeById(addedNode.attr[this.parentIDAttr]);
var preSilbing = tree.getNodeById(addedNode.attr[this.preSilbAttr]);
parent.beginUpdate();
// Append the Node to the Parent or Insert it after the presilbing.
if(!preSilbing){
parent.appendChild(this.createNode(addedNode));
} else {
parent.insertBefore(this.createNode(addedNode), preSilbing.nextSibling);
}
parent.endUpdate();
}
}
// Update all the nodes the Update array contains
var updated = o[this.updatedName];
if(updated && updated.length){
for(var i2 = 0; updated.length > i2; i2++){
var updatedNode = updated[i2];
/**
* @type Ext.tree.TreeNode
*/
var toUpdate = tree.getNodeById(updatedNode.id);
toUpdate.beginUpdate();
// toUpdate.updateNode(updatedNode);
// update should only handle the attributes of a node nor the children
if(updateNode.children) {
delete updateNode.children;
}
Ext.apply(toUpdate, updateNode);
// update the text of the node
if(updateNode.text){
toUpdate.setText(updateNode.text);
}
// replace the Icon
if(updateNode.iconCls){
Ext.fly(toUpdate.getUI().getIconEl()).replaceClass(oldIconCls, updateNode.iconCls);
toUpdate.iconCls = updateNode.iconCls;
}
// replace the quicktip
if(updateNode.qtip) {
var ui = toUpdate.getUI();
if(ui.textNode.setAttributeNS){
ui.textNode.setAttributeNS("ext", "qtip", updateNode.qtip);
if(updateNode.qtipTitle){
ui.textNode.setAttributeNS("ext", "qtitle", updateNode.qtipTitle);
}
}else{
ui.textNode.setAttribute("ext:qtip", updateNode.qtip);
if(updateNode.qtipTitle){
ui.textNode.setAttribute("ext:qtitle", updateNode.qtipTitle);
}
}
}
toUpdate.endUpdate();
}
}
if(typeof callback == "function"){
callback(this, node);
}
}
else {
node.beginUpdate();
for(var i = 0, len = o.length; i < len; i++){
var n = this.createNode(o[i]);
if(n){
node.appendChild(n);
}
}
node.endUpdate();
if(typeof callback == "function"){
callback(this, node);
}
}
}catch(e){
this.handleFailure(response);
}
}
});