Ronaldo
19 Nov 2008, 2:32 PM
Hi all,
When building a tree (and a grid too) I find myself repeatingly implementing more of the same... (new, copy, delete, properties).
So here is my humble solution: Ext.ux.TreeCommands.
Before explaining it all, lets give a lot of credits to jsakalos (http://extjs.com/forum/member.php?u=2178), as I 'borrowed' a lot of tricks for manipulation the tree from his wonderfull filetreepanel.
The problem this set of plugin tries to solve is to be able to create a fully fledged tree panel with
Minimal configuration
Reusable code
Consistent icon/language usage The drag/drop part and the new and copy actions in a tree are not that straightforward. So I'd like to solve them once and for all if possible.
I'm using the command pattern, which theoretically allows for undo/redo functionality. I've been working on that, but in this version it's disabled.
Here's my tree panel code:
Ext.bb.PageTreePanel = Ext.extend(Ext.tree.TreePanel, {
baseUrl :Ext.bb.Url.pageprefix,
autoScroll :true,
rootVisible :true,
loadMask :true,
enableDD :true,
containerScroll :true,
selectOnEdit: false,
root : {
id :'0',
text :'Pages',
draggable :false,
type :'folder',
expand :true
},
// Would allow undo/redo functionality if not disabled (and not only here)
//cmdStack: new Ext.ux.util.CommandStack(),
plugins: [
new Ext.ux.tree.DragDropPlugin({cmdCfg:{baseUrl: '/page/'}}),
new Ext.ux.tree.ContextMenuAdapterPlugin({
cmdCfg: {baseUrl: '/page/'},
items:[
Ext.ux.tree.CtmItemNew,
'-',
Ext.ux.tree.CtmItemRename,
Ext.ux.tree.CtmItemCopy,
Ext.ux.tree.CtmItemRefresh,
'-',
Ext.ux.tree.CtmItemDelete,
'-',
Ext.ux.tree.CtmItemProperties
]
}),
new Ext.ux.tree.ToolbarAdapterPlugin({
cmdCfg: {baseUrl: Ext.bb.Url.pageprefix},
items:[
Ext.ux.tree.TbItemNew,
Ext.ux.tree.TbItemProperties,
'-',
Ext.ux.tree.TbItemRefresh,
]
})
],
initComponent : function() {
this.loader = new Ext.tree.TreeLoader( {
dataUrl :this.baseUrl+'getChildren'
});
// This *must* be set here in order to let the toollbar adapter plugin work
this.tbar = new Ext.Toolbar();
Ext.bb.PageTreePanel.superclass.initComponent.call(this);
this.treeEditor = new Ext.tree.TreeEditor(this, {
...
});
this.on("dblclick", this.onProperties, this);
this.on("beforeexecutecmd", this.onBeforeExecuteCmd, this);
},
createContextMenu: function(menu) {
// Allows specific menu additions apart from the plugins
},
onBeforeExecuteCmd: function(tree, req) {
// Change the request parameters before executing the ajax command
if(req.node)
req.params.nodeType = req.node.attributes.entityName;
},
/** s is an array of selected nodes */
onProperties : function(s) {
// Called from the Ext.ux.tree.PropertiesCommand
var node = Ext.isArray(s) ? s[0] : s;
if(!node) return;
this.main.createPageForm({id: node.id},{mainTreeId: node.id});
},
/** Utility function used in the commands
to make sure the selection is always an (optionally empty) array of selected nodes */
getSelection: function() {
var s=null, sm = this.getSelectionModel();
if(typeof sm.getSelectedNode=='function') {
s = sm.getSelectedNode();
s = s ? [s] : [];
} else {
s = sm.getSelectedNodes();
}
return s;
},
selectNodeById : function(id, clearOther) {
if(clearOther == true)
this.getSelectionModel().clearSelections();
var node = this.getNodeById(id);
if(node) {
node.select();
node.ensureVisible();
}
return node;
}
});
There are 3 plugins:
Ext.ux.tree.DragDropPlugin - Plugin that completely manages drag and drop functionality.Use the standard onbeforedrag/drop events to cancel events as you would do normally.
And the
Ext.ux.tree.ContextMenuAdapterPlugin
Ext.ux.tree.ToolbarAdapterPluginWhich adapt their items, which are defined as static items:
Ext.ux.tree.CtmItemNew = {
cmdName: 'newNode',
text: 'New',
iconCls:'iconNew',
getCmd: function(cfg) {
return new Ext.ux.tree.NewCommand(cfg);
},
isEnabledFn: Ext.ux.tree.isEnabledFn
}
For clarity, here's the toolbar version:
Ext.ux.tree.TbItemCopy = {
cmdName: 'copyNode',
// text: 'Copy', // Don't want no text for this button
iconCls:'iconCopy',
getCmd: function(cfg) {
return new Ext.ux.tree.CopyCommand(cfg);
},
isEnabledFn: Ext.ux.tree.isEnabledFn
}These items are defined in Ext.ux.tree.ContextMenuCommandItems.js and Ext.ux.tree.ToolbarCommandItems.js.
I've duplicated these items as a contextmenu item can have other text (or a shortcut key) than the toolbar item. Moreover, this way they're translatable.
These items are cloned when necessary and can be reused in every tree you need.
Basically, clicking on a toolbar or context menu item triggers an adapter event listener,
which check if the button or item has a getCmd function.
Then, a new object is instantiated that carries out the request (ie new node, delete node, copy node etc).
The adapters provide functionality to enable/disable items based on the selection.
All commands use JSON, and expect a result like
{success:true}
On some (like copy command), if you return
{success:true, data=[{id:10, name:'I am a new node', otherattribute: 'hello world'}]}
will also update the new node with the id, and set node.attributes.otherattribute to 'hello world'.
Returning errors like
{success:false, errors:[{id:null, msg:'Duplicate record'}]}
will cause the command to undo what it has already done (like adding a new node when copying) and display the error message.
The baseURL (config) is passed to all commands, so the new command would go to
/page/createNode
and its full request is something like:
/page/createNode?cmdName=createNode&name=New node
The /page/ part of the url isDefined in the plugin definition
new Ext.ux.tree.ContextMenuAdapterPlugin({
cmdCfg: {baseUrl: '/page/'},
items:...and "createNode" is defined as url property in the Ext.ux.NewCommand.js
Oh, the code can be downloaded at http://www.twensoc.nl/Ext.ux.TreeCommands 0.9.rar (http://www.twensoc.nl/Ext.ux.TreeCommands%200.9.rar)
Even though some code comments state LGPL, I feel free to change that license later on,
As I'm not sure yet. You can use this version anyway as you like, except that you can't sell it ;) That is, use it in any (commercially) project, but you can't sell this code as part of any other toolkit/code/extensions code.
I'm working on more documentation :) As always, remarks/comments/discussion is welcome.
Ronaldo
When building a tree (and a grid too) I find myself repeatingly implementing more of the same... (new, copy, delete, properties).
So here is my humble solution: Ext.ux.TreeCommands.
Before explaining it all, lets give a lot of credits to jsakalos (http://extjs.com/forum/member.php?u=2178), as I 'borrowed' a lot of tricks for manipulation the tree from his wonderfull filetreepanel.
The problem this set of plugin tries to solve is to be able to create a fully fledged tree panel with
Minimal configuration
Reusable code
Consistent icon/language usage The drag/drop part and the new and copy actions in a tree are not that straightforward. So I'd like to solve them once and for all if possible.
I'm using the command pattern, which theoretically allows for undo/redo functionality. I've been working on that, but in this version it's disabled.
Here's my tree panel code:
Ext.bb.PageTreePanel = Ext.extend(Ext.tree.TreePanel, {
baseUrl :Ext.bb.Url.pageprefix,
autoScroll :true,
rootVisible :true,
loadMask :true,
enableDD :true,
containerScroll :true,
selectOnEdit: false,
root : {
id :'0',
text :'Pages',
draggable :false,
type :'folder',
expand :true
},
// Would allow undo/redo functionality if not disabled (and not only here)
//cmdStack: new Ext.ux.util.CommandStack(),
plugins: [
new Ext.ux.tree.DragDropPlugin({cmdCfg:{baseUrl: '/page/'}}),
new Ext.ux.tree.ContextMenuAdapterPlugin({
cmdCfg: {baseUrl: '/page/'},
items:[
Ext.ux.tree.CtmItemNew,
'-',
Ext.ux.tree.CtmItemRename,
Ext.ux.tree.CtmItemCopy,
Ext.ux.tree.CtmItemRefresh,
'-',
Ext.ux.tree.CtmItemDelete,
'-',
Ext.ux.tree.CtmItemProperties
]
}),
new Ext.ux.tree.ToolbarAdapterPlugin({
cmdCfg: {baseUrl: Ext.bb.Url.pageprefix},
items:[
Ext.ux.tree.TbItemNew,
Ext.ux.tree.TbItemProperties,
'-',
Ext.ux.tree.TbItemRefresh,
]
})
],
initComponent : function() {
this.loader = new Ext.tree.TreeLoader( {
dataUrl :this.baseUrl+'getChildren'
});
// This *must* be set here in order to let the toollbar adapter plugin work
this.tbar = new Ext.Toolbar();
Ext.bb.PageTreePanel.superclass.initComponent.call(this);
this.treeEditor = new Ext.tree.TreeEditor(this, {
...
});
this.on("dblclick", this.onProperties, this);
this.on("beforeexecutecmd", this.onBeforeExecuteCmd, this);
},
createContextMenu: function(menu) {
// Allows specific menu additions apart from the plugins
},
onBeforeExecuteCmd: function(tree, req) {
// Change the request parameters before executing the ajax command
if(req.node)
req.params.nodeType = req.node.attributes.entityName;
},
/** s is an array of selected nodes */
onProperties : function(s) {
// Called from the Ext.ux.tree.PropertiesCommand
var node = Ext.isArray(s) ? s[0] : s;
if(!node) return;
this.main.createPageForm({id: node.id},{mainTreeId: node.id});
},
/** Utility function used in the commands
to make sure the selection is always an (optionally empty) array of selected nodes */
getSelection: function() {
var s=null, sm = this.getSelectionModel();
if(typeof sm.getSelectedNode=='function') {
s = sm.getSelectedNode();
s = s ? [s] : [];
} else {
s = sm.getSelectedNodes();
}
return s;
},
selectNodeById : function(id, clearOther) {
if(clearOther == true)
this.getSelectionModel().clearSelections();
var node = this.getNodeById(id);
if(node) {
node.select();
node.ensureVisible();
}
return node;
}
});
There are 3 plugins:
Ext.ux.tree.DragDropPlugin - Plugin that completely manages drag and drop functionality.Use the standard onbeforedrag/drop events to cancel events as you would do normally.
And the
Ext.ux.tree.ContextMenuAdapterPlugin
Ext.ux.tree.ToolbarAdapterPluginWhich adapt their items, which are defined as static items:
Ext.ux.tree.CtmItemNew = {
cmdName: 'newNode',
text: 'New',
iconCls:'iconNew',
getCmd: function(cfg) {
return new Ext.ux.tree.NewCommand(cfg);
},
isEnabledFn: Ext.ux.tree.isEnabledFn
}
For clarity, here's the toolbar version:
Ext.ux.tree.TbItemCopy = {
cmdName: 'copyNode',
// text: 'Copy', // Don't want no text for this button
iconCls:'iconCopy',
getCmd: function(cfg) {
return new Ext.ux.tree.CopyCommand(cfg);
},
isEnabledFn: Ext.ux.tree.isEnabledFn
}These items are defined in Ext.ux.tree.ContextMenuCommandItems.js and Ext.ux.tree.ToolbarCommandItems.js.
I've duplicated these items as a contextmenu item can have other text (or a shortcut key) than the toolbar item. Moreover, this way they're translatable.
These items are cloned when necessary and can be reused in every tree you need.
Basically, clicking on a toolbar or context menu item triggers an adapter event listener,
which check if the button or item has a getCmd function.
Then, a new object is instantiated that carries out the request (ie new node, delete node, copy node etc).
The adapters provide functionality to enable/disable items based on the selection.
All commands use JSON, and expect a result like
{success:true}
On some (like copy command), if you return
{success:true, data=[{id:10, name:'I am a new node', otherattribute: 'hello world'}]}
will also update the new node with the id, and set node.attributes.otherattribute to 'hello world'.
Returning errors like
{success:false, errors:[{id:null, msg:'Duplicate record'}]}
will cause the command to undo what it has already done (like adding a new node when copying) and display the error message.
The baseURL (config) is passed to all commands, so the new command would go to
/page/createNode
and its full request is something like:
/page/createNode?cmdName=createNode&name=New node
The /page/ part of the url isDefined in the plugin definition
new Ext.ux.tree.ContextMenuAdapterPlugin({
cmdCfg: {baseUrl: '/page/'},
items:...and "createNode" is defined as url property in the Ext.ux.NewCommand.js
Oh, the code can be downloaded at http://www.twensoc.nl/Ext.ux.TreeCommands 0.9.rar (http://www.twensoc.nl/Ext.ux.TreeCommands%200.9.rar)
Even though some code comments state LGPL, I feel free to change that license later on,
As I'm not sure yet. You can use this version anyway as you like, except that you can't sell it ;) That is, use it in any (commercially) project, but you can't sell this code as part of any other toolkit/code/extensions code.
I'm working on more documentation :) As always, remarks/comments/discussion is welcome.
Ronaldo