mviens
27 Oct 2008, 9:13 AM
I have taken all of the posts in this thread (http://extjs.com/forum/showthread.php?t=25076) and merged the updates into what I hope is the latest code. I am posting it here so that someone can confirm this is indeed the latest revision, and also to provide a starting point for newer users.
Ultimately, I would like to see this code get moved into the Central ux repository at: http://extjs-ux.org
code.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Menu Configuration</title>
<link rel="stylesheet" type="text/css" href="../resources/css/ext-all.css" />
<link rel="stylesheet" type="text/css" href="editable-column-tree.css" />
<script type="text/javascript" src="../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../ext-all.js"></script>
<!--script type="text/javascript" src="ColumnModel_Hook.js"></script-->
<script type="text/javascript" src="ColumnNodeUI.js"></script>
<script type="text/javascript" src="ColumnTreeEditor.js"></script>
<script type="text/javascript" src="editor_test.js"></script>
</head>
<body>
<table border="0" width="100%" height="100%">
<tr>
<td align="center" valign="top" style="padding-top:25px;">
<div id="tree-ct" style="text-align:left"></div>
</td>
</tr>
</table>
</body>
</html>
editable-column-tree.css (adjust image paths as appropriate)
.x-column-tree.x-tree-node {
zoom: 1;
}
.x-column-tree.x-tree-node-el {
/*border-bottom:1px solid #eee; borders? */
zoom: 1;
}
.x-column-tree.x-tree-selected {
background: #d9e8fb;
}
.x-column-tree.x-tree-node a {
line-height: 18px;
vertical-align: middle;
}
.x-column-tree.x-tree-node a span { }
.x-column-tree .x-tree-node .x-tree-selected a span {
background: transparent;
color: #000;
}
.x-tree-col {
float: left;
overflow: hidden;
padding: 0 1px;
zoom: 1;
}
.x-tree-col-text, .x-tree-hd-text {
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
padding: 3px 3px 3px 5px;
white-space: nowrap;
height: 1.2em; /*height:20px;*/
font: normal 11px arial, tahoma, helvetica, sans-serif;
}
.x-tree-hd-text {
height: 1.3em; /*height:20px;*/
}
.x-tree-headers {
background: #f9f9f9 url(../resources/images/default/grid/grid3-hrow.gif) repeat-x 0 bottom;
cursor: default;
zoom: 1;
}
.x-tree-hd {
float: left;
overflow: hidden;
border-left: 1px solid #eee;
border-right: 1px solid #d0d0d0;
}
.page-icon {
background-image: url(../../icons/add.png) !important;
}
.delete-icon {
background-image: url(../../icons/delete.png) !important;
}
.folder-icon {
background-image: url(../../icons/folder_add.png) !important;
}
.save-icon {
background-image: url(../../images/disk.gif) !important;
}
ColumnEditor.js
Ext.tree.ColumnTreeEditor = function(tree, colIndex, editorConfig)
{
var field;
if (editorConfig.xtype)
{
field = new Ext.ComponentMgr.create(editorConfig);
Ext.tree.ColumnTreeEditor.superclass.constructor.call(this, field);
}
else
{
field = {};
}
this.tree = tree;
this.columnIndex = colIndex;
if (! this.tree.rendered)
{
this.tree.on('render', this.initEditor, this);
}
else
{
this.initEditor(this.tree);
}
};
Ext.extend(Ext.tree.ColumnTreeEditor, Ext.Editor,
{
onTargetBeforeClick: function(node, event)
{
var sinceLast = (this.lastClick ? this.lastClick.getElapsed() : 0);
this.lastClick = new Date();
if (sinceLast <= this.editDelay || !this.tree.getSelectionModel().isSelected(node))
{
this.completeEdit();
}
var obj = event.target;
if (Ext.select('.x-tree-node-anchor', false, obj).getCount() == 1)
{
obj = Ext.select('.x-tree-node-anchor', false, obj).elements[0].firstChild;
}
if (obj.nodeName != 'SPAN' && obj.nodeName != 'DIV')
{
return true;
}
var colIndex = 0;
if (this.tree.fireEvent('beforecelledit', this.tree, node, this.columnIndex) === false)
{
return true;
}
var elt = Ext.Element.fly(obj);
if (elt.hasClass('x-tree-col-' + this.columnIndex))
{
this.triggerEdit(node, event, this.columnIndex);
event.stopEvent();
return false;
}
else
{
return true;
}
},
alignment: 'l-l',
autoSize: false,
hideEl: false,
cls: 'x-small-editor x-tree-editor',
shim: false,
shadow: 'frame',
maxWidth: 250,
editDelay: 0,
initEditor: function(tree)
{
this.tree.on('beforeclick', this.onTargetBeforeClick, this);
this.on('complete', this.updateNode, this);
//this.on('beforestartedit', this.fitToTree, this);
this.on('startedit', this.bindScroll, this,
{
delay: 10
});
this.on('specialkey', this.onSpecialKey, this);
},
fitToTree: function(ed, el)
{
var td = this.tree.getTreeEl().dom, nd = el.dom;
if (td.scrollLeft > nd.offsetLeft)
{
td.scrollLeft = nd.offsetLeft;
}
var w = Math.min(this.maxWidth, (td.clientWidth > 20 ? td.clientWidth : td.offsetWidth) - Math.max(0, nd.offsetLeft - td.scrollLeft) - 5);
this.setSize(w, '');
},
triggerEdit: function(node, e, colIndex)
{
var obj = e.target;
if (Ext.select('.x-tree-node-anchor', false, obj).getCount() == 1)
{
obj = Ext.select('.x-tree-node-anchor', false, obj).elements[0].firstChild;
}
else if (obj.nodeName == 'SPAN' || obj.nodeName == 'DIV')
{
obj = e.target;
}
else
{
return false;
}
this.completeEdit();
this.editNode = node;
this.editCol = obj;
this.editColIndex = colIndex;
this.startEdit(obj);
if (obj.nodeName == 'DIV')
{
var width = obj.offsetWidth;
this.setSize(width);
}
},
bindScroll: function()
{
this.tree.getTreeEl().on('scroll', this.cancelEdit, this);
},
beforeNodeClick: function(node, e)
{
var sinceLast = (this.lastClick ? this.lastClick.getElapsed() : 0);
this.lastClick = new Date();
if (sinceLast > this.editDelay && this.tree.getSelectionModel().isSelected(node))
{
e.stopEvent();
this.triggerEdit(node, e);
return false;
}
else
{
this.completeEdit();
}
},
updateNode: function(ed, value)
{
if (value != '' && value != this.editCol.innerHTML)
{
value = (this.editNode.renderers[this.editColIndex].renderer ? this.editNode.renderers[this.editColIndex].renderer(value) : value);
this.tree.getTreeEl().un('scroll', this.cancelEdit, this);
this.editNode.cols[this.editColIndex] = value; //for internal use only
this.editNode.attributes[this.editColIndex] = value; //duplicate into array of node attributes
this.editCol.innerHTML = value;
}
},
onHide: function()
{
Ext.tree.ColumnTreeEditor.superclass.onHide.call(this);
if (this.editNode)
{
this.editNode.ui.focus();
}
},
onSpecialKey: function(field, e)
{
var k = e.getKey();
if (k == e.ESC)
{
e.stopEvent();
this.cancelEdit();
}
else if (k == e.ENTER && !e.hasModifier())
{
e.stopEvent();
this.completeEdit();
}
}
});
ColumnNodeUI.js
Ext.tree.ColumnTree = Ext.extend(Ext.tree.TreePanel,
{
lines: false,
borderWidth: Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
cls: 'x-column-tree',
collapsible: false,
onRender: function()
{
Ext.tree.ColumnTree.superclass.onRender.apply(this, arguments);
this.headers = this.body.createChild(
{
cls: 'x-tree-headers'
}, this.innerCt.dom);
var cols = this.columns, c;
var totalWidth = 0;
for (var i = 0, len = cols.length; i < len; i++)
{
c = cols[i];
totalWidth += c.width;
this.headers.createChild(
{
cls: 'x-tree-hd ' + (c.cls ? c.cls + '-hd' : ''),
cn:
{
cls: 'x-tree-hd-text',
html: c.header
},
style: 'width:' + (c.width - this.borderWidth) + 'px;'
});
}
this.headers.createChild(
{
cls: 'x-clear'
});
// prevent floats from wrapping when clipped
this.headers.setWidth(totalWidth);
this.innerCt.setWidth(totalWidth);
},
initComponent: function()
{
Ext.tree.ColumnTree.superclass.initComponent.apply(this, arguments);
this.addEvents('beforecelledit');
for (var i = 0; i < this.columns.length; i++)
{
var col = this.columns[i];
if (col.editorField)
{
var editor = new Ext.tree.ColumnTreeEditor(this, col.dataIndex, col.editorField);
this.editor = editor;
}
}
}
});
Ext.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI,
{
focus: Ext.emptyFn, // prevent odd scrolling behavior
renderElements: function(n, a, targetNode, bulkRender)
{
this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
var t = n.getOwnerTree();
var cols = t.columns;
var bw = t.borderWidth;
var c = cols[0];
n.cols = new Array();
n.renderers = new Array();
var text = n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]);
n.cols[cols[0].dataIndex] = text;
var buf =
[
'<li class="x-tree-node" unselectable="on"><div ext:tree-node-id="',
n.id,
'" class="x-tree-node-el x-tree-node-leaf ',
a.cls,
'" unselectable="on">',
'<div class="x-tree-col" style="width:',
c.width - bw,
'px;" unselectable="on">',
'<span class="x-tree-node-indent" unselectable="on">',
this.indentMarkup,
"</span>",
'<img src="',
this.emptyIcon,
'" class="x-tree-ec-icon x-tree-elbow" unselectable="on">',
'<img src="',
a.icon || this.emptyIcon,
'" class="x-tree-node-icon',
(a.icon ? ' x-tree-node-inline-icon' : ''),
(a.iconCls ? ' ' + a.iconCls : ''),
'" unselectable="on">',
'<a hidefocus="on" class="x-tree-node-anchor" href="',
a.href ? a.href : '#',
'" tabIndex="1" ',
a.hrefTarget ? ' target="' + a.hrefTarget + '"' : '',
' unselectable="on">',
'<span unselectable="on">',
text,
'</span></a>',
'</div>'
];
for (var i = 1, len = cols.length; i < len; i++)
{
c = cols[i];
var text = (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]);
n.cols[c.dataIndex] = text;
n.renderers[c.dataIndex] = { renderer: ( c.renderer ? c.renderer : false ) }
buf.push('<div class="x-tree-col ', (c.cls ? c.cls : ''), '" style="width:', c.width - bw,
'px;" unselectable="on">', '<div class="x-tree-col-text x-tree-col-', c.dataIndex, '" unselectable="on">', text, '</div>', '</div>');
}
buf.push('<div class="x-clear" unselectable="on"></div></div>',
'<ul class="x-tree-node-ct" style="display:none;" unselectable="on"></ul>', '</li>');
if (bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl())
{
this.wrap = Ext.DomHelper.insertHtml('beforeBegin', n.nextSibling.ui.getEl(), buf.join(''));
}
else
{
this.wrap = Ext.DomHelper.insertHtml('beforeEnd', targetNode, buf.join(''));
}
this.elNode = this.wrap.childNodes[0];
this.ctNode = this.wrap.childNodes[1];
var cs = this.elNode.firstChild.childNodes;
this.indentNode = cs[0];
this.ecNode = cs[1];
this.iconNode = cs[2];
this.anchor = cs[3];
this.textNode = cs[3].firstChild;
}
});
editor_test.js
Ext.BLANK_IMAGE_URL = '../resources/images/default/s.gif';
Ext.onReady(function()
{
var store = new Ext.data.SimpleStore(
{
fields:
[
'chart_report'
],
data:
[
[
'Password Reset Summary'
],
[
'Password Reset By Agents'
]
]
});
var store2 = new Ext.data.SimpleStore(
{
fields:
[
'report'
],
data:
[
[
'Pasry'
],
[
'Pasents'
]
]
});
var combo_config =
{
id: 'chart_combo_list',
store: store,
displayField: 'chart_report',
xtype: 'combo',
mode: 'local',
triggerAction: 'all',
width: 100,
emptyText: 'Please select report type...',
selectOnFocus: true
};
var combo_config2 =
{
id: 'chart_combo_list2',
store: store2,
displayField: 'report',
xtype: 'combo',
mode: 'local',
triggerAction: 'all',
width: 100,
emptyText: 'Please select report type..',
selectOnFocus: true
};
var tree = new Ext.tree.ColumnTree(
{
el: 'tree-ct',
width: 700,
autoHeight: true,
rootVisible: false,
autoScroll: true,
expandable: false,
enableDD: true,
title: 'Menu Configuration',
tbar:
[
{
text: 'Save Menu',
tooltip: 'Save Menu',
iconCls: 'save-icon',
listeners:
{
'click': function()
{
var json = tree.toJsonString(null, function(key, val)
{
return (key == 'leaf' || key == 'id' || key == 'menu_item' || key == 'menu_url');
},
{
menu_item: 'text',
menu_url: 'href'
});
alert(json);
},
scope: this
}
},
{
xtype: 'tbseparator'
},
{
text: 'Add Folder Item',
tooltip: 'Add Folder Item',
iconCls: 'folder-icon',
listeners:
{
'click': function()
{
var selectedItem = tree.getSelectionModel().getSelectedNode();
if (! selectedItem)
{
selectedItem = tree.getRootNode();
}
handleCreate = function (btn, text, cBoxes)
{
if (btn == 'ok' && text)
{
var newNode = new Ext.tree.TreeNode(
{
menu_item: text,
menu_url: '',
leaf: false,
expandable: true,
uiProvider: Ext.tree.ColumnNodeUI
});
if (selectedItem.isLeaf())
{
selectedItem.parentNode.insertBefore(newNode, selectedItem.nextSibling);
}
else
{
selectedItem.insertBefore(newNode, selectedItem.firstChild);
}
}
}
Ext.MessageBox.show(
{
title: 'Add new Folder Item',
msg: 'Name of Folder Item:',
buttons: Ext.MessageBox.OKCANCEL,
prompt: true,
fn: handleCreate
});
}
}
},
{
xtype: 'tbseparator'
},
{
text: 'Add Page Item',
tooltip: 'Add Page Item',
iconCls: 'page-icon',
listeners:
{
'click': function()
{
var selectedItem = tree.getSelectionModel().getSelectedNode();
if (!selectedItem)
{
Ext.Msg.alert('Warning', 'Please select an Item after which you want to add a new one.');
return false;
}
handleCreate = function (btn, text, cBoxes)
{
if (btn == 'ok' && text)
{
var newNode = new Ext.tree.TreeNode(
{
menu_item: text,
menu_url: '',
leaf: true,
allowChildren: false,
uiProvider: Ext.tree.ColumnNodeUI
});
if (selectedItem.isLeaf())
{
selectedItem.parentNode.insertBefore(newNode, selectedItem.nextSibling);
}
else
{
selectedItem.insertBefore(newNode, selectedItem.firstChild);
}
}
}
Ext.MessageBox.show(
{
title: 'Add new Page Item',
msg: 'Name of Page Item:',
buttons: Ext.MessageBox.OKCANCEL,
prompt: true,
fn: handleCreate
});
}
}
},
{
xtype: 'tbseparator'
},
{
text: 'Delete Item',
tooltip: 'Delete Item',
iconCls: 'delete-icon',
listeners:
{
'click': function()
{
var selectedItem = tree.getSelectionModel().getSelectedNode();
if (!selectedItem)
{
Ext.Msg.alert('Warning', 'Please select an Item to delete.');
return false;
}
handleDelete = function (btn)
{
if (btn == 'ok')
{
selectedItem.remove();
}
}
Ext.MessageBox.show(
{
title: 'Confirm your action',
msg: 'Are you sure you want to delete this item and its children?',
buttons: Ext.MessageBox.OKCANCEL,
fn: handleDelete
});
}
}
},
{
xtype: 'tbseparator'
}
],
columns:
[
{
header: 'Menu Item',
width: 200,
dataIndex: 'menu_item'
// editorField: combo_config
},
{
header: 'URL',
width: 150,
dataIndex: 'menu_url',
editorField:
{
xtype: 'textfield',
editable: true
}
},
{
header: 'Approval Status',
width: 120,
dataIndex: 'approval',
editorField: combo_config2
}
],
loader: new Ext.tree.TreeLoader(
{
preloadChildren: true,
uiProviders:
{
'col': Ext.tree.ColumnNodeUI
}
}),
root: new Ext.tree.AsyncTreeNode(
{
allowChildren: true,
children:
[
{
menu_item: 'Current State',
menu_url: 'blah blah',
uiProvider: 'col',
approval: 'text',
children:
[
{
menu_item: 'Executive Summary',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability Scorecard',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability Distribution',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
}
]
},
{
menu_item: 'Trends',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
children:
[
{
menu_item: 'Host Changes',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Hosts and Vulnerability',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Application and Vulnerability',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability by Operation System',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability by Networks',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability by Application Group',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
}
]
},
{
menu_item: 'Technical',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
children:
[
{
menu_item: 'Risk Matrix',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Host Inventory',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Application Inventory',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability Inventory',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Most Common Vulnerabilites',
menu_url: 'blah blah',
uiProvider: 'col',
approval: '',
leaf: true
},
{
menu_item: 'Most Common SANS Vulnerabilities',
menu_url: 'blah blah',
uiProvider: 'col',
approval: '',
leaf: true
},
{
menu_item: 'Most Vulnerable Applications',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Most Vulnerable Hosts',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'PCI Compliance',
menu_url: 'blah blah',
uiProvider: 'col',
approval: '',
leaf: true
}
]
}
]
})
});
tree.render();
tree.expandAll();
});
Mike V.
Ultimately, I would like to see this code get moved into the Central ux repository at: http://extjs-ux.org
code.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Menu Configuration</title>
<link rel="stylesheet" type="text/css" href="../resources/css/ext-all.css" />
<link rel="stylesheet" type="text/css" href="editable-column-tree.css" />
<script type="text/javascript" src="../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../ext-all.js"></script>
<!--script type="text/javascript" src="ColumnModel_Hook.js"></script-->
<script type="text/javascript" src="ColumnNodeUI.js"></script>
<script type="text/javascript" src="ColumnTreeEditor.js"></script>
<script type="text/javascript" src="editor_test.js"></script>
</head>
<body>
<table border="0" width="100%" height="100%">
<tr>
<td align="center" valign="top" style="padding-top:25px;">
<div id="tree-ct" style="text-align:left"></div>
</td>
</tr>
</table>
</body>
</html>
editable-column-tree.css (adjust image paths as appropriate)
.x-column-tree.x-tree-node {
zoom: 1;
}
.x-column-tree.x-tree-node-el {
/*border-bottom:1px solid #eee; borders? */
zoom: 1;
}
.x-column-tree.x-tree-selected {
background: #d9e8fb;
}
.x-column-tree.x-tree-node a {
line-height: 18px;
vertical-align: middle;
}
.x-column-tree.x-tree-node a span { }
.x-column-tree .x-tree-node .x-tree-selected a span {
background: transparent;
color: #000;
}
.x-tree-col {
float: left;
overflow: hidden;
padding: 0 1px;
zoom: 1;
}
.x-tree-col-text, .x-tree-hd-text {
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
padding: 3px 3px 3px 5px;
white-space: nowrap;
height: 1.2em; /*height:20px;*/
font: normal 11px arial, tahoma, helvetica, sans-serif;
}
.x-tree-hd-text {
height: 1.3em; /*height:20px;*/
}
.x-tree-headers {
background: #f9f9f9 url(../resources/images/default/grid/grid3-hrow.gif) repeat-x 0 bottom;
cursor: default;
zoom: 1;
}
.x-tree-hd {
float: left;
overflow: hidden;
border-left: 1px solid #eee;
border-right: 1px solid #d0d0d0;
}
.page-icon {
background-image: url(../../icons/add.png) !important;
}
.delete-icon {
background-image: url(../../icons/delete.png) !important;
}
.folder-icon {
background-image: url(../../icons/folder_add.png) !important;
}
.save-icon {
background-image: url(../../images/disk.gif) !important;
}
ColumnEditor.js
Ext.tree.ColumnTreeEditor = function(tree, colIndex, editorConfig)
{
var field;
if (editorConfig.xtype)
{
field = new Ext.ComponentMgr.create(editorConfig);
Ext.tree.ColumnTreeEditor.superclass.constructor.call(this, field);
}
else
{
field = {};
}
this.tree = tree;
this.columnIndex = colIndex;
if (! this.tree.rendered)
{
this.tree.on('render', this.initEditor, this);
}
else
{
this.initEditor(this.tree);
}
};
Ext.extend(Ext.tree.ColumnTreeEditor, Ext.Editor,
{
onTargetBeforeClick: function(node, event)
{
var sinceLast = (this.lastClick ? this.lastClick.getElapsed() : 0);
this.lastClick = new Date();
if (sinceLast <= this.editDelay || !this.tree.getSelectionModel().isSelected(node))
{
this.completeEdit();
}
var obj = event.target;
if (Ext.select('.x-tree-node-anchor', false, obj).getCount() == 1)
{
obj = Ext.select('.x-tree-node-anchor', false, obj).elements[0].firstChild;
}
if (obj.nodeName != 'SPAN' && obj.nodeName != 'DIV')
{
return true;
}
var colIndex = 0;
if (this.tree.fireEvent('beforecelledit', this.tree, node, this.columnIndex) === false)
{
return true;
}
var elt = Ext.Element.fly(obj);
if (elt.hasClass('x-tree-col-' + this.columnIndex))
{
this.triggerEdit(node, event, this.columnIndex);
event.stopEvent();
return false;
}
else
{
return true;
}
},
alignment: 'l-l',
autoSize: false,
hideEl: false,
cls: 'x-small-editor x-tree-editor',
shim: false,
shadow: 'frame',
maxWidth: 250,
editDelay: 0,
initEditor: function(tree)
{
this.tree.on('beforeclick', this.onTargetBeforeClick, this);
this.on('complete', this.updateNode, this);
//this.on('beforestartedit', this.fitToTree, this);
this.on('startedit', this.bindScroll, this,
{
delay: 10
});
this.on('specialkey', this.onSpecialKey, this);
},
fitToTree: function(ed, el)
{
var td = this.tree.getTreeEl().dom, nd = el.dom;
if (td.scrollLeft > nd.offsetLeft)
{
td.scrollLeft = nd.offsetLeft;
}
var w = Math.min(this.maxWidth, (td.clientWidth > 20 ? td.clientWidth : td.offsetWidth) - Math.max(0, nd.offsetLeft - td.scrollLeft) - 5);
this.setSize(w, '');
},
triggerEdit: function(node, e, colIndex)
{
var obj = e.target;
if (Ext.select('.x-tree-node-anchor', false, obj).getCount() == 1)
{
obj = Ext.select('.x-tree-node-anchor', false, obj).elements[0].firstChild;
}
else if (obj.nodeName == 'SPAN' || obj.nodeName == 'DIV')
{
obj = e.target;
}
else
{
return false;
}
this.completeEdit();
this.editNode = node;
this.editCol = obj;
this.editColIndex = colIndex;
this.startEdit(obj);
if (obj.nodeName == 'DIV')
{
var width = obj.offsetWidth;
this.setSize(width);
}
},
bindScroll: function()
{
this.tree.getTreeEl().on('scroll', this.cancelEdit, this);
},
beforeNodeClick: function(node, e)
{
var sinceLast = (this.lastClick ? this.lastClick.getElapsed() : 0);
this.lastClick = new Date();
if (sinceLast > this.editDelay && this.tree.getSelectionModel().isSelected(node))
{
e.stopEvent();
this.triggerEdit(node, e);
return false;
}
else
{
this.completeEdit();
}
},
updateNode: function(ed, value)
{
if (value != '' && value != this.editCol.innerHTML)
{
value = (this.editNode.renderers[this.editColIndex].renderer ? this.editNode.renderers[this.editColIndex].renderer(value) : value);
this.tree.getTreeEl().un('scroll', this.cancelEdit, this);
this.editNode.cols[this.editColIndex] = value; //for internal use only
this.editNode.attributes[this.editColIndex] = value; //duplicate into array of node attributes
this.editCol.innerHTML = value;
}
},
onHide: function()
{
Ext.tree.ColumnTreeEditor.superclass.onHide.call(this);
if (this.editNode)
{
this.editNode.ui.focus();
}
},
onSpecialKey: function(field, e)
{
var k = e.getKey();
if (k == e.ESC)
{
e.stopEvent();
this.cancelEdit();
}
else if (k == e.ENTER && !e.hasModifier())
{
e.stopEvent();
this.completeEdit();
}
}
});
ColumnNodeUI.js
Ext.tree.ColumnTree = Ext.extend(Ext.tree.TreePanel,
{
lines: false,
borderWidth: Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
cls: 'x-column-tree',
collapsible: false,
onRender: function()
{
Ext.tree.ColumnTree.superclass.onRender.apply(this, arguments);
this.headers = this.body.createChild(
{
cls: 'x-tree-headers'
}, this.innerCt.dom);
var cols = this.columns, c;
var totalWidth = 0;
for (var i = 0, len = cols.length; i < len; i++)
{
c = cols[i];
totalWidth += c.width;
this.headers.createChild(
{
cls: 'x-tree-hd ' + (c.cls ? c.cls + '-hd' : ''),
cn:
{
cls: 'x-tree-hd-text',
html: c.header
},
style: 'width:' + (c.width - this.borderWidth) + 'px;'
});
}
this.headers.createChild(
{
cls: 'x-clear'
});
// prevent floats from wrapping when clipped
this.headers.setWidth(totalWidth);
this.innerCt.setWidth(totalWidth);
},
initComponent: function()
{
Ext.tree.ColumnTree.superclass.initComponent.apply(this, arguments);
this.addEvents('beforecelledit');
for (var i = 0; i < this.columns.length; i++)
{
var col = this.columns[i];
if (col.editorField)
{
var editor = new Ext.tree.ColumnTreeEditor(this, col.dataIndex, col.editorField);
this.editor = editor;
}
}
}
});
Ext.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI,
{
focus: Ext.emptyFn, // prevent odd scrolling behavior
renderElements: function(n, a, targetNode, bulkRender)
{
this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
var t = n.getOwnerTree();
var cols = t.columns;
var bw = t.borderWidth;
var c = cols[0];
n.cols = new Array();
n.renderers = new Array();
var text = n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]);
n.cols[cols[0].dataIndex] = text;
var buf =
[
'<li class="x-tree-node" unselectable="on"><div ext:tree-node-id="',
n.id,
'" class="x-tree-node-el x-tree-node-leaf ',
a.cls,
'" unselectable="on">',
'<div class="x-tree-col" style="width:',
c.width - bw,
'px;" unselectable="on">',
'<span class="x-tree-node-indent" unselectable="on">',
this.indentMarkup,
"</span>",
'<img src="',
this.emptyIcon,
'" class="x-tree-ec-icon x-tree-elbow" unselectable="on">',
'<img src="',
a.icon || this.emptyIcon,
'" class="x-tree-node-icon',
(a.icon ? ' x-tree-node-inline-icon' : ''),
(a.iconCls ? ' ' + a.iconCls : ''),
'" unselectable="on">',
'<a hidefocus="on" class="x-tree-node-anchor" href="',
a.href ? a.href : '#',
'" tabIndex="1" ',
a.hrefTarget ? ' target="' + a.hrefTarget + '"' : '',
' unselectable="on">',
'<span unselectable="on">',
text,
'</span></a>',
'</div>'
];
for (var i = 1, len = cols.length; i < len; i++)
{
c = cols[i];
var text = (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]);
n.cols[c.dataIndex] = text;
n.renderers[c.dataIndex] = { renderer: ( c.renderer ? c.renderer : false ) }
buf.push('<div class="x-tree-col ', (c.cls ? c.cls : ''), '" style="width:', c.width - bw,
'px;" unselectable="on">', '<div class="x-tree-col-text x-tree-col-', c.dataIndex, '" unselectable="on">', text, '</div>', '</div>');
}
buf.push('<div class="x-clear" unselectable="on"></div></div>',
'<ul class="x-tree-node-ct" style="display:none;" unselectable="on"></ul>', '</li>');
if (bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl())
{
this.wrap = Ext.DomHelper.insertHtml('beforeBegin', n.nextSibling.ui.getEl(), buf.join(''));
}
else
{
this.wrap = Ext.DomHelper.insertHtml('beforeEnd', targetNode, buf.join(''));
}
this.elNode = this.wrap.childNodes[0];
this.ctNode = this.wrap.childNodes[1];
var cs = this.elNode.firstChild.childNodes;
this.indentNode = cs[0];
this.ecNode = cs[1];
this.iconNode = cs[2];
this.anchor = cs[3];
this.textNode = cs[3].firstChild;
}
});
editor_test.js
Ext.BLANK_IMAGE_URL = '../resources/images/default/s.gif';
Ext.onReady(function()
{
var store = new Ext.data.SimpleStore(
{
fields:
[
'chart_report'
],
data:
[
[
'Password Reset Summary'
],
[
'Password Reset By Agents'
]
]
});
var store2 = new Ext.data.SimpleStore(
{
fields:
[
'report'
],
data:
[
[
'Pasry'
],
[
'Pasents'
]
]
});
var combo_config =
{
id: 'chart_combo_list',
store: store,
displayField: 'chart_report',
xtype: 'combo',
mode: 'local',
triggerAction: 'all',
width: 100,
emptyText: 'Please select report type...',
selectOnFocus: true
};
var combo_config2 =
{
id: 'chart_combo_list2',
store: store2,
displayField: 'report',
xtype: 'combo',
mode: 'local',
triggerAction: 'all',
width: 100,
emptyText: 'Please select report type..',
selectOnFocus: true
};
var tree = new Ext.tree.ColumnTree(
{
el: 'tree-ct',
width: 700,
autoHeight: true,
rootVisible: false,
autoScroll: true,
expandable: false,
enableDD: true,
title: 'Menu Configuration',
tbar:
[
{
text: 'Save Menu',
tooltip: 'Save Menu',
iconCls: 'save-icon',
listeners:
{
'click': function()
{
var json = tree.toJsonString(null, function(key, val)
{
return (key == 'leaf' || key == 'id' || key == 'menu_item' || key == 'menu_url');
},
{
menu_item: 'text',
menu_url: 'href'
});
alert(json);
},
scope: this
}
},
{
xtype: 'tbseparator'
},
{
text: 'Add Folder Item',
tooltip: 'Add Folder Item',
iconCls: 'folder-icon',
listeners:
{
'click': function()
{
var selectedItem = tree.getSelectionModel().getSelectedNode();
if (! selectedItem)
{
selectedItem = tree.getRootNode();
}
handleCreate = function (btn, text, cBoxes)
{
if (btn == 'ok' && text)
{
var newNode = new Ext.tree.TreeNode(
{
menu_item: text,
menu_url: '',
leaf: false,
expandable: true,
uiProvider: Ext.tree.ColumnNodeUI
});
if (selectedItem.isLeaf())
{
selectedItem.parentNode.insertBefore(newNode, selectedItem.nextSibling);
}
else
{
selectedItem.insertBefore(newNode, selectedItem.firstChild);
}
}
}
Ext.MessageBox.show(
{
title: 'Add new Folder Item',
msg: 'Name of Folder Item:',
buttons: Ext.MessageBox.OKCANCEL,
prompt: true,
fn: handleCreate
});
}
}
},
{
xtype: 'tbseparator'
},
{
text: 'Add Page Item',
tooltip: 'Add Page Item',
iconCls: 'page-icon',
listeners:
{
'click': function()
{
var selectedItem = tree.getSelectionModel().getSelectedNode();
if (!selectedItem)
{
Ext.Msg.alert('Warning', 'Please select an Item after which you want to add a new one.');
return false;
}
handleCreate = function (btn, text, cBoxes)
{
if (btn == 'ok' && text)
{
var newNode = new Ext.tree.TreeNode(
{
menu_item: text,
menu_url: '',
leaf: true,
allowChildren: false,
uiProvider: Ext.tree.ColumnNodeUI
});
if (selectedItem.isLeaf())
{
selectedItem.parentNode.insertBefore(newNode, selectedItem.nextSibling);
}
else
{
selectedItem.insertBefore(newNode, selectedItem.firstChild);
}
}
}
Ext.MessageBox.show(
{
title: 'Add new Page Item',
msg: 'Name of Page Item:',
buttons: Ext.MessageBox.OKCANCEL,
prompt: true,
fn: handleCreate
});
}
}
},
{
xtype: 'tbseparator'
},
{
text: 'Delete Item',
tooltip: 'Delete Item',
iconCls: 'delete-icon',
listeners:
{
'click': function()
{
var selectedItem = tree.getSelectionModel().getSelectedNode();
if (!selectedItem)
{
Ext.Msg.alert('Warning', 'Please select an Item to delete.');
return false;
}
handleDelete = function (btn)
{
if (btn == 'ok')
{
selectedItem.remove();
}
}
Ext.MessageBox.show(
{
title: 'Confirm your action',
msg: 'Are you sure you want to delete this item and its children?',
buttons: Ext.MessageBox.OKCANCEL,
fn: handleDelete
});
}
}
},
{
xtype: 'tbseparator'
}
],
columns:
[
{
header: 'Menu Item',
width: 200,
dataIndex: 'menu_item'
// editorField: combo_config
},
{
header: 'URL',
width: 150,
dataIndex: 'menu_url',
editorField:
{
xtype: 'textfield',
editable: true
}
},
{
header: 'Approval Status',
width: 120,
dataIndex: 'approval',
editorField: combo_config2
}
],
loader: new Ext.tree.TreeLoader(
{
preloadChildren: true,
uiProviders:
{
'col': Ext.tree.ColumnNodeUI
}
}),
root: new Ext.tree.AsyncTreeNode(
{
allowChildren: true,
children:
[
{
menu_item: 'Current State',
menu_url: 'blah blah',
uiProvider: 'col',
approval: 'text',
children:
[
{
menu_item: 'Executive Summary',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability Scorecard',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability Distribution',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
}
]
},
{
menu_item: 'Trends',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
children:
[
{
menu_item: 'Host Changes',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Hosts and Vulnerability',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Application and Vulnerability',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability by Operation System',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability by Networks',
menu_url: 'blah blah',
approval: 'text',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability by Application Group',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
}
]
},
{
menu_item: 'Technical',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
children:
[
{
menu_item: 'Risk Matrix',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Host Inventory',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Application Inventory',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Vulnerability Inventory',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Most Common Vulnerabilites',
menu_url: 'blah blah',
uiProvider: 'col',
approval: '',
leaf: true
},
{
menu_item: 'Most Common SANS Vulnerabilities',
menu_url: 'blah blah',
uiProvider: 'col',
approval: '',
leaf: true
},
{
menu_item: 'Most Vulnerable Applications',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'Most Vulnerable Hosts',
menu_url: 'blah blah',
approval: '',
uiProvider: 'col',
leaf: true
},
{
menu_item: 'PCI Compliance',
menu_url: 'blah blah',
uiProvider: 'col',
approval: '',
leaf: true
}
]
}
]
})
});
tree.render();
tree.expandAll();
});
Mike V.