diff --git a/plugins/dojo/src/main/java/org/apache/struts2/dojo/components/Tree.java b/plugins/dojo/src/main/java/org/apache/struts2/dojo/components/Tree.java
index f73c2e1d3..1b8b07198 100644
--- a/plugins/dojo/src/main/java/org/apache/struts2/dojo/components/Tree.java
+++ b/plugins/dojo/src/main/java/org/apache/struts2/dojo/components/Tree.java
@@ -37,18 +37,15 @@ import com.opensymphony.xwork2.util.ValueStack;
*
* Renders a tree widget with AJAX support.
*
- * The "id "attribute is normally specified, such that it could be looked up using
- * javascript if necessary. The "id" attribute is required if the "treeSelectedTopic"
- * attribute is going to be used.
+ * The "id "attribute is normally specified(recommended), such that it could be looked up using
+ * javascript if necessary. The "id" attribute is required if the "selectedNotifyTopic" or the
+ * "href" attributes are going to be used.
*
*
*
- * Examples
- *
+ *
*
- *
- *
- * <-- statically -->
+ * Tree loaded statically
* <s:tree id="..." label="...">
* <s:treenode id="..." label="..." />
* <s:treenode id="..." label="...">
@@ -57,18 +54,62 @@ import com.opensymphony.xwork2.util.ValueStack;
* &;lt;/s:treenode>
* <s:treenode id="..." label="..." />
* </s:tree>
- *
- * <-- dynamically -->
+ *
+ *
+ *
+ *
+ * Tree loaded dynamically
+ *
* <s:tree
* id="..."
* rootNode="..."
* nodeIdProperty="..."
* nodeTitleProperty="..."
* childCollectionProperty="..." />
- *
- *
*
- *
+ *
+ *
+ *
+ * Tree loaded dynamically using AJAX
+ *
+ * <s:url id="nodesUrl" namespace="/nodecorate" action="getNodes" />
+ * <div style="float:left; margin-right: 50px;">
+ * <sx:tree id="tree" href="%{#nodesUrl}" />
+ * </div>
+ *
+ *
+ * On this example the url specified on the "href" attibute will be called to load
+ * the elements on the root. The response is expected to be a JSON array of objects like:
+ *
+ *
+ * [
+ * {
+ * label: "Node 1",
+ * hasChildren: false,
+ * id: "Node1"
+ * },
+ * {
+ * label: "Node 2",
+ * hasChildren: true,
+ * id: "Node2"
+ * },
+ * ]
+ *
+ *
+ * "label" is the text that will be displayed for the node. "hasChildren" marks the node has
+ * having children or not (if true, a plus icon will be assigned to the node so it can be
+ * expanded). The "id" attribute will be used to load the children of the node, when the node
+ * is expanded. When a node is expanded a request will be made to the url in the "href" attribute
+ * and the node's "id" will be passed in the parameter "nodeId".
+ *
+ * The children collection for a node will be loaded only once, to reload the children of a
+ * node, use the "reload()" function of the treenode widget. To reload the children nodes of "Node1"
+ * from the example above use the following javascript:
+ *
+ *
+ * dojo.widget.byId("Node1").reload();
+ *
+ *
*/
@StrutsTag(name="tree", tldTagClass="org.apache.struts2.dojo.views.jsp.ui.TreeTag", description="Render a tree widget.")
public class Tree extends ClosingUIBean {
@@ -76,31 +117,33 @@ public class Tree extends ClosingUIBean {
private static final String TEMPLATE = "tree-close";
private static final String OPEN_TEMPLATE = "tree";
- private String toggle = "fade";
- private String selectedNotifyTopics;
- private String expandedNotifyTopics;
- private String collapsedNotifyTopics;
+ protected String toggle;
+ protected String selectedNotifyTopics;
+ protected String expandedNotifyTopics;
+ protected String collapsedNotifyTopics;
protected String rootNodeAttr;
protected String childCollectionProperty;
protected String nodeTitleProperty;
protected String nodeIdProperty;
- private String showRootGrid;
-
- private String showGrid;
- private String blankIconSrc;
- private String gridIconSrcL;
- private String gridIconSrcV;
- private String gridIconSrcP;
- private String gridIconSrcC;
- private String gridIconSrcX;
- private String gridIconSrcY;
- private String expandIconSrcPlus;
- private String expandIconSrcMinus;
- private String iconWidth;
- private String iconHeight;
- private String toggleDuration;
- private String templateCssPath;
+ protected String showRootGrid;
+ protected String showGrid;
+ protected String blankIconSrc;
+ protected String gridIconSrcL;
+ protected String gridIconSrcV;
+ protected String gridIconSrcP;
+ protected String gridIconSrcC;
+ protected String gridIconSrcX;
+ protected String gridIconSrcY;
+ protected String expandIconSrcPlus;
+ protected String expandIconSrcMinus;
+ protected String iconWidth;
+ protected String iconHeight;
+ protected String toggleDuration;
+ protected String templateCssPath;
+ protected String href;
+ protected String errorNotifyTopics;
+
public Tree(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
super(stack, request, response);
}
@@ -108,12 +151,12 @@ public class Tree extends ClosingUIBean {
public boolean start(Writer writer) {
boolean result = super.start(writer);
- if (this.label == null) {
+ if (this.label == null && (href == null)) {
if ((rootNodeAttr == null)
|| (childCollectionProperty == null)
|| (nodeTitleProperty == null)
|| (nodeIdProperty == null)) {
- fieldError("label","The TreeTag requires either a value for 'label' or ALL of 'rootNode', " +
+ fieldError("label","The TreeTag requires either a value for 'label' or 'href' or ALL of 'rootNode', " +
"'childCollectionProperty', 'nodeTitleProperty', and 'nodeIdProperty'", null);
}
}
@@ -125,6 +168,8 @@ public class Tree extends ClosingUIBean {
if (toggle != null) {
addParameter("toggle", findString(toggle));
+ } else {
+ addParameter("toggle", "fade");
}
if (selectedNotifyTopics != null) {
@@ -212,6 +257,11 @@ public class Tree extends ClosingUIBean {
if (templateCssPath != null) {
addParameter("templateCssPath", findString(templateCssPath));
}
+ if (href != null)
+ addParameter("href", findString(href));
+ if (errorNotifyTopics != null)
+ addParameter("errorNotifyTopics", findString(errorNotifyTopics));
+
}
@Override
@@ -469,5 +519,17 @@ public class Tree extends ClosingUIBean {
public void setSelectedNotifyTopics(String selectedNotifyTopics) {
this.selectedNotifyTopics = selectedNotifyTopics;
}
+
+ @StrutsTagAttribute(description="Url used to load the list of children nodes for an specific node, whose id will be " +
+ "passed as a parameter named 'nodeId' (empty for root)")
+ public void setHref(String href) {
+ this.href = href;
+ }
+
+ @StrutsTagAttribute(description="Comma delimmited list of topics that will published after the request(if the request fails)." +
+ "Only valid if 'href' is set")
+ public void setErrorNotifyTopics(String errorNotifyTopics) {
+ this.errorNotifyTopics = errorNotifyTopics;
+ }
}
diff --git a/plugins/dojo/src/main/java/org/apache/struts2/dojo/views/jsp/ui/TreeTag.java b/plugins/dojo/src/main/java/org/apache/struts2/dojo/views/jsp/ui/TreeTag.java
index a00e2c533..4eb673212 100644
--- a/plugins/dojo/src/main/java/org/apache/struts2/dojo/views/jsp/ui/TreeTag.java
+++ b/plugins/dojo/src/main/java/org/apache/struts2/dojo/views/jsp/ui/TreeTag.java
@@ -36,32 +36,33 @@ public class TreeTag extends AbstractClosingTag {
private static final long serialVersionUID = 2735218501058548013L;
- private String toggle;
- private String selectedNotifyTopics;
- private String expandedNotifyTopics;
- private String collapsedNotifyTopics;
- private String rootNode;
- private String childCollectionProperty;
- private String nodeTitleProperty;
- private String nodeIdProperty;
- private String showRootGrid;
-
- private String showGrid;
- private String blankIconSrc;
- private String gridIconSrcL;
- private String gridIconSrcV;
- private String gridIconSrcP;
- private String gridIconSrcC;
- private String gridIconSrcX;
- private String gridIconSrcY;
- private String expandIconSrcPlus;
- private String expandIconSrcMinus;
- private String iconWidth;
- private String iconHeight;
- private String toggleDuration;
- private String templateCssPath;
-
+ protected String toggle;
+ protected String selectedNotifyTopics;
+ protected String expandedNotifyTopics;
+ protected String collapsedNotifyTopics;
+ protected String rootNode;
+ protected String childCollectionProperty;
+ protected String nodeTitleProperty;
+ protected String nodeIdProperty;
+ protected String showRootGrid;
+ protected String showGrid;
+ protected String blankIconSrc;
+ protected String gridIconSrcL;
+ protected String gridIconSrcV;
+ protected String gridIconSrcP;
+ protected String gridIconSrcC;
+ protected String gridIconSrcX;
+ protected String gridIconSrcY;
+ protected String expandIconSrcPlus;
+ protected String expandIconSrcMinus;
+ protected String iconWidth;
+ protected String iconHeight;
+ protected String toggleDuration;
+ protected String templateCssPath;
+ protected String href;
+ protected String errorNotifyTopics;
+
public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
return new Tree(stack,req,res);
}
@@ -70,53 +71,32 @@ public class TreeTag extends AbstractClosingTag {
super.populateParams();
Tree tree = (Tree) component;
- if (childCollectionProperty != null)
- tree.setChildCollectionProperty(childCollectionProperty);
- if (nodeIdProperty != null)
- tree.setNodeIdProperty(nodeIdProperty);
- if (nodeTitleProperty != null)
- tree.setNodeTitleProperty(nodeTitleProperty);
- if (rootNode != null)
- tree.setRootNode(rootNode);
- if (toggle != null)
- tree.setToggle(toggle);
- if (selectedNotifyTopics != null)
- tree.setSelectedNotifyTopics(selectedNotifyTopics);
- if (expandedNotifyTopics != null)
- tree.setExpandedNotifyTopics(expandedNotifyTopics);
- if (collapsedNotifyTopics != null)
- tree.setCollapsedNotifyTopics(collapsedNotifyTopics);
- if (showRootGrid != null)
- tree.setShowRootGrid(showRootGrid);
+ tree.setChildCollectionProperty(childCollectionProperty);
+ tree.setNodeIdProperty(nodeIdProperty);
+ tree.setNodeTitleProperty(nodeTitleProperty);
+ tree.setRootNode(rootNode);
+ tree.setToggle(toggle);
+ tree.setSelectedNotifyTopics(selectedNotifyTopics);
+ tree.setExpandedNotifyTopics(expandedNotifyTopics);
+ tree.setCollapsedNotifyTopics(collapsedNotifyTopics);
+ tree.setShowRootGrid(showRootGrid);
- if (showGrid != null)
- tree.setShowGrid(showGrid);
- if (blankIconSrc != null)
- tree.setBlankIconSrc(blankIconSrc);
- if (gridIconSrcL != null)
- tree.setGridIconSrcL(gridIconSrcC);
- if (gridIconSrcV != null)
- tree.setGridIconSrcV(gridIconSrcV);
- if (gridIconSrcP != null)
- tree.setGridIconSrcP(gridIconSrcP);
- if (gridIconSrcC != null)
- tree.setGridIconSrcC(gridIconSrcC);
- if (gridIconSrcX != null)
- tree.setGridIconSrcX(gridIconSrcX);
- if (gridIconSrcY != null)
- tree.setGridIconSrcY(gridIconSrcY);
- if (expandIconSrcPlus != null)
- tree.setExpandIconSrcPlus(expandIconSrcPlus);
- if (expandIconSrcMinus != null)
- tree.setExpandIconSrcMinus(expandIconSrcMinus);
- if (iconWidth != null)
- tree.setIconWidth(iconWidth);
- if (iconHeight != null)
- tree.setIconHeight(iconHeight);
- if (toggleDuration != null)
- tree.setToggleDuration(toggleDuration);
- if (templateCssPath != null)
- tree.setTemplateCssPath(templateCssPath);
+ tree.setShowGrid(showGrid);
+ tree.setBlankIconSrc(blankIconSrc);
+ tree.setGridIconSrcL(gridIconSrcC);
+ tree.setGridIconSrcV(gridIconSrcV);
+ tree.setGridIconSrcP(gridIconSrcP);
+ tree.setGridIconSrcC(gridIconSrcC);
+ tree.setGridIconSrcX(gridIconSrcX);
+ tree.setGridIconSrcY(gridIconSrcY);
+ tree.setExpandIconSrcPlus(expandIconSrcPlus);
+ tree.setExpandIconSrcMinus(expandIconSrcMinus);
+ tree.setIconWidth(iconWidth);
+ tree.setIconHeight(iconHeight);
+ tree.setToggleDuration(toggleDuration);
+ tree.setTemplateCssPath(templateCssPath);
+ tree.setHref(href);
+ tree.setErrorNotifyTopics(errorNotifyTopics);
}
public String getToggle() {
@@ -305,5 +285,13 @@ public class TreeTag extends AbstractClosingTag {
public void setSelectedNotifyTopics(String selectedNotifyTopics) {
this.selectedNotifyTopics = selectedNotifyTopics;
}
+
+ public void setHref(String href) {
+ this.href = href;
+ }
+
+ public void setErrorNotifyTopics(String errorNotifyTopics) {
+ this.errorNotifyTopics = errorNotifyTopics;
+ }
}
diff --git a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTree.js b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTree.js
new file mode 100644
index 000000000..fa4a26330
--- /dev/null
+++ b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTree.js
@@ -0,0 +1,59 @@
+dojo.provide("struts.widget.StrutsTree");
+
+dojo.require("dojo.widget.Tree");
+
+dojo.widget.defineWidget(
+ "struts.widget.StrutsTree",
+ dojo.widget.Tree, {
+ widgetType : "StrutsTree",
+
+ href : "",
+ errorNotifyTopics : "",
+ errorNotifyTopicsArray : null,
+
+ postCreate : function() {
+ struts.widget.StrutsTree.superclass.postCreate.apply(this);
+
+ //error topics
+ if(!dojo.string.isBlank(this.errorNotifyTopics)) {
+ this.errorNotifyTopicsArray = this.errorNotifyTopics.split(",");
+ }
+
+ var self = this;
+ if(!dojo.string.isBlank(this.href)) {
+ dojo.io.bind({
+ url: this.href,
+ useCache: false,
+ preventCache: true,
+ handler: function(type, data, e) {
+ if(type == 'load') {
+ //data should be an array
+ if(data) {
+ dojo.lang.forEach(data, function(descr) {
+ //create node for eachd descriptor
+ var newNode = dojo.widget.createWidget("struts:StrutsTreeNode",{
+ title : descr.label,
+ isFolder: descr.hasChildren,
+ widgetId: descr.id
+ });
+ self.addChild(newNode);
+ });
+ }
+ } else {
+ //publish error topics
+ if(self.errorNotifyTopicsArray) {
+ dojo.lang.forEach(self.errorNotifyTopicsArray, function(topic) {
+ try {
+ dojo.event.topic.publish(topic, data, e, self);
+ } catch(ex){
+ dojo.debug(ex);
+ }
+ });
+ }
+ }
+ },
+ mimetype: "text/json"
+ });
+ }
+ }
+});
\ No newline at end of file
diff --git a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTreeNode.js b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTreeNode.js
new file mode 100644
index 000000000..1c5a80660
--- /dev/null
+++ b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTreeNode.js
@@ -0,0 +1,66 @@
+dojo.provide("struts.widget.StrutsTreeNode");
+
+dojo.require("dojo.widget.TreeNode");
+
+dojo.widget.defineWidget(
+ "struts.widget.StrutsTreeNode",
+ dojo.widget.TreeNode, {
+ widgetType : "StrutsTreeNode",
+
+ loaded : false,
+
+ expand : function() {
+ if(!this.loaded) {
+ this.reload();
+ }
+ struts.widget.StrutsTreeNode.superclass.expand.apply(this);
+ },
+
+ removeChildren : function() {
+ var self = this;
+ var childrenCopy = dojo.lang.toArray(this.children);
+ dojo.lang.forEach(childrenCopy, function(node) {
+ self.removeNode(node);
+ });
+ },
+
+ reload : function() {
+ var href = this.tree.href;
+ this.loaded = true;
+
+ if(!dojo.string.isBlank(href)) {
+ //clear children list
+ this.removeChildren();
+ //pass widgetId as parameter
+ var tmpHref = href + (href.indexOf("?") > -1 ? "&" : "?") + "nodeId=" + this.widgetId;
+
+ var self = this;
+ this.markLoading();
+
+ dojo.io.bind({
+ url: tmpHref,
+ useCache: false,
+ preventCache: true,
+ handler: function(type, data, e) {
+ if(type == 'load') {
+ //data should be an array
+ if(data) {
+ dojo.lang.forEach(data, function(descr) {
+ //create node for eachd descriptor
+ var newNode = dojo.widget.createWidget("struts:StrutsTreeNode",{
+ title : descr.label,
+ isFolder: descr.hasChildren,
+ widgetId: descr.id
+ });
+ self.addChild(newNode);
+ });
+ }
+ }
+
+ self.unMarkLoading();
+ },
+ mimetype: "text/json"
+ });
+ }
+ }
+});
\ No newline at end of file
diff --git a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/__package__.js b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/__package__.js
index ddf55e554..8f1f6fcd2 100644
--- a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/__package__.js
+++ b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/__package__.js
@@ -7,6 +7,8 @@ dojo.kwCompoundRequire({
"struts.widget.StrutsDatePicker",
"struts.widget.BindEvent",
"struts.widget.StrutsTreeSelector",
- "struts.widget.StrutsTabContainer"]
+ "struts.widget.StrutsTabContainer",
+ "struts.widget.StrutsTreeNode",
+ "struts.widget.StrutsTree"]
});
dojo.provide("struts.widget.*");
diff --git a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js
index 54dc8658f..cdd0bbf5f 100644
--- a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js
+++ b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js
@@ -17706,6 +17706,968 @@ return t;
}
}
}});
-dojo.kwCompoundRequire({common:["struts.widget.Bind","struts.widget.BindDiv","struts.widget.BindAnchor","struts.widget.ComboBox","struts.widget.StrutsTimePicker","struts.widget.StrutsDatePicker","struts.widget.BindEvent","struts.widget.StrutsTreeSelector","struts.widget.StrutsTabContainer"]});
+dojo.provide("dojo.widget.TreeNode");
+dojo.widget.defineWidget("dojo.widget.TreeNode",dojo.widget.HtmlWidget,function(){
+this.actionsDisabled=[];
+},{widgetType:"TreeNode",loadStates:{UNCHECKED:"UNCHECKED",LOADING:"LOADING",LOADED:"LOADED"},actions:{MOVE:"MOVE",REMOVE:"REMOVE",EDIT:"EDIT",ADDCHILD:"ADDCHILD"},isContainer:true,lockLevel:0,templateString:(" "+"
"+"\t\t${this.title} "+" "+"
${this.afterLabel} "+"
"+"
").replace(/(>|<)\s+/g,"$1"),childIconSrc:"",childIconFolderSrc:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/closed.gif"),childIconDocumentSrc:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/document.gif"),childIcon:null,isTreeNode:true,objectId:"",afterLabel:"",afterLabelNode:null,expandIcon:null,title:"",object:"",isFolder:false,labelNode:null,titleNode:null,imgs:null,expandLevel:"",tree:null,depth:0,isExpanded:false,state:null,domNodeInitialized:false,isFirstChild:function(){
+return this.getParentIndex()==0?true:false;
+},isLastChild:function(){
+return this.getParentIndex()==this.parent.children.length-1?true:false;
+},lock:function(){
+return this.tree.lock.apply(this,arguments);
+},unlock:function(){
+return this.tree.unlock.apply(this,arguments);
+},isLocked:function(){
+return this.tree.isLocked.apply(this,arguments);
+},cleanLock:function(){
+return this.tree.cleanLock.apply(this,arguments);
+},actionIsDisabled:function(_ead){
+var _eae=this;
+var _eaf=false;
+if(this.tree.strictFolders&&_ead==this.actions.ADDCHILD&&!this.isFolder){
+_eaf=true;
+}
+if(dojo.lang.inArray(_eae.actionsDisabled,_ead)){
+_eaf=true;
+}
+if(this.isLocked()){
+_eaf=true;
+}
+return _eaf;
+},getInfo:function(){
+var info={widgetId:this.widgetId,objectId:this.objectId,index:this.getParentIndex(),isFolder:this.isFolder};
+return info;
+},initialize:function(args,frag){
+this.state=this.loadStates.UNCHECKED;
+for(var i=0;i0){
+for(var i=0;i<_eb4;i++){
+var img=this.tree.makeBlankImg();
+this.imgs.unshift(img);
+dojo.html.insertBefore(this.imgs[0],this.domNode.firstChild);
+}
+}
+if(_eb4<0){
+for(var i=0;i<-_eb4;i++){
+this.imgs.shift();
+dojo.html.removeNode(this.domNode.firstChild);
+}
+}
+},markLoading:function(){
+this._markLoadingSavedIcon=this.expandIcon.src;
+this.expandIcon.src=this.tree.expandIconSrcLoading;
+},unMarkLoading:function(){
+if(!this._markLoadingSavedIcon){
+return;
+}
+var im=new Image();
+im.src=this.tree.expandIconSrcLoading;
+if(this.expandIcon.src==im.src){
+this.expandIcon.src=this._markLoadingSavedIcon;
+}
+this._markLoadingSavedIcon=null;
+},setFolder:function(){
+dojo.event.connect(this.expandIcon,"onclick",this,"onTreeClick");
+this.expandIcon.src=this.isExpanded?this.tree.expandIconSrcMinus:this.tree.expandIconSrcPlus;
+this.isFolder=true;
+},createDOMNode:function(tree,_eb9){
+this.tree=tree;
+this.depth=_eb9;
+this.imgs=[];
+for(var i=0;i-1?"&":"?")+"nodeId="+this.widgetId;
+var self=this;
+this.markLoading();
+dojo.io.bind({url:_ece,useCache:false,preventCache:true,handler:function(type,data,e){
+if(type=="load"){
+if(data){
+dojo.lang.forEach(data,function(_ed3){
+var _ed4=dojo.widget.createWidget("struts:StrutsTreeNode",{title:_ed3.label,isFolder:_ed3.hasChildren,widgetId:_ed3.id});
+self.addChild(_ed4);
+});
+}
+}
+self.unMarkLoading();
+},mimetype:"text/json"});
+}
+}});
+dojo.provide("dojo.json");
+dojo.json={jsonRegistry:new dojo.AdapterRegistry(),register:function(name,_ed6,wrap,_ed8){
+dojo.json.jsonRegistry.register(name,_ed6,wrap,_ed8);
+},evalJson:function(json){
+try{
+return eval("("+json+")");
+}
+catch(e){
+dojo.debug(e);
+return json;
+}
+},serialize:function(o){
+var _edb=typeof (o);
+if(_edb=="undefined"){
+return "undefined";
+}else{
+if((_edb=="number")||(_edb=="boolean")){
+return o+"";
+}else{
+if(o===null){
+return "null";
+}
+}
+}
+if(_edb=="string"){
+return dojo.string.escapeString(o);
+}
+var me=arguments.callee;
+var _edd;
+if(typeof (o.__json__)=="function"){
+_edd=o.__json__();
+if(o!==_edd){
+return me(_edd);
+}
+}
+if(typeof (o.json)=="function"){
+_edd=o.json();
+if(o!==_edd){
+return me(_edd);
+}
+}
+if(_edb!="function"&&typeof (o.length)=="number"){
+var res=[];
+for(var i=0;i_f0f.getParentIndex()){
+_f11--;
+}
+return _f11;
+},onDrop:function(e){
+var _f13=this.position;
+this.onDragOut(e);
+var _f14=e.dragObject.treeNode;
+if(!dojo.lang.isObject(_f14)){
+dojo.raise("TreeNode not found in dragObject");
+}
+if(_f13=="onto"){
+return this.controller.move(_f14,this.treeNode,0);
+}else{
+var _f15=this.getTargetParentIndex(_f14,_f13);
+return this.controller.move(_f14,this.treeNode.parent,_f15);
+}
+}});
+dojo.dnd.TreeDNDController=function(_f16){
+this.treeController=_f16;
+this.dragSources={};
+this.dropTargets={};
+};
+dojo.lang.extend(dojo.dnd.TreeDNDController,{listenTree:function(tree){
+dojo.event.topic.subscribe(tree.eventNames.createDOMNode,this,"onCreateDOMNode");
+dojo.event.topic.subscribe(tree.eventNames.moveFrom,this,"onMoveFrom");
+dojo.event.topic.subscribe(tree.eventNames.moveTo,this,"onMoveTo");
+dojo.event.topic.subscribe(tree.eventNames.addChild,this,"onAddChild");
+dojo.event.topic.subscribe(tree.eventNames.removeNode,this,"onRemoveNode");
+dojo.event.topic.subscribe(tree.eventNames.treeDestroy,this,"onTreeDestroy");
+},unlistenTree:function(tree){
+dojo.event.topic.unsubscribe(tree.eventNames.createDOMNode,this,"onCreateDOMNode");
+dojo.event.topic.unsubscribe(tree.eventNames.moveFrom,this,"onMoveFrom");
+dojo.event.topic.unsubscribe(tree.eventNames.moveTo,this,"onMoveTo");
+dojo.event.topic.unsubscribe(tree.eventNames.addChild,this,"onAddChild");
+dojo.event.topic.unsubscribe(tree.eventNames.removeNode,this,"onRemoveNode");
+dojo.event.topic.unsubscribe(tree.eventNames.treeDestroy,this,"onTreeDestroy");
+},onTreeDestroy:function(_f19){
+this.unlistenTree(_f19.source);
+},onCreateDOMNode:function(_f1a){
+this.registerDNDNode(_f1a.source);
+},onAddChild:function(_f1b){
+this.registerDNDNode(_f1b.child);
+},onMoveFrom:function(_f1c){
+var _f1d=this;
+dojo.lang.forEach(_f1c.child.getDescendants(),function(node){
+_f1d.unregisterDNDNode(node);
+});
+},onMoveTo:function(_f1f){
+var _f20=this;
+dojo.lang.forEach(_f1f.child.getDescendants(),function(node){
+_f20.registerDNDNode(node);
+});
+},registerDNDNode:function(node){
+if(!node.tree.DNDMode){
+return;
+}
+var _f23=null;
+var _f24=null;
+if(!node.actionIsDisabled(node.actions.MOVE)){
+var _f23=new dojo.dnd.TreeDragSource(node.labelNode,this,node.tree.widgetId,node);
+this.dragSources[node.widgetId]=_f23;
+}
+var _f24=new dojo.dnd.TreeDropTarget(node.labelNode,this.treeController,node.tree.DNDAcceptTypes,node);
+this.dropTargets[node.widgetId]=_f24;
+},unregisterDNDNode:function(node){
+if(this.dragSources[node.widgetId]){
+dojo.dnd.dragManager.unregisterDragSource(this.dragSources[node.widgetId]);
+delete this.dragSources[node.widgetId];
+}
+if(this.dropTargets[node.widgetId]){
+dojo.dnd.dragManager.unregisterDropTarget(this.dropTargets[node.widgetId]);
+delete this.dropTargets[node.widgetId];
+}
+}});
+dojo.provide("dojo.widget.TreeBasicController");
+dojo.widget.defineWidget("dojo.widget.TreeBasicController",dojo.widget.HtmlWidget,{widgetType:"TreeBasicController",DNDController:"",dieWithTree:false,initialize:function(args,frag){
+if(this.DNDController=="create"){
+this.DNDController=new dojo.dnd.TreeDNDController(this);
+}
+},listenTree:function(tree){
+dojo.event.topic.subscribe(tree.eventNames.createDOMNode,this,"onCreateDOMNode");
+dojo.event.topic.subscribe(tree.eventNames.treeClick,this,"onTreeClick");
+dojo.event.topic.subscribe(tree.eventNames.treeCreate,this,"onTreeCreate");
+dojo.event.topic.subscribe(tree.eventNames.treeDestroy,this,"onTreeDestroy");
+if(this.DNDController){
+this.DNDController.listenTree(tree);
+}
+},unlistenTree:function(tree){
+dojo.event.topic.unsubscribe(tree.eventNames.createDOMNode,this,"onCreateDOMNode");
+dojo.event.topic.unsubscribe(tree.eventNames.treeClick,this,"onTreeClick");
+dojo.event.topic.unsubscribe(tree.eventNames.treeCreate,this,"onTreeCreate");
+dojo.event.topic.unsubscribe(tree.eventNames.treeDestroy,this,"onTreeDestroy");
+},onTreeDestroy:function(_f2a){
+var tree=_f2a.source;
+this.unlistenTree(tree);
+if(this.dieWithTree){
+this.destroy();
+}
+},onCreateDOMNode:function(_f2c){
+var node=_f2c.source;
+if(node.expandLevel>0){
+this.expandToLevel(node,node.expandLevel);
+}
+},onTreeCreate:function(_f2e){
+var tree=_f2e.source;
+var _f30=this;
+if(tree.expandLevel){
+dojo.lang.forEach(tree.children,function(_f31){
+_f30.expandToLevel(_f31,tree.expandLevel-1);
+});
+}
+},expandToLevel:function(node,_f33){
+if(_f33==0){
+return;
+}
+var _f34=node.children;
+var _f35=this;
+var _f36=function(node,_f38){
+this.node=node;
+this.expandLevel=_f38;
+this.process=function(){
+for(var i=0;i",isExpanded:true,isTree:true,objectId:"",controller:"",selector:"",menu:"",expandLevel:"",blankIconSrc:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_blank.gif"),gridIconSrcT:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_t.gif"),gridIconSrcL:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_l.gif"),gridIconSrcV:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_v.gif"),gridIconSrcP:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_p.gif"),gridIconSrcC:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_c.gif"),gridIconSrcX:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_x.gif"),gridIconSrcY:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_y.gif"),gridIconSrcZ:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_z.gif"),expandIconSrcPlus:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_expand_plus.gif"),expandIconSrcMinus:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_expand_minus.gif"),expandIconSrcLoading:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_loading.gif"),iconWidth:18,iconHeight:18,showGrid:true,showRootGrid:true,actionIsDisabled:function(_f63){
+var _f64=this;
+return dojo.lang.inArray(_f64.actionsDisabled,_f63);
+},actions:{ADDCHILD:"ADDCHILD"},getInfo:function(){
+var info={widgetId:this.widgetId,objectId:this.objectId};
+return info;
+},initializeController:function(){
+if(this.controller!="off"){
+if(this.controller){
+this.controller=dojo.widget.byId(this.controller);
+}else{
+this.controller=dojo.widget.createWidget("TreeBasicController",{DNDController:(this.DNDMode?"create":""),dieWithTree:true});
+}
+this.controller.listenTree(this);
+}else{
+this.controller=null;
+}
+},initializeSelector:function(){
+if(this.selector!="off"){
+if(this.selector){
+this.selector=dojo.widget.byId(this.selector);
+}else{
+this.selector=dojo.widget.createWidget("TreeSelector",{dieWithTree:true});
+}
+this.selector.listenTree(this);
+}else{
+this.selector=null;
+}
+},initialize:function(args,frag){
+var _f68=this;
+for(name in this.eventNamesDefault){
+if(dojo.lang.isUndefined(this.eventNames[name])){
+this.eventNames[name]=this.widgetId+"/"+this.eventNamesDefault[name];
+}
+}
+for(var i=0;i0){
+_f87[_f88-1].updateExpandGridColumn();
+}
+if(_f86 instanceof dojo.widget.Tree&&_f88==0&&_f87.length>0){
+_f87[0].updateExpandGrid();
+}
+_f85.parent=_f85.tree=null;
+return _f85;
+},markLoading:function(){
+},unMarkLoading:function(){
+},lock:function(){
+!this.lockLevel&&this.markLoading();
+this.lockLevel++;
+},unlock:function(){
+if(!this.lockLevel){
+dojo.raise("unlock: not locked");
+}
+this.lockLevel--;
+!this.lockLevel&&this.unMarkLoading();
+},isLocked:function(){
+var node=this;
+while(true){
+if(node.lockLevel){
+return true;
+}
+if(node instanceof dojo.widget.Tree){
+break;
+}
+node=node.parent;
+}
+return false;
+},flushLock:function(){
+this.lockLevel=0;
+this.unMarkLoading();
+}});
+dojo.provide("struts.widget.StrutsTree");
+dojo.widget.defineWidget("struts.widget.StrutsTree",dojo.widget.Tree,{widgetType:"StrutsTree",href:"",errorNotifyTopics:"",errorNotifyTopicsArray:null,postCreate:function(){
+struts.widget.StrutsTree.superclass.postCreate.apply(this);
+if(!dojo.string.isBlank(this.errorNotifyTopics)){
+this.errorNotifyTopicsArray=this.errorNotifyTopics.split(",");
+}
+var self=this;
+if(!dojo.string.isBlank(this.href)){
+dojo.io.bind({url:this.href,useCache:false,preventCache:true,handler:function(type,data,e){
+if(type=="load"){
+if(data){
+dojo.lang.forEach(data,function(_f8e){
+var _f8f=dojo.widget.createWidget("struts:StrutsTreeNode",{title:_f8e.label,isFolder:_f8e.hasChildren,widgetId:_f8e.id});
+self.addChild(_f8f);
+});
+}
+}else{
+if(self.errorNotifyTopicsArray){
+dojo.lang.forEach(self.errorNotifyTopicsArray,function(_f90){
+try{
+dojo.event.topic.publish(_f90,data,e,self);
+}
+catch(ex){
+dojo.debug(ex);
+}
+});
+}
+}
+},mimetype:"text/json"});
+}
+}});
+dojo.kwCompoundRequire({common:["struts.widget.Bind","struts.widget.BindDiv","struts.widget.BindAnchor","struts.widget.ComboBox","struts.widget.StrutsTimePicker","struts.widget.StrutsDatePicker","struts.widget.BindEvent","struts.widget.StrutsTreeSelector","struts.widget.StrutsTabContainer","struts.widget.StrutsTreeNode","struts.widget.StrutsTree"]});
dojo.provide("struts.widget.*");
diff --git a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js
index cdf6b58b3..66e5f25c7 100644
--- a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js
+++ b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js
@@ -28831,6 +28831,2089 @@ dojo.widget.defineWidget(
}
});
+dojo.provide("dojo.widget.TreeNode");
+
+
+
+
+
+dojo.widget.defineWidget("dojo.widget.TreeNode", dojo.widget.HtmlWidget, function() {
+ this.actionsDisabled = [];
+},
+{
+ widgetType: "TreeNode",
+
+ loadStates: {
+ UNCHECKED: "UNCHECKED",
+ LOADING: "LOADING",
+ LOADED: "LOADED"
+ },
+
+
+ actions: {
+ MOVE: "MOVE",
+ REMOVE: "REMOVE",
+ EDIT: "EDIT",
+ ADDCHILD: "ADDCHILD"
+ },
+
+ isContainer: true,
+
+ lockLevel: 0, // lock ++ unlock --, so nested locking works fine
+
+
+ templateString: (' '
+ + '
'
+ + ' ${this.title} '
+ + ' '
+ + '
${this.afterLabel} '
+ + '
'
+ + '
').replace(/(>|<)\s+/g, '$1'), // strip whitespaces between nodes
+
+
+ childIconSrc: "",
+ childIconFolderSrc: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/closed.gif"), // for under root parent item child icon,
+ childIconDocumentSrc: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/document.gif"), // for under root parent item child icon,
+
+ childIcon: null,
+ isTreeNode: true,
+
+ objectId: "", // the widget represents an object
+
+ afterLabel: "",
+ afterLabelNode: null, // node to the left of labelNode
+
+ // an icon left from childIcon: imgs[-2].
+ // if +/- for folders, blank for leaves
+ expandIcon: null,
+
+ title: "",
+ object: "", // node may have object attached, settable from HTML
+ isFolder: false,
+
+ labelNode: null, // the item label
+ titleNode: null, // the item title
+ imgs: null, // an array of icons imgs
+
+ expandLevel: "", // expand to level
+
+ tree: null,
+
+ depth: 0,
+
+ isExpanded: false,
+
+ state: null, // after creation will change to loadStates: "loaded/loading/unchecked"
+ domNodeInitialized: false, // domnode is initialized with icons etc
+
+
+ isFirstChild: function() {
+ return this.getParentIndex() == 0 ? true: false;
+ },
+
+ isLastChild: function() {
+ return this.getParentIndex() == this.parent.children.length-1 ? true : false;
+ },
+
+ lock: function(){ return this.tree.lock.apply(this, arguments) },
+ unlock: function(){ return this.tree.unlock.apply(this, arguments) },
+ isLocked: function(){ return this.tree.isLocked.apply(this, arguments) },
+ cleanLock: function(){ return this.tree.cleanLock.apply(this, arguments) },
+
+ actionIsDisabled: function(action) {
+ var _this = this;
+
+ var disabled = false;
+
+ if (this.tree.strictFolders && action == this.actions.ADDCHILD && !this.isFolder) {
+ disabled = true;
+ }
+
+ if (dojo.lang.inArray(_this.actionsDisabled, action)) {
+ disabled = true;
+ }
+
+ if (this.isLocked()) {
+ disabled = true;
+ }
+
+ return disabled;
+ },
+
+ getInfo: function() {
+ // No title here (title may be widget)
+ var info = {
+ widgetId: this.widgetId,
+ objectId: this.objectId,
+ index: this.getParentIndex(),
+ isFolder: this.isFolder
+ }
+
+ return info;
+ },
+
+ initialize: function(args, frag){
+
+ //dojo.debug(this.title)
+
+ this.state = this.loadStates.UNCHECKED;
+
+ for(var i=0; i move right, negative => move left
+ */
+ adjustDepth: function(depthDiff) {
+
+ for(var i=0; i0) {
+ for(var i=0; i= this.imgs.length-2) return;
+ this.imgs[idx].style.backgroundImage = 'url(' + src + ')';
+ },
+
+
+ updateIconTree: function(){
+ this.tree.updateIconTree.call(this);
+ },
+
+
+
+
+ expand: function(){
+ if (this.isExpanded) return;
+
+ if (this.children.length) {
+ this.showChildren();
+ }
+
+ this.isExpanded = true;
+
+ this.updateExpandIcon();
+
+ dojo.event.topic.publish(this.tree.eventNames.expand, {source: this} );
+ },
+
+ collapse: function(){
+ if (!this.isExpanded) return;
+
+ this.hideChildren();
+ this.isExpanded = false;
+
+ this.updateExpandIcon();
+
+ dojo.event.topic.publish(this.tree.eventNames.collapse, {source: this} );
+ },
+
+ hideChildren: function(){
+ this.tree.toggleObj.hide(
+ this.containerNode, this.toggleDuration, this.explodeSrc, dojo.lang.hitch(this, "onHide")
+ );
+
+ /* if dnd is in action, recalculate changed coordinates */
+ if(dojo.exists(dojo, 'dnd.dragManager.dragObjects') && dojo.dnd.dragManager.dragObjects.length) {
+ dojo.dnd.dragManager.cacheTargetLocations();
+ }
+ },
+
+ showChildren: function(){
+ this.tree.toggleObj.show(
+ this.containerNode, this.toggleDuration, this.explodeSrc, dojo.lang.hitch(this, "onShow")
+ );
+
+ /* if dnd is in action, recalculate changed coordinates */
+ if(dojo.exists(dojo, 'dnd.dragManager.dragObjects') && dojo.dnd.dragManager.dragObjects.length) {
+ dojo.dnd.dragManager.cacheTargetLocations();
+ }
+ },
+
+ addChild: function(){
+ return this.tree.addChild.apply(this, arguments);
+ },
+
+ doAddChild: function(){
+ return this.tree.doAddChild.apply(this, arguments);
+ },
+
+
+
+ /* Edit current node : change properties and update contents */
+ edit: function(props) {
+ dojo.lang.mixin(this, props);
+ if (props.title) {
+ this.titleNode.innerHTML = this.title;
+ }
+
+ if (props.afterLabel) {
+ this.afterLabelNode.innerHTML = this.afterLabel;
+ }
+
+ if (props.childIconSrc) {
+ this.buildChildIcon();
+ }
+
+
+ },
+
+
+ removeNode: function(){ return this.tree.removeNode.apply(this, arguments) },
+ doRemoveNode: function(){ return this.tree.doRemoveNode.apply(this, arguments) },
+
+
+ toString: function() {
+ return "["+this.widgetType+" Tree:"+this.tree+" ID:"+this.widgetId+" Title:"+this.title+"]";
+
+ }
+
+});
+
+
+
+
+
+dojo.provide("struts.widget.StrutsTreeNode");
+
+
+
+dojo.widget.defineWidget(
+ "struts.widget.StrutsTreeNode",
+ dojo.widget.TreeNode, {
+ widgetType : "StrutsTreeNode",
+
+ loaded : false,
+
+ expand : function() {
+ if(!this.loaded) {
+ this.reload();
+ }
+ struts.widget.StrutsTreeNode.superclass.expand.apply(this);
+ },
+
+ removeChildren : function() {
+ var self = this;
+ var childrenCopy = dojo.lang.toArray(this.children);
+ dojo.lang.forEach(childrenCopy, function(node) {
+ self.removeNode(node);
+ });
+ },
+
+ reload : function() {
+ var href = this.tree.href;
+ this.loaded = true;
+
+ if(!dojo.string.isBlank(href)) {
+ //clear children list
+ this.removeChildren();
+ //pass widgetId as parameter
+ var tmpHref = href + (href.indexOf("?") > -1 ? "&" : "?") + "nodeId=" + this.widgetId;
+
+ var self = this;
+ this.markLoading();
+
+ dojo.io.bind({
+ url: tmpHref,
+ useCache: false,
+ preventCache: true,
+ handler: function(type, data, e) {
+ if(type == 'load') {
+ //data should be an array
+ if(data) {
+ dojo.lang.forEach(data, function(descr) {
+ //create node for eachd descriptor
+ var newNode = dojo.widget.createWidget("struts:StrutsTreeNode",{
+ title : descr.label,
+ isFolder: descr.hasChildren,
+ widgetId: descr.id
+ });
+ self.addChild(newNode);
+ });
+ }
+ }
+
+ self.unMarkLoading();
+ },
+ mimetype: "text/json"
+ });
+ }
+ }
+});
+
+dojo.provide("dojo.json");
+
+
+
+
+dojo.json = {
+ // jsonRegistry: AdapterRegistry a registry of type-based serializers
+ jsonRegistry: new dojo.AdapterRegistry(),
+
+ register: function( /*String*/ name,
+ /*function*/ check,
+ /*function*/ wrap,
+ /*optional, boolean*/ override){
+ // summary:
+ // Register a JSON serialization function. JSON serialization
+ // functions should take one argument and return an object
+ // suitable for JSON serialization:
+ // - string
+ // - number
+ // - boolean
+ // - undefined
+ // - object
+ // - null
+ // - Array-like (length property that is a number)
+ // - Objects with a "json" method will have this method called
+ // - Any other object will be used as {key:value, ...} pairs
+ //
+ // If override is given, it is used as the highest priority JSON
+ // serialization, otherwise it will be used as the lowest.
+ // name:
+ // a descriptive type for this serializer
+ // check:
+ // a unary function that will be passed an object to determine
+ // whether or not wrap will be used to serialize the object
+ // wrap:
+ // the serialization function
+ // override:
+ // optional, determines if the this serialization function will be
+ // given priority in the test order
+
+ dojo.json.jsonRegistry.register(name, check, wrap, override);
+ },
+
+ evalJson: function(/*String*/ json){
+ // summary:
+ // evaluates the passed string-form of a JSON object
+ // json:
+ // a string literal of a JSON item, for instance:
+ // '{ "foo": [ "bar", 1, { "baz": "thud" } ] }'
+ // return:
+ // the result of the evaluation
+
+ // FIXME: should this accept mozilla's optional second arg?
+ try {
+ return eval("(" + json + ")");
+ }catch(e){
+ dojo.debug(e);
+ return json;
+ }
+ },
+
+ serialize: function(/*Object*/ o){
+ // summary:
+ // Create a JSON serialization of an object, note that this
+ // doesn't check for infinite recursion, so don't do that!
+ // o:
+ // an object to be serialized. Objects may define their own
+ // serialization via a special "__json__" or "json" function
+ // property. If a specialized serializer has been defined, it will
+ // be used as a fallback.
+ // return:
+ // a String representing the serialized version of the passed
+ // object
+
+ var objtype = typeof(o);
+ if(objtype == "undefined"){
+ return "undefined";
+ }else if((objtype == "number")||(objtype == "boolean")){
+ return o + "";
+ }else if(o === null){
+ return "null";
+ }
+ if (objtype == "string") { return dojo.string.escapeString(o); }
+ // recurse
+ var me = arguments.callee;
+ // short-circuit for objects that support "json" serialization
+ // if they return "self" then just pass-through...
+ var newObj;
+ if(typeof(o.__json__) == "function"){
+ newObj = o.__json__();
+ if(o !== newObj){
+ return me(newObj);
+ }
+ }
+ if(typeof(o.json) == "function"){
+ newObj = o.json();
+ if (o !== newObj) {
+ return me(newObj);
+ }
+ }
+ // array
+ if(objtype != "function" && typeof(o.length) == "number"){
+ var res = [];
+ for(var i = 0; i < o.length; i++){
+ var val = me(o[i]);
+ if(typeof(val) != "string"){
+ val = "undefined";
+ }
+ res.push(val);
+ }
+ return "[" + res.join(",") + "]";
+ }
+ // look in the registry
+ try {
+ window.o = o;
+ newObj = dojo.json.jsonRegistry.match(o);
+ return me(newObj);
+ }catch(e){
+ // dojo.debug(e);
+ }
+ // it's a function with no adapter, bad
+ if(objtype == "function"){
+ return null;
+ }
+ // generic object code path
+ res = [];
+ for (var k in o){
+ var useKey;
+ if (typeof(k) == "number"){
+ useKey = '"' + k + '"';
+ }else if (typeof(k) == "string"){
+ useKey = dojo.string.escapeString(k);
+ }else{
+ // skip non-string or number keys
+ continue;
+ }
+ val = me(o[k]);
+ if(typeof(val) != "string"){
+ // skip non-serializable values
+ continue;
+ }
+ res.push(useKey + ":" + val);
+ }
+ return "{" + res.join(",") + "}";
+ }
+};
+
+/**
+ * TreeDrag* specialized on managing subtree drags
+ * It selects nodes and visualises what's going on,
+ * but delegates real actions upon tree to the controller
+ *
+ * This code is considered a part of controller
+*/
+
+dojo.provide("dojo.dnd.TreeDragAndDrop");
+
+
+
+
+
+
+
+dojo.dnd.TreeDragSource = function(node, syncController, type, treeNode){
+ this.controller = syncController;
+ this.treeNode = treeNode;
+
+ dojo.dnd.HtmlDragSource.call(this, node, type);
+}
+
+dojo.inherits(dojo.dnd.TreeDragSource, dojo.dnd.HtmlDragSource);
+
+dojo.lang.extend(dojo.dnd.TreeDragSource, {
+ onDragStart: function(){
+ /* extend adds functions to prototype */
+ var dragObject = dojo.dnd.HtmlDragSource.prototype.onDragStart.call(this);
+ //dojo.debugShallow(dragObject)
+
+ dragObject.treeNode = this.treeNode;
+
+ dragObject.onDragStart = dojo.lang.hitch(dragObject, function(e) {
+
+ /* save selection */
+ this.savedSelectedNode = this.treeNode.tree.selector.selectedNode;
+ if (this.savedSelectedNode) {
+ this.savedSelectedNode.unMarkSelected();
+ }
+
+ var result = dojo.dnd.HtmlDragObject.prototype.onDragStart.apply(this, arguments);
+
+
+ /* remove background grid from cloned object */
+ var cloneGrid = this.dragClone.getElementsByTagName('img');
+ for(var i=0; i it was allowed before, no accept check is needed
+ if (position=="onto" ||
+ (!this.isAdjacentNode(sourceTreeNode, position)
+ && this.controller.canMove(sourceTreeNode, this.treeNode.parent)
+ )
+ ) {
+ return position;
+ } else {
+ return false;
+ }
+
+ },
+
+ onDragOut: function(e) {
+ this.clearAutoExpandTimer();
+
+ this.hideIndicator();
+ },
+
+
+ clearAutoExpandTimer: function() {
+ if (this.autoExpandTimer) {
+ clearTimeout(this.autoExpandTimer);
+ this.autoExpandTimer = null;
+ }
+ },
+
+
+
+ onDragMove: function(e, dragObjects){
+
+ var sourceTreeNode = dragObjects[0].treeNode;
+
+ var position = this.getAcceptPosition(e, sourceTreeNode);
+
+ if (position) {
+ this.showIndicator(position);
+ }
+
+ },
+
+ isAdjacentNode: function(sourceNode, position) {
+
+ if (sourceNode === this.treeNode) return true;
+ if (sourceNode.getNextSibling() === this.treeNode && position=="before") return true;
+ if (sourceNode.getPreviousSibling() === this.treeNode && position=="after") return true;
+
+ return false;
+ },
+
+
+ /* get DNDMode and see which position e fits */
+ getPosition: function(e, DNDMode) {
+ var node = dojo.byId(this.treeNode.labelNode);
+ var mousey = e.pageY || e.clientY + dojo.body().scrollTop;
+ var nodey = dojo.html.getAbsolutePosition(node).y;
+ var height = dojo.html.getBorderBox(node).height;
+
+ var relY = mousey - nodey;
+ var p = relY / height;
+
+ var position = ""; // "" <=> forbidden
+ if (DNDMode & dojo.widget.Tree.prototype.DNDModes.ONTO
+ && DNDMode & dojo.widget.Tree.prototype.DNDModes.BETWEEN) {
+ if (p<=0.3) {
+ position = "before";
+ } else if (p<=0.7) {
+ position = "onto";
+ } else {
+ position = "after";
+ }
+ } else if (DNDMode & dojo.widget.Tree.prototype.DNDModes.BETWEEN) {
+ if (p<=0.5) {
+ position = "before";
+ } else {
+ position = "after";
+ }
+ }
+ else if (DNDMode & dojo.widget.Tree.prototype.DNDModes.ONTO) {
+ position = "onto";
+ }
+
+
+ return position;
+ },
+
+
+
+ getTargetParentIndex: function(sourceTreeNode, position) {
+
+ var index = position == "before" ? this.treeNode.getParentIndex() : this.treeNode.getParentIndex()+1;
+ if (this.treeNode.parent === sourceTreeNode.parent
+ && this.treeNode.getParentIndex() > sourceTreeNode.getParentIndex()) {
+ index--; // dragging a node is different for simple move bacause of before-after issues
+ }
+
+ return index;
+ },
+
+
+ onDrop: function(e){
+ // onDragOut will clean position
+
+
+ var position = this.position;
+
+//dojo.debug(position);
+
+ this.onDragOut(e);
+
+ var sourceTreeNode = e.dragObject.treeNode;
+
+ if (!dojo.lang.isObject(sourceTreeNode)) {
+ dojo.raise("TreeNode not found in dragObject")
+ }
+
+ if (position == "onto") {
+ return this.controller.move(sourceTreeNode, this.treeNode, 0);
+ } else {
+ var index = this.getTargetParentIndex(sourceTreeNode, position);
+ return this.controller.move(sourceTreeNode, this.treeNode.parent, index);
+ }
+
+ //dojo.debug('drop2');
+
+
+
+ }
+
+
+});
+
+
+
+dojo.dnd.TreeDNDController = function(treeController) {
+
+ // I use this controller to perform actions
+ this.treeController = treeController;
+
+ this.dragSources = {};
+
+ this.dropTargets = {};
+
+}
+
+dojo.lang.extend(dojo.dnd.TreeDNDController, {
+
+
+ listenTree: function(tree) {
+ //dojo.debug("Listen tree "+tree);
+ dojo.event.topic.subscribe(tree.eventNames.createDOMNode, this, "onCreateDOMNode");
+ dojo.event.topic.subscribe(tree.eventNames.moveFrom, this, "onMoveFrom");
+ dojo.event.topic.subscribe(tree.eventNames.moveTo, this, "onMoveTo");
+ dojo.event.topic.subscribe(tree.eventNames.addChild, this, "onAddChild");
+ dojo.event.topic.subscribe(tree.eventNames.removeNode, this, "onRemoveNode");
+ dojo.event.topic.subscribe(tree.eventNames.treeDestroy, this, "onTreeDestroy");
+ },
+
+
+ unlistenTree: function(tree) {
+ //dojo.debug("Listen tree "+tree);
+ dojo.event.topic.unsubscribe(tree.eventNames.createDOMNode, this, "onCreateDOMNode");
+ dojo.event.topic.unsubscribe(tree.eventNames.moveFrom, this, "onMoveFrom");
+ dojo.event.topic.unsubscribe(tree.eventNames.moveTo, this, "onMoveTo");
+ dojo.event.topic.unsubscribe(tree.eventNames.addChild, this, "onAddChild");
+ dojo.event.topic.unsubscribe(tree.eventNames.removeNode, this, "onRemoveNode");
+ dojo.event.topic.unsubscribe(tree.eventNames.treeDestroy, this, "onTreeDestroy");
+ },
+
+ onTreeDestroy: function(message) {
+ this.unlistenTree(message.source);
+ // I'm not widget so don't use destroy() call and dieWithTree
+ },
+
+ onCreateDOMNode: function(message) {
+ this.registerDNDNode(message.source);
+ },
+
+ onAddChild: function(message) {
+ this.registerDNDNode(message.child);
+ },
+
+ onMoveFrom: function(message) {
+ var _this = this;
+ dojo.lang.forEach(
+ message.child.getDescendants(),
+ function(node) { _this.unregisterDNDNode(node); }
+ );
+ },
+
+ onMoveTo: function(message) {
+ var _this = this;
+ dojo.lang.forEach(
+ message.child.getDescendants(),
+ function(node) { _this.registerDNDNode(node); }
+ );
+ },
+
+ /**
+ * Controller(node model) creates DNDNodes because it passes itself to node for synchroneous drops processing
+ * I can't process DnD with events cause an event can't return result success/false
+ */
+ registerDNDNode: function(node) {
+ if (!node.tree.DNDMode) return;
+
+//dojo.debug("registerDNDNode "+node);
+
+ /* I drag label, not domNode, because large domNodes are very slow to copy and large to drag */
+
+ var source = null;
+ var target = null;
+
+ if (!node.actionIsDisabled(node.actions.MOVE)) {
+ //dojo.debug("reg source")
+ var source = new dojo.dnd.TreeDragSource(node.labelNode, this, node.tree.widgetId, node);
+ this.dragSources[node.widgetId] = source;
+ }
+
+ var target = new dojo.dnd.TreeDropTarget(node.labelNode, this.treeController, node.tree.DNDAcceptTypes, node);
+
+ this.dropTargets[node.widgetId] = target;
+
+ },
+
+
+ unregisterDNDNode: function(node) {
+
+ if (this.dragSources[node.widgetId]) {
+ dojo.dnd.dragManager.unregisterDragSource(this.dragSources[node.widgetId]);
+ delete this.dragSources[node.widgetId];
+ }
+
+ if (this.dropTargets[node.widgetId]) {
+ dojo.dnd.dragManager.unregisterDropTarget(this.dropTargets[node.widgetId]);
+ delete this.dropTargets[node.widgetId];
+ }
+ }
+
+
+
+
+
+});
+
+
+dojo.provide("dojo.widget.TreeBasicController");
+
+
+
+
+
+
+dojo.widget.defineWidget("dojo.widget.TreeBasicController", dojo.widget.HtmlWidget, {
+ widgetType: "TreeBasicController",
+
+ DNDController: "",
+
+ dieWithTree: false,
+
+ initialize: function(args, frag){
+
+ /* no DND by default for compatibility */
+ if (this.DNDController == "create") {
+
+ this.DNDController = new dojo.dnd.TreeDNDController(this);
+ }
+
+
+
+ },
+
+
+ /**
+ * Binds controller to all tree events
+ */
+ listenTree: function(tree) {
+ //dojo.debug("Event "+tree.eventNames.treeClick);
+ dojo.event.topic.subscribe(tree.eventNames.createDOMNode, this, "onCreateDOMNode");
+ dojo.event.topic.subscribe(tree.eventNames.treeClick, this, "onTreeClick");
+ dojo.event.topic.subscribe(tree.eventNames.treeCreate, this, "onTreeCreate");
+ dojo.event.topic.subscribe(tree.eventNames.treeDestroy, this, "onTreeDestroy");
+
+ if (this.DNDController) {
+ this.DNDController.listenTree(tree);
+ }
+ },
+
+ unlistenTree: function(tree) {
+ dojo.event.topic.unsubscribe(tree.eventNames.createDOMNode, this, "onCreateDOMNode");
+ dojo.event.topic.unsubscribe(tree.eventNames.treeClick, this, "onTreeClick");
+ dojo.event.topic.unsubscribe(tree.eventNames.treeCreate, this, "onTreeCreate");
+ dojo.event.topic.unsubscribe(tree.eventNames.treeDestroy, this, "onTreeDestroy");
+ },
+
+ onTreeDestroy: function(message) {
+ var tree = message.source;
+
+ this.unlistenTree(tree);
+
+ if (this.dieWithTree) {
+ //alert("Killing myself "+this.widgetId);
+ this.destroy();
+ //dojo.debug("done");
+ }
+ },
+
+ onCreateDOMNode: function(message) {
+
+ var node = message.source;
+
+
+ if (node.expandLevel > 0) {
+ this.expandToLevel(node, node.expandLevel);
+ }
+ },
+
+ // perform actions-initializers for tree
+ onTreeCreate: function(message) {
+ var tree = message.source;
+ var _this = this;
+ if (tree.expandLevel) {
+ dojo.lang.forEach(tree.children,
+ function(child) {
+ _this.expandToLevel(child, tree.expandLevel-1)
+ }
+ );
+ }
+ },
+
+ expandToLevel: function(node, level) {
+ if (level == 0) return;
+
+ var children = node.children;
+ var _this = this;
+
+ var handler = function(node, expandLevel) {
+ this.node = node;
+ this.expandLevel = expandLevel;
+ // recursively expand opened node
+ this.process = function() {
+ //dojo.debug("Process "+node+" level "+level);
+ for(var i=0; icreateDOMNode is called(program way) OR createDOMNode (html-way)
+ // hook events to operate on new DOMNode, create dropTargets etc
+ createDOMNode: "createDOMNode",
+ // tree created.. Perform tree-wide actions if needed
+ treeCreate: "treeCreate",
+ treeDestroy: "treeDestroy",
+ // expand icon clicked
+ treeClick: "treeClick",
+ // node icon clicked
+ iconClick: "iconClick",
+ // node title clicked
+ titleClick: "titleClick",
+
+ moveFrom: "moveFrom",
+ moveTo: "moveTo",
+ addChild: "addChild",
+ removeNode: "removeNode",
+ expand: "expand",
+ collapse: "collapse"
+ },
+
+ isContainer: true,
+
+ DNDMode: "off",
+
+ lockLevel: 0, // lock ++ unlock --, so nested locking works fine
+
+ strictFolders: true,
+
+ DNDModes: {
+ BETWEEN: 1,
+ ONTO: 2
+ },
+
+ DNDAcceptTypes: "",
+
+ templateCssPath: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/Tree.css"),
+
+ templateString: '',
+
+ isExpanded: true, // consider this "root node" to be always expanded
+
+ isTree: true,
+
+ objectId: "",
+
+ // autoCreate if not "off"
+ // used to get the autocreated controller ONLY.
+ // generally, tree DOES NOT KNOW about its CONTROLLER, it just doesn't care
+ // controller gets messages via dojo.event
+ controller: "",
+
+ // autoCreate if not "off"
+ // used to get the autocreated selector ONLY.
+ // generally, tree DOES NOT KNOW its SELECTOR
+ // binding is made with dojo.event
+ selector: "",
+
+ // used ONLY at initialization time
+ menu: "", // autobind menu if menu's widgetId is set here
+
+ expandLevel: "", // expand to level automatically
+
+ //
+ // these icons control the grid and expando buttons for the whole tree
+ //
+
+ blankIconSrc: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_blank.gif"),
+
+ gridIconSrcT: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_t.gif"), // for non-last child grid
+ gridIconSrcL: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_l.gif"), // for last child grid
+ gridIconSrcV: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_v.gif"), // vertical line
+ gridIconSrcP: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_p.gif"), // for under parent item child icons
+ gridIconSrcC: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_c.gif"), // for under child item child icons
+ gridIconSrcX: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_x.gif"), // grid for sole root item
+ gridIconSrcY: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_y.gif"), // grid for last rrot item
+ gridIconSrcZ: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_z.gif"), // for under root parent item child icon
+
+ expandIconSrcPlus: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_expand_plus.gif"),
+ expandIconSrcMinus: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_expand_minus.gif"),
+ expandIconSrcLoading: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_loading.gif"),
+
+
+ iconWidth: 18,
+ iconHeight: 18,
+
+
+ //
+ // tree options
+ //
+
+ showGrid: true,
+ showRootGrid: true,
+
+ actionIsDisabled: function(action) {
+ var _this = this;
+ return dojo.lang.inArray(_this.actionsDisabled, action)
+ },
+
+
+ actions: {
+ ADDCHILD: "ADDCHILD"
+ },
+
+
+ getInfo: function() {
+ var info = {
+ widgetId: this.widgetId,
+ objectId: this.objectId
+ }
+
+ return info;
+ },
+
+ initializeController: function() {
+ if (this.controller != "off") {
+ if (this.controller) {
+ this.controller = dojo.widget.byId(this.controller);
+ }
+ else {
+ // create default controller here
+
+ this.controller = dojo.widget.createWidget("TreeBasicController",
+ { DNDController: (this.DNDMode ? "create" : ""), dieWithTree: true }
+ );
+
+ }
+ this.controller.listenTree(this); // controller listens to my events
+
+ } else {
+ this.controller = null;
+ }
+ },
+
+ initializeSelector: function() {
+
+ if (this.selector != "off") {
+ if (this.selector) {
+ this.selector = dojo.widget.byId(this.selector);
+ }
+ else {
+ // create default controller here
+
+ this.selector = dojo.widget.createWidget("TreeSelector", {dieWithTree: true});
+ }
+
+ this.selector.listenTree(this);
+
+ } else {
+ this.selector = null;
+ }
+ },
+
+ initialize: function(args, frag){
+
+ var _this = this;
+
+ for(name in this.eventNamesDefault) {
+ if (dojo.lang.isUndefined(this.eventNames[name])) {
+ this.eventNames[name] = this.widgetId+"/"+this.eventNamesDefault[name];
+ }
+ }
+
+ for(var i=0; i no createDOMNode => no createDOMNode event
+ domNodeInitialized: child.domNodeInitialized
+ }
+
+ this.doAddChild.apply(this, arguments);
+
+ dojo.event.topic.publish(this.tree.eventNames.addChild, message);
+ },
+
+
+ // not called for initial tree building. See createDOMNode instead.
+ // builds child html node if needed
+ // index is "last node" by default
+ /**
+ * FIXME: Is it possible that removeNode from the tree will cause leaks cause of attached events ?
+ * if yes, then only attach events in addChild and detach in remove.. Seems all ok yet.
+ */
+ doAddChild: function(child, index){
+
+ if (dojo.lang.isUndefined(index)) {
+ index = this.children.length;
+ }
+
+ if (!child.isTreeNode){
+ dojo.raise("You can only add TreeNode widgets to a "+this.widgetType+" widget!");
+ return;
+ }
+
+ // usually it is impossible to change "isFolder" state, but if anyone wants to add a child to leaf,
+ // it is possible program-way.
+ if (this.isTreeNode){
+ if (!this.isFolder) { // just became a folder.
+ //dojo.debug("becoming folder "+this);
+ this.setFolder();
+ }
+ }
+
+ // adjust tree
+ var _this = this;
+ dojo.lang.forEach(child.getDescendants(), function(elem) { elem.tree = _this.tree; });
+
+ // fix parent
+ child.parent = this;
+
+
+ // no dynamic loading for those who become parents
+ if (this.isTreeNode) {
+ this.state = this.loadStates.LOADED;
+ }
+
+ // add new child into DOM after it was added into children
+ if (index < this.children.length) { // children[] already has child
+ //dojo.debug("Inserting before "+this.children[index].title);
+ dojo.html.insertBefore(child.domNode, this.children[index].domNode);
+ } else {
+ this.containerNode.appendChild(child.domNode);
+ if (this.isExpanded && this.isTreeNode) {
+ /* When I add children to hidden containerNode => show container w/ them */
+ this.showChildren();
+ }
+ }
+
+
+ this.children.splice(index, 0, child);
+
+ //dojo.debugShallow(this.children);
+
+
+ // if node exists - adjust its depth, otherwise build it
+ if (child.domNodeInitialized) {
+ var d = this.isTreeNode ? this.depth : -1;
+ child.adjustDepth( d - child.depth + 1 );
+
+
+ // update icons to link generated dom with Tree => updateParentGrid
+ // if I moved child from LastNode inside the tree => need to link it up'n'down =>
+ // updateExpandGridColumn
+ // if I change depth => need to update all grid..
+ child.updateIconTree();
+ } else {
+ //dojo.debug("Create domnode ");
+ child.depth = this.isTreeNode ? this.depth+1 : 0;
+ child.createDOMNode(child.tree, child.depth);
+ }
+
+
+
+ // Use-case:
+ // When previous sibling was created => it was last, no children after it
+ // so it did not create link down => let's add it for all descendants
+ // Use-case:
+ // a child was moved down under the last node so last node should be updated
+ var prevSibling = child.getPreviousSibling();
+ if (child.isLastChild() && prevSibling) {
+ prevSibling.updateExpandGridColumn();
+ }
+
+
+ //dojo.debug("Added child "+child);
+
+
+
+ },
+
+
+
+
+ makeBlankImg: function() {
+ var img = document.createElement('img');
+
+ img.style.width = this.iconWidth + 'px';
+ img.style.height = this.iconHeight + 'px';
+ img.src = this.blankIconSrc;
+ img.style.verticalAlign = 'middle';
+
+ return img;
+ },
+
+
+ updateIconTree: function(){
+
+ //dojo.debug("Update icons for "+this)
+ if (!this.isTree) {
+ this.updateIcons();
+ }
+
+ for(var i=0; i0) {
+ children[index-1].updateExpandGridColumn();
+ }
+ // if it WAS first node in WHOLE TREE -
+ // update link up of its former lower neighbour(if exists still)
+ if (parent instanceof dojo.widget.Tree && index == 0 && children.length>0) {
+ children[0].updateExpandGrid();
+ }
+
+ //parent.updateIconTree();
+
+
+ child.parent = child.tree = null;
+
+ return child;
+ },
+
+ markLoading: function() {
+ // no way to mark tree loading
+ },
+
+ unMarkLoading: function() {
+ // no way to show that tree finished loading
+ },
+
+
+ lock: function() {
+ !this.lockLevel && this.markLoading();
+ this.lockLevel++;
+ },
+ unlock: function() {
+ if (!this.lockLevel) {
+ dojo.raise("unlock: not locked");
+ }
+ this.lockLevel--;
+ !this.lockLevel && this.unMarkLoading();
+ },
+
+ isLocked: function() {
+ var node = this;
+ while (true) {
+ if (node.lockLevel) {
+ return true;
+ }
+ if (node instanceof dojo.widget.Tree) {
+ break;
+ }
+ node = node.parent;
+ }
+
+ return false;
+ },
+
+ flushLock: function() {
+ this.lockLevel = 0;
+ this.unMarkLoading();
+ }
+});
+
+
+
+dojo.provide("struts.widget.StrutsTree");
+
+
+
+dojo.widget.defineWidget(
+ "struts.widget.StrutsTree",
+ dojo.widget.Tree, {
+ widgetType : "StrutsTree",
+
+ href : "",
+ errorNotifyTopics : "",
+ errorNotifyTopicsArray : null,
+
+ postCreate : function() {
+ struts.widget.StrutsTree.superclass.postCreate.apply(this);
+
+ //error topics
+ if(!dojo.string.isBlank(this.errorNotifyTopics)) {
+ this.errorNotifyTopicsArray = this.errorNotifyTopics.split(",");
+ }
+
+ var self = this;
+ if(!dojo.string.isBlank(this.href)) {
+ dojo.io.bind({
+ url: this.href,
+ useCache: false,
+ preventCache: true,
+ handler: function(type, data, e) {
+ if(type == 'load') {
+ //data should be an array
+ if(data) {
+ dojo.lang.forEach(data, function(descr) {
+ //create node for eachd descriptor
+ var newNode = dojo.widget.createWidget("struts:StrutsTreeNode",{
+ title : descr.label,
+ isFolder: descr.hasChildren,
+ widgetId: descr.id
+ });
+ self.addChild(newNode);
+ });
+ }
+ } else {
+ //publish error topics
+ if(self.errorNotifyTopicsArray) {
+ dojo.lang.forEach(self.errorNotifyTopicsArray, function(topic) {
+ try {
+ dojo.event.topic.publish(topic, data, e, self);
+ } catch(ex){
+ dojo.debug(ex);
+ }
+ });
+ }
+ }
+ },
+ mimetype: "text/json"
+ });
+ }
+ }
+});
+
dojo.kwCompoundRequire({
common: ["struts.widget.Bind",
"struts.widget.BindDiv",
@@ -28840,7 +30923,9 @@ dojo.kwCompoundRequire({
"struts.widget.StrutsDatePicker",
"struts.widget.BindEvent",
"struts.widget.StrutsTreeSelector",
- "struts.widget.StrutsTabContainer"]
+ "struts.widget.StrutsTabContainer",
+ "struts.widget.StrutsTreeNode",
+ "struts.widget.StrutsTree"]
});
dojo.provide("struts.widget.*");
diff --git a/plugins/dojo/src/main/resources/template/ajax/tree.ftl b/plugins/dojo/src/main/resources/template/ajax/tree.ftl
index 2100cc1de..9dea78789 100644
--- a/plugins/dojo/src/main/resources/template/ajax/tree.ftl
+++ b/plugins/dojo/src/main/resources/template/ajax/tree.ftl
@@ -21,7 +21,13 @@
>
#if>
-
+ href="${parameters.href}"
+ #if>
+ <#if parameters.errorNotifyTopics?if_exists != "">
+ errorNotifyTopics="${parameters.errorNotifyTopics?html}"<#rt/>
+ #if>
<#if parameters.blankIconSrc?exists>
gridIconSrcT="<@s.url value='${parameters.blankIconSrc}' encode="false" includeParams='none'/>"
#if>
@@ -77,15 +83,12 @@
|| parameters.collapsedNotifyTopics?exists>
selector="treeSelector_${parameters.id?default("")}"
#if>
- <#if parameters.treeCollapsedTopic?exists>
- publishCollapsedTopic="${parameters.treeCollapsedTopic?html}"
- #if>
<#if parameters.toggle?exists>
toggle="${parameters.toggle?html}"
#if>
>
<#if parameters.label?exists>
-
id="${stack.findValue(parameters.nodeIdProperty)}"
<#else>
diff --git a/plugins/dojo/src/main/resources/template/ajax/treenode-include.ftl b/plugins/dojo/src/main/resources/template/ajax/treenode-include.ftl
index 02c2d1d75..e594e19a6 100644
--- a/plugins/dojo/src/main/resources/template/ajax/treenode-include.ftl
+++ b/plugins/dojo/src/main/resources/template/ajax/treenode-include.ftl
@@ -1,4 +1,4 @@
-
+
<#list stack.findValue(parameters.childCollectionProperty.toString()) as child>
${stack.push(child)}
<#include "/${parameters.templateDir}/ajax/treenode-include.ftl" />
diff --git a/plugins/dojo/src/main/resources/template/ajax/treenode.ftl b/plugins/dojo/src/main/resources/template/ajax/treenode.ftl
index 858ed45e0..bffa9c606 100644
--- a/plugins/dojo/src/main/resources/template/ajax/treenode.ftl
+++ b/plugins/dojo/src/main/resources/template/ajax/treenode.ftl
@@ -1,4 +1,4 @@
-
childIconSrc="<@s.url value='${parameters.childIconSrc}' includeParams='none' encode='false' />"
#if>
diff --git a/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-1.txt b/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-1.txt
index eadb67ce2..302cf16e2 100644
--- a/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-1.txt
+++ b/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-1.txt
@@ -6,36 +6,36 @@
// dojo.hostenv.writeIncludes();
-->
-
-
-
-
-
diff --git a/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-2.txt b/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-2.txt
index f64c2b601..a7a871bb3 100644
--- a/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-2.txt
+++ b/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-2.txt
@@ -6,58 +6,58 @@
// dojo.hostenv.writeIncludes();
-->
-