<apex:component controller="HierarchyController" allowDML="true">
<!-- a VF component for modifying the hierarchy of an SObject (Campaign,
Account, etc.) using an ExtJS TreePanel -->
<!-- Jeff Trull jetrull@sbcglobal.net 2010-12-01 -->
<apex:attribute name="object" type="Object" description="sObject to edit hierarchy on"
default="Campaign" />
<apex:attribute name="fn" type="String" description="name of a Javascript function to call with ID once a Campaign is selected."
required="false" default="" />
<!-- load ExtJS -->
<apex:stylesheet value="{!$Resource.ExtJS}/resources/css/ext-all.css"
/>
<apex:includeScript value="{!$Resource.ExtJS}/adapter/ext/ext-base.js"
/>
<apex:includeScript value="{!$Resource.ExtJS}/ext-all.js" />
<script type="text/javascript">
Ext.BLANK_IMAGE_URL = "{!$Resource.ExtJS}/resources/images/default/s.gif"
</script>
<!-- Create a JS function that calls the child object finding Apex method,
-->
<!-- then loads the results when the AJAX request returns -->
<apex:actionFunction name="fetchObjects" action="{!findChildObjects}"
rerender="unrollpanel" oncomplete="fetchCallback.call();">
<apex:param name="sobjname" assignTo="{!fetchObject}" value="" />
<apex:param name="parentid" assignTo="{!fetchParentId}" value="" />
</apex:actionFunction>
<!-- Define function returning child data. Function definition changes
after reach AJAX response -->
<!-- to contain the result of our child record search -->
<apex:outputPanel id="unrollpanel">
<!-- if not in an outputPanel, the rerender fails! -->
<!-- A function that just takes the results from a single tree query and
returns it -->
<!-- regenerated after every tree node expansion and then called from
the actionFunction's oncomplete -->
<script type="text/javascript">
function nodeResults(forceLeaf) {
var loadData = new Array();
// unroll child nodes list with apex:repeat
// node properties: if level 5, a leaf (cannot have children)
// if no children, show as folder, but without "expand" button
// if has children, folder with expand button
< apex: repeat value = "{!fetchResults}"
var = "obj" > loadData.push({
id: "{!obj.id}",
text: "{!obj.name}",
expanded: { !! obj.hasChildren
},
loaded: { !! obj.hasChildren
},
leaf: forceLeaf
}); < /apex:repeat>
return loadData;
}
</script>
</apex:outputPanel>
<!-- Create a JS function to call the Apex method that reassigns a record
parent -->
<apex:actionFunction name="updateParent" action="{!setParent}" rerender="setstatuspanel">
<apex:param name="parent" assignTo="{!parentIdToSet}" value="" />
<apex:param name="child" assignTo="{!childIdToSet}" value="" />
</apex:actionFunction>
<!-- a function regenerated by the parent ID set result after server completes
-->
<apex:outputPanel id="setstatuspanel">
<script type="text/javascript">
function getSetStatus() {
return {
!idSetSuccess
};
}
</script>
</apex:outputPanel>
<script type="text/javascript">
Ext.onReady(function() {
var tree = new Ext.tree.TreePanel({
renderTo: 'treediv',
useArrows: true,
autoScroll: true,
animate: true,
containerScroll: true,
border: false,
enableDD: true,
loader: new Ext.tree.TreeLoader({
directFn: function(loadid, callback) {
// determine if we are expanding a level 4 record (current limit is 5 -> leaf)
var lvl4 = (tree.getNodeById(loadid).getDepth() >= 4);
// establish callback for when AJAX request completes
fetchCallback = function() {
callback.apply(window, [nodeResults(lvl4),
{
status: true
}]);
}.createDelegate(this);
// call actionFunction created for loading
fetchObjects('{!object}', loadid);
}
}),
root: new Ext.tree.AsyncTreeNode({
text: 'All {!object}s',
expanded: false,
draggable: false,
id: 'root' // because you cannot use a blank id (Ext would create one)
}),
listeners: {
beforemovenode: function(tree, node, oldParent, newParent) {
if (oldParent.id == newParent.id) {
// just a change to ordering; no need for server request
return true;
}
var parent = newParent.id;
if (parent == 'root') {
parent = ''; // translate back into Apex world
}
// this is, unfortunately, a synchronous approach
updateParent(parent, node.id);
// if level has changed to/from L5, adjust "leaf" property
if ((oldParent.getDepth() == 4) || (newParent.getDepth() == 4)) {
node.leaf = (newParent.getDepth() == 4);
}
return true;
},
movenode: function(tree, node, oldParent, newParent) {
// TreePanel automatically shows nodes without children as leafs
// it will also automatically change those leafs into folders when another
// node is dropped on them. However, it will not change the folders back
// into leaf icons when the last child is removed. This is a workaround.
if (!oldParent.hasChildNodes()) {
oldParent.getUI().removeClass("x-tree-node-collapsed");
oldParent.getUI().addClass("x-tree-node-leaf");
}
},
beforedblclick: function(node) {
if (('{!fn}' != '') && (node.id != 'root')) {
{
!fn
}(node.id);
}
}
}
});
tree.getRootNode().expand(); // trigger load of top level objects
});
</script>
<div id="treediv" />
</apex:component>